enable UI during backups, fix state management bugs

This commit is contained in:
Matt Hill
2021-10-20 13:19:20 -06:00
committed by Aiden McClelland
parent d5dd37b165
commit 896069f1a1
37 changed files with 491 additions and 478 deletions

View File

@@ -0,0 +1,16 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="close()">
<ion-icon [name]="type === 'backup' ? 'arrow-back' : 'close'"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ title }}</ion-title>
<ion-buttons slot="end">
<ion-button [disabled]="backupService.loading" (click)="refresh()">
Refresh
<ion-icon slot="end" name="refresh"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

View File

@@ -0,0 +1,70 @@
<ion-item-group>
<!-- always -->
<ion-item class="ion-margin-bottom">
<ion-label>{{ message }}</ion-label>
</ion-item>
<!-- loading -->
<ng-container *ngIf="backupService.loading; else loaded">
<ion-item-divider>
<ion-skeleton-text animated style="width: 120px; height: 16px;"></ion-skeleton-text>
</ion-item-divider>
<ion-item>
<ion-avatar slot="start" style="margin-right: 24px;">
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
</ion-avatar>
<ion-label>
<ion-skeleton-text animated style="width: 100px; height: 20px; margin-bottom: 12px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 50px; height: 16px; margin-bottom: 16px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 100px;"></ion-skeleton-text>
</ion-label>
</ion-item>
</ng-container>
<!-- loaded -->
<ng-template #loaded>
<!-- error -->
<ion-item *ngIf="backupService.loadingError; else noError">
<ion-label>
<ion-text color="danger">
{{ backupService.loadingError }}
</ion-text>
</ion-label>
</ion-item>
<ng-template #noError>
<ion-item *ngIf="!backupService.drives.length; else hasDrives">
<ion-label>
<ion-text color="warning">
No drives found. Insert a backup drive into your Embassy and click "Refresh" above.
</ion-text>
</ion-label>
</ion-item>
<ng-template #hasDrives>
<ion-item-group>
<div *ngFor="let drive of backupService.drives">
<ion-item-divider>
{{ drive.vendor || 'Unknown Vendor' }} - {{ drive.model || 'Unknown Model' }} - {{ drive.capacity | convertBytes }}
</ion-item-divider>
<ion-item button *ngFor="let partition of drive.partitions" [disabled]="type === 'restore' && !partition.hasBackup" (click)="handleSelect(partition)">
<ion-icon slot="start" name="save-outline" size="large"></ion-icon>
<ion-label>
<h1>{{ partition.label || partition.logicalname }}</h1>
<h2>{{ partition.capacity | convertBytes }}</h2>
<p *ngIf="partition.hasBackup">
<ion-text color="success">
Embassy backups detected
</ion-text>
</p>
</ion-label>
<ion-icon *ngIf="partition.hasBackup" slot="end" color="danger" name="lock-closed-outline"></ion-icon>
</ion-item>
</div>
</ion-item-group>
</ng-template>
</ng-template>
</ng-template>
</ion-item-group>

View File

@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { BackupDrivesComponent, BackupDrivesHeaderComponent } from './backup-drives.component'
import { SharingModule } from '../../modules/sharing.module'
@NgModule({
declarations: [
BackupDrivesComponent,
BackupDrivesHeaderComponent,
],
imports: [
CommonModule,
IonicModule,
SharingModule,
],
exports: [
BackupDrivesComponent,
BackupDrivesHeaderComponent,
],
})
export class BackupDrivesComponentModule { }

View File

@@ -0,0 +1,63 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { BackupService } from './backup.service'
import { MappedPartitionInfo } from 'src/app/util/misc.util'
@Component({
selector: 'backup-drives',
templateUrl: './backup-drives.component.html',
styleUrls: ['./backup-drives.component.scss'],
})
export class BackupDrivesComponent {
@Input() type: 'backup' | 'recover'
@Output() onSelect: EventEmitter<MappedPartitionInfo> = new EventEmitter()
message: string
constructor (
public readonly backupService: BackupService,
) { }
ngOnInit () {
if (this.type === 'backup') {
this.message = 'Select the drive where you want to create a backup of your Embassy.'
} else {
this.message = 'Select the drive containing the backup you would like to restore.'
}
this.backupService.getExternalDrives()
}
handleSelect (partition: MappedPartitionInfo): void {
this.onSelect.emit(partition)
}
}
@Component({
selector: 'backup-drives-header',
templateUrl: './backup-drives-header.component.html',
styleUrls: ['./backup-drives.component.scss'],
})
export class BackupDrivesHeaderComponent {
@Input() type: 'backup' | 'recover'
@Output() onClose: EventEmitter<void> = new EventEmitter()
title: string
constructor (
public readonly backupService: BackupService,
) { }
ngOnInit () {
if (this.type === 'backup') {
this.title = 'Create Backup'
} else {
this.title = 'Restore From Backup'
}
}
close (): void {
this.onClose.emit()
}
refresh () {
this.backupService.getExternalDrives()
}
}

View File

@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core'
import { IonicSafeString } from '@ionic/core'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { getErrorMessage } from 'src/app/services/error-toast.service'
import { MappedDriveInfo, MappedPartitionInfo } from 'src/app/util/misc.util'
import { Emver } from 'src/app/services/emver.service'
@Injectable({
providedIn: 'root',
})
export class BackupService {
drives: MappedDriveInfo[]
loading = true
loadingError: string | IonicSafeString
constructor (
private readonly embassyApi: ApiService,
private readonly emver: Emver,
) { }
async getExternalDrives (): Promise<void> {
this.loading = true
try {
const drives = await this.embassyApi.getDrives({ })
this.drives = drives.map(d => {
const partionInfo: MappedPartitionInfo[] = d.partitions.map(p => {
return {
...p,
hasBackup: [0, 1].includes(this.emver.compare(p['embassy-os']?.version, '0.3.0')),
}
})
return {
...d,
partitions: partionInfo,
}
})
} catch (e) {
this.loadingError = getErrorMessage(e)
} finally {
this.loading = false
}
}
}

View File

@@ -1,3 +0,0 @@
ion-note {
width: 50%;
}