mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
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:
committed by
Aiden McClelland
parent
22b273b145
commit
45a6a930c9
@@ -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],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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: () =>
|
||||||
|
|||||||
@@ -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 {}
|
||||||
@@ -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 {}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.target-label {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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!',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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' },
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
8
frontend/projects/shared/src/pipes/guid/guid.module.ts
Normal file
8
frontend/projects/shared/src/pipes/guid/guid.module.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { GuidPipe } from './guid.pipe'
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [GuidPipe],
|
||||||
|
exports: [GuidPipe],
|
||||||
|
})
|
||||||
|
export class GuidPipePipesModule {}
|
||||||
11
frontend/projects/shared/src/pipes/guid/guid.pipe.ts
Normal file
11
frontend/projects/shared/src/pipes/guid/guid.pipe.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user