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;