Add guid to partition type (#1932)

* add guid to partitions and implement pipe in shared to return guid for any disk

* fix bug and clean up
This commit is contained in:
Matt Hill
2022-11-10 10:20:52 -07:00
committed by Aiden McClelland
parent 22b273b145
commit 45a6a930c9
21 changed files with 393 additions and 131 deletions

View File

@@ -5,7 +5,10 @@ import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { HomePage } from './home.page'
import { SwiperModule } from 'swiper/angular'
import { UnitConversionPipesModule } from '@start9labs/shared'
import {
UnitConversionPipesModule,
GuidPipePipesModule,
} from '@start9labs/shared'
const routes: Routes = [
{
@@ -22,6 +25,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
SwiperModule,
UnitConversionPipesModule,
GuidPipePipesModule,
],
declarations: [HomePage],
})

View File

@@ -27,6 +27,7 @@
</ion-card-header>
<ion-card-content class="ion-margin-bottom">
<swiper (swiper)="setSwiperInstance($event)">
<!-- SLIDE 1 -->
<ng-template swiperSlide>
<ion-item
*ngFor="let disk of disks"
@@ -51,43 +52,53 @@
</ion-label>
</ion-item>
</ng-template>
<!-- SLIDE 2 -->
<ng-template swiperSlide>
<ion-item
*ngIf="!!selectedDisk?.guid"
button
(click)="tryInstall(false)"
>
<ion-icon
color="dark"
slot="start"
size="large"
name="medkit-outline"
></ion-icon>
<ion-label>
<h1>
<ion-text color="success">Re-Install embassyOS</ion-text>
</h1>
<h2>Will preserve existing embassyOS data</h2>
</ion-label>
</ion-item>
<ion-item button lines="none" (click)="tryInstall(true)">
<ion-icon
color="dark"
slot="start"
size="large"
name="download-outline"
></ion-icon>
<ion-label>
<h1>
<ion-text
[color]="!!selectedDisk?.guid ? 'danger' : 'success'"
>{{ !!selectedDisk?.guid ? 'Factory Reset' : 'Install
embassyOS' }}</ion-text
>
</h1>
<h2>Will delete existing data on disk</h2>
</ion-label>
</ion-item>
<ng-container *ngIf="selectedDisk">
<!-- re-install -->
<ion-item
*ngIf="selectedDisk | guid"
button
(click)="tryInstall(false)"
>
<ion-icon
color="dark"
slot="start"
size="large"
name="medkit-outline"
></ion-icon>
<ion-label>
<h1>
<ion-text color="success"
>Re-Install embassyOS</ion-text
>
</h1>
<h2>Will preserve existing embassyOS data</h2>
</ion-label>
</ion-item>
<!-- fresh install -->
<ion-item button lines="none" (click)="tryInstall(true)">
<ion-icon
color="dark"
slot="start"
size="large"
name="download-outline"
></ion-icon>
<ion-label>
<h1>
<ion-text
[color]="(selectedDisk | guid) ? 'danger' : 'success'"
>
{{ (selectedDisk | guid) ? 'Factory Reset' : 'Install
embassyOS' }}
</ion-text>
</h1>
<h2>Will delete existing data on disk</h2>
</ion-label>
</ion-item>
</ng-container>
</ng-template>
</swiper>
</ion-card-content>

View File

@@ -24,6 +24,7 @@ export class MockApiService implements ApiService {
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
guid: null,
},
],
capacity: 123456789123,
@@ -40,15 +41,39 @@ export class MockApiService implements ApiService {
capacity: 73264762332,
used: null,
'embassy-os': {
version: '0.3.1',
version: '0.3.3',
full: true,
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
guid: null,
},
],
capacity: 123456789123,
capacity: 124456789123,
guid: null,
},
{
logicalname: 'wxyz',
vendor: 'SanDisk',
model: 'Specialness',
partitions: [
{
logicalname: 'pbcba',
label: null,
capacity: 73264762332,
used: null,
'embassy-os': {
version: '0.3.2',
full: true,
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
guid: 'guid-guid-guid-guid',
},
],
capacity: 123459789123,
guid: null,
},
]

View File

@@ -8,6 +8,11 @@ const routes: Routes = [
loadChildren: () =>
import('./pages/home/home.module').then(m => m.HomePageModule),
},
{
path: 'attach',
loadChildren: () =>
import('./pages/attach/attach.module').then(m => m.AttachPageModule),
},
{
path: 'recover',
loadChildren: () =>

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { AttachPage } from './attach.page'
const routes: Routes = [
{
path: '',
component: AttachPage,
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AttachPageRoutingModule {}

View File

@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import {
GuidPipePipesModule,
UnitConversionPipesModule,
} from '@start9labs/shared'
import { AttachPage } from './attach.page'
import { AttachPageRoutingModule } from './attach-routing.module'
@NgModule({
declarations: [AttachPage],
imports: [
CommonModule,
IonicModule,
AttachPageRoutingModule,
UnitConversionPipesModule,
GuidPipePipesModule,
],
})
export class AttachPageModule {}

View File

@@ -0,0 +1,74 @@
<ion-content>
<ion-grid>
<ion-row>
<ion-col>
<div style="padding-bottom: 32px" class="ion-text-center">
<img src="assets/img/logo.png" style="max-width: 240px" />
</div>
<ion-card color="dark">
<ion-card-header class="ion-text-center">
<ion-card-title>Use Existing Drive</ion-card-title>
<ion-card-subtitle
>Select the physical drive containing your Embassy
data</ion-card-subtitle
>
</ion-card-header>
<ion-card-content class="ion-margin">
<ion-spinner
*ngIf="loading"
class="center-spinner"
name="lines"
></ion-spinner>
<!-- loaded -->
<ion-item-group *ngIf="!loading" class="ion-text-center">
<!-- drives -->
<h2 class="target-label">Available Drives</h2>
<p *ngIf="!drives.length">
No valid Embassy data drives found. Please make sure the drive
is a valid Embassy data drive (not a backup) and is firmly
connected, then refresh the page.
</p>
<ng-container *ngFor="let drive of drives">
<ion-item
*ngIf="drive | guid as guid"
button
(click)="select(guid)"
lines="none"
>
<ion-icon
slot="start"
name="save-outline"
size="large"
color="light"
></ion-icon>
<ion-label>
<h1>{{ drive.logicalname }}</h1>
<p>
{{ drive.vendor || 'Unknown Vendor' }} - {{ drive.model ||
'Unknown Model' }}
</p>
<p>Capacity: {{ drive.capacity | convertBytes }}</p>
</ion-label>
</ion-item>
</ng-container>
<ion-button
class="ion-margin-top"
fill="clear"
color="primary"
(click)="refresh()"
>
<ion-icon slot="start" name="refresh"></ion-icon>
Refresh
</ion-button>
</ion-item-group>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

View File

@@ -0,0 +1,4 @@
.target-label {
font-weight: bold;
padding-bottom: 6px;
}

View File

@@ -0,0 +1,77 @@
import { Component } from '@angular/core'
import {
LoadingController,
ModalController,
NavController,
} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { DiskInfo, ErrorToastService } from '@start9labs/shared'
import { StateService } from 'src/app/services/state.service'
import { PasswordPage } from 'src/app/modals/password/password.page'
@Component({
selector: 'app-attach',
templateUrl: 'attach.page.html',
styleUrls: ['attach.page.scss'],
})
export class AttachPage {
loading = true
drives: DiskInfo[] = []
constructor(
private readonly apiService: ApiService,
private readonly navCtrl: NavController,
private readonly errToastService: ErrorToastService,
private readonly stateService: StateService,
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
) {}
async ngOnInit() {
await this.getDrives()
}
async refresh() {
this.loading = true
await this.getDrives()
}
async getDrives() {
try {
const drives = await this.apiService.getDrives()
this.drives = drives.filter(d => d.partitions.length)
} catch (e: any) {
this.errToastService.present(e)
} finally {
this.loading = false
}
}
async select(guid: string) {
const modal = await this.modalCtrl.create({
component: PasswordPage,
componentProps: { storageDrive: true },
})
modal.onDidDismiss().then(res => {
if (res.data && res.data.password) {
this.attachDrive(guid, res.data.password)
}
})
await modal.present()
}
private async attachDrive(guid: string, password: string) {
const loader = await this.loadingCtrl.create({
message: 'Attaching Drive',
})
await loader.present()
try {
await this.stateService.importDrive(guid, password)
await this.navCtrl.navigateForward(`/success`)
} catch (e: any) {
this.errToastService.present(e)
} finally {
loader.dismiss()
}
}
}

View File

@@ -2,7 +2,10 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { UnitConversionPipesModule } from '@start9labs/shared'
import {
GuidPipePipesModule,
UnitConversionPipesModule,
} from '@start9labs/shared'
import { EmbassyPage } from './embassy.page'
import { PasswordPageModule } from '../../modals/password/password.module'
import { EmbassyPageRoutingModule } from './embassy-routing.module'
@@ -15,6 +18,7 @@ import { EmbassyPageRoutingModule } from './embassy-routing.module'
EmbassyPageRoutingModule,
PasswordPageModule,
UnitConversionPipesModule,
GuidPipePipesModule,
],
declarations: [EmbassyPage],
})

View File

@@ -10,7 +10,7 @@ import {
BackupRecoverySource,
DiskRecoverySource,
} from 'src/app/services/api/api.service'
import { DiskInfo, ErrorToastService } from '@start9labs/shared'
import { DiskInfo, ErrorToastService, GuidPipe } from '@start9labs/shared'
import { StateService } from 'src/app/services/state.service'
import { PasswordPage } from '../../modals/password/password.page'
import { ActivatedRoute } from '@angular/router'
@@ -19,6 +19,7 @@ import { ActivatedRoute } from '@angular/router'
selector: 'app-embassy',
templateUrl: 'embassy.page.html',
styleUrls: ['embassy.page.scss'],
providers: [GuidPipe],
})
export class EmbassyPage {
storageDrives: DiskInfo[] = []
@@ -32,6 +33,7 @@ export class EmbassyPage {
private readonly stateService: StateService,
private readonly loadingCtrl: LoadingController,
private readonly errorToastService: ErrorToastService,
private readonly guidPipe: GuidPipe,
private route: ActivatedRoute,
) {}
@@ -71,7 +73,10 @@ export class EmbassyPage {
}
async chooseDrive(drive: DiskInfo) {
if (!!drive.partitions.find(p => p.used) || !!drive.guid) {
if (
this.guidPipe.transform(drive) ||
!!drive.partitions.find(p => p.used)
) {
const alert = await this.alertCtrl.create({
header: 'Warning',
subHeader: 'Drive contains data!',

View File

@@ -1,4 +1,4 @@
<ion-content *ngIf="loaded">
<ion-content>
<ion-grid>
<ion-row>
<ion-col class="ion-text-center">
@@ -6,7 +6,7 @@
<img src="assets/img/logo.png" style="max-width: 240px" />
</div>
<ion-card color="dark">
<ion-card *ngIf="!loading" color="dark">
<ion-card-header>
<ion-button
*ngIf="swiper?.activeIndex === 1"
@@ -23,8 +23,10 @@
</ion-card-title>
</ion-card-header>
<ion-card-content class="ion-margin-bottom">
<swiper [autoHeight]="true" (swiper)="setSwiperInstance($event)">
<swiper (swiper)="setSwiperInstance($event)">
<!-- SLIDE 1 -->
<ng-template swiperSlide>
<!-- fresh -->
<ion-item
button
[disabled]="error"
@@ -37,6 +39,8 @@
<p>Get started with a brand new Embassy</p>
</ion-label>
</ion-item>
<!-- recover -->
<ion-item
button
[disabled]="error"
@@ -51,7 +55,10 @@
</ion-label>
</ion-item>
</ng-template>
<!-- SLIDE 2 -->
<ng-template swiperSlide>
<!-- restore from backup -->
<ion-item button detail="true" routerLink="/recover">
<ion-icon
color="dark"
@@ -62,10 +69,17 @@
<h2>
<ion-text color="warning">Restore From Backup</ion-text>
</h2>
<p>Recover an Embassy from an encrypted backup</p>
<p>Restore an Embassy from an encrypted backup</p>
</ion-label>
</ion-item>
<ion-item button detail="true" lines="none" (click)="import()">
<!-- attach -->
<ion-item
button
detail="true"
lines="none"
routerLink="/attach"
>
<ion-icon
color="dark"
slot="start"
@@ -75,9 +89,13 @@
<h2>
<ion-text color="primary">Use Existing Drive</ion-text>
</h2>
<p>Attach and use a valid Embassy data drive</p>
<p>
Use an existing, valid Embassy data drive (not a backup)
</p>
</ion-label>
</ion-item>
<!-- transfer -->
<ion-item
button
detail="true"
@@ -94,8 +112,9 @@
<ion-text color="success">Transfer</ion-text>
</h2>
<p>
Transfer data to a new drive<br />(e.g. upgrade to a
larger drive or an Embassy Pro)
Transfer data from an existing, valid Embassy data drive
(not a backup) to a new drive<br />(e.g. in order to
transfer data to another device)
</p>
</ion-label>
</ion-item>

View File

@@ -1,14 +1,6 @@
import { Component } from '@angular/core'
import {
AlertController,
IonicSlides,
LoadingController,
ModalController,
NavController,
} from '@ionic/angular'
import { PasswordPage } from 'src/app/modals/password/password.page'
import { IonicSlides } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { StateService } from 'src/app/services/state.service'
import SwiperCore, { Swiper } from 'swiper'
import { ErrorToastService } from '@start9labs/shared'
@@ -21,35 +13,26 @@ SwiperCore.use([IonicSlides])
})
export class HomePage {
swiper?: Swiper
guid?: string | null
error = false
loaded = false
loading = true
constructor(
private readonly api: ApiService,
private readonly modalCtrl: ModalController,
private readonly alertCtrl: AlertController,
private readonly loadingCtrl: LoadingController,
private readonly stateService: StateService,
private readonly navCtrl: NavController,
private readonly errToastService: ErrorToastService,
) {}
async ngOnInit() {
async ionViewDidEnter() {
if (this.swiper) {
this.swiper.allowTouchMove = false
}
try {
await this.api.getPubKey()
const disks = await this.api.getDrives()
this.guid = disks.find(d => !!d.guid)?.guid
} catch (e: any) {
this.error = true
this.errToastService.present(e)
}
}
async ionViewDidEnter() {
this.loaded = true // needed to accommodate autoHight="true" on swiper. Otherwise Swiper height might be 0 when navigating *to* this page from later page. Happens on refresh.
if (this.swiper) {
this.swiper.allowTouchMove = false
} finally {
this.loading = false
}
}
@@ -64,41 +47,4 @@ export class HomePage {
previous() {
this.swiper?.slidePrev(500)
}
async import() {
if (this.guid) {
const modal = await this.modalCtrl.create({
component: PasswordPage,
componentProps: { storageDrive: true },
})
modal.onDidDismiss().then(res => {
if (res.data && res.data.password) {
this.importDrive(res.data.password)
}
})
await modal.present()
} else {
const alert = await this.alertCtrl.create({
header: 'Drive Not Found',
message:
'Please make sure the drive is a valid Embassy data drive (not a backup) and is firmly connected, then refresh the page.',
})
await alert.present()
}
}
private async importDrive(password: string) {
const loader = await this.loadingCtrl.create({
message: 'Importing Drive',
})
await loader.present()
try {
await this.stateService.importDrive(this.guid!, password)
await this.navCtrl.navigateForward(`/success`)
} catch (e: any) {
this.errToastService.present(e)
} finally {
loader.dismiss()
}
}
}

View File

@@ -1,7 +1,10 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { UnitConversionPipesModule } from '@start9labs/shared'
import {
GuidPipePipesModule,
UnitConversionPipesModule,
} from '@start9labs/shared'
import { TransferPage } from './transfer.page'
import { TransferPageRoutingModule } from './transfer-routing.module'
@@ -12,6 +15,7 @@ import { TransferPageRoutingModule } from './transfer-routing.module'
IonicModule,
TransferPageRoutingModule,
UnitConversionPipesModule,
GuidPipePipesModule,
],
})
export class TransferPageModule {}

View File

@@ -10,7 +10,7 @@
<ion-card-header class="ion-text-center">
<ion-card-title>Transfer</ion-card-title>
<ion-card-subtitle
>Select the physical drive containing your previous Embassy
>Select the physical drive containing your Embassy
data</ion-card-subtitle
>
</ion-card-header>
@@ -28,7 +28,12 @@
<h2 class="target-label">Available Drives</h2>
<ng-container *ngFor="let drive of drives">
<ion-item button (click)="select(drive)" lines="none">
<ion-item
*ngIf="drive | guid as guid"
button
(click)="select(guid)"
lines="none"
>
<ion-icon
slot="start"
name="save-outline"

View File

@@ -32,8 +32,8 @@ export class TransferPage {
async getDrives() {
try {
const disks = await this.apiService.getDrives()
this.drives = disks.filter(d => d.partitions.length && d.guid)
const drives = await this.apiService.getDrives()
this.drives = drives.filter(d => d.partitions.length)
} catch (e: any) {
this.errToastService.present(e)
} finally {
@@ -41,11 +41,7 @@ export class TransferPage {
}
}
async select(target: DiskInfo) {
const { logicalname, guid } = target
if (!logicalname) return
async select(guid: string) {
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
@@ -60,7 +56,7 @@ export class TransferPage {
handler: () => {
this.stateService.recoverySource = {
type: 'migrate',
guid: guid!,
guid,
}
this.navCtrl.navigateForward(`/embassy`, {
queryParams: { action: 'transfer' },

View File

@@ -24,9 +24,8 @@ export class MockApiService extends ApiService {
async getPubKey() {
await pauseFor(1000)
const keystore = jose.JWK.createKeyStore()
// randomly generated
// const keystore = jose.JWK.createKeyStore()
// this.pubkey = await keystore.generate('EC', 'P-256')
// generated from backend
@@ -58,6 +57,7 @@ export class MockApiService extends ApiService {
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
guid: null,
},
],
capacity: 123456789123,
@@ -65,8 +65,8 @@ export class MockApiService extends ApiService {
},
{
logicalname: 'dcba',
vendor: 'Samsung',
model: 'T5',
vendor: 'Crucial',
model: 'MX500',
partitions: [
{
logicalname: 'pbcba',
@@ -74,16 +74,40 @@ export class MockApiService extends ApiService {
capacity: 73264762332,
used: null,
'embassy-os': {
version: '0.3.1',
version: '0.3.3',
full: true,
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
guid: null,
},
],
capacity: 123456789123,
guid: 'uuid-uuid-uuid-uuid',
capacity: 124456789123,
guid: null,
},
{
logicalname: 'wxyz',
vendor: 'SanDisk',
model: 'Specialness',
partitions: [
{
logicalname: 'pbcba',
label: null,
capacity: 73264762332,
used: null,
'embassy-os': {
version: '0.3.2',
full: true,
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
guid: 'guid-guid-guid-guid',
},
],
capacity: 123459789123,
guid: null,
},
]
}

View File

@@ -0,0 +1,8 @@
import { NgModule } from '@angular/core'
import { GuidPipe } from './guid.pipe'
@NgModule({
declarations: [GuidPipe],
exports: [GuidPipe],
})
export class GuidPipePipesModule {}

View File

@@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core'
import { DiskInfo } from '../../types/api'
@Pipe({
name: 'guid',
})
export class GuidPipe implements PipeTransform {
transform(disk: DiskInfo): string | null {
return disk.guid || disk.partitions.find(p => p.guid)?.guid || null
}
}

View File

@@ -24,6 +24,8 @@ export * from './directives/safe-links/safe-links.module'
export * from './pipes/emver/emver.module'
export * from './pipes/emver/emver.pipe'
export * from './pipes/guid/guid.module'
export * from './pipes/guid/guid.pipe'
export * from './pipes/markdown/markdown.module'
export * from './pipes/markdown/markdown.pipe'
export * from './pipes/shared/shared.module'

View File

@@ -32,6 +32,7 @@ export interface PartitionInfo {
capacity: number
used: number | null
'embassy-os': EmbassyOSDiskInfo | null
guid: string | null
}
export type EmbassyOSDiskInfo = {