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 { FormsModule } from '@angular/forms'
import { HomePage } from './home.page' import { HomePage } from './home.page'
import { SwiperModule } from 'swiper/angular' import { SwiperModule } from 'swiper/angular'
import { UnitConversionPipesModule } from '@start9labs/shared' import {
UnitConversionPipesModule,
GuidPipePipesModule,
} from '@start9labs/shared'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -22,6 +25,7 @@ const routes: Routes = [
RouterModule.forChild(routes), RouterModule.forChild(routes),
SwiperModule, SwiperModule,
UnitConversionPipesModule, UnitConversionPipesModule,
GuidPipePipesModule,
], ],
declarations: [HomePage], declarations: [HomePage],
}) })

View File

@@ -27,6 +27,7 @@
</ion-card-header> </ion-card-header>
<ion-card-content class="ion-margin-bottom"> <ion-card-content class="ion-margin-bottom">
<swiper (swiper)="setSwiperInstance($event)"> <swiper (swiper)="setSwiperInstance($event)">
<!-- SLIDE 1 -->
<ng-template swiperSlide> <ng-template swiperSlide>
<ion-item <ion-item
*ngFor="let disk of disks" *ngFor="let disk of disks"
@@ -51,43 +52,53 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
</ng-template> </ng-template>
<!-- SLIDE 2 -->
<ng-template swiperSlide> <ng-template swiperSlide>
<ion-item <ng-container *ngIf="selectedDisk">
*ngIf="!!selectedDisk?.guid" <!-- re-install -->
button <ion-item
(click)="tryInstall(false)" *ngIf="selectedDisk | guid"
> button
<ion-icon (click)="tryInstall(false)"
color="dark" >
slot="start" <ion-icon
size="large" color="dark"
name="medkit-outline" slot="start"
></ion-icon> size="large"
<ion-label> name="medkit-outline"
<h1> ></ion-icon>
<ion-text color="success">Re-Install embassyOS</ion-text> <ion-label>
</h1> <h1>
<h2>Will preserve existing embassyOS data</h2> <ion-text color="success"
</ion-label> >Re-Install embassyOS</ion-text
</ion-item> >
<ion-item button lines="none" (click)="tryInstall(true)"> </h1>
<ion-icon <h2>Will preserve existing embassyOS data</h2>
color="dark" </ion-label>
slot="start" </ion-item>
size="large"
name="download-outline" <!-- fresh install -->
></ion-icon> <ion-item button lines="none" (click)="tryInstall(true)">
<ion-label> <ion-icon
<h1> color="dark"
<ion-text slot="start"
[color]="!!selectedDisk?.guid ? 'danger' : 'success'" size="large"
>{{ !!selectedDisk?.guid ? 'Factory Reset' : 'Install name="download-outline"
embassyOS' }}</ion-text ></ion-icon>
> <ion-label>
</h1> <h1>
<h2>Will delete existing data on disk</h2> <ion-text
</ion-label> [color]="(selectedDisk | guid) ? 'danger' : 'success'"
</ion-item> >
{{ (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> </ng-template>
</swiper> </swiper>
</ion-card-content> </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', '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null, 'wrapped-key': null,
}, },
guid: null,
}, },
], ],
capacity: 123456789123, capacity: 123456789123,
@@ -40,15 +41,39 @@ export class MockApiService implements ApiService {
capacity: 73264762332, capacity: 73264762332,
used: null, used: null,
'embassy-os': { 'embassy-os': {
version: '0.3.1', version: '0.3.3',
full: true, full: true,
'password-hash': 'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ', '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null, '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, guid: null,
}, },
] ]

View File

@@ -8,6 +8,11 @@ const routes: Routes = [
loadChildren: () => loadChildren: () =>
import('./pages/home/home.module').then(m => m.HomePageModule), import('./pages/home/home.module').then(m => m.HomePageModule),
}, },
{
path: 'attach',
loadChildren: () =>
import('./pages/attach/attach.module').then(m => m.AttachPageModule),
},
{ {
path: 'recover', path: 'recover',
loadChildren: () => 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 { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { UnitConversionPipesModule } from '@start9labs/shared' import {
GuidPipePipesModule,
UnitConversionPipesModule,
} from '@start9labs/shared'
import { EmbassyPage } from './embassy.page' import { EmbassyPage } from './embassy.page'
import { PasswordPageModule } from '../../modals/password/password.module' import { PasswordPageModule } from '../../modals/password/password.module'
import { EmbassyPageRoutingModule } from './embassy-routing.module' import { EmbassyPageRoutingModule } from './embassy-routing.module'
@@ -15,6 +18,7 @@ import { EmbassyPageRoutingModule } from './embassy-routing.module'
EmbassyPageRoutingModule, EmbassyPageRoutingModule,
PasswordPageModule, PasswordPageModule,
UnitConversionPipesModule, UnitConversionPipesModule,
GuidPipePipesModule,
], ],
declarations: [EmbassyPage], declarations: [EmbassyPage],
}) })

View File

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

View File

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

View File

@@ -1,14 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { import { IonicSlides } from '@ionic/angular'
AlertController,
IonicSlides,
LoadingController,
ModalController,
NavController,
} from '@ionic/angular'
import { PasswordPage } from 'src/app/modals/password/password.page'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
import { StateService } from 'src/app/services/state.service'
import SwiperCore, { Swiper } from 'swiper' import SwiperCore, { Swiper } from 'swiper'
import { ErrorToastService } from '@start9labs/shared' import { ErrorToastService } from '@start9labs/shared'
@@ -21,35 +13,26 @@ SwiperCore.use([IonicSlides])
}) })
export class HomePage { export class HomePage {
swiper?: Swiper swiper?: Swiper
guid?: string | null
error = false error = false
loaded = false loading = true
constructor( constructor(
private readonly api: ApiService, 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, private readonly errToastService: ErrorToastService,
) {} ) {}
async ngOnInit() { async ionViewDidEnter() {
if (this.swiper) {
this.swiper.allowTouchMove = false
}
try { try {
await this.api.getPubKey() await this.api.getPubKey()
const disks = await this.api.getDrives()
this.guid = disks.find(d => !!d.guid)?.guid
} catch (e: any) { } catch (e: any) {
this.error = true this.error = true
this.errToastService.present(e) this.errToastService.present(e)
} } finally {
} this.loading = false
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
} }
} }
@@ -64,41 +47,4 @@ export class HomePage {
previous() { previous() {
this.swiper?.slidePrev(500) 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 { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { UnitConversionPipesModule } from '@start9labs/shared' import {
GuidPipePipesModule,
UnitConversionPipesModule,
} from '@start9labs/shared'
import { TransferPage } from './transfer.page' import { TransferPage } from './transfer.page'
import { TransferPageRoutingModule } from './transfer-routing.module' import { TransferPageRoutingModule } from './transfer-routing.module'
@@ -12,6 +15,7 @@ import { TransferPageRoutingModule } from './transfer-routing.module'
IonicModule, IonicModule,
TransferPageRoutingModule, TransferPageRoutingModule,
UnitConversionPipesModule, UnitConversionPipesModule,
GuidPipePipesModule,
], ],
}) })
export class TransferPageModule {} export class TransferPageModule {}

View File

@@ -10,7 +10,7 @@
<ion-card-header class="ion-text-center"> <ion-card-header class="ion-text-center">
<ion-card-title>Transfer</ion-card-title> <ion-card-title>Transfer</ion-card-title>
<ion-card-subtitle <ion-card-subtitle
>Select the physical drive containing your previous Embassy >Select the physical drive containing your Embassy
data</ion-card-subtitle data</ion-card-subtitle
> >
</ion-card-header> </ion-card-header>
@@ -28,7 +28,12 @@
<h2 class="target-label">Available Drives</h2> <h2 class="target-label">Available Drives</h2>
<ng-container *ngFor="let drive of drives"> <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 <ion-icon
slot="start" slot="start"
name="save-outline" name="save-outline"

View File

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

View File

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

View File

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