diff --git a/setup-wizard/src/app/pages/password/password.page.html b/setup-wizard/src/app/pages/password/password.page.html
index 4bb43db1c..264113861 100644
--- a/setup-wizard/src/app/pages/password/password.page.html
+++ b/setup-wizard/src/app/pages/password/password.page.html
@@ -25,6 +25,7 @@
[class]="pwError ? 'error-border' : password && !!storageDrive ? 'success-border' : ''"
>
this.elem.setFocus(), 400)
+ }
+
async verifyPw () {
if (!this.recoveryPartition || !this.recoveryPartition['embassy-os']) this.pwError = 'No recovery drive' // unreachable
diff --git a/setup-wizard/src/app/pages/prod-key-modal/prod-key-modal.page.html b/setup-wizard/src/app/pages/prod-key-modal/prod-key-modal.page.html
index 7e9ce9c55..aa0d991ce 100644
--- a/setup-wizard/src/app/pages/prod-key-modal/prod-key-modal.page.html
+++ b/setup-wizard/src/app/pages/prod-key-modal/prod-key-modal.page.html
@@ -17,6 +17,7 @@
[class]="error ? 'error-border' : ''"
>
Product Key
{{ error }}
diff --git a/setup-wizard/src/app/pages/product-key/product-key.page.ts b/setup-wizard/src/app/pages/product-key/product-key.page.ts
index dea2195d8..16a0b28cd 100644
--- a/setup-wizard/src/app/pages/product-key/product-key.page.ts
+++ b/setup-wizard/src/app/pages/product-key/product-key.page.ts
@@ -1,5 +1,5 @@
-import { Component } from '@angular/core'
-import { LoadingController, NavController } from '@ionic/angular'
+import { Component, ViewChild } from '@angular/core'
+import { IonInput, LoadingController, NavController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { HttpService } from 'src/app/services/api/http.service'
import { StateService } from 'src/app/services/state.service'
@@ -10,6 +10,7 @@ import { StateService } from 'src/app/services/state.service'
styleUrls: ['product-key.page.scss'],
})
export class ProductKeyPage {
+ @ViewChild('focusInput', { static: false }) elem: IonInput
productKey: string
error: string
@@ -21,6 +22,10 @@ export class ProductKeyPage {
private readonly httpService: HttpService,
) { }
+ ngAfterViewInit () {
+ setTimeout(() => this.elem.setFocus(), 400)
+ }
+
async submit () {
if (!this.productKey) return this.error = 'Must enter product key'
diff --git a/setup-wizard/src/app/pages/recover/recover.page.html b/setup-wizard/src/app/pages/recover/recover.page.html
index 1e6a5dbf7..e12095b2d 100644
--- a/setup-wizard/src/app/pages/recover/recover.page.html
+++ b/setup-wizard/src/app/pages/recover/recover.page.html
@@ -15,7 +15,7 @@
-
+
No recovery drives found
Please connect a recovery drive to your Embassy and refresh the page.
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
- {{ p.partition.logicalname }} - {{ p.partition.label }}
-
- {{ p.vendor }}
- -
- {{ p.model }}
-
- Embassy version: {{p.partition['embassy-os'].version}}
-
-
-
+
+
+
+ {{ drive.vendor || 'Unknown Vendor' }} - {{ drive.model || 'Unknown Model' }} - {{ drive.capacity | convertBytes }}
+
+
+
+
+ {{ partition.label || partition.logicalname }}
+ {{ drive.capacity | convertBytes }}
+
+
+ Embassy backups detected
+
+
+
+
+
+
+
-
-
- Next
-
+
diff --git a/setup-wizard/src/app/pages/recover/recover.page.ts b/setup-wizard/src/app/pages/recover/recover.page.ts
index 2616b7d10..38fc22958 100644
--- a/setup-wizard/src/app/pages/recover/recover.page.ts
+++ b/setup-wizard/src/app/pages/recover/recover.page.ts
@@ -1,6 +1,6 @@
import { Component } from '@angular/core'
-import { ModalController, NavController } from '@ionic/angular'
-import { ApiService, PartitionInfo } from 'src/app/services/api/api.service'
+import { AlertController, LoadingController, ModalController, NavController } from '@ionic/angular'
+import { ApiService, DiskInfo, PartitionInfo } from 'src/app/services/api/api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { StateService } from 'src/app/services/state.service'
import { PasswordPage } from '../password/password.page'
@@ -12,39 +12,60 @@ import { ProdKeyModal } from '../prod-key-modal/prod-key-modal.page'
styleUrls: ['recover.page.scss'],
})
export class RecoverPage {
- passwords = { }
- prodKeys = { }
- recoveryPartitions: { partition: PartitionInfo, model: string, vendor: string }[] = []
selectedPartition: PartitionInfo = null
loading = true
+ drives: DiskInfo[] = []
+ hasShownGuidAlert = false
constructor (
private readonly apiService: ApiService,
private readonly navCtrl: NavController,
private readonly modalController: ModalController,
+ private readonly alertCtrl: AlertController,
+ private readonly loadingCtrl: LoadingController,
readonly stateService: StateService,
private readonly errorToastService: ErrorToastService,
) { }
async ngOnInit () {
- await this.getPartitions()
+ await this.getDrives()
}
async refresh () {
- this.recoveryPartitions = []
this.selectedPartition = null
this.loading = true
- await this.getPartitions()
+ await this.getDrives()
}
- async getPartitions () {
- try {
- let drives = await this.apiService.getDrives()
+ partitionClickable (partition: PartitionInfo) {
+ return partition['embassy-os']?.full && ((!this.stateService.hasProductKey && partition['embassy-os']?.version.startsWith('0.2') ) || this.stateService.hasProductKey)
+ }
- this.recoveryPartitions = drives.map(d => d.partitions.map(p => ({ partition: p, vendor: d.vendor, model: d.model})).filter(p => p.partition['embassy-os']?.full)).flat()
- // if theres no product key, only show 0.2s
- if (!this.stateService.hasProductKey) {
- this.recoveryPartitions = this.recoveryPartitions.filter(p => p.partition['embassy-os']?.version.startsWith('0.2'))
+ async getDrives () {
+ try {
+ this.drives = await this.apiService.getDrives()
+
+ const importableDrive = this.drives.filter(d => !!d.guid)[0]
+ if (!!importableDrive && !this.hasShownGuidAlert) {
+ const alert = await this.alertCtrl.create({
+ header: 'Warning',
+ subHeader: 'Drive contains data!',
+ message: 'All data stored on this drive will be permanently deleted.',
+ buttons: [
+ {
+ role: 'cancel',
+ text: 'Dismiss',
+ },
+ {
+ text: 'Use',
+ handler: async () => {
+ await this.importDrive(importableDrive.guid)
+ },
+ },
+ ],
+ })
+ await alert.present()
+ this.hasShownGuidAlert = true
}
} catch (e) {
this.errorToastService.present(`${e.message}: ${e.data}`)
@@ -53,15 +74,27 @@ export class RecoverPage {
}
}
- async choosePartition (partition: PartitionInfo) {
- if (this.selectedPartition?.logicalname === partition.logicalname) {
- this.selectedPartition = null
- return
- } else {
- this.selectedPartition = partition
+ async importDrive (guid: string) {
+ const loader = await this.loadingCtrl.create({
+ message: 'Importing Drive',
+ })
+ await loader.present()
+ try {
+ await this.stateService.importDrive(guid)
+ await this.navCtrl.navigateForward(`/success`)
+ } catch (e) {
+ this.errorToastService.present(`${e.message}: ${e.data}`)
+ } finally {
+ loader.dismiss()
}
+ }
- if ((partition['embassy-os'].version.startsWith('0.2') && this.stateService.hasProductKey) || this.passwords[partition.logicalname] || this.prodKeys[partition.logicalname]) return
+ async choosePartition (partition: PartitionInfo) {
+ this.selectedPartition = partition
+
+ if (partition['embassy-os'].version.startsWith('0.2')) {
+ return this.selectRecoveryPartition()
+ }
if (this.stateService.hasProductKey) {
const modal = await this.modalController.create({
@@ -75,7 +108,7 @@ export class RecoverPage {
if (!ret.data) {
this.selectedPartition = null
} else if (ret.data.password) {
- this.passwords[partition.logicalname] = ret.data.password
+ this.selectRecoveryPartition(ret.data.password)
}
})
@@ -92,7 +125,7 @@ export class RecoverPage {
if (!ret.data) {
this.selectedPartition = null
} else if (ret.data.productKey) {
- this.prodKeys[partition.logicalname] = ret.data.productKey
+ this.selectRecoveryPartition()
}
})
@@ -100,11 +133,10 @@ export class RecoverPage {
}
}
- async selectRecoveryPartition () {
+ async selectRecoveryPartition (password?: string) {
this.stateService.recoveryPartition = this.selectedPartition
- const pw = this.passwords[this.selectedPartition.logicalname]
- if (pw) {
- this.stateService.recoveryPassword = pw
+ if (password) {
+ this.stateService.recoveryPassword = password
}
await this.navCtrl.navigateForward(`/embassy`)
}
diff --git a/setup-wizard/src/app/services/api/api.service.ts b/setup-wizard/src/app/services/api/api.service.ts
index 361ad3658..02e41f00e 100644
--- a/setup-wizard/src/app/services/api/api.service.ts
+++ b/setup-wizard/src/app/services/api/api.service.ts
@@ -8,6 +8,7 @@ export abstract class ApiService {
// encrypted
abstract verifyProductKey (): Promise
// echo - throws error if invalid
abstract verify03XPassword (logicalname: string, password: string): Promise // setup.recovery.test-password
+ abstract importDrive (guid: string): Promise // setup.execute
abstract setupEmbassy (setupInfo: SetupEmbassyReq): Promise // setup.execute
}
@@ -35,6 +36,7 @@ export interface DiskInfo {
model: string | null,
partitions: PartitionInfo[],
capacity: number,
+ guid: string | null, // cant back up if guid exists
}
export interface RecoveryStatusRes {
diff --git a/setup-wizard/src/app/services/api/live-api.service.ts b/setup-wizard/src/app/services/api/live-api.service.ts
index 1a1c63f87..745f7537f 100644
--- a/setup-wizard/src/app/services/api/live-api.service.ts
+++ b/setup-wizard/src/app/services/api/live-api.service.ts
@@ -57,6 +57,13 @@ export class LiveApiService extends ApiService {
})
}
+ async importDrive (guid: string) {
+ return this.http.rpcRequest({
+ method: 'setup.execute',
+ params: { guid },
+ })
+ }
+
async setupEmbassy (setupInfo: SetupEmbassyReq) {
return this.http.rpcRequest({
method: 'setup.execute',
diff --git a/setup-wizard/src/app/services/api/mock-api.service.ts b/setup-wizard/src/app/services/api/mock-api.service.ts
index e67218ba6..0d5152d35 100644
--- a/setup-wizard/src/app/services/api/mock-api.service.ts
+++ b/setup-wizard/src/app/services/api/mock-api.service.ts
@@ -30,6 +30,7 @@ export class MockApiService extends ApiService {
vendor: 'Vendor',
model: 'Model',
logicalname: '/dev/sda',
+ guid: 'theguid',
partitions: [
{
logicalname: 'sda1',
@@ -54,11 +55,13 @@ export class MockApiService extends ApiService {
logicalname: 'dev/sdb',
partitions: [],
capacity: 1600.01234,
+ guid: null,
},
{
vendor: 'Vendor',
model: 'Model',
logicalname: 'dev/sdc',
+ guid: null,
partitions: [
{
logicalname: 'sdc1',
@@ -100,7 +103,8 @@ export class MockApiService extends ApiService {
vendor: 'Vendor',
model: 'Model',
logicalname: '/dev/sdd',
- partitions: [
+ guid: null,
+ partitions: [
{
logicalname: 'sdd1',
label: null,
@@ -143,6 +147,15 @@ export class MockApiService extends ApiService {
return password.length > 8
}
+ async importDrive (guid: string) {
+ await pauseFor(3000)
+ return {
+ 'tor-address': 'asdfasdfasdf.onion',
+ 'lan-address': 'embassy-dfasdf.local',
+ 'root-ca': rootCA,
+ }
+ }
+
async setupEmbassy (setupInfo: SetupEmbassyReq) {
await pauseFor(3000)
return {
diff --git a/setup-wizard/src/app/services/state.service.ts b/setup-wizard/src/app/services/state.service.ts
index 6df7147ea..0398581e9 100644
--- a/setup-wizard/src/app/services/state.service.ts
+++ b/setup-wizard/src/app/services/state.service.ts
@@ -59,6 +59,14 @@ export class StateService {
this.pollDataTransferProgress()
}
+
+ async importDrive (guid: string) : Promise {
+ const ret = await this.apiService.importDrive(guid)
+ this.torAddress = 'http://' + ret['tor-address']
+ this.lanAddress = 'https://' + ret['lan-address']
+ this.cert = ret['root-ca']
+ }
+
async setupEmbassy () : Promise {
const ret = await this.apiService.setupEmbassy({
'embassy-logicalname': this.storageDrive.logicalname,
diff --git a/ui/src/app/components/backup-drives/backup.service.ts b/ui/src/app/components/backup-drives/backup.service.ts
index e3415ad88..d47596d76 100644
--- a/ui/src/app/components/backup-drives/backup.service.ts
+++ b/ui/src/app/components/backup-drives/backup.service.ts
@@ -24,19 +24,19 @@ export class BackupService {
try {
const drives = await this.embassyApi.getDrives({ })
this.drives = drives
- .filter(d => !d.internal)
- .map(d => {
- const partionInfo: MappedPartitionInfo[] = d.partitions.map(p => {
+ .filter(d => !d.guid)
+ .map(d => {
+ const partionInfo: MappedPartitionInfo[] = d.partitions.map(p => {
+ return {
+ ...p,
+ hasBackup: [0, 1].includes(this.emver.compare(p['embassy-os']?.version, '0.3.0')),
+ }
+ })
return {
- ...p,
- hasBackup: [0, 1].includes(this.emver.compare(p['embassy-os']?.version, '0.3.0')),
+ ...d,
+ partitions: partionInfo,
}
})
- return {
- ...d,
- partitions: partionInfo,
- }
- })
} catch (e) {
this.loadingError = getErrorMessage(e)
} finally {
diff --git a/ui/src/app/services/api/api.fixures.ts b/ui/src/app/services/api/api.fixures.ts
index c251cbe2d..0747e6a8a 100644
--- a/ui/src/app/services/api/api.fixures.ts
+++ b/ui/src/app/services/api/api.fixures.ts
@@ -990,7 +990,7 @@ export module Mock {
},
],
capacity: 1000000000000,
- internal: true,
+ guid: 'asdfasdf',
},
{
logicalname: '/dev/sdb',
@@ -1016,7 +1016,7 @@ export module Mock {
},
],
capacity: 10000000000,
- internal: false,
+ guid: null,
},
]
diff --git a/ui/src/app/services/api/api.types.ts b/ui/src/app/services/api/api.types.ts
index f19145bed..f97c2dc0e 100644
--- a/ui/src/app/services/api/api.types.ts
+++ b/ui/src/app/services/api/api.types.ts
@@ -300,7 +300,7 @@ export interface DriveInfo {
model: string | null
partitions: PartitionInfo[]
capacity: number
- internal: boolean
+ guid: string | null
}
export interface PartitionInfo {