Feature/setup migrate (#1841)

* add migrate component

* finish out migrate page and adjust recover options

* fix typo

* rename migrate -> transfer, adjust copy and typos, update transfer component logic

* add alert for old drive data when transferring

* comments for clarity

* auto adjust swiper slide height

* cleanup uneeded imports from transfer module

* pr feedback suggestions

* remove 02x from setup wiz

* clean up copy/styling for recover flow

* add support for migrating from old drive

* add RecoverySource lifted type

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Lucy C
2022-11-01 09:00:25 -06:00
committed by Aiden McClelland
parent 1d151d8fa6
commit 74af03408f
25 changed files with 502 additions and 695 deletions

View File

@@ -13,6 +13,13 @@ const routes: Routes = [
loadChildren: () =>
import('./pages/recover/recover.module').then(m => m.RecoverPageModule),
},
{
path: 'transfer',
loadChildren: () =>
import('./pages/transfer/transfer.module').then(
m => m.TransferPageModule,
),
},
{
path: 'embassy',
loadChildren: () =>

View File

@@ -16,6 +16,7 @@ import { SuccessPageModule } from './pages/success/success.module'
import { HomePageModule } from './pages/home/home.module'
import { LoadingPageModule } from './pages/loading/loading.module'
import { RecoverPageModule } from './pages/recover/recover.module'
import { TransferPageModule } from './pages/transfer/transfer.module'
import { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared'
const {
@@ -37,6 +38,7 @@ const {
HomePageModule,
LoadingPageModule,
RecoverPageModule,
TransferPageModule,
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },

View File

@@ -53,18 +53,7 @@ export class CifsModal {
await loader.dismiss()
const is02x = embassyOS.version.startsWith('0.2')
if (is02x) {
this.modalController.dismiss(
{
cifs: this.cifs,
},
'success',
)
} else {
this.presentModalPassword(embassyOS)
}
this.presentModalPassword(embassyOS)
} catch (e) {
await loader.dismiss()
this.presentAlertFailed()

View File

@@ -14,9 +14,6 @@
Choose a password for your Embassy.
<i>Make it good. Write it down.</i>
</p>
<p style="color: var(--ion-color-warning)">
Losing your password can result in total loss of data.
</p>
</ng-template>
<p *ngIf="!storageDrive else choose">
Enter the password that was used to encrypt this drive.

View File

@@ -25,8 +25,8 @@
>
<ion-card-title>No drives found</ion-card-title>
<ion-card-subtitle
>Please connect a storage drive to your Embassy and click
"Refresh".</ion-card-subtitle
>Please connect an external storage drive to your Embassy, if
applicable. Next, click "Refresh".</ion-card-subtitle
>
</ion-card-header>
</ng-template>

View File

@@ -7,12 +7,14 @@ import {
} from '@ionic/angular'
import {
ApiService,
BackupRecoverySource,
DiskInfo,
DiskRecoverySource,
} from 'src/app/services/api/api.service'
import { ErrorToastService } from '@start9labs/shared'
import { StateService } from 'src/app/services/state.service'
import { PasswordPage } from '../../modals/password/password.page'
import { ActivatedRoute } from '@angular/router'
@Component({
selector: 'app-embassy',
@@ -31,6 +33,7 @@ export class EmbassyPage {
private readonly stateService: StateService,
private readonly loadingCtrl: LoadingController,
private readonly errorToastService: ErrorToastService,
private route: ActivatedRoute,
) {}
async ngOnInit() {
@@ -55,8 +58,10 @@ export class EmbassyPage {
!d.partitions
.map(p => p.logicalname)
.includes(
(this.stateService.recoverySource as DiskRecoverySource)
?.logicalname,
(
(this.stateService.recoverySource as BackupRecoverySource)
?.target as DiskRecoverySource
)?.logicalname,
),
)
} catch (e: any) {
@@ -80,12 +85,14 @@ export class EmbassyPage {
{
text: 'Continue',
handler: () => {
// for backup recoveries
if (this.stateService.recoveryPassword) {
this.setupEmbassy(
drive.logicalname,
this.stateService.recoveryPassword,
)
} else {
// for migrations and fresh setups
this.presentModalPassword(drive.logicalname)
}
},
@@ -94,9 +101,11 @@ export class EmbassyPage {
})
await alert.present()
} else {
// for backup recoveries
if (this.stateService.recoveryPassword) {
this.setupEmbassy(drive.logicalname, this.stateService.recoveryPassword)
} else {
// for migrations and fresh setups
this.presentModalPassword(drive.logicalname)
}
}
@@ -129,7 +138,9 @@ export class EmbassyPage {
try {
await this.stateService.setupEmbassy(logicalname, password)
if (!!this.stateService.recoverySource) {
await this.navCtrl.navigateForward(`/loading`)
await this.navCtrl.navigateForward(`/loading`, {
queryParams: { action: this.route.snapshot.paramMap.get('action') },
})
} else {
await this.navCtrl.navigateForward(`/success`)
}

View File

@@ -1,4 +1,4 @@
<ion-content>
<ion-content *ngIf="loaded">
<ion-grid>
<ion-row>
<ion-col class="ion-text-center">
@@ -23,7 +23,7 @@
</ion-card-title>
</ion-card-header>
<ion-card-content class="ion-margin-bottom">
<swiper (swiper)="setSwiperInstance($event)">
<swiper [autoHeight]="true" (swiper)="setSwiperInstance($event)">
<ng-template swiperSlide>
<ion-item
button
@@ -47,24 +47,30 @@
<ion-icon color="dark" slot="start" name="reload"></ion-icon>
<ion-label>
<h2><ion-text color="danger">Recover</ion-text></h2>
<p>
Restore from backup or use an existing Embassy data drive
</p>
<p>Recover, restore, or transfer Embassy data</p>
</ion-label>
</ion-item>
</ng-template>
<ng-template swiperSlide>
<ion-item button detail="true" routerLink="/recover">
<ion-icon color="dark" slot="start" name="save"></ion-icon>
<ion-icon
color="dark"
slot="start"
name="save-outline"
></ion-icon>
<ion-label>
<h2>
<ion-text color="warning">Restore From Backup</ion-text>
</h2>
<p>Recover an Embassy from encrypted backup</p>
<p>Recover an Embassy from an encrypted backup</p>
</ion-label>
</ion-item>
<ion-item button detail="true" lines="none" (click)="import()">
<ion-icon color="dark" slot="start" name="cube"></ion-icon>
<ion-icon
color="dark"
slot="start"
name="cube-outline"
></ion-icon>
<ion-label>
<h2>
<ion-text color="primary">Use Existing Drive</ion-text>
@@ -72,6 +78,27 @@
<p>Attach and use a valid Embassy data drive</p>
</ion-label>
</ion-item>
<ion-item
button
detail="true"
lines="none"
routerLink="/transfer"
>
<ion-icon
color="dark"
slot="start"
name="share-outline"
></ion-icon>
<ion-label>
<h2>
<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)
</p>
</ion-label>
</ion-item>
</ng-template>
</swiper>
</ion-card-content>

View File

@@ -23,6 +23,7 @@ export class HomePage {
swiper?: Swiper
guid?: string | null
error = false
loaded = false
constructor(
private readonly api: ApiService,
@@ -46,6 +47,7 @@ export class HomePage {
}
async ionViewDidEnter() {
this.loaded = true // needed to accomodate autoHight="true" on swiper. Otherwise Swiper height might be 0 when navigatging *to* this page from later page. Happens on refresh.
if (this.swiper) {
this.swiper.allowTouchMove = false
}
@@ -100,11 +102,3 @@ export class HomePage {
}
}
}
function decodeHex(hex: string) {
let str = ''
for (let n = 0; n < hex.length; n += 2) {
str += String.fromCharCode(parseInt(hex.substring(n, 2), 16))
}
return str
}

View File

@@ -8,7 +8,10 @@
<ion-card color="dark">
<ion-card-header>
<ion-card-title style="font-size: 40px">Recovering</ion-card-title>
<ion-card-title style="font-size: 40px">
<span *ngIf="incomingAction === 'transfer'">Transferring</span>
<span *ngIf="incomingAction === 'recover'">Recovering</span>
</ion-card-title>
<ion-card-subtitle
>Progress: {{ (stateService.dataProgress * 100).toFixed(0)
}}%</ion-card-subtitle

View File

@@ -1,4 +1,5 @@
import { Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { NavController } from '@ionic/angular'
import { StateService } from 'src/app/services/state.service'
@@ -8,12 +9,16 @@ import { StateService } from 'src/app/services/state.service'
styleUrls: ['loading.page.scss'],
})
export class LoadingPage {
incomingAction!: string
constructor(
public stateService: StateService,
private navCtrl: NavController,
private route: ActivatedRoute,
) {}
ngOnInit() {
this.incomingAction = this.route.snapshot.paramMap.get('action')!
this.stateService.pollDataTransferProgress()
const progSub = this.stateService.dataCompletionSubject.subscribe(
async complete => {

View File

@@ -2,7 +2,7 @@
<!-- has backup -->
<h2 *ngIf="hasValidBackup; else noBackup">
<ion-icon name="cloud-done" color="success"></ion-icon>
{{ is02x ? 'Embassy 0.2.x detected' : 'Embassy backup detected' }}
Embassy backup detected
</h2>
<!-- no backup -->
<ng-template #noBackup>
@@ -11,4 +11,4 @@
No Embassy backup
</h2>
</ng-template>
</div>
</div>

View File

@@ -8,11 +8,7 @@
<ion-card color="dark">
<ion-card-header class="ion-text-center">
<ion-card-title>Recover</ion-card-title>
<ion-card-subtitle
>Select the LAN Shared Folder or physical drive containing your
Embassy backup</ion-card-subtitle
>
<ion-card-title>Restore from Backup</ion-card-title>
</ion-card-header>
<ion-card-content class="ion-margin">
@@ -25,17 +21,10 @@
<!-- loaded -->
<ion-item-group *ngIf="!loading" class="ion-text-center">
<!-- cifs -->
<h2 class="target-label">LAN Shared Folder</h2>
<p class="ion-padding-bottom">
Using a LAN Shared Folder is the recommended way to recover from
backup, since it works with all Embassy hardware configurations.
View the
<a
href="https://start9.com/latest/user-manual/backups/backup-restore"
target="_blank"
noreferrer
>instructions</a
>.
<h2 class="target-label">Network Folder</h2>
<p class="ion-padding-bottom ion-text-left">
Restore your Embassy from a folder on another computer that is
connected to the same network as your Embassy.
</p>
<!-- connect -->
@@ -47,7 +36,7 @@
color="light"
></ion-icon>
<ion-label>
<b>Open New</b>
<b>Open</b>
</ion-label>
</ion-item>
@@ -55,18 +44,20 @@
<br />
<!-- drives -->
<h2 class="target-label">Physical Drives</h2>
<p class="ion-padding-bottom">
Warning! Plugging in more than one physical drive to Embassy can
lead to power failure and data corruption. To recover from a
physical drive, please follow the
<a
href="https://start9.com/latest/user-manual/backups/backup-restore"
target="_blank"
noreferrer
>instructions</a
>.
</p>
<h2 class="target-label">Physical Drive</h2>
<div class="ion-text-left ion-padding-bottom">
<p>
Restore your Emabssy from a physcial drive that is plugged
directly into your Embassy.
</p>
<br />
<b>
Warning. Do not use this option if you are using a Raspberry
Pi with an external SSD as your main data drive. The Raspberry
Pi cannot not support more than one external drive without
additional power and can cause data corruption.
</b>
</div>
<ng-container *ngFor="let mapped of mappedDrives">
<ion-item
@@ -86,7 +77,6 @@
<h1>{{ drive.label || drive.logicalname }}</h1>
<drive-status
[hasValidBackup]="mapped.hasValidBackup"
[is02x]="mapped.is02x"
></drive-status>
<p>
{{ drive.vendor || 'Unknown Vendor' }} - {{ drive.model ||

View File

@@ -56,7 +56,6 @@ export class RecoverPage {
}
this.mappedDrives.push({
hasValidBackup: !!p['embassy-os']?.full,
is02x: !!drive['embassy-os']?.version.startsWith('0.2'),
drive,
})
})
@@ -76,11 +75,14 @@ export class RecoverPage {
if (res.role === 'success') {
const { hostname, path, username, password } = res.data.cifs
this.stateService.recoverySource = {
type: 'cifs',
hostname,
path,
username,
password,
type: 'backup',
target: {
type: 'cifs',
hostname,
path,
username,
password,
},
}
this.stateService.recoveryPassword = res.data.recoveryPassword
this.navCtrl.navigateForward('/embassy')
@@ -90,35 +92,35 @@ export class RecoverPage {
}
async select(target: DiskBackupTarget) {
const is02x = target['embassy-os']?.version.startsWith('0.2')
const { logicalname } = target
if (!logicalname) return
if (is02x) {
this.selectRecoverySource(logicalname)
} else {
const modal = await this.modalController.create({
component: PasswordPage,
componentProps: { target },
cssClass: 'alertlike-modal',
})
modal.onDidDismiss().then(res => {
if (res.data?.password) {
this.selectRecoverySource(logicalname, res.data.password)
}
})
await modal.present()
}
const modal = await this.modalController.create({
component: PasswordPage,
componentProps: { target },
cssClass: 'alertlike-modal',
})
modal.onDidDismiss().then(res => {
if (res.data?.password) {
this.selectRecoverySource(logicalname, res.data.password)
}
})
await modal.present()
}
private async selectRecoverySource(logicalname: string, password?: string) {
this.stateService.recoverySource = {
type: 'disk',
logicalname,
type: 'backup',
target: {
type: 'disk',
logicalname,
},
}
this.stateService.recoveryPassword = password
this.navCtrl.navigateForward(`/embassy`)
this.navCtrl.navigateForward(`/embassy`, {
queryParams: { action: 'recover' },
})
}
}
@@ -129,11 +131,9 @@ export class RecoverPage {
})
export class DriveStatusComponent {
@Input() hasValidBackup!: boolean
@Input() is02x!: boolean
}
interface MappedDisk {
is02x: boolean
hasValidBackup: boolean
drive: DiskBackupTarget
}

View File

@@ -11,7 +11,7 @@
<ion-card-title>Setup Complete</ion-card-title>
<ion-card-subtitle
><b
>You have successully claimed your Embassy!</b
>You have successfully claimed your Embassy!</b
></ion-card-subtitle
>
<br />
@@ -19,11 +19,13 @@
<ion-card-content>
<br />
<br />
<h2
*ngIf="recoverySource && recoverySource.type === 'disk'"
class="ion-padding-bottom"
>
You can now safely unplug your backup drive.
<h2 *ngIf="recoverySource" class="ion-padding-bottom">
<span *ngIf="recoverySource.type === 'backup'"
>You can now safely unplug your backup drive.</span
>
<span *ngIf="recoverySource.type === 'migrate'"
>You can now safely unplug your old drive.</span
>
</h2>
<h2 style="font-weight: bold">
Access your Embassy using the methods below. You should
@@ -40,7 +42,7 @@
<div class="ion-padding ion-text-start">
<p>
Visit the address below when you are conncted to the same WiFi
Visit the address below when you are connected to the same WiFi
or Local Area Network (LAN) as your Embassy:
</p>
@@ -94,7 +96,7 @@
follow the instructions
<ion-icon name="open-outline"></ion-icon>
</a>
to downlaod and trust your Embassy's Root Certificate Authority.
to download and trust your Embassy's Root Certificate Authority.
</p>
<ion-button style="margin-top: 24px" (click)="installCert()">

View File

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

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { UnitConversionPipesModule } from '@start9labs/shared'
import { TransferPage } from './transfer.page'
import { TransferPageRoutingModule } from './transfer-routing.module'
@NgModule({
declarations: [TransferPage],
imports: [
CommonModule,
IonicModule,
TransferPageRoutingModule,
UnitConversionPipesModule,
],
})
export class TransferPageModule {}

View File

@@ -0,0 +1,63 @@
<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>Transfer</ion-card-title>
<ion-card-subtitle
>Select the physical drive containing your previous 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>
<ng-container *ngFor="let drive of drives">
<ion-item button (click)="select(drive)" 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,74 @@
import { Component } from '@angular/core'
import { AlertController, NavController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service'
import { ErrorToastService } from '@start9labs/shared'
import { StateService } from 'src/app/services/state.service'
@Component({
selector: 'app-transfer',
templateUrl: 'transfer.page.html',
styleUrls: ['transfer.page.scss'],
})
export class TransferPage {
loading = true
drives: DiskInfo[] = []
constructor(
private readonly apiService: ApiService,
private readonly navCtrl: NavController,
private readonly alertCtrl: AlertController,
private readonly errToastService: ErrorToastService,
private readonly stateService: StateService,
) {}
async ngOnInit() {
await this.getDrives()
}
async refresh() {
this.loading = true
await this.getDrives()
}
async getDrives() {
try {
const disks = await this.apiService.getDrives()
this.drives = disks.filter(d => d.partitions.length && d.guid)
} catch (e: any) {
this.errToastService.present(e)
} finally {
this.loading = false
}
}
async select(target: DiskInfo) {
const { logicalname, guid } = target
if (!logicalname) return
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
'Data from this drive will not be deleted, but you will not be able to use this drive to run embassyOS after the data is transferred. Attempting to use this drive after data is transferred could cause transferred services to not function properly, and may cause data corruption.',
buttons: [
{
role: 'cancel',
text: 'Cancel',
},
{
text: 'Continue',
handler: () => {
this.stateService.recoverySource = {
type: 'migrate',
guid: guid!,
}
this.navCtrl.navigateForward(`/embassy`, {
queryParams: { action: 'transfer' },
})
},
},
],
})
await alert.present()
}
}

View File

@@ -5,7 +5,6 @@ export abstract class ApiService {
abstract getStatus(): Promise<GetStatusRes> // setup.status
abstract getPubKey(): Promise<void> // setup.get-pubkey
abstract getDrives(): Promise<DiskListResponse> // setup.disk.list
abstract set02XDrive(logicalname: string): Promise<void> // setup.recovery.v2.set
abstract getRecoveryStatus(): Promise<RecoveryStatusRes> // setup.recovery.status
abstract verifyCifs(cifs: CifsRecoverySource): Promise<EmbassyOSRecoveryInfo> // setup.cifs.verify
abstract importDrive(importInfo: ImportDriveReq): Promise<SetupEmbassyRes> // setup.attach
@@ -39,7 +38,7 @@ export type ImportDriveReq = {
export type SetupEmbassyReq = {
'embassy-logicalname': string
'embassy-password': Encrypted
'recovery-source': CifsRecoverySource | DiskRecoverySource | null
'recovery-source': RecoverySource | null
'recovery-password': Encrypted | null
}
@@ -81,6 +80,17 @@ export type DiskRecoverySource = {
logicalname: string // partition logicalname
}
export type BackupRecoverySource = {
type: 'backup'
target: CifsRecoverySource | DiskRecoverySource
}
export type RecoverySource = BackupRecoverySource | DiskMigrateSource
export type DiskMigrateSource = {
type: 'migrate'
guid: string
}
export type CifsRecoverySource = {
type: 'cifs'
hostname: string
@@ -95,7 +105,7 @@ export type DiskInfo = {
model: string | null
partitions: PartitionInfo[]
capacity: number
guid: string | null // cant back up if guid exists
guid: string | null // cant back up if guid exists, but needed if migrating
}
export type RecoveryStatusRes = {

View File

@@ -10,6 +10,7 @@ import {
ApiService,
CifsRecoverySource,
DiskListResponse,
DiskMigrateSource,
DiskRecoverySource,
EmbassyOSRecoveryInfo,
GetStatusRes,
@@ -58,13 +59,6 @@ export class LiveApiService extends ApiService {
})
}
async set02XDrive(logicalname: string) {
return this.rpcRequest<void>({
method: 'setup.recovery.v2.set',
params: { logicalname },
})
}
async getRecoveryStatus() {
return this.rpcRequest<RecoveryStatusRes>({
method: 'setup.recovery.status',
@@ -93,10 +87,12 @@ export class LiveApiService extends ApiService {
}
async setupEmbassy(setupInfo: SetupEmbassyReq) {
if (isCifsSource(setupInfo['recovery-source'])) {
setupInfo['recovery-source'].path = setupInfo[
'recovery-source'
].path.replace('/\\/g', '/')
if (setupInfo['recovery-source']?.type === 'backup') {
if (isCifsSource(setupInfo['recovery-source'].target)) {
setupInfo['recovery-source'].target.path = setupInfo[
'recovery-source'
].target.path.replace('/\\/g', '/')
}
}
const res = await this.rpcRequest<SetupEmbassyRes>({

View File

@@ -54,7 +54,30 @@ export class MockApiService extends ApiService {
'embassy-os': {
version: '0.2.17',
full: true,
'password-hash': null,
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
},
],
capacity: 123456789123,
guid: 'uuid-uuid-uuid-uuid',
},
{
logicalname: 'dcba',
vendor: 'Samsung',
model: 'T5',
partitions: [
{
logicalname: 'pbcba',
label: null,
capacity: 73264762332,
used: null,
'embassy-os': {
version: '0.3.1',
full: true,
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': null,
},
},
@@ -65,11 +88,6 @@ export class MockApiService extends ApiService {
]
}
async set02XDrive() {
await pauseFor(1000)
return
}
async getRecoveryStatus() {
tries = Math.min(tries + 1, 4)
return {
@@ -134,105 +152,3 @@ const setupRes = {
'lan-address': 'https://embassy-abcdefgh.local',
'root-ca': encodeBase64(rootCA),
}
const disks = [
{
vendor: 'Samsung',
model: 'SATA',
logicalname: '/dev/sda',
guid: 'theguid',
partitions: [
{
logicalname: 'sda1',
label: 'label 1',
capacity: 100000,
used: 200.1255312,
'embassy-os': null,
},
{
logicalname: 'sda2',
label: 'label 2',
capacity: 50000,
used: 200.1255312,
'embassy-os': null,
},
],
capacity: 150000,
},
{
vendor: 'Samsung',
model: null,
logicalname: 'dev/sdb',
partitions: [],
capacity: 34359738369,
guid: null,
},
{
vendor: 'Crucial',
model: 'MX500',
logicalname: 'dev/sdc',
guid: null,
partitions: [
{
logicalname: 'sdc1',
label: 'label 1',
capacity: 0,
used: null,
'embassy-os': {
version: '0.3.3',
full: true,
'password-hash': 'asdfasdfasdf',
'wrapped-key': '',
},
},
{
logicalname: 'sdc1MOCKTESTER',
label: 'label 1',
capacity: 0,
used: null,
'embassy-os': {
version: '0.3.6',
full: true,
// password is 'asdfasdf'
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': '',
},
},
{
logicalname: 'sdc1',
label: 'label 1',
capacity: 0,
used: null,
'embassy-os': {
version: '0.3.3',
full: false,
'password-hash': 'asdfasdfasdf',
'wrapped-key': '',
},
},
],
capacity: 100000,
},
{
vendor: 'Sandisk',
model: null,
logicalname: '/dev/sdd',
guid: null,
partitions: [
{
logicalname: 'sdd1',
label: null,
capacity: 10000,
used: null,
'embassy-os': {
version: '0.2.7',
full: true,
'password-hash': 'asdfasdfasdf',
'wrapped-key': '',
},
},
],
capacity: 10000,
},
]

View File

@@ -1,10 +1,6 @@
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'
import {
ApiService,
CifsRecoverySource,
DiskRecoverySource,
} from './api/api.service'
import { ApiService, RecoverySource } from './api/api.service'
import { pauseFor, ErrorToastService } from '@start9labs/shared'
@Injectable({
@@ -14,7 +10,7 @@ export class StateService {
polling = false
embassyLoaded = false
recoverySource?: CifsRecoverySource | DiskRecoverySource
recoverySource?: RecoverySource
recoveryPassword?: string
dataTransferProgress?: {

View File

@@ -70,14 +70,14 @@ export class BackupDrivesComponent {
): void {
if (target.entry.type === 'cifs' && !target.entry.mountable) {
const message =
'Unable to connect to LAN Shared Folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.'
'Unable to connect to Network Folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.'
this.presentAlertError(message)
return
}
if (this.type === 'restore' && !target.hasValidBackup) {
const message = `${
target.entry.type === 'cifs' ? 'LAN Shared Folder' : 'Drive partition'
target.entry.type === 'cifs' ? 'Network Folder' : 'Drive partition'
} does not contain a valid Embassy backup.`
this.presentAlertError(message)
return
@@ -90,7 +90,7 @@ export class BackupDrivesComponent {
const modal = await this.modalCtrl.create({
component: GenericFormPage,
componentProps: {
title: 'New LAN Shared Folder',
title: 'New Network Folder',
spec: CifsSpec,
buttons: [
{