diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 5230697da..4049a958d 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -75,7 +75,6 @@ export class AppComponent { async init () { await this.storage.create() await this.authService.init() - await this.emver.init() await this.patch.init() this.router.initialNavigation() diff --git a/ui/src/app/components/config-header/config-header.component.html b/ui/src/app/components/config-header/config-header.component.html index 9a85e1300..de76491b3 100644 --- a/ui/src/app/components/config-header/config-header.component.html +++ b/ui/src/app/components/config-header/config-header.component.html @@ -11,7 +11,7 @@ - +

Description

@@ -20,7 +20,7 @@
- +

Warning!

diff --git a/ui/src/app/components/form-object/form-object.component.html b/ui/src/app/components/form-object/form-object.component.html index 38ce2139f..90fa0561e 100644 --- a/ui/src/app/components/form-object/form-object.component.html +++ b/ui/src/app/components/form-object/form-object.component.html @@ -15,13 +15,13 @@ -

+

-

+

diff --git a/ui/src/app/components/form-object/form-object.component.scss b/ui/src/app/components/form-object/form-object.component.scss index 2d1b5e5bd..135cff64c 100644 --- a/ui/src/app/components/form-object/form-object.component.scss +++ b/ui/src/app/components/form-object/form-object.component.scss @@ -1,7 +1,8 @@ .help-icon { display: inline-block; vertical-align: middle; - padding-bottom: 2px; + padding-bottom: 1px; + padding-right: 6px; font-size: 18px; color: var(--ion-color-dark); cursor: pointer; @@ -18,17 +19,6 @@ ion-item-divider { border-bottom: 1px solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, 0.13)))) } -.input-label { - // padding-top: 10px; - margin-bottom: 5px; - font-size: medium; - font-weight: bold; - * { - display: inline-block; - vertical-align: middle; - } -} - .nested-wrapper { padding: 0 0 30px 30px; // border-bottom: 1px solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, 0.13)))) diff --git a/ui/src/app/components/object-config/object-config-item.component.html b/ui/src/app/components/object-config/object-config-item.component.html deleted file mode 100644 index 7092aa6b9..000000000 --- a/ui/src/app/components/object-config/object-config-item.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - -
- - {{ spec.name }} - (new) - -
- - {{ displayValue }} -
- diff --git a/ui/src/app/components/object-config/object-config.component.html b/ui/src/app/components/object-config/object-config.component.html deleted file mode 100644 index fb5de98bd..000000000 --- a/ui/src/app/components/object-config/object-config.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - \ No newline at end of file diff --git a/ui/src/app/components/object-config/object-config.component.module.ts b/ui/src/app/components/object-config/object-config.component.module.ts deleted file mode 100644 index 3938c0614..000000000 --- a/ui/src/app/components/object-config/object-config.component.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { FormsModule } from '@angular/forms' -import { ObjectConfigComponent, ObjectConfigItemComponent } from './object-config.component' -import { IonicModule } from '@ionic/angular' -import { SharingModule } from 'src/app/modules/sharing.module' - -@NgModule({ - declarations: [ - ObjectConfigComponent, - ObjectConfigItemComponent, - ], - imports: [ - CommonModule, - FormsModule, - IonicModule, - SharingModule, - ], - exports: [ - ObjectConfigComponent, - ObjectConfigItemComponent, - ], -}) -export class ObjectConfigComponentModule { } diff --git a/ui/src/app/components/object-config/object-config.component.scss b/ui/src/app/components/object-config/object-config.component.scss deleted file mode 100644 index 8dbbe218e..000000000 --- a/ui/src/app/components/object-config/object-config.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -.add-margin { - margin: 0 16px; -} - -.new-tag { - padding: 0px 5px; - font-weight: bold; - font-size: smaller; - color: #cecece; - font-style: italic; -} - -.bold { - font-weight: bold; -} - -.organizer { - display: flex; - align-items: center; -} \ No newline at end of file diff --git a/ui/src/app/components/object-config/object-config.component.ts b/ui/src/app/components/object-config/object-config.component.ts deleted file mode 100644 index c101e6385..000000000 --- a/ui/src/app/components/object-config/object-config.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' -import { Annotation, Annotations } from '../../pkg-config/config-utilities' -import { ConfigCursor } from 'src/app/pkg-config/config-cursor' -import { ValueSpecOf, ValueSpec } from 'src/app/pkg-config/config-types' -import { MaskPipe } from 'src/app/pipes/mask.pipe' -import { IonNav } from '@ionic/angular' -import { SubNavService } from 'src/app/services/sub-nav.service' - -@Component({ - selector: 'object-config', - templateUrl: './object-config.component.html', - styleUrls: ['./object-config.component.scss'], -}) -export class ObjectConfigComponent { - @Input() cursor: ConfigCursor<'object' | 'union'> - @Output() onEdit = new EventEmitter() - spec: ValueSpecOf<'object' | 'union'> - value: object - annotations: Annotations<'object' | 'union'> - - constructor ( - private readonly subNav: SubNavService, - private readonly nav: IonNav, - ) { } - - ngOnInit () { - this.spec = this.cursor.spec() - this.value = this.cursor.config() - this.annotations = this.cursor.getAnnotations() - } - - async handleClick (key: string) { - const nextCursor = this.cursor.seekNext(key) - nextCursor.createFirstEntryForList() - this.subNav.push(key, nextCursor, this.nav) - } - - asIsOrder () { - return 0 - } -} - -@Component({ - selector: 'object-config-item', - templateUrl: './object-config-item.component.html', - styleUrls: ['./object-config.component.scss'], -}) -export class ObjectConfigItemComponent { - @Input() key: string - @Input() spec: ValueSpec - @Input() value: string | number - @Input() anno: Annotation - @Output() onClick = new EventEmitter() - maskPipe: MaskPipe = new MaskPipe() - - displayValue?: string | number | boolean - - ngOnChanges () { - switch (this.spec.type) { - case 'string': - if (this.value) { - if (this.spec.masked) { - this.displayValue = this.maskPipe.transform(this.value as string, 4) - } else { - this.displayValue = this.value - } - } else { - this.displayValue = '-' - } - break - case 'boolean': - this.displayValue = String(this.value) - break - case 'number': - this.displayValue = this.value || '-' - if (this.displayValue && this.spec.units) { - this.displayValue = `${this.displayValue} ${this.spec.units}` - } - break - case 'enum': - this.displayValue = this.spec['value-names'][this.value] - break - case 'pointer': - this.displayValue = 'System Defined' - break - default: - return - } - } - - async handleClick (): Promise { - if (this.spec.type === 'pointer') return - this.onClick.emit(true) - } -} \ No newline at end of file diff --git a/ui/src/app/components/skeleton-list/skeleton-list.component.html b/ui/src/app/components/skeleton-list/skeleton-list.component.html index 6975050cf..1ce63aa1c 100644 --- a/ui/src/app/components/skeleton-list/skeleton-list.component.html +++ b/ui/src/app/components/skeleton-list/skeleton-list.component.html @@ -2,14 +2,14 @@ - + - + - + @@ -17,12 +17,14 @@
- - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/app/modals/app-config/app-config.page.html b/ui/src/app/modals/app-config/app-config.page.html index 4a1cce58d..723d9341d 100644 --- a/ui/src/app/modals/app-config/app-config.page.html +++ b/ui/src/app/modals/app-config/app-config.page.html @@ -1,8 +1,9 @@ - + Config + Reset Defaults @@ -20,7 +21,7 @@ - +

Initial Config @@ -32,7 +33,7 @@ - +

@@ -60,7 +61,7 @@ - +

No config options for {{ pkg.manifest.title }} {{ pkg.manifest.version }}.

diff --git a/ui/src/app/modals/app-restore/app-restore.component.html b/ui/src/app/modals/app-restore/app-restore.component.html index 093ce9aa1..8b4d55f9f 100644 --- a/ui/src/app/modals/app-restore/app-restore.component.html +++ b/ui/src/app/modals/app-restore/app-restore.component.html @@ -18,43 +18,37 @@ - -

Warning

+

- Restoring from backup will overwrite all current data for {{ patch.data['package-data'][pkgId].manifest.title }} . + Select the drive containing the backup you would like to restore. +
+
+ + Warning! All current data for {{ patch.data['package-data'][pkgId].manifest.title }} will be overwritten by the backup. +

- Select Backup Drive - - No partitions available. Insert the storage device containing the backup you intend to restore. + No drives found containing a valid backup for {{ patch.data['package-data'][pkgId].manifest.title }}. Insert the storage device containing the backup you intend to restore. - - - - {{ disk.value.size }} - - - {{ disk.key }} - - - - - - - -

{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})

-

Available

- -

Unavailable

-
-
-
-
-
-
+ +
+ {{ disk.key }} - {{ disk.value.size }} + + + +

{{ partition.value.label || partition.key }}

+

{{ partition.value.size || 'unknown size' }}

+

Available

+ +

Unavailable

+
+
+
+
+
diff --git a/ui/src/app/modals/app-restore/app-restore.component.ts b/ui/src/app/modals/app-restore/app-restore.component.ts index 8d655298a..6a07a4ac1 100644 --- a/ui/src/app/modals/app-restore/app-restore.component.ts +++ b/ui/src/app/modals/app-restore/app-restore.component.ts @@ -1,10 +1,9 @@ import { Component, Input } from '@angular/core' -import { LoadingController, ModalController } from '@ionic/angular' +import { ModalController } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component' import { DiskInfo } from 'src/app/services/api/api.types' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { Subscription } from 'rxjs' import { ErrorToastService } from 'src/app/services/error-toast.service' @Component({ @@ -15,29 +14,22 @@ import { ErrorToastService } from 'src/app/services/error-toast.service' export class AppRestoreComponent { @Input() pkgId: string disks: DiskInfo - title: string loading = true - submitting = false allPartitionsMounted: boolean - - subs: Subscription[] = [] + modal: HTMLIonModalElement constructor ( private readonly modalCtrl: ModalController, private readonly embassyApi: ApiService, - private readonly loadingCtrl: LoadingController, private readonly errToast: ErrorToastService, public readonly patch: PatchDbService, ) { } - ngOnInit () { + async ngOnInit () { this.getExternalDisks() + this.modal = await this.modalCtrl.getTop() } - // ngAfterViewInit () { - // this.content.scrollToPoint(undefined, 1) - // } - async refresh () { this.loading = true await this.getExternalDisks() @@ -55,42 +47,36 @@ export class AppRestoreComponent { } async presentModal (logicalname: string): Promise { - const m = await this.modalCtrl.create({ + const modal = await this.modalCtrl.create({ componentProps: { - type: 'restore', + title: 'Enter Password', + message: 'Backup encrypted. Enter the password that was originally used to encrypt this backup.', + label: 'Password', + useMask: true, + buttonText: 'Restore', + submitFn: async (value: string) => await this.restore(logicalname, value), }, cssClass: 'alertlike-modal', + presentingElement: await this.modalCtrl.getTop(), component: BackupConfirmationComponent, - backdropDismiss: false, }) - m.onWillDismiss().then(res => { - const data = res.data - if (data.cancel) return - this.restore(logicalname, data.password) + modal.onWillDismiss().then(res => { + if (res.role === 'success') this.modal.dismiss(undefined, 'success') }) - await m.present() + await modal.present() } dismiss () { - this.modalCtrl.dismiss({ }) + this.modalCtrl.dismiss() } private async restore (logicalname: string, password: string): Promise { - this.submitting = true - // await loader.present() - - try { - await this.embassyApi.restorePackage({ - id: this.pkgId, - logicalname, - password, - }) - } catch (e) { - this.modalCtrl.dismiss({ error: e }) - } finally { - this.modalCtrl.dismiss({ }) - } + await this.embassyApi.restorePackage({ + id: this.pkgId, + logicalname, + password, + }) } } diff --git a/ui/src/app/modals/backup-confirmation/backup-confirmation.component.html b/ui/src/app/modals/backup-confirmation/backup-confirmation.component.html index 33322e8e5..0460528e4 100644 --- a/ui/src/app/modals/backup-confirmation/backup-confirmation.component.html +++ b/ui/src/app/modals/backup-confirmation/backup-confirmation.component.html @@ -1,31 +1,37 @@ -
-
-

Encrypt Backup

-

Enter your master password to create an encrypted backup.

-
-
-

Decrypt Backup

-

Enter the password that was originally used to encrypt this backup.

-
+
-
- - Master Password - - - - {{ error }} - -
+ + +

{{ title }}

+
+

{{ message }}

+
+
-
- - Cancel - - - {{ type === 'backup' ? 'Create Backup' : 'Restore Backup' }} - -
+
+
+

{{ label }}

+ + + + + + + +

+ {{ error || 'placeholder' }} +

+
+ +
+ + Cancel + + + {{ buttonText }} + +
+
diff --git a/ui/src/app/modals/backup-confirmation/backup-confirmation.component.ts b/ui/src/app/modals/backup-confirmation/backup-confirmation.component.ts index a9eb75769..56edf0e6f 100644 --- a/ui/src/app/modals/backup-confirmation/backup-confirmation.component.ts +++ b/ui/src/app/modals/backup-confirmation/backup-confirmation.component.ts @@ -1,5 +1,6 @@ import { Component, Input } from '@angular/core' -import { ModalController } from '@ionic/angular' +import { IonicSafeString, LoadingController, ModalController } from '@ionic/angular' +import { getErrorMessage } from 'src/app/services/error-toast.service' @Component({ selector: 'backup-confirmation', @@ -7,13 +8,19 @@ import { ModalController } from '@ionic/angular' styleUrls: ['./backup-confirmation.component.scss'], }) export class BackupConfirmationComponent { - @Input() type: 'backup' | 'restore' + @Input() title: string + @Input() message: string + @Input() label = 'Enter value' + @Input() buttonText = 'Submit' + @Input() useMask = false + @Input() value = '' + @Input() submitFn: (value: string) => Promise unmasked = false - password = '' - error = '' + error: string | IonicSafeString constructor ( private readonly modalCtrl: ModalController, + private readonly loadingCtrl: LoadingController, ) { } toggleMask () { @@ -21,15 +28,23 @@ export class BackupConfirmationComponent { } cancel () { - this.modalCtrl.dismiss({ cancel: true }) + this.modalCtrl.dismiss() } - submit () { - if (!this.password || this.password.length < 12) { - this.error = 'Password must be at least 12 characters in length.' - return + async submit () { + const loader = await this.loadingCtrl.create({ + spinner: 'lines', + cssClass: 'loader', + }) + loader.present() + + try { + await this.submitFn(this.value) + this.modalCtrl.dismiss(undefined, 'success') + } catch (e) { + this.error = getErrorMessage(e) + } finally { + loader.dismiss() } - const { password } = this - this.modalCtrl.dismiss({ password }) } } diff --git a/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html b/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html index 5292551e6..c635bc72a 100644 --- a/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html +++ b/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html @@ -1,7 +1,7 @@ - - + +

{{ action.name }}

-

{{ action.description }}

+

{{ action.description }}

\ No newline at end of file diff --git a/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts b/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts index fa801013d..e8d43f8e5 100644 --- a/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts +++ b/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts @@ -110,15 +110,13 @@ export class AppActionsPage { pkgId: this.pkgId, }, component: AppRestoreComponent, - backdropDismiss: false, }) modal.onWillDismiss().then(res => { - const data = res.data - if (data.error) this.errToast.present(data.error) + if (res.role === 'success') this.navCtrl.back() }) - return await modal.present() + await modal.present() } async uninstall (manifest: Manifest) { diff --git a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html index 680209901..346f7cb37 100644 --- a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html +++ b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html @@ -1,14 +1,14 @@ - - + +

{{ interface.def.name }}

{{ interface.def.description }}

-
+
- +

Tor Address

{{ tor }}

@@ -23,7 +23,7 @@
- +

Tor Address

Service does not use a Tor Address

@@ -31,7 +31,7 @@ - +

LAN Address

{{ lan }}

@@ -46,7 +46,7 @@
- +

LAN Address

Service does not use a LAN Address

diff --git a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.scss b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.scss index 3cb183411..e69de29bb 100644 --- a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.scss +++ b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.scss @@ -1,4 +0,0 @@ -.vertical-align { - display: inline-block; - vertical-align: middle; -} \ No newline at end of file diff --git a/ui/src/app/pages/apps-routes/app-list/app-list.page.html b/ui/src/app/pages/apps-routes/app-list/app-list.page.html index 03231d77d..192ba70cc 100644 --- a/ui/src/app/pages/apps-routes/app-list/app-list.page.html +++ b/ui/src/app/pages/apps-routes/app-list/app-list.page.html @@ -24,8 +24,8 @@ -
-
+
+
diff --git a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html index 35f4ce147..94ea0eb52 100644 --- a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html +++ b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html @@ -6,7 +6,8 @@ Properties - + + Refresh @@ -18,14 +19,14 @@ - +

Service not running. Information on this page could be inaccurate.

- +

No properties.

@@ -38,7 +39,7 @@ - +

{{ prop.key }}

@@ -47,7 +48,7 @@ - +

{{ prop.key }}

{{ prop.value.masked && !unmasked[prop.key] ? (prop.value.value | mask ) : (prop.value.value | truncateEnd : 100) }}

diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.html b/ui/src/app/pages/apps-routes/app-show/app-show.page.html index ee2310853..274f5da8e 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.html +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.html @@ -18,35 +18,35 @@ - - - - Status - - - - - - - Open UI - - - Configure - - - Stop - - - Fix - - - Start - - - - - + + + Status + + + + + + + Open UI + + + Configure + + + Stop + + + Fix + + + Start + + + + + + Health Checks @@ -78,7 +78,7 @@ - +

{{ pkg.installed['dependency-info'][dep.key].manifest.title }}

{{ pkg.manifest.dependencies[dep.key].version | displayEmver }}

{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}

@@ -112,27 +112,30 @@
-
+ + App is undergoing maintenance. + +
+
- -
-

Downloading: {{ (pkg['install-progress'] | installState).downloadProgress }}%

- + +
+

Downloading: {{ (pkg['install-progress'] | installState).downloadProgress }}%

+ -

Validating: {{ (pkg['install-progress'] | installState).validateProgress }}%

- +

Validating: {{ (pkg['install-progress'] | installState).validateProgress }}%

+ -

Installing: {{ (pkg['install-progress'] | installState).unpackProgress }}%

- -
- +

Installing: {{ (pkg['install-progress'] | installState).unpackProgress }}%

+ +
diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index 45b2fb5aa..eb82b326e 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -8,7 +8,7 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' import { ConfigService } from 'src/app/services/config.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, MainStatus, PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model' +import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, MainStatus, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model' import { FEStatus, PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service' import { ConnectionService } from 'src/app/services/connection.service' import { ErrorToastService } from 'src/app/services/error-toast.service' @@ -31,6 +31,7 @@ export class AppShowPage { rendering: PkgStatusRendering Math = Math mainStatus: MainStatus + PackageMainStatus = PackageMainStatus @ViewChild(IonContent) content: IonContent subs: Subscription[] = [] diff --git a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html index 0113c999f..4b8368000 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html +++ b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html @@ -1,6 +1,5 @@ - - + @@ -8,11 +7,23 @@ - +

Embassy Marketplace

+ + + + +
+ + + +
+ +
+
+ + -

Embassy Marketplace

-
- -
- -
- - - - - - - - - - -

Now Available...

-

Embassy OS {{ eos.version }}

-

{{ eos.headline }}

-
-
-
- - - - - - -

{{ pkg.manifest.title }}

-

{{ pkg.manifest.description.short }}

- -

- Installed - Update Available -

-

- {{ localPkg.state | titlecase }}...{{ (localPkg['install-progress'] | installState).totalProgress }}% -

-

- {{ localPkg.state | Removing }} - -

-
- -

Not Installed

-
-
-
-
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Now Available...

+

Embassy OS {{ eos.version }}

+

{{ eos.headline }}

+
+
+
+ + + + + + +

{{ pkg.manifest.title }}

+

{{ pkg.manifest.description.short }}

+ +

+ Installed + Update Available +

+

+ {{ localPkg.state | titlecase }}...{{ (localPkg['install-progress'] | installState).totalProgress }}% +

+

+ {{ localPkg.state | Removing }} + +

+
+ +

Not Installed

+
+
+
+
+
+
+
+ diff --git a/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html b/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html index 810b96bb3..2a7e8486a 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html +++ b/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html @@ -70,7 +70,7 @@ - +

@@ -98,14 +98,14 @@ - +
Description - +
{{ pkg.manifest.description.long }}
@@ -119,7 +119,7 @@ - +

{{ pkg['dependency-metadata'][dep.key].title }} (recommended) diff --git a/ui/src/app/pages/notifications/notifications.page.html b/ui/src/app/pages/notifications/notifications.page.html index c25b36052..3eeef3452 100644 --- a/ui/src/app/pages/notifications/notifications.page.html +++ b/ui/src/app/pages/notifications/notifications.page.html @@ -11,14 +11,30 @@ - - - - + + + + + + + + + + + + + + + + + + - + + +
- -

Inbox Empty

+ +

Inbox Empty

@@ -42,7 +58,7 @@ - +

{{ not.title }}

diff --git a/ui/src/app/pages/notifications/notifications.page.scss b/ui/src/app/pages/notifications/notifications.page.scss index d472cbe22..c9219c817 100644 --- a/ui/src/app/pages/notifications/notifications.page.scss +++ b/ui/src/app/pages/notifications/notifications.page.scss @@ -1,3 +1,3 @@ .notification-message { - margin: 10px 0 12px 0; + margin: 6px 0 8px 0; } \ No newline at end of file diff --git a/ui/src/app/pages/notifications/notifications.page.ts b/ui/src/app/pages/notifications/notifications.page.ts index e3d15ae64..8939805eb 100644 --- a/ui/src/app/pages/notifications/notifications.page.ts +++ b/ui/src/app/pages/notifications/notifications.page.ts @@ -32,7 +32,7 @@ export class NotificationsPage { this.loading = false } - async doRefresh (e: any) { + async refresh (e: any) { this.page = 1 this.notifications = await this.getNotifications(), e.target.complete() diff --git a/ui/src/app/pages/server-routes/lan/lan.page.html b/ui/src/app/pages/server-routes/lan/lan.page.html index 17ab83a6d..ed3d7e4c9 100644 --- a/ui/src/app/pages/server-routes/lan/lan.page.html +++ b/ui/src/app/pages/server-routes/lan/lan.page.html @@ -11,25 +11,27 @@ - - -

About

-

You can connect to your Embassy over your Local Area Network (LAN). This can be useful for achieving a faster experience, as well as a fallback in case the Tor network is experiencing issues.

+ + +

+ Connecting to your Embassy over the Local Area Network (LAN) is great for achieving a faster experience, as well as a fallback in case Tor is experiencing issues. + View instructions +

- - - View Instructions - + - - Download Root Certificate Authority + + +

Download Root CA

+

Download and trust your Embassy's Root Certificate Authority to achieve a secure connection on the LAN.

+
- +

Setup

@@ -37,16 +39,12 @@
- - - -

Troubleshooting

-

If you are having issues connecting to your Embassy over LAN, try refreshing your LAN services by clicking the button below.

-
-
- - Refresh LAN + + +

Refresh LAN

+

If you are having issues connecting to your Embassy over LAN, try refreshing your LAN services by clicking the button below.

+
diff --git a/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.html b/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.html index 9bc4cca6b..649023f87 100644 --- a/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.html +++ b/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.html @@ -8,14 +8,37 @@ - + + +
+ + + + + + + + + + + + + + + + + +
+
+ + - Current Session + This Session - - + +

{{ getPlatformName(current.metadata.platforms) }}

Last Active: {{ current['last-active'] | date : 'medium' }}

{{ current['user-agent'] }}

@@ -28,8 +51,8 @@ [id]="session.key" *ngIf="session.key !== sessionInfo.current" > - - + +

{{ getPlatformName(session.value.metadata.platforms) }}

Last Active: {{ session.value['last-active'] | date : 'medium' }}

{{ session.value['user-agent'] }}

diff --git a/ui/src/app/pages/server-routes/security-routes/ssh-keys/ssh-keys.page.html b/ui/src/app/pages/server-routes/security-routes/ssh-keys/ssh-keys.page.html index adcf10f4b..f0c9226ff 100644 --- a/ui/src/app/pages/server-routes/security-routes/ssh-keys/ssh-keys.page.html +++ b/ui/src/app/pages/server-routes/security-routes/ssh-keys/ssh-keys.page.html @@ -4,38 +4,62 @@ SSH Keys - - - - - - - - + + - -

About

-

Adding an SSH key to your Embassy can be useful for advanced usage from the command line, as well as for debugging purposes.

+ +

+ Adding SSH keys to your Embassy is useful for command line access, as well as for debugging purposes. + View instructions +

- - - View Instructions - + Saved Keys - - - {{ ssh.value.alg }} {{ ssh.key }} {{ ssh.value.hostname }} - - - - + + + + Add new key + + + + + + + + + + + + + + + + + + + + + + + +

{{ ssh.value.hostname }}

+

{{ ssh.value['created-at'] | date: 'short' }}

+

{{ ssh.value.alg }} {{ ssh.key }}

+
+ + + Remove + +
+
+
\ No newline at end of file diff --git a/ui/src/app/pages/server-routes/server-backup/server-backup.page.html b/ui/src/app/pages/server-routes/server-backup/server-backup.page.html index 96b690b0d..439800675 100644 --- a/ui/src/app/pages/server-routes/server-backup/server-backup.page.html +++ b/ui/src/app/pages/server-routes/server-backup/server-backup.page.html @@ -4,11 +4,6 @@ Create Backup - - - - - @@ -18,45 +13,33 @@ - -

About

+

- Create frequent backups of your Embassy to avoid loss of data. + Select the drive where you want to create a backup of your Embassy, including all your installed services.

- Select Backup Drive - - No partitions available. To begin a backup, insert a storage device into your Embassy. - No partitions available. Insert the storage device containing the backup you wish to restore. + No partitions available. To begin a backup, insert a storage device into your Embassy. - - - - {{ disk.value.size }} - - - {{ disk.key }} - - - - - - - -

{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})

-

Available

- -

Unavailable

-
-
-
-
-
-
+ +
+ {{ disk.key }} - {{ disk.value.size }} + + + +

{{ partition.value.label || partition.key }}

+

{{ partition.value.size || 'unknown size' }}

+

Available

+ +

Unavailable

+
+
+
+
+
diff --git a/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts b/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts index 321bcc605..51895fca1 100644 --- a/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts +++ b/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core' -import { LoadingController, ModalController } from '@ionic/angular' +import { ModalController } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component' import { DiskInfo } from 'src/app/services/api/api.types' @@ -19,7 +19,6 @@ export class ServerBackupPage { private readonly modalCtrl: ModalController, private readonly embassyApi: ApiService, private readonly errToast: ErrorToastService, - private readonly loadingCtrl: LoadingController, ) { } ngOnInit () { @@ -45,36 +44,21 @@ export class ServerBackupPage { async presentModal (logicalname: string): Promise { const m = await this.modalCtrl.create({ componentProps: { - type: 'backup', + title: 'Create Backup', + message: `Enter your master password to create an encrypted backup of your Embassy and all its installed services.`, + label: 'Password', + useMask: true, + buttonText: 'Create Backup', + submitFn: async (value: string) => await this.create(logicalname, value), }, cssClass: 'alertlike-modal', component: BackupConfirmationComponent, - backdropDismiss: false, - }) - - m.onWillDismiss().then(res => { - const data = res.data - if (data.cancel) return - this.create(logicalname, data.password) }) return await m.present() } private async create (logicalname: string, password: string): Promise { - const loader = await this.loadingCtrl.create({ - spinner: 'lines', - message: 'Starting backup...', - cssClass: 'loader', - }) - await loader.present() - - try { - await this.embassyApi.createBackup({ logicalname, password }) - } catch (e) { - this.errToast.present(e) - } finally { - loader.dismiss() - } + await this.embassyApi.createBackup({ logicalname, password }) } } diff --git a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html index 082c4e4cf..13e5ff85a 100644 --- a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html +++ b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html @@ -8,7 +8,7 @@ - + diff --git a/ui/src/app/pages/server-routes/server-specs/server-specs.page.html b/ui/src/app/pages/server-routes/server-specs/server-specs.page.html index 110a56c29..8032a13f9 100644 --- a/ui/src/app/pages/server-routes/server-specs/server-specs.page.html +++ b/ui/src/app/pages/server-routes/server-specs/server-specs.page.html @@ -40,7 +40,7 @@
- Specs + Hardware Specs diff --git a/ui/src/app/pages/server-routes/wifi/wifi.page.html b/ui/src/app/pages/server-routes/wifi/wifi.page.html index bb2815a1d..2ac9aaa61 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi.page.html +++ b/ui/src/app/pages/server-routes/wifi/wifi.page.html @@ -15,7 +15,7 @@ - +

About

Embassy will automatically connect to saved WiFi networks when they are available, allowing you to remove the Ethernet cable.

diff --git a/ui/src/app/pipes/ui.pipe.ts b/ui/src/app/pipes/ui.pipe.ts index 8c2025a09..972454579 100644 --- a/ui/src/app/pipes/ui.pipe.ts +++ b/ui/src/app/pipes/ui.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core' -import { PackageDataEntry } from '../services/patch-db/data-model' +import { InterfaceDef, PackageMainStatus, PackageState } from '../services/patch-db/data-model' import { ConfigService, hasUi } from '../services/config.service' @Pipe({ @@ -7,8 +7,7 @@ import { ConfigService, hasUi } from '../services/config.service' }) export class HasUiPipe implements PipeTransform { - transform (pkg: PackageDataEntry): boolean { - const interfaces = pkg.manifest.interfaces + transform (interfaces: { [id: string]: InterfaceDef }): boolean { return hasUi(interfaces) } } @@ -20,7 +19,7 @@ export class LaunchablePipe implements PipeTransform { constructor (private configService: ConfigService) { } - transform (pkg: PackageDataEntry): boolean { - return this.configService.isLaunchable(pkg) + transform (state: PackageState, status: PackageMainStatus, interfaces: { [id: string]: InterfaceDef }): boolean { + return this.configService.isLaunchable(state, status, interfaces) } } diff --git a/ui/src/app/services/api/api.fixures.ts b/ui/src/app/services/api/api.fixures.ts index eab1638ac..7d319384c 100644 --- a/ui/src/app/services/api/api.fixures.ts +++ b/ui/src/app/services/api/api.fixures.ts @@ -832,11 +832,13 @@ export module Mock { export const SshKeys: RR.GetSSHKeysRes = { '28:d2:7e:78:61:b4:bf:g2:de:24:15:96:4e:d4:15:53': { + 'created-at': new Date().toISOString(), alg: 'ed25519', hostname: 'Matt Key', hash: 'VeryLongHashOfSSHKey1', }, '12:f8:7e:78:61:b4:bf:e2:de:24:15:96:4e:d4:72:53': { + 'created-at': new Date().toISOString(), alg: 'ed25519', hostname: 'Aiden Key', hash: 'VeryLongHashOfSSHKey2', @@ -845,6 +847,7 @@ export module Mock { export const SshKey: RR.AddSSHKeyRes = { '44:44:7e:78:61:b4:bf:g2:de:24:15:96:4e:d4:15:53': { + 'created-at': new Date().toISOString(), alg: 'ed25519', hostname: 'Lucy Key', hash: 'VeryLongHashOfSSHKey3', diff --git a/ui/src/app/services/api/api.types.ts b/ui/src/app/services/api/api.types.ts index 2914a5247..e417c1086 100644 --- a/ui/src/app/services/api/api.types.ts +++ b/ui/src/app/services/api/api.types.ts @@ -308,6 +308,7 @@ export interface SSHKeys { } export interface SSHKeyEntry { + 'created-at': string alg: string hostname: string hash: string diff --git a/ui/src/app/services/config.service.ts b/ui/src/app/services/config.service.ts index 8f82cbb55..276bbb46e 100644 --- a/ui/src/app/services/config.service.ts +++ b/ui/src/app/services/config.service.ts @@ -49,15 +49,15 @@ export class ConfigService { return this.isConsulate || (mocks.enabled && mocks.connection === 'poll') } - isLaunchable (pkg: PackageDataEntry): boolean { - if (this.isConsulate || pkg.state !== PackageState.Installed) { + isLaunchable (state: PackageState, status: PackageMainStatus, interfaces: { [id: string]: InterfaceDef }): boolean { + if (this.isConsulate || state !== PackageState.Installed) { return false } - return pkg.installed.status.main.status === PackageMainStatus.Running && + return status === PackageMainStatus.Running && ( - (hasTorUi(pkg.manifest.interfaces) && this.isTor()) || - (hasLanUi(pkg.manifest.interfaces) && !this.isTor()) + (hasTorUi(interfaces) && this.isTor()) || + (hasLanUi(interfaces) && !this.isTor()) ) } diff --git a/ui/src/app/services/emver.service.ts b/ui/src/app/services/emver.service.ts index eb6e53f16..bb7c15c3e 100644 --- a/ui/src/app/services/emver.service.ts +++ b/ui/src/app/services/emver.service.ts @@ -1,24 +1,21 @@ import { Injectable } from '@angular/core' +import * as emver from '@start9labs/emver' + @Injectable({ providedIn: 'root', }) export class Emver { - private e: typeof import('@start9labs/emver') constructor () { } - async init () { - this.e = await import('@start9labs/emver') - } - compare (lhs: string, rhs: string): number { - console.log('EMVER', this.e) - const compare = this.e.compare(lhs, rhs) + console.log('EMVER', emver) + const compare = emver.compare(lhs, rhs) console.log('COMPARE', compare) return compare } satisfies (version: string, range: string): boolean { - return this.e.satisfies(version, range) + return emver.satisfies(version, range) } } \ No newline at end of file diff --git a/ui/src/app/services/error-toast.service.ts b/ui/src/app/services/error-toast.service.ts index 83fe7caf3..2d4175dc7 100644 --- a/ui/src/app/services/error-toast.service.ts +++ b/ui/src/app/services/error-toast.service.ts @@ -17,24 +17,9 @@ export class ErrorToastService { if (this.toast) return - let message: string | IonicSafeString - - if (e.code) message = String(e.code) - if (e.message) message = `${message ? message + ' ' : ''}${e.message}` - if (e.details) message = `${message ? message + ': ' : ''}${e.details}` - - if (!message) { - message = 'Unknown Error.' - link = 'https://docs.start9.com' - } - - if (link) { - message = new IonicSafeString(`${message}

Get Help`) - } - this.toast = await this.toastCtrl.create({ header: 'Error', - message, + message: getErrorMessage(e, link), duration: 0, position: 'top', cssClass: 'error-toast', @@ -57,4 +42,23 @@ export class ErrorToastService { this.toast = undefined } } +} + +export function getErrorMessage (e: RequestError, link?: string): string | IonicSafeString { + let message: string | IonicSafeString + + if (e.code) message = String(e.code) + if (e.message) message = `${message ? message + ' ' : ''}${e.message}` + if (e.details) message = `${message ? message + ': ' : ''}${e.details}` + + if (!message) { + message = 'Unknown Error.' + link = 'https://docs.start9.com' + } + + if (link) { + message = new IonicSafeString(`${message}

Get Help`) + } + + return message } \ No newline at end of file diff --git a/ui/src/app/services/server-config.service.ts b/ui/src/app/services/server-config.service.ts index ab8ac7144..05627009c 100644 --- a/ui/src/app/services/server-config.service.ts +++ b/ui/src/app/services/server-config.service.ts @@ -1,12 +1,12 @@ import { Injectable } from '@angular/core' import { AlertInput, AlertButton } from '@ionic/core' -// import { AppConfigValuePage } from '../modals/app-config-value/app-config-value.page' import { ApiService } from './api/embassy-api.service' -import { ConfigSpec } from '../pkg-config/config-types' +import { ConfigSpec, ValueSpecString } from '../pkg-config/config-types' import { SSHService } from '../pages/server-routes/security-routes/ssh-keys/ssh.service' import { AlertController, LoadingController } from '@ionic/angular' import { ErrorToastService } from './error-toast.service' -// import { ModalController } from '@ionic/angular' +import { ModalController } from '@ionic/angular' +import { BackupConfirmationComponent } from '../modals/backup-confirmation/backup-confirmation.component' @Injectable({ providedIn: 'root', @@ -14,7 +14,7 @@ import { ErrorToastService } from './error-toast.service' export class ServerConfigService { constructor ( - // private readonly modalCtrl: ModalController, + private readonly modalCtrl: ModalController, private readonly loadingCtrl: LoadingController, private readonly errToast: ErrorToastService, private readonly alertCtrl: AlertController, @@ -71,16 +71,8 @@ export class ServerConfigService { }, ] break - case 'string': - inputs = [ - { - name: key, - type: 'textarea', - placeholder: 'Enter SSH public key', - value: current, - }, - ] - break + default: + return } const alert = await this.alertCtrl.create({ @@ -92,6 +84,24 @@ export class ServerConfigService { await alert.present() } + async presentInputModal (key: string, current?: string) { + const { name, description, masked } = serverConfig[key] as ValueSpecString + + const modal = await this.modalCtrl.create({ + component: BackupConfirmationComponent, + componentProps: { + title: name, + message: description, + label: name, + useMask: masked, + value: current, + submitFn: this.saveFns[key], + }, + cssClass: 'alertlike-modal', + }) + await modal.present() + } + // async presentModalForm (key: string, current?: string) { // const modal = await this.modalCtrl.create({ // component: AppConfigValuePage, @@ -105,7 +115,6 @@ export class ServerConfigService { saveFns: { [key: string]: (val: any) => Promise } = { 'auto-check-updates': async (enabled: boolean) => { - console.log('SAVING auto check', enabled) return this.embassyApi.setDbValue({ pointer: '/auto-check-updates', value: enabled }) }, ssh: async (pubkey: string) => { @@ -136,7 +145,7 @@ export const serverConfig: ConfigSpec = { ssh: { type: 'string', name: 'SSH Key', - description: 'Enter an SSH public key to authorize root access from the command line.', + description: 'Enter the SSH public key of you would like to authorize for root access to your Embassy.', nullable: false, // @TODO regex for SSH Key // pattern: '', diff --git a/ui/src/global.scss b/ui/src/global.scss index 4d4c8b3c9..95be02836 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -43,11 +43,18 @@ $subheader-height: 48px; word-break: break-all; } -.loader { - --spinner-color: var(--ion-color-warning) !important; +.input-label { + // padding-top: 10px; + margin-bottom: 6px; + font-size: medium; + font-weight: 500; + * { + display: inline-block; + vertical-align: middle; + } } -.loader-ontop-of-all { +.loader { --spinner-color: var(--ion-color-warning) !important; z-index: 40000 !important; } @@ -142,13 +149,6 @@ ion-button { --color: var(--ion-color-dark) !important; } -* { - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; -} - .text-ellipses { overflow: hidden; text-overflow: ellipsis; @@ -183,7 +183,7 @@ ion-button { .alertlike-modal { .modal-wrapper { - height: 50% !important; + max-height: 380px !important; top: 25% !important; width: 90% !important; left: 5% !important; @@ -194,8 +194,8 @@ ion-button { @media (min-width:1000px) { .alertlike-modal { .modal-wrapper { - width: 50% !important; - left: 25% !important; + width: 40% !important; + left: 30% !important; } } } @@ -241,6 +241,10 @@ ion-item { border-radius: 4px; } +ion-label { + white-space: normal !important; +} + ion-loading { z-index: 100 !important; }