diff --git a/frontend/config-sample.json b/frontend/config-sample.json
index 2f5f8a528..bcbb037fa 100644
--- a/frontend/config-sample.json
+++ b/frontend/config-sample.json
@@ -13,6 +13,7 @@
"mocks": {
"maskAs": "tor",
"skipStartupAlerts": true
- }
+ },
+ "targetArch": "aarch64"
}
}
diff --git a/frontend/projects/marketplace/src/pages/show/about/about.component.html b/frontend/projects/marketplace/src/pages/show/about/about.component.html
index 61fc0b24f..30d208fe5 100644
--- a/frontend/projects/marketplace/src/pages/show/about/about.component.html
+++ b/frontend/projects/marketplace/src/pages/show/about/about.component.html
@@ -3,7 +3,7 @@
New in {{ pkg.manifest.version | displayEmver }}
All Release Notes
-
+
diff --git a/frontend/projects/marketplace/src/pages/show/additional/additional.component.html b/frontend/projects/marketplace/src/pages/show/additional/additional.component.html
index 524952f40..b6ed8ee5b 100644
--- a/frontend/projects/marketplace/src/pages/show/additional/additional.component.html
+++ b/frontend/projects/marketplace/src/pages/show/additional/additional.component.html
@@ -9,14 +9,14 @@
Other Versions
Click to view other versions
-
+
License
{{ pkg.manifest.license }}
-
+
Instructions
Click to view instructions
-
+
diff --git a/frontend/projects/shared/src/types/workspace-config.ts b/frontend/projects/shared/src/types/workspace-config.ts
index 6d912fa12..2bebcb6c1 100644
--- a/frontend/projects/shared/src/types/workspace-config.ts
+++ b/frontend/projects/shared/src/types/workspace-config.ts
@@ -1,4 +1,5 @@
export type WorkspaceConfig = {
+ targetArch: 'aarch64' | 'x86_64'
gitHash: string
useMocks: boolean
// each key corresponds to a project and values adjust settings for that project, eg: ui, setup-wizard, diagnostic-ui
diff --git a/frontend/projects/ui/src/app/app.component.html b/frontend/projects/ui/src/app/app.component.html
index ba9552876..c9d40c2da 100644
--- a/frontend/projects/ui/src/app/app.component.html
+++ b/frontend/projects/ui/src/app/app.component.html
@@ -119,6 +119,7 @@
+
@@ -165,7 +166,9 @@
+
+
@@ -177,12 +180,15 @@
+
+
+
@@ -190,6 +196,7 @@
+
@@ -220,7 +227,9 @@
+
+
@@ -234,6 +243,21 @@
+
+
+
a
+ a
+ a
+ a
+ a
+ a
+
+
+
+
+
+
+
-
-
-
- {{ params.title }}
- {{ params.headline }}
-
-
-
-
{{ note.key }}
-
+
+
{{ params.title }}
+
+
+
+
+ {{ v.version }}
+ (Current Version)
+
+
+
+
+
diff --git a/frontend/projects/ui/src/app/components/install-wizard/notes/notes.component.ts b/frontend/projects/ui/src/app/components/install-wizard/notes/notes.component.ts
index 0f4b87321..c2ab3fcea 100644
--- a/frontend/projects/ui/src/app/components/install-wizard/notes/notes.component.ts
+++ b/frontend/projects/ui/src/app/components/install-wizard/notes/notes.component.ts
@@ -8,17 +8,17 @@ import { BehaviorSubject, Subject } from 'rxjs'
})
export class NotesComponent {
@Input() params: {
- notes: { [version: string]: string }
+ versions: { version: string; notes: string }[]
title: string
titleColor: string
headline: string
}
- load () { }
+ load() {}
loading$ = new BehaviorSubject(false)
cancel$ = new Subject
()
- asIsOrder () {
+ asIsOrder() {
return 0
}
}
diff --git a/frontend/projects/ui/src/app/components/install-wizard/prebaked-wizards.ts b/frontend/projects/ui/src/app/components/install-wizard/prebaked-wizards.ts
index 7febb4520..0f1e1a6d8 100644
--- a/frontend/projects/ui/src/app/components/install-wizard/prebaked-wizards.ts
+++ b/frontend/projects/ui/src/app/components/install-wizard/prebaked-wizards.ts
@@ -103,6 +103,16 @@ export class WizardBaker {
}): InstallWizardComponent['params'] {
const { version, releaseNotes, headline } = values
+ const versions = Object.keys(releaseNotes)
+ .sort()
+ .reverse()
+ .map(version => {
+ return {
+ version,
+ notes: releaseNotes[version],
+ }
+ })
+
const action = 'update'
const title = 'EmbassyOS'
const toolbar: TopbarParams = { action, title, version }
@@ -112,7 +122,7 @@ export class WizardBaker {
slide: {
selector: 'notes',
params: {
- notes: releaseNotes,
+ versions,
title: 'Release Notes',
titleColor: 'dark',
headline,
diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts
index 0c1a5fa37..c34ec1ab9 100644
--- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts
+++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts
@@ -99,7 +99,7 @@ function loading(
return pipe(
// Show notification on error
catchError(e => from(errToast.present(e))),
- // Map any result to false to stop loading inidicator
+ // Map any result to false to stop loading indicator
mapTo(false),
// Start operation with true
startWith(true),
diff --git a/frontend/projects/ui/src/app/pages/server-routes/restore/restore.component.ts b/frontend/projects/ui/src/app/pages/server-routes/restore/restore.component.ts
index c973458a6..0c321a947 100644
--- a/frontend/projects/ui/src/app/pages/server-routes/restore/restore.component.ts
+++ b/frontend/projects/ui/src/app/pages/server-routes/restore/restore.component.ts
@@ -1,5 +1,9 @@
import { Component } from '@angular/core'
-import { ModalController, NavController } from '@ionic/angular'
+import {
+ LoadingController,
+ ModalController,
+ NavController,
+} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import {
GenericInputComponent,
@@ -12,7 +16,6 @@ import {
DiskBackupTarget,
} from 'src/app/services/api/api.types'
import { AppRecoverSelectPage } from 'src/app/modals/app-recover-select/app-recover-select.page'
-import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import * as argon2 from '@start9labs/argon2'
@Component({
@@ -25,21 +28,24 @@ export class RestorePage {
private readonly modalCtrl: ModalController,
private readonly navCtrl: NavController,
private readonly embassyApi: ApiService,
- private readonly patch: PatchDbService,
+ private readonly loadingCtrl: LoadingController,
) {}
async presentModalPassword(
target: MappedBackupTarget,
): Promise {
const options: GenericInputOptions = {
- title: 'Master Password Required',
+ title: 'Password Required',
message:
- 'Enter your master password. On the next screen, you will select the individual services you want to restore.',
+ 'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.',
label: 'Master Password',
placeholder: 'Enter master password',
useMask: true,
buttonText: 'Next',
- submitFn: (password: string) => this.decryptDrive(target, password),
+ submitFn: async (password: string) => {
+ argon2.verify(target.entry['embassy-os']['password-hash'], password)
+ await this.restoreFromBackup(target, password)
+ },
}
const modal = await this.modalCtrl.create({
@@ -52,57 +58,27 @@ export class RestorePage {
await modal.present()
}
- private async decryptDrive(
- target: MappedBackupTarget,
- password: string,
- ): Promise {
- const passwordHash = this.patch.getData()['server-info']['password-hash']
- argon2.verify(passwordHash, password)
-
- try {
- argon2.verify(target.entry['embassy-os']['password-hash'], password)
- await this.restoreFromBackup(target, password)
- } catch (e) {
- setTimeout(() => this.presentModalOldPassword(target, password), 500)
- }
- }
-
- private async presentModalOldPassword(
- target: MappedBackupTarget,
- password: string,
- ): Promise {
- const options: GenericInputOptions = {
- title: 'Original Password Needed',
- message:
- 'This backup was created with a different password. Enter the ORIGINAL password that was used to encrypt this backup.',
- label: 'Original Password',
- placeholder: 'Enter original password',
- useMask: true,
- buttonText: 'Restore From Backup',
- submitFn: (oldPassword: string) =>
- this.restoreFromBackup(target, password, oldPassword),
- }
-
- const m = await this.modalCtrl.create({
- component: GenericInputComponent,
- componentProps: { options },
- presentingElement: await this.modalCtrl.getTop(),
- cssClass: 'alertlike-modal',
- })
-
- await m.present()
- }
-
private async restoreFromBackup(
target: MappedBackupTarget,
password: string,
oldPassword?: string,
): Promise {
- const backupInfo = await this.embassyApi.getBackupInfo({
- 'target-id': target.id,
- password,
+ const loader = await this.loadingCtrl.create({
+ spinner: 'lines',
+ message: 'Decrypting drive...',
+ cssClass: 'loader',
})
- this.presentModalSelect(target.id, backupInfo, password, oldPassword)
+ await loader.present()
+
+ try {
+ const backupInfo = await this.embassyApi.getBackupInfo({
+ 'target-id': target.id,
+ password,
+ })
+ this.presentModalSelect(target.id, backupInfo, password, oldPassword)
+ } finally {
+ loader.dismiss()
+ }
}
private async presentModalSelect(
diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts
index e3a460b69..08fbbaa7b 100644
--- a/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts
+++ b/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts
@@ -86,7 +86,29 @@ export class ServerBackupPage {
placeholder: 'Enter master password',
useMask: true,
buttonText: 'Create Backup',
- submitFn: (password: string) => this.test(target, password),
+ submitFn: async (password: string) => {
+ // confirm password matches current master password
+ const passwordHash =
+ this.patch.getData()['server-info']['password-hash']
+ argon2.verify(passwordHash, password)
+
+ // first time backup
+ if (!target.hasValidBackup) {
+ await this.createBackup(target.id, password)
+ // existing backup
+ } else {
+ try {
+ argon2.verify(target.entry['embassy-os']['password-hash'], password)
+ } catch {
+ setTimeout(
+ () => this.presentModalOldPassword(target, password),
+ 500,
+ )
+ return
+ }
+ await this.createBackup(target.id, password)
+ }
+ },
}
const m = await this.modalCtrl.create({
@@ -98,33 +120,6 @@ export class ServerBackupPage {
await m.present()
}
- private async test(
- target: MappedBackupTarget,
- password: string,
- oldPassword?: string,
- ): Promise {
- const passwordHash = this.patch.getData()['server-info']['password-hash']
- argon2.verify(passwordHash, password)
-
- if (!target.hasValidBackup) {
- await this.createBackup(target.id, password)
- } else {
- try {
- argon2.verify(
- target.entry['embassy-os']['password-hash'],
- oldPassword || password,
- )
- await this.createBackup(target.id, password)
- } catch (e) {
- if (oldPassword) {
- throw e
- } else {
- setTimeout(() => this.presentModalOldPassword(target, password), 500)
- }
- }
- }
- }
-
private async presentModalOldPassword(
target: MappedBackupTarget,
password: string,
@@ -137,8 +132,10 @@ export class ServerBackupPage {
placeholder: 'Enter original password',
useMask: true,
buttonText: 'Create Backup',
- submitFn: (oldPassword: string) =>
- this.test(target, password, oldPassword),
+ submitFn: async (oldPassword: string) => {
+ argon2.verify(target.entry['embassy-os']['password-hash'], oldPassword)
+ await this.createBackup(target.id, password, oldPassword)
+ },
}
const m = await this.modalCtrl.create({
diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts
index 8cd3d741c..71f0431c4 100644
--- a/frontend/projects/ui/src/app/services/api/api.fixures.ts
+++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts
@@ -1,5 +1,4 @@
import { PackageState } from 'src/app/types/package-state'
-import { ConfigSpec } from 'src/app/pkg-config/config-types'
import {
DependencyErrorType,
DockerIoFormat,
@@ -1034,7 +1033,8 @@ export module Mock {
version: '0.3.0',
full: true,
'password-hash':
- '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNK',
+ // password is asdfasdf
+ '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'wrapped-key': '',
},
},
@@ -1056,21 +1056,23 @@ export module Mock {
mountable: true,
'embassy-os': null,
},
- // 'powjefhjbnwhdva': {
- // type: 'disk',
- // logicalname: 'sdba1',
- // label: 'Another Drive',
- // capacity: 2000000000000,
- // used: 100000000000,
- // model: null,
- // vendor: 'SSK',
- // 'embassy-os': {
- // version: '0.3.0',
- // full: true,
- // 'password-hash': '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
- // 'wrapped-key': '',
- // },
- // },
+ powjefhjbnwhdva: {
+ type: 'disk',
+ logicalname: 'sdba1',
+ label: 'Another Drive',
+ capacity: 2000000000000,
+ used: 100000000000,
+ model: null,
+ vendor: 'SSK',
+ 'embassy-os': {
+ version: '0.3.0',
+ full: true,
+ // password is asdfasdf
+ 'password-hash':
+ '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
+ 'wrapped-key': '',
+ },
+ },
}
export const BackupInfo: RR.GetBackupInfoRes = {
diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts
index 9c312744a..2f716e3f5 100644
--- a/frontend/projects/ui/src/app/services/api/api.types.ts
+++ b/frontend/projects/ui/src/app/services/api/api.types.ts
@@ -245,7 +245,7 @@ export module RR {
export type GetMarketplaceDataRes = MarketplaceData
export type GetMarketplaceEOSReq = {
- 'eos-version-compat': string
+ 'eos-version': string
}
export type GetMarketplaceEOSRes = MarketplaceEOS
diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts
index 7058fa105..1dc34fe06 100644
--- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts
+++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts
@@ -105,8 +105,9 @@ export class LiveApiService extends ApiService {
// marketplace URLs
- async marketplaceProxy(path: string, params: {}, url: string): Promise {
- const fullURL = `${url}${path}?${new URLSearchParams(params).toString()}`
+ async marketplaceProxy(path: string, qp: {}, url: string): Promise {
+ Object.assign(qp, { arch: this.config.targetArch })
+ const fullURL = `${url}${path}?${new URLSearchParams(qp).toString()}`
return this.http.rpcRequest({
method: 'marketplace.get',
params: { url: fullURL },
diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts
index 05d49685e..7dab2033d 100644
--- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts
+++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts
@@ -374,7 +374,7 @@ export class MockApiService extends ApiService {
params: RR.CreateBackupReq,
): Promise {
await pauseFor(2000)
- const path = '/server-info/status'
+ const path = '/server-info/status-info/backing-up'
const ids = ['bitcoind', 'lnd']
setTimeout(async () => {
@@ -402,7 +402,7 @@ export class MockApiService extends ApiService {
{
op: PatchOp.REPLACE,
path,
- value: ServerStatus.Running,
+ value: false,
},
]
this.updateMock(lastPatch)
@@ -412,7 +412,7 @@ export class MockApiService extends ApiService {
{
op: PatchOp.REPLACE,
path,
- value: ServerStatus.BackingUp,
+ value: true,
},
]
@@ -788,6 +788,10 @@ export class MockApiService extends ApiService {
op: PatchOp.REMOVE,
path: `/package-data/${id}/install-progress`,
},
+ {
+ op: PatchOp.REMOVE,
+ path: `/recovered-packages/${id}`,
+ },
]
this.updateMock(patch2)
}, 1000)
diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts
index 396edfcba..a4b0a63ed 100644
--- a/frontend/projects/ui/src/app/services/api/mock-patch.ts
+++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts
@@ -25,10 +25,15 @@ export const mockPatchData: DataModel = {
'lan-address': 'https://embassy-abcdefgh.local',
'tor-address': 'http://myveryownspecialtoraddress.onion',
'unread-notification-count': 4,
+ // password is asdfasdf
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'eos-version-compat': '>=0.3.0 <=0.3.0.1',
- 'status-info': null,
+ 'status-info': {
+ 'backing-up': false,
+ updated: false,
+ 'update-progress': null,
+ },
},
'recovered-packages': {
'btc-rpc-proxy': {
diff --git a/frontend/projects/ui/src/app/services/config.service.ts b/frontend/projects/ui/src/app/services/config.service.ts
index c580dea1f..55ba88269 100644
--- a/frontend/projects/ui/src/app/services/config.service.ts
+++ b/frontend/projects/ui/src/app/services/config.service.ts
@@ -8,6 +8,7 @@ import {
} from 'src/app/services/patch-db/data-model'
const {
+ targetArch,
gitHash,
useMocks,
ui: { patchDb, api, mocks, marketplace },
@@ -19,15 +20,13 @@ const {
export class ConfigService {
origin = removePort(removeProtocol(window.origin))
version = require('../../../../../package.json').version
-
useMocks = useMocks
mocks = mocks
-
+ targetArch = targetArch
gitHash = gitHash
patchDb = patchDb
api = api
marketplace = marketplace
-
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
isConsulate = window['platform'] === 'ios'
supportsWebSockets = !!window.WebSocket || this.isConsulate
diff --git a/frontend/projects/ui/src/app/services/eos.service.ts b/frontend/projects/ui/src/app/services/eos.service.ts
index e4bac77ea..53943937c 100644
--- a/frontend/projects/ui/src/app/services/eos.service.ts
+++ b/frontend/projects/ui/src/app/services/eos.service.ts
@@ -19,9 +19,9 @@ export class EOSService {
) {}
async getEOS(): Promise {
+ const version = this.patch.getData()['server-info'].version
this.eos = await this.api.getEos({
- 'eos-version-compat':
- this.patch.getData()['server-info']['eos-version-compat'],
+ 'eos-version': version,
})
const updateAvailable =
this.emver.compare(
diff --git a/frontend/projects/ui/src/styles.scss b/frontend/projects/ui/src/styles.scss
index 2ade488eb..4cf8a8cd7 100644
--- a/frontend/projects/ui/src/styles.scss
+++ b/frontend/projects/ui/src/styles.scss
@@ -33,13 +33,6 @@
src: url('/assets/fonts/Open_Sans/OpenSans-Bold.ttf');
}
-@font-face {
- font-family: 'Open Sans';
- font-style: normal;
- font-weight: 500;
- src: url('/assets/fonts/Open_Sans/OpenSans-SemiBold.ttf');
-}
-
@font-face {
font-family: 'Open Sans';
font-style: normal;
@@ -81,7 +74,7 @@ $subheader-height: 48px;
.input-label {
margin-bottom: 6px;
font-size: medium;
- font-weight: 500;
+ font-weight: bold;
* {
display: inline-block;
vertical-align: middle;