diff --git a/ui/package-lock.json b/ui/package-lock.json index e127a20e9..e28d4127e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -16,7 +16,7 @@ "@angular/router": "12.2.0", "@ionic/angular": "5.6.13", "@ionic/storage-angular": "3.0.6", - "@start9labs/emver": "0.1.4", + "@start9labs/emver": "0.1.5", "ajv": "6.12.6", "compare-versions": "3.6.0", "core-js": "3.16.1", @@ -54,16 +54,16 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "rxjs": "^6.6.3", - "sorted-btree": "^1.5.0", - "uuid": "^8.3.2" + "rxjs": "6.6.7", + "sorted-btree": "1.5.0", + "uuid": "8.3.2" }, "devDependencies": { - "@types/node": "^15.0.0", - "@types/uuid": "^8.3.0", - "ts-node": "^9.1.1", - "tslint": "^6.1.0", - "typescript": "4.1.5" + "@types/node": "16.4.13", + "@types/uuid": "8.3.1", + "ts-node": "10.2.0", + "tslint": "6.1.3", + "typescript": "4.3.5" } }, "node_modules/@ampproject/remapping": { @@ -2861,9 +2861,9 @@ "dev": true }, "node_modules/@start9labs/emver": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.4.tgz", - "integrity": "sha512-lWhc94tGhWjJYHTYlHydHESViBi8DqjIqdwtPKMeVkGp3RKZdcv8RRpvEatQHlnCoT5IfKuH/29BZtLnwGL4CQ==" + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.5.tgz", + "integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg==" }, "node_modules/@stencil/core": { "version": "2.6.0", @@ -16820,9 +16820,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "dev": true, - "requires": { - "ajv": "^8.0.0" - } + "requires": {} }, "core-js": { "version": "3.16.0", @@ -16900,9 +16898,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "dev": true, - "requires": { - "ajv": "^8.0.0" - } + "requires": {} }, "json-schema-traverse": { "version": "1.0.0", @@ -16946,9 +16942,7 @@ "integrity": "sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==", "dev": true, "peer": true, - "requires": { - "ajv": "^8.0.0" - } + "requires": {} }, "json-schema-traverse": { "version": "1.0.0", @@ -17001,9 +16995,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "dev": true, - "requires": { - "ajv": "^8.0.0" - } + "requires": {} }, "json-schema-traverse": { "version": "1.0.0", @@ -17081,9 +17073,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "dev": true, - "requires": { - "ajv": "^8.0.0" - } + "requires": {} }, "json-schema-traverse": { "version": "1.0.0", @@ -18632,9 +18622,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "dev": true, - "requires": { - "ajv": "^8.0.0" - } + "requires": {} }, "json-schema-traverse": { "version": "1.0.0", @@ -18645,9 +18633,9 @@ } }, "@start9labs/emver": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.4.tgz", - "integrity": "sha512-lWhc94tGhWjJYHTYlHydHESViBi8DqjIqdwtPKMeVkGp3RKZdcv8RRpvEatQHlnCoT5IfKuH/29BZtLnwGL4CQ==" + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.5.tgz", + "integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg==" }, "@stencil/core": { "version": "2.6.0", @@ -24503,14 +24491,14 @@ "patch-db-client": { "version": "file:../../patch-db/client", "requires": { - "@types/node": "^15.0.0", - "@types/uuid": "^8.3.0", - "rxjs": "^6.6.3", - "sorted-btree": "^1.5.0", - "ts-node": "^9.1.1", - "tslint": "^6.1.0", - "typescript": "4.1.5", - "uuid": "^8.3.2" + "@types/node": "16.4.13", + "@types/uuid": "8.3.1", + "rxjs": "6.6.7", + "sorted-btree": "1.5.0", + "ts-node": "10.2.0", + "tslint": "6.1.3", + "typescript": "4.3.5", + "uuid": "8.3.2" } }, "path-dirname": { diff --git a/ui/package.json b/ui/package.json index c0f18ac63..376e4d221 100644 --- a/ui/package.json +++ b/ui/package.json @@ -20,7 +20,7 @@ "@angular/router": "12.2.0", "@ionic/angular": "5.6.13", "@ionic/storage-angular": "3.0.6", - "@start9labs/emver": "0.1.4", + "@start9labs/emver": "0.1.5", "ajv": "6.12.6", "compare-versions": "3.6.0", "core-js": "3.16.1", diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 163ab55dc..4ac355b14 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -77,6 +77,7 @@ + @@ -87,6 +88,7 @@ + diff --git a/ui/src/app/components/form-object/form-error.component.html b/ui/src/app/components/form-object/form-error.component.html new file mode 100644 index 000000000..7cfbedf30 --- /dev/null +++ b/ui/src/app/components/form-object/form-error.component.html @@ -0,0 +1,31 @@ +
+ +

+ {{ spec.name }} is required +

+ + +

+ {{ spec['pattern-description'] }} +

+ + + +

+ {{ spec.name }} must be an integer +

+

+ Number not in range +

+
+ + + +

+ List not in range +

+

+ {{ spec['pattern-description'] }} +

+
+
\ No newline at end of file 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 90fa0561e..689ddd9fe 100644 --- a/ui/src/app/components/form-object/form-object.component.html +++ b/ui/src/app/components/form-object/form-object.component.html @@ -151,11 +151,12 @@ - -

- {{ validation.message }} -

-
+ + @@ -183,11 +184,12 @@ -
-

- {{ spec.name }}: {{ validation.message }} -

-
+ + \ No newline at end of file diff --git a/ui/src/app/components/form-object/form-object.component.module.ts b/ui/src/app/components/form-object/form-object.component.module.ts index 6fec75e08..c42265c63 100644 --- a/ui/src/app/components/form-object/form-object.component.module.ts +++ b/ui/src/app/components/form-object/form-object.component.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' -import { FormObjectComponent, FormLabelComponent } from './form-object.component' +import { FormObjectComponent, FormLabelComponent, FormErrorComponent } from './form-object.component' import { IonicModule } from '@ionic/angular' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { SharingModule } from 'src/app/modules/sharing.module' @@ -10,6 +10,7 @@ import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module' declarations: [ FormObjectComponent, FormLabelComponent, + FormErrorComponent, ], imports: [ CommonModule, @@ -22,6 +23,7 @@ import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module' exports: [ FormObjectComponent, FormLabelComponent, + FormErrorComponent, ], }) export class FormObjectComponentModule { } 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 135cff64c..286d86f6c 100644 --- a/ui/src/app/components/form-object/form-object.component.scss +++ b/ui/src/app/components/form-object/form-object.component.scss @@ -27,5 +27,6 @@ ion-item-divider { .validation-error { p { font-size: small; + color: var(--ion-color-danger); } } \ No newline at end of file diff --git a/ui/src/app/components/form-object/form-object.component.ts b/ui/src/app/components/form-object/form-object.component.ts index ade22405b..efce9ab09 100644 --- a/ui/src/app/components/form-object/form-object.component.ts +++ b/ui/src/app/components/form-object/form-object.component.ts @@ -1,5 +1,5 @@ import { Component, Input, SimpleChange } from '@angular/core' -import { FormArray, FormGroup } from '@angular/forms' +import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms' import { AlertController, ModalController } from '@ionic/angular' import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types' import { FormService } from 'src/app/services/form.service' @@ -85,7 +85,7 @@ export class FormObjectComponent { // const validators = this.formService.getListItemValidators(this.objectSpec[key] as ValueSpecList, key, arr.length) // arr.push(new FormControl(value, validators)) const listSpec = this.objectSpec[key] as ValueSpecList - const newItem = this.formService.getListItem(key, arr.length, listSpec, val) + const newItem = this.formService.getListItem(listSpec, val) newItem.markAllAsTouched() arr.insert(0, newItem) if (['object', 'union'].includes(listSpec.subtype)) { @@ -222,3 +222,14 @@ export class FormLabelComponent { await alert.present() } } + + +@Component({ + selector: 'form-error', + templateUrl: './form-error.component.html', + styleUrls: ['./form-object.component.scss'], +}) +export class FormErrorComponent { + @Input() control: AbstractFormGroupDirective + @Input() spec: ValueSpec +} diff --git a/ui/src/app/modals/app-action-input/app-action-input.page.ts b/ui/src/app/modals/app-action-input/app-action-input.page.ts deleted file mode 100644 index b73fb3b32..000000000 --- a/ui/src/app/modals/app-action-input/app-action-input.page.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Component, Input } from '@angular/core' -import { FormGroup } from '@angular/forms' -import { ModalController } from '@ionic/angular' -import { Action } from 'src/app/services/patch-db/data-model' -import { FormService } from 'src/app/services/form.service' - -@Component({ - selector: 'app-action-input', - templateUrl: './app-action-input.page.html', - styleUrls: ['./app-action-input.page.scss'], -}) -export class AppActionInputPage { - @Input() action: Action - actionForm: FormGroup - - constructor ( - private readonly modalCtrl: ModalController, - private readonly formService: FormService, - ) { } - - ngOnInit () { - this.actionForm = this.formService.createForm(this.action['input-spec']) - } - - async dismiss (): Promise { - this.modalCtrl.dismiss() - } - - async save (): Promise { - if (this.actionForm.invalid) { - this.actionForm.markAllAsTouched() - document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' }) - return - } - this.modalCtrl.dismiss(this.actionForm.value) - } - - asIsOrder () { - return 0 - } -} diff --git a/ui/src/app/modals/app-action-input/app-action-input.module.ts b/ui/src/app/modals/generic-form/generic-form.module.ts similarity index 67% rename from ui/src/app/modals/app-action-input/app-action-input.module.ts rename to ui/src/app/modals/generic-form/generic-form.module.ts index 3559304a3..ed2343e5c 100644 --- a/ui/src/app/modals/app-action-input/app-action-input.module.ts +++ b/ui/src/app/modals/generic-form/generic-form.module.ts @@ -1,12 +1,12 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { IonicModule } from '@ionic/angular' -import { AppActionInputPage } from './app-action-input.page' +import { GenericFormPage } from './generic-form.page' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module' @NgModule({ - declarations: [AppActionInputPage], + declarations: [GenericFormPage], imports: [ CommonModule, IonicModule, @@ -14,7 +14,7 @@ import { FormObjectComponentModule } from 'src/app/components/form-object/form-o ReactiveFormsModule, FormObjectComponentModule, ], - entryComponents: [AppActionInputPage], - exports: [AppActionInputPage], + entryComponents: [GenericFormPage], + exports: [GenericFormPage], }) -export class AppActionInputPageModule { } \ No newline at end of file +export class GenericFormPageModule { } \ No newline at end of file diff --git a/ui/src/app/modals/app-action-input/app-action-input.page.html b/ui/src/app/modals/generic-form/generic-form.page.html similarity index 56% rename from ui/src/app/modals/app-action-input/app-action-input.page.html rename to ui/src/app/modals/generic-form/generic-form.page.html index 5c7b67628..c58450265 100644 --- a/ui/src/app/modals/app-action-input/app-action-input.page.html +++ b/ui/src/app/modals/generic-form/generic-form.page.html @@ -1,14 +1,14 @@ - {{ action.name }} + {{ title }} -
+
@@ -16,13 +16,13 @@ - + Cancel - - Execute + + {{ button.text }} diff --git a/ui/src/app/modals/app-action-input/app-action-input.page.scss b/ui/src/app/modals/generic-form/generic-form.page.scss similarity index 100% rename from ui/src/app/modals/app-action-input/app-action-input.page.scss rename to ui/src/app/modals/generic-form/generic-form.page.scss diff --git a/ui/src/app/modals/generic-form/generic-form.page.ts b/ui/src/app/modals/generic-form/generic-form.page.ts new file mode 100644 index 000000000..898ab1bbf --- /dev/null +++ b/ui/src/app/modals/generic-form/generic-form.page.ts @@ -0,0 +1,46 @@ +import { Component, Input } from '@angular/core' +import { FormGroup } from '@angular/forms' +import { ModalController } from '@ionic/angular' +import { FormService } from 'src/app/services/form.service' +import { ConfigSpec } from 'src/app/pkg-config/config-types' + +export interface ActionButton { + text: string + handler: (value: any) => Promise +} + +@Component({ + selector: 'generic-form', + templateUrl: './generic-form.page.html', + styleUrls: ['./generic-form.page.scss'], +}) +export class GenericFormPage { + @Input() title: string + @Input() spec: ConfigSpec + @Input() buttons: ActionButton[] + formGroup: FormGroup + + constructor ( + private readonly modalCtrl: ModalController, + private readonly formService: FormService, + ) { } + + ngOnInit () { + this.formGroup = this.formService.createForm(this.spec) + } + + async dismiss (): Promise { + this.modalCtrl.dismiss() + } + + async handleClick (button: ActionButton): Promise { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched() + document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' }) + return + } + + const success = await button.handler(this.formGroup.value) + if (success !== false) this.modalCtrl.dismiss() + } +} diff --git a/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts b/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts index 7a8a32f54..2185092b2 100644 --- a/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts +++ b/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts @@ -5,7 +5,7 @@ import { IonicModule } from '@ionic/angular' import { AppActionsPage, AppActionsItemComponent } from './app-actions.page' import { QRComponentModule } from 'src/app/components/qr/qr.component.module' import { SharingModule } from 'src/app/modules/sharing.module' -import { AppActionInputPageModule } from 'src/app/modals/app-action-input/app-action-input.module' +import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module' import { AppRestoreComponentModule } from 'src/app/modals/app-restore/app-restore.component.module' const routes: Routes = [ @@ -22,7 +22,7 @@ const routes: Routes = [ RouterModule.forChild(routes), QRComponentModule, SharingModule, - AppActionInputPageModule, + GenericFormPageModule, AppRestoreComponentModule, ], declarations: [ 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 e8d43f8e5..64ed16f7a 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 @@ -7,7 +7,7 @@ import { Action, Manifest, PackageDataEntry, PackageMainStatus } from 'src/app/s import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' import { Subscription } from 'rxjs' -import { AppActionInputPage } from 'src/app/modals/app-action-input/app-action-input.page' +import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { ErrorToastService } from 'src/app/services/error-toast.service' import { AppRestoreComponent } from 'src/app/modals/app-restore/app-restore.component' @@ -50,15 +50,20 @@ export class AppActionsPage { if ((action.value['allowed-statuses'] as PackageMainStatus[]).includes(pkg.installed.status.main.status)) { if (action.value['input-spec']) { const modal = await this.modalCtrl.create({ - component: AppActionInputPage, + component: GenericFormPage, componentProps: { - action: action.value, + title: action.value.name, + spec: action.value['input-spec'], + buttons: [ + { + text: 'Execute', + handler: (value: any) => { + return this.executeAction(pkg.manifest.id, action.key, value) + }, + }, + ], }, }) - modal.onWillDismiss().then(({ data }) => { - if (!data) return - this.executeAction(pkg.manifest.id, action.key, data) - }) await modal.present() } else { const alert = await this.alertCtrl.create({ @@ -135,7 +140,7 @@ export class AppActionsPage { return this.navCtrl.navigateRoot('/services') } - private async executeAction (pkgId: string, actionId: string, input?: object): Promise { + private async executeAction (pkgId: string, actionId: string, input?: object): Promise { const loader = await this.loadingCtrl.create({ spinner: 'lines', message: 'Executing action...', @@ -155,9 +160,12 @@ export class AppActionsPage { message: res.message.split('\n').join('

'), buttons: ['OK'], }) - await successAlert.present() + + setTimeout(() => successAlert.present(), 400) + } catch (e) { this.errToast.present(e) + return false } finally { loader.dismiss() } diff --git a/ui/src/app/pages/maintenance/maintenance.page.html b/ui/src/app/pages/maintenance/maintenance.page.html index b7258715e..4c3ea615a 100644 --- a/ui/src/app/pages/maintenance/maintenance.page.html +++ b/ui/src/app/pages/maintenance/maintenance.page.html @@ -3,9 +3,14 @@ - -

Embassy is updating

-

Embassy is backing up

+ +

Embassy Updating

+ +
+ +

Backing Up

+ +
diff --git a/ui/src/app/pages/maintenance/maintenance.page.scss b/ui/src/app/pages/maintenance/maintenance.page.scss index e69de29bb..b1b6d5480 100644 --- a/ui/src/app/pages/maintenance/maintenance.page.scss +++ b/ui/src/app/pages/maintenance/maintenance.page.scss @@ -0,0 +1,4 @@ +img { + width: 20%; + border-radius: 0; +} \ No newline at end of file diff --git a/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.ts b/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.ts index 6b5e4f047..989c38168 100644 --- a/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.ts +++ b/ui/src/app/pages/server-routes/security-routes/sessions/sessions.page.ts @@ -22,9 +22,9 @@ export class SessionsPage { async ngOnInit () { try { - this.sessionInfo = await this.embassyApi.getSessions({}) + this.sessionInfo = await this.embassyApi.getSessions({ }) } catch (e) { - this.errToast.present(e.message) + this.errToast.present(e) } finally { this.loading = false } 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 f0c9226ff..a7bc8fec1 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 @@ -8,9 +8,9 @@ - - + +

@@ -22,7 +22,7 @@ Saved Keys - + Add new key diff --git a/ui/src/app/pages/server-routes/server-routing.module.ts b/ui/src/app/pages/server-routes/server-routing.module.ts index 9fc82a90a..de682db47 100644 --- a/ui/src/app/pages/server-routes/server-routing.module.ts +++ b/ui/src/app/pages/server-routes/server-routing.module.ts @@ -24,7 +24,7 @@ const routes: Routes = [ }, { path: 'wifi', - loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiListPageModule), + loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiPageModule), }, { path: 'lan', diff --git a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.module.ts b/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.module.ts deleted file mode 100644 index a574af4d2..000000000 --- a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { FormsModule } from '@angular/forms' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { WifiAddPage } from './wifi-add.page' -import { SharingModule } from 'src/app/modules/sharing.module' - -const routes: Routes = [ - { - path: '', - component: WifiAddPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - IonicModule, - RouterModule.forChild(routes), - SharingModule, - ], - declarations: [WifiAddPage], -}) -export class WifiAddPageModule { } diff --git a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.html b/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.html deleted file mode 100644 index db9008fa5..000000000 --- a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - Add Network - - - - - - - Select Country - - - {{ country.key }} - {{ country.value }} - - - - Network and Password - - - - - - - - - - - - -

Save

-
-
- - -

Save & Connect

-
-
-
-
- -
\ No newline at end of file diff --git a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.scss b/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts b/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts deleted file mode 100644 index 9c83d2017..000000000 --- a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Component } from '@angular/core' -import { LoadingController, NavController } from '@ionic/angular' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { WifiService } from '../wifi.service' -import { ErrorToastService } from 'src/app/services/error-toast.service' - -@Component({ - selector: 'wifi-add', - templateUrl: 'wifi-add.page.html', - styleUrls: ['wifi-add.page.scss'], -}) -export class WifiAddPage { - countries = require('../../../../util/countries.json') - countryCode = 'US' - ssid = '' - password = '' - - constructor ( - private readonly navCtrl: NavController, - private readonly errToast: ErrorToastService, - private readonly embassyApi: ApiService, - private readonly loadingCtrl: LoadingController, - private readonly wifiService: WifiService, - ) { } - - async save (): Promise { - const loader = await this.loadingCtrl.create({ - spinner: 'lines', - message: 'Saving...', - cssClass: 'loader', - }) - await loader.present() - - try { - await this.embassyApi.addWifi({ - ssid: this.ssid, - password: this.password, - country: this.countryCode, - priority: 0, - connect: false, - }) - this.navCtrl.back() - } catch (e) { - this.errToast.present(e) - } finally { - loader.dismiss() - } - } - - async saveAndConnect (): Promise { - const loader = await this.loadingCtrl.create({ - spinner: 'lines', - message: 'Connecting. This could take while...', - cssClass: 'loader', - }) - await loader.present() - - try { - await this.embassyApi.addWifi({ - ssid: this.ssid, - password: this.password, - country: this.countryCode, - priority: 0, - connect: true, - }) - const success = this.wifiService.confirmWifi(this.ssid) - if (success) { - this.navCtrl.back() - } - } catch (e) { - this.errToast.present(e) - } finally { - loader.dismiss() - } - } - - asIsOrder (a: any, b: any) { - return 0 - } -} diff --git a/ui/src/app/pages/server-routes/wifi/wifi.module.ts b/ui/src/app/pages/server-routes/wifi/wifi.module.ts index 5f19caa7e..8f54ab544 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi.module.ts +++ b/ui/src/app/pages/server-routes/wifi/wifi.module.ts @@ -2,17 +2,13 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { IonicModule } from '@ionic/angular' import { RouterModule, Routes } from '@angular/router' -import { WifiListPage } from './wifi.page' +import { WifiPage } from './wifi.page' import { SharingModule } from 'src/app/modules/sharing.module' const routes: Routes = [ { path: '', - component: WifiListPage, - }, - { - path: 'add', - loadChildren: () => import('./wifi-add/wifi-add.module').then(m => m.WifiAddPageModule), + component: WifiPage, }, ] @@ -23,6 +19,6 @@ const routes: Routes = [ RouterModule.forChild(routes), SharingModule, ], - declarations: [WifiListPage], + declarations: [WifiPage], }) -export class WifiListPageModule { } +export class WifiPageModule { } 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 2ac9aaa61..77b03b542 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi.page.html +++ b/ui/src/app/pages/server-routes/wifi/wifi.page.html @@ -4,30 +4,70 @@ WiFi Settings - - - - - + + -

About

-

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

+

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

- Saved Networks - - + Country + + + + + + + + + + + + + + + + + + {{ wifi.country }} - {{ this.countries[wifi.country] }} + Select Country + + + + + + Saved Networks + + + + + + + + + + + + + Saved Networks + + + Add new network + + +
+ {{ ssid }} - Connected +
-
\ No newline at end of file diff --git a/ui/src/app/pages/server-routes/wifi/wifi.page.scss b/ui/src/app/pages/server-routes/wifi/wifi.page.scss index e69de29bb..c5d70052b 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi.page.scss +++ b/ui/src/app/pages/server-routes/wifi/wifi.page.scss @@ -0,0 +1,6 @@ +.skeleton-parts { + ion-button::part(native) { + padding-inline-start: 0; + padding-inline-end: 0; + } +} \ No newline at end of file diff --git a/ui/src/app/pages/server-routes/wifi/wifi.page.ts b/ui/src/app/pages/server-routes/wifi/wifi.page.ts index 1e3b764d6..893bccd52 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi.page.ts +++ b/ui/src/app/pages/server-routes/wifi/wifi.page.ts @@ -1,44 +1,124 @@ import { Component } from '@angular/core' -import { ActionSheetController, LoadingController } from '@ionic/angular' +import { ActionSheetController, AlertController, LoadingController, ModalController, ToastController } from '@ionic/angular' +import { AlertInput } from '@ionic/core' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ActionSheetButton } from '@ionic/core' -import { WifiService } from './wifi.service' -import { WiFiInfo } from 'src/app/services/patch-db/data-model' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { Subscription } from 'rxjs' import { ErrorToastService } from 'src/app/services/error-toast.service' +import { ValueSpecObject } from 'src/app/pkg-config/config-types' +import { RR } from 'src/app/services/api/api.types' +import { pauseFor } from 'src/app/util/misc.util' +import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' @Component({ selector: 'wifi', templateUrl: 'wifi.page.html', styleUrls: ['wifi.page.scss'], }) -export class WifiListPage { - subs: Subscription[] = [] +export class WifiPage { + loading = true + wifi: RR.GetWifiRes = { } as any + countries = require('../../../util/countries.json') as { [key: string]: string } constructor ( - private readonly embassyApi: ApiService, + private readonly api: ApiService, + private readonly toastCtrl: ToastController, + private readonly alertCtrl: AlertController, private readonly loadingCtrl: LoadingController, + private readonly modalCtrl: ModalController, private readonly errToast: ErrorToastService, private readonly actionCtrl: ActionSheetController, - private readonly wifiService: WifiService, - public readonly patch: PatchDbService, ) { } - async presentAction (ssid: string, wifi: WiFiInfo) { + async ngOnInit () { + try { + await this.getWifi() + } catch (e) { + this.errToast.present(e.message) + } finally { + this.loading = false + } + } + + async getWifi (timeout?: number): Promise { + this.wifi = await this.api.getWifi({ }, timeout) + if (!this.wifi.country) { + await this.presentAlertCountry() + } + } + + async presentAlertCountry (): Promise { + const inputs: AlertInput[] = Object.entries(this.countries).map(([country, fullName]) => { + return { + name: fullName, + type: 'radio', + label: `${country} - ${fullName}`, + value: country, + checked: country === this.wifi.country, + } + }) + + const alert = await this.alertCtrl.create({ + header: 'Select Country', + inputs, + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Save', + handler: async (country: string) => { + this.setCountry(country) + }, + }, + ], + cssClass: 'wide-alert', + }) + await alert.present() + } + + async presentModalAdd () { + const modal = await this.modalCtrl.create({ + component: GenericFormPage, + componentProps: { + title: wifiSpec.name, + spec: wifiSpec.spec, + buttons: [ + { + text: 'Save', + handler: async (value: { ssid: string, password: string }) => { + await this.save(value.ssid, value.password) + }, + }, + { + text: 'Save and Connect', + handler: async (value: { ssid: string, password: string }) => { + await this.saveAndConnect(value.ssid, value.password) + }, + }, + ], + }, + }) + + await modal.present() + } + + async presentAction (ssid: string) { const buttons: ActionSheetButton[] = [ { text: 'Forget', + icon: 'trash', handler: () => { this.delete(ssid) }, }, ] - if (ssid !== wifi.connected) { + if (ssid !== this.wifi.connected) { buttons.unshift( { text: 'Connect', + icon: 'wifi', handler: () => { this.connect(ssid) }, @@ -47,14 +127,113 @@ export class WifiListPage { } const action = await this.actionCtrl.create({ + header: ssid, + subHeader: 'Manage network', + mode: 'ios', buttons, }) await action.present() } - // Let's add country code here - async connect (ssid: string): Promise { + getWifiIcon (): string { + const strength = this.wifi['signal-strength'] + if (!strength) return + + let path = 'assets/img/icons/wifi-' + + switch (true) { + case strength > 66: + path = path + '3' + break + case strength > 33 || strength <= 66: + path = path + '2' + break + case strength < 33: + path = path + '1' + break + } + + return path + '.png' + } + + private async setCountry (country: string): Promise { + const loader = await this.loadingCtrl.create({ + spinner: 'lines', + cssClass: 'loader', + }) + await loader.present() + + try { + await this.api.setWifiCountry({ country }) + this.wifi.country = country + } catch (e) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } + + private async confirmWifi (ssid: string): Promise { + const timeout = 4000 + const maxAttempts = 5 + let attempts = 0 + + while (attempts < maxAttempts) { + try { + const start = new Date().valueOf() + await this.getWifi(timeout) + const end = new Date().valueOf() + if (this.wifi.connected === ssid) { + this.presentAlertSuccess(ssid) + break + } else { + attempts++ + const diff = end - start + await pauseFor(Math.max(1000, timeout - diff)) + if (attempts === maxAttempts) { + this.presentToastFail() + } + } + } catch (e) { + attempts++ + console.error(e) + } + } + } + + private async presentAlertSuccess (ssid: string): Promise { + const alert = await this.alertCtrl.create({ + header: `Connected to "${ssid}"`, + message: 'Note. It may take several minutes to an hour for your Embassy to reconnect over Tor.', + buttons: ['OK'], + }) + + await alert.present() + } + + private async presentToastFail (): Promise { + const toast = await this.toastCtrl.create({ + header: 'Failed to connect:', + message: `Check credentials and try again`, + position: 'bottom', + duration: 4000, + buttons: [ + { + side: 'start', + icon: 'close', + handler: () => { + return true + }, + }, + ], + cssClass: 'warning-toast', + }) + + await toast.present() + } + + private async connect (ssid: string): Promise { const loader = await this.loadingCtrl.create({ spinner: 'lines', message: 'Connecting. This could take while...', @@ -63,8 +242,8 @@ export class WifiListPage { await loader.present() try { - await this.embassyApi.connectWifi({ ssid }) - this.wifiService.confirmWifi(ssid) + await this.api.connectWifi({ ssid }) + await this.confirmWifi(ssid) } catch (e) { this.errToast.present(e) } finally { @@ -72,7 +251,7 @@ export class WifiListPage { } } - async delete (ssid: string): Promise { + private async delete (ssid: string): Promise { const loader = await this.loadingCtrl.create({ spinner: 'lines', message: 'Deleting...', @@ -81,7 +260,55 @@ export class WifiListPage { await loader.present() try { - await this.embassyApi.deleteWifi({ ssid }) + await this.api.deleteWifi({ ssid }) + } catch (e) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } + + private async save (ssid: string, password: string): Promise { + const loader = await this.loadingCtrl.create({ + spinner: 'lines', + message: 'Saving...', + cssClass: 'loader', + }) + await loader.present() + + try { + await this.api.addWifi({ + ssid, + password, + priority: 0, + connect: false, + }) + await this.getWifi() + } catch (e) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } + + private async saveAndConnect (ssid: string, password: string): Promise { + const loader = await this.loadingCtrl.create({ + spinner: 'lines', + message: 'Connecting. This could take while...', + cssClass: 'loader', + }) + await loader.present() + + try { + await this.api.addWifi({ + ssid, + password, + priority: 0, + connect: true, + }) + + await this.confirmWifi(ssid) + } catch (e) { this.errToast.present(e) } finally { @@ -89,3 +316,26 @@ export class WifiListPage { } } } + +const wifiSpec: ValueSpecObject = { + type: 'object', + name: 'WiFi Credentials', + description: 'Enter the network SSID and password. You can connect now or save the network for later.', + 'unique-by': null, + spec: { + ssid: { + type: 'string', + name: 'Network SSID', + nullable: false, + masked: false, + copyable: false, + }, + password: { + type: 'string', + name: 'Password', + nullable: false, + masked: true, + copyable: false, + }, + }, +} diff --git a/ui/src/app/pages/server-routes/wifi/wifi.service.ts b/ui/src/app/pages/server-routes/wifi/wifi.service.ts deleted file mode 100644 index df4ca2599..000000000 --- a/ui/src/app/pages/server-routes/wifi/wifi.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Injectable } from '@angular/core' -import { AlertController, ToastController } from '@ionic/angular' -import { merge, Observable, timer } from 'rxjs' -import { filter, map, take, tap } from 'rxjs/operators' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' - -@Injectable({ - providedIn: 'root', -}) -export class WifiService { - - constructor ( - private readonly toastCtrl: ToastController, - private readonly alertCtrl: AlertController, - private readonly patch: PatchDbService, - ) { } - - confirmWifi (ssid: string): Observable { - const success$ = this.patch.watch$('server-info', 'wifi', 'connected') - .pipe( - filter(connected => connected === ssid), - tap(connected => this.presentAlertSuccess(connected)), - map(_ => true), - ) - - const timer$ = timer(20000) - .pipe( - map(_ => false), - tap(_ => this.presentToastFail()), - ) - - return merge(success$, timer$).pipe(take(1)) - } - - private async presentAlertSuccess (ssid: string): Promise { - const alert = await this.alertCtrl.create({ - header: `Connected to "${ssid}"`, - message: 'Note. It may take several minutes to an hour for your Embassy to reconnect over Tor.', - buttons: ['OK'], - }) - - await alert.present() - } - - private async presentToastFail (): Promise { - const toast = await this.toastCtrl.create({ - header: 'Failed to connect:', - message: `Check credentials and try again`, - position: 'bottom', - duration: 4000, - buttons: [ - { - side: 'start', - icon: 'close', - handler: () => { - return true - }, - }, - ], - cssClass: 'warning-toast', - }) - - await toast.present() - } -} diff --git a/ui/src/app/services/api/api.fixures.ts b/ui/src/app/services/api/api.fixures.ts index 7d319384c..c5db94810 100644 --- a/ui/src/app/services/api/api.fixures.ts +++ b/ui/src/app/services/api/api.fixures.ts @@ -854,6 +854,14 @@ export module Mock { }, } + export const Wifi: RR.GetWifiRes = { + ethernet: true, + ssids: ['Goosers', 'Goosers5G'], + connected: 'Goosers', + country: 'US', + 'signal-strength': 50, + } + export const Disks: RR.GetDisksRes = { '/dev/sda': { size: '32GB', @@ -1516,7 +1524,6 @@ export module Mock { // 'tor-address': 'myveryownspecialtoraddress.onion', // wifi: { // ssids: ['Goosers', 'Goosers5G'], - // selected: 'Goosers5G', // connected: 'Goosers5G', // }, // 'eos-marketplace': 'https://registry.start9.com', diff --git a/ui/src/app/services/api/api.types.ts b/ui/src/app/services/api/api.types.ts index e417c1086..545d146c5 100644 --- a/ui/src/app/services/api/api.types.ts +++ b/ui/src/app/services/api/api.types.ts @@ -19,7 +19,7 @@ export module RR { export type LoginReq = { password: string, metadata: SessionMetadata } // auth.login - unauthed export type loginRes = null - export type LogoutReq = {} // auth.logout + export type LogoutReq = { } // auth.logout export type LogoutRes = null // server @@ -30,26 +30,26 @@ export module RR { export type GetServerLogsReq = { before?: string } // server.logs export type GetServerLogsRes = Log[] - export type GetServerMetricsReq = {} // server.metrics + export type GetServerMetricsReq = { } // server.metrics export type GetServerMetricsRes = Metrics - export type UpdateServerReq = WithExpire<{}> // server.update + export type UpdateServerReq = WithExpire<{ }> // server.update export type UpdateServerRes = WithRevision - export type RestartServerReq = {} // server.restart + export type RestartServerReq = { } // server.restart export type RestartServerRes = null - export type ShutdownServerReq = {} // server.shutdown + export type ShutdownServerReq = { } // server.shutdown export type ShutdownServerRes = null // network - export type RefreshLanReq = {} // network.lan.refresh + export type RefreshLanReq = { } // network.lan.refresh export type RefreshLanRes = null // sessions - export type GetSessionsReq = {} // sessions.list + export type GetSessionsReq = { } // sessions.list export type GetSessionsRes = { current: string, sessions: { [hash: string]: Session } @@ -67,6 +67,7 @@ export module RR { export type SetPackageMarketplaceRes = WithRevision // password + export type UpdatePasswordReq = { password: string } // password.set export type UpdatePasswordRes = null @@ -78,29 +79,40 @@ export module RR { export type DeleteNotificationReq = { id: string } // notification.delete export type DeleteNotificationRes = null - export type DeleteAllNotificationsReq = {} // notification.delete.all + export type DeleteAllNotificationsReq = { } // notification.delete.all export type DeleteAllNotificationsRes = null // wifi + export type SetWifiCountryReq = { country: string } + export type SetWifiCountryRes = null + + export type GetWifiReq = { } + export type GetWifiRes = { // wifi.get + ethernet: boolean + ssids: string[] + connected: string | null + country: string | null + 'signal-strength': number + } + export type AddWifiReq = { // wifi.add ssid: string password: string - country: string priority: number connect: boolean } export type AddWifiRes = null - export type ConnectWifiReq = WithExpire<{ ssid: string }> // wifi.connect - export type ConnectWifiRes = WithRevision + export type ConnectWifiReq = { ssid: string } // wifi.connect + export type ConnectWifiRes = null - export type DeleteWifiReq = WithExpire<{ ssid: string }> // wifi.delete - export type DeleteWifiRes = WithRevision + export type DeleteWifiReq = { ssid: string } // wifi.delete + export type DeleteWifiRes = null // ssh - export type GetSSHKeysReq = {} // ssh.get + export type GetSSHKeysReq = { } // ssh.get export type GetSSHKeysRes = SSHKeys export type AddSSHKeyReq = { pubkey: string } // ssh.add @@ -119,7 +131,7 @@ export module RR { // disk - export type GetDisksReq = {} // disk.list + export type GetDisksReq = { } // disk.list export type GetDisksRes = DiskInfo export type EjectDisksReq = { logicalname: string } // disk.eject @@ -178,10 +190,10 @@ export module RR { // marketplace - export type GetMarketplaceDataReq = {} + export type GetMarketplaceDataReq = { } export type GetMarketplaceDataRes = MarketplaceData - export type GetMarketplaceEOSReq = {} + export type GetMarketplaceEOSReq = { } export type GetMarketplaceEOSRes = MarketplaceEOS export type GetMarketplacePackagesReq = { diff --git a/ui/src/app/services/api/embassy-api.service.ts b/ui/src/app/services/api/embassy-api.service.ts index 9f786a1e1..b1396c248 100644 --- a/ui/src/app/services/api/embassy-api.service.ts +++ b/ui/src/app/services/api/embassy-api.service.ts @@ -96,17 +96,15 @@ export abstract class ApiService implements Source, Http { // wifi + abstract getWifi (params: RR.GetWifiReq, timeout: number): Promise + + abstract setWifiCountry (params: RR.SetWifiCountryReq): Promise + abstract addWifi (params: RR.AddWifiReq): Promise - protected abstract connectWifiRaw (params: RR.ConnectWifiReq): Promise - connectWifi = (params: RR.ConnectWifiReq) => this.syncResponse( - () => this.connectWifiRaw(params), - )() + abstract connectWifi (params: RR.ConnectWifiReq): Promise - protected abstract deleteWifiRaw (params: RR.DeleteWifiReq): Promise - deleteWifi = (params: RR.DeleteWifiReq) => this.syncResponse( - () => this.deleteWifiRaw(params), - )() + abstract deleteWifi (params: RR.DeleteWifiReq): Promise // ssh diff --git a/ui/src/app/services/api/embassy-live-api.service.ts b/ui/src/app/services/api/embassy-live-api.service.ts index bb252b624..a4441aa1e 100644 --- a/ui/src/app/services/api/embassy-live-api.service.ts +++ b/ui/src/app/services/api/embassy-live-api.service.ts @@ -139,130 +139,138 @@ export class LiveApiService extends ApiService { // notification - async getNotificationsRaw (params: RR.GetNotificationsReq): Promise < RR.GetNotificationsRes > { + async getNotificationsRaw (params: RR.GetNotificationsReq): Promise { return this.http.rpcRequest({ method: 'notification.list', params }) } - async deleteNotification (params: RR.DeleteNotificationReq): Promise < RR.DeleteNotificationRes > { + async deleteNotification (params: RR.DeleteNotificationReq): Promise { return this.http.rpcRequest({ method: 'notification.delete', params }) } - async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise < RR.DeleteAllNotificationsRes > { + async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise { return this.http.rpcRequest({ method: 'notification.delete.all', params }) } // wifi - async addWifi (params: RR.AddWifiReq): Promise < RR.AddWifiRes > { + async getWifi (params: RR.GetWifiReq, timeout?: number): Promise { + return this.http.rpcRequest({ method: 'wifi.get', params, timeout }) + } + + async setWifiCountry (params: RR.SetWifiCountryReq): Promise { + return this.http.rpcRequest({ method: 'wifi.country.set', params }) + } + + async addWifi (params: RR.AddWifiReq): Promise { return this.http.rpcRequest({ method: 'wifi.add', params }) } - async connectWifiRaw (params: RR.ConnectWifiReq): Promise < RR.ConnectWifiRes > { + async connectWifi (params: RR.ConnectWifiReq): Promise { return this.http.rpcRequest({ method: 'wifi.connect', params }) } - async deleteWifiRaw (params: RR.DeleteWifiReq): Promise < RR.DeleteWifiRes > { + async deleteWifi (params: RR.DeleteWifiReq): Promise { return this.http.rpcRequest({ method: 'wifi.delete', params }) } // ssh - async getSshKeys (params: RR.GetSSHKeysReq): Promise < RR.GetSSHKeysRes > { + async getSshKeys (params: RR.GetSSHKeysReq): Promise { return this.http.rpcRequest({ method: 'ssh.get', params }) } - async addSshKey (params: RR.AddSSHKeyReq): Promise < RR.AddSSHKeyRes > { + async addSshKey (params: RR.AddSSHKeyReq): Promise { return this.http.rpcRequest({ method: 'ssh.add', params }) } - async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise < RR.DeleteSSHKeyRes > { + async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise { return this.http.rpcRequest({ method: 'ssh.delete', params }) } // backup - async createBackupRaw (params: RR.CreateBackupReq): Promise < RR.CreateBackupRes > { + async createBackupRaw (params: RR.CreateBackupReq): Promise { return this.http.rpcRequest({ method: 'backup.create', params }) } - async restoreBackupRaw (params: RR.RestoreBackupReq): Promise < RR.RestoreBackupRes > { + async restoreBackupRaw (params: RR.RestoreBackupReq): Promise { return this.http.rpcRequest({ method: 'backup.restore', params }) } // disk - getDisks (params: RR.GetDisksReq): Promise < RR.GetDisksRes > { + getDisks (params: RR.GetDisksReq): Promise { return this.http.rpcRequest({ method: 'disk.list', params }) } - ejectDisk (params: RR.EjectDisksReq): Promise < RR.EjectDisksRes > { + ejectDisk (params: RR.EjectDisksReq): Promise { return this.http.rpcRequest({ method: 'disk.eject', params }) } // package - async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise < RR.GetPackagePropertiesRes < any > ['data'] > { + async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise ['data'] > { return this.http.rpcRequest({ method: 'package.properties', params }) .then(parsePropertiesPermissive) } - async getPackageLogs (params: RR.GetPackageLogsReq): Promise < RR.GetPackageLogsRes > { + async getPackageLogs (params: RR.GetPackageLogsReq): Promise { return this.http.rpcRequest( { method: 'package.logs', params }) } - async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise < RR.GetPackageMetricsRes > { + async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise { return this.http.rpcRequest({ method: 'package.metrics', params }) } - async installPackageRaw (params: RR.InstallPackageReq): Promise < RR.InstallPackageRes > { + async installPackageRaw (params: RR.InstallPackageReq): Promise { return this.http.rpcRequest({ method: 'package.install', params }) } - async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise < RR.DryUpdatePackageRes > { + async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise { return this.http.rpcRequest({ method: 'package.update.dry', params }) } - async getPackageConfig (params: RR.GetPackageConfigReq): Promise < RR.GetPackageConfigRes > { + async getPackageConfig (params: RR.GetPackageConfigReq): Promise { return this.http.rpcRequest({ method: 'package.config.get', params }) } - async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise < RR.DrySetPackageConfigRes > { + async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise { return this.http.rpcRequest({ method: 'package.config.set.dry', params }) } - async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise < RR.SetPackageConfigRes > { + async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise { return this.http.rpcRequest({ method: 'package.config.set', params }) } - async restorePackageRaw (params: RR.RestorePackageReq): Promise < RR.RestorePackageRes > { + async restorePackageRaw (params: RR.RestorePackageReq): Promise { return this.http.rpcRequest({ method: 'package.restore', params }) } - async executePackageAction (params: RR.ExecutePackageActionReq): Promise < RR.ExecutePackageActionRes > { + async executePackageAction (params: RR.ExecutePackageActionReq): Promise { return this.http.rpcRequest({ method: 'package.action', params }) } - async startPackageRaw (params: RR.StartPackageReq): Promise < RR.StartPackageRes > { + async startPackageRaw (params: RR.StartPackageReq): Promise { return this.http.rpcRequest({ method: 'package.start', params }) } - async dryStopPackage (params: RR.DryStopPackageReq): Promise < RR.DryStopPackageRes > { + async dryStopPackage (params: RR.DryStopPackageReq): Promise { return this.http.rpcRequest({ method: 'package.stop.dry', params }) } - async stopPackageRaw (params: RR.StopPackageReq): Promise < RR.StopPackageRes > { + async stopPackageRaw (params: RR.StopPackageReq): Promise { return this.http.rpcRequest({ method: 'package.stop', params }) } - async dryRemovePackage (params: RR.DryRemovePackageReq): Promise < RR.DryRemovePackageRes > { + async dryRemovePackage (params: RR.DryRemovePackageReq): Promise { return this.http.rpcRequest({ method: 'package.remove.dry', params }) } - async removePackageRaw (params: RR.RemovePackageReq): Promise < RR.RemovePackageRes > { + async removePackageRaw (params: RR.RemovePackageReq): Promise { return this.http.rpcRequest({ method: 'package.remove', params }) } - async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise < RR.DryConfigureDependencyRes > { + async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise { return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params }) } } diff --git a/ui/src/app/services/api/embassy-mock-api.service.ts b/ui/src/app/services/api/embassy-mock-api.service.ts index 790cf8e5d..a764bfe5b 100644 --- a/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/ui/src/app/services/api/embassy-mock-api.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core' import { pauseFor } from '../../util/misc.util' import { ApiService } from './embassy-api.service' -import { Operation, PatchOp } from 'patch-db-client' +import { PatchOp } from 'patch-db-client' import { InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' import { RR, WithRevision } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' @@ -223,47 +223,29 @@ export class MockApiService extends ApiService { // wifi + async getWifi (params: RR.GetWifiReq): Promise < RR.GetWifiRes > { + await pauseFor(2000) + return Mock.Wifi + } + + async setWifiCountry (params: RR.SetWifiCountryReq): Promise { + await pauseFor(2000) + return null + } + async addWifi (params: RR.AddWifiReq): Promise { await pauseFor(2000) return null } - async connectWifiRaw (params: RR.ConnectWifiReq): Promise { + async connectWifi (params: RR.ConnectWifiReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/wifi/selected', - value: params.ssid, - }, - { - op: PatchOp.REPLACE, - path: '/server-info/wifi/connected', - value: params.ssid, - }, - ] - return this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) + return null } - async deleteWifiRaw (params: RR.DeleteWifiReq): Promise { + async deleteWifi (params: RR.DeleteWifiReq): Promise { await pauseFor(2000) - const patch: Operation[] = [ - { - op: PatchOp.REMOVE, - path: `/server-info/wifi/ssids/${params.ssid}`, - }, - // { - // op: PatchOp.REPLACE, - // path: '/server-info/wifi/selected', - // value: null, - // }, - // { - // op: PatchOp.REPLACE, - // path: '/server-info/wifi/connected', - // value: null, - // }, - ] - return this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) + return null } // ssh diff --git a/ui/src/app/services/emver.service.ts b/ui/src/app/services/emver.service.ts index bb7c15c3e..6ad0dd0e9 100644 --- a/ui/src/app/services/emver.service.ts +++ b/ui/src/app/services/emver.service.ts @@ -9,9 +9,7 @@ export class Emver { constructor () { } compare (lhs: string, rhs: string): number { - console.log('EMVER', emver) const compare = emver.compare(lhs, rhs) - console.log('COMPARE', compare) return compare } diff --git a/ui/src/app/services/form.service.ts b/ui/src/app/services/form.service.ts index 21abc2cc3..5db7affec 100644 --- a/ui/src/app/services/form.service.ts +++ b/ui/src/app/services/form.service.ts @@ -1,14 +1,12 @@ import { Injectable } from '@angular/core' import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms' -import { By } from '@angular/platform-browser' -import { ConfigSpec, isValueSpecListOf, ListValueSpecNumber, ListValueSpecObject, ListValueSpecOf, ListValueSpecString, ListValueSpecUnion, UniqueBy, ValueSpec, ValueSpecEnum, ValueSpecList, ValueSpecNumber, ValueSpecObject, ValueSpecString, ValueSpecUnion } from '../pkg-config/config-types' +import { ConfigSpec, isValueSpecListOf, ListValueSpecNumber, ListValueSpecObject, ListValueSpecString, ListValueSpecUnion, UniqueBy, ValueSpec, ValueSpecEnum, ValueSpecList, ValueSpecNumber, ValueSpecString, ValueSpecUnion } from '../pkg-config/config-types' import { getDefaultString, Range } from '../pkg-config/config-utilities' @Injectable({ providedIn: 'root', }) export class FormService { - validationMessages: { [key: string]: { type: string, message: string }[] } = { } constructor ( private readonly formBuilder: FormBuilder, @@ -18,13 +16,11 @@ export class FormService { return this.getFormGroup(config, [], current) } - getListItemValidators (spec: ValueSpecList, key: string, index: number) { - const listKey = `${key}/${index}` - this.validationMessages[listKey] = [] + getListItemValidators (spec: ValueSpecList) { if (isValueSpecListOf(spec, 'string')) { - return this.stringValidators(listKey, spec.spec) + return this.stringValidators(spec.spec) } else if (isValueSpecListOf(spec, 'number')) { - return this.numberValidators(listKey, spec.spec) + return this.numberValidators(spec.spec) } } @@ -53,8 +49,8 @@ export class FormService { return this.formBuilder.group(group, { validators } ) } - getListItem (key: string, index: number, spec: ValueSpecList, entry: any) { - const listItemValidators = this.getListItemValidators(spec, key, index) + getListItem (spec: ValueSpecList, entry: any) { + const listItemValidators = this.getListItemValidators(spec) if (isValueSpecListOf(spec, 'string')) { return this.formBuilder.control(entry, listItemValidators) } else if (isValueSpecListOf(spec, 'number')) { @@ -69,12 +65,11 @@ export class FormService { } private getFormEntry (key: string, spec: ValueSpec, currentValue: any): FormGroup | FormArray | FormControl { - this.validationMessages[key] = [] let validators: ValidatorFn[] let value: any switch (spec.type) { case 'string': - validators = this.stringValidators(key, spec) + validators = this.stringValidators(spec) if (currentValue !== undefined) { value = currentValue } else { @@ -82,7 +77,7 @@ export class FormService { } return this.formBuilder.control(value, validators) case 'number': - validators = this.numberValidators(key, spec) + validators = this.numberValidators(spec) if (currentValue !== undefined) { value = currentValue } else { @@ -92,9 +87,9 @@ export class FormService { case 'object': return this.getFormGroup(spec.spec, [], currentValue) case 'list': - validators = this.listValidators(key, spec) + validators = this.listValidators(spec) const mapped = (Array.isArray(currentValue) ? currentValue : spec.default as any[]).map((entry: any, index) => { - return this.getListItem(key, index, spec, entry) + return this.getListItem(spec, entry) }) return this.formBuilder.array(mapped, validators) case 'union': @@ -106,71 +101,43 @@ export class FormService { } } - private stringValidators (key: string, spec: ValueSpecString | ListValueSpecString): ValidatorFn[] { + private stringValidators (spec: ValueSpecString | ListValueSpecString): ValidatorFn[] { const validators: ValidatorFn[] = [] if (!(spec as ValueSpecString).nullable) { validators.push(Validators.required) - this.validationMessages[key].push({ - type: 'required', - message: 'Cannot be blank.', - }) } if (spec.pattern) { validators.push(Validators.pattern(spec.pattern)) - this.validationMessages[key].push({ - type: 'pattern', - message: spec['pattern-description'], - }) } return validators } - private numberValidators (key: string, spec: ValueSpecNumber | ListValueSpecNumber): ValidatorFn[] { + private numberValidators (spec: ValueSpecNumber | ListValueSpecNumber): ValidatorFn[] { const validators: ValidatorFn[] = [] if (!(spec as ValueSpecNumber).nullable) { validators.push(Validators.required) - this.validationMessages[key].push({ - type: 'required', - message: 'Cannot be blank.', - }) } if (spec.integral) { validators.push(isInteger()) - this.validationMessages[key].push({ - type: 'numberNotInteger', - message: 'Number must be an integer.', - }) } validators.push(numberInRange(spec.range)) - this.validationMessages[key].push({ - type: 'numberNotInRange', - message: 'Number not in range.', - }) return validators } - private listValidators (key: string, spec: ValueSpecList): ValidatorFn[] { + private listValidators (spec: ValueSpecList): ValidatorFn[] { const validators: ValidatorFn[] = [] validators.push(listInRange(spec.range)) - this.validationMessages[key].push({ - type: 'listNotInRange', - message: 'List not in range.', - }) if (!isValueSpecListOf(spec, 'enum')) { validators.push(listUnique(spec)) - this.validationMessages[key].push({ - type: 'listNotUnique', - message: 'List contains duplicate entries.', - }) } return validators diff --git a/ui/src/app/services/http.service.ts b/ui/src/app/services/http.service.ts index d88000071..947ba05b0 100644 --- a/ui/src/app/services/http.service.ts +++ b/ui/src/app/services/http.service.ts @@ -27,11 +27,12 @@ export class HttpService { async rpcRequest (rpcOpts: RPCOptions): Promise { const { url, version } = this.config.api rpcOpts.params = rpcOpts.params || { } - const httpOpts = { + const httpOpts: HttpOptions = { method: Method.POST, body: rpcOpts, url: `/${url}/${version}`, } + if (rpcOpts.timeout) httpOpts.timeout = rpcOpts.timeout const res = await this.httpRequest>(httpOpts) @@ -78,6 +79,7 @@ export class HttpService { case Method.PATCH: req = this.http.patch(url, httpOpts.body, options) as any; break case Method.DELETE: req = this.http.delete(url, options) as any; break } + console.log('REQUEST', options) return (httpOpts.timeout ? withTimeout(req, httpOpts.timeout) : req) .toPromise() @@ -139,6 +141,7 @@ export interface RPCOptions { params?: { [param: string]: string | number | boolean | object | string[] | number[]; } + timeout?: number } interface RPCBase { diff --git a/ui/src/app/services/patch-db/data-model.ts b/ui/src/app/services/patch-db/data-model.ts index 883d08c04..30d19af6c 100644 --- a/ui/src/app/services/patch-db/data-model.ts +++ b/ui/src/app/services/patch-db/data-model.ts @@ -19,7 +19,6 @@ export interface ServerInfo { status: ServerStatus 'eos-marketplace': URL 'package-marketplace': URL | null // uses EOS marketplace if null - wifi: WiFiInfo 'unread-notification-count': number specs: { cpu: string @@ -38,12 +37,6 @@ export enum ServerStatus { BackingUp = 'backing-up', } -export interface WiFiInfo { - ssids: string[] - selected: string | null - connected: string | null -} - export interface PackageDataEntry { state: PackageState 'static-files': { diff --git a/ui/src/app/services/server-config.service.ts b/ui/src/app/services/server-config.service.ts index 05627009c..bf9872549 100644 --- a/ui/src/app/services/server-config.service.ts +++ b/ui/src/app/services/server-config.service.ts @@ -44,7 +44,7 @@ export class ServerConfigService { try { await this.saveFns[key](data) } catch (e) { - this.errToast.present(e.message) + this.errToast.present(e) } finally { loader.dismiss() } @@ -84,7 +84,7 @@ export class ServerConfigService { await alert.present() } - async presentInputModal (key: string, current?: string) { + async presentModalInput (key: string, current?: string) { const { name, description, masked } = serverConfig[key] as ValueSpecString const modal = await this.modalCtrl.create({ @@ -102,14 +102,20 @@ export class ServerConfigService { await modal.present() } - // async presentModalForm (key: string, current?: string) { + // async presentModalForm (key: string) { // const modal = await this.modalCtrl.create({ - // component: AppConfigValuePage, + // component: AppActionInputPage, // componentProps: { - // cursor, - // saveFn: this.saveFns[key], + // title: serverConfig[key].name, + // spec: (serverConfig[key] as ValueSpecObject).spec, // }, // }) + + // modal.onWillDismiss().then(res => { + // if (!res.data) return + // this.saveFns[key](res.data) + // }) + // await modal.present() // } diff --git a/ui/src/app/util/countries.json b/ui/src/app/util/countries.json index e2c91e3d7..72e285326 100644 --- a/ui/src/app/util/countries.json +++ b/ui/src/app/util/countries.json @@ -1,252 +1,252 @@ { - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Aland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthelemy", - "BM": "Bermuda", - "BN": "Brunei", - "BO": "Bolivia", - "BQ": "Bonaire, Saint Eustatius and Saba ", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos Islands", - "CD": "Democratic Republic of the Congo", - "CF": "Central African Republic", - "CG": "Republic of the Congo", - "CH": "Switzerland", - "CI": "Ivory Coast", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cape Verde", - "CW": "Curacao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czech Republic", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestinian Territory", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Reunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten", - "SY": "Syria", - "SZ": "Swaziland", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "French Southern Territories", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "East Timor", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Vatican", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Vietnam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "XK": "Kosovo", - "YE": "Yemen", - "YT": "Mayotte", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe" + "AD": "Andorra", + "AE": "United Arab Emirates", + "AF": "Afghanistan", + "AG": "Antigua and Barbuda", + "AI": "Anguilla", + "AL": "Albania", + "AM": "Armenia", + "AO": "Angola", + "AQ": "Antarctica", + "AR": "Argentina", + "AS": "American Samoa", + "AT": "Austria", + "AU": "Australia", + "AW": "Aruba", + "AX": "Aland Islands", + "AZ": "Azerbaijan", + "BA": "Bosnia and Herzegovina", + "BB": "Barbados", + "BD": "Bangladesh", + "BE": "Belgium", + "BF": "Burkina Faso", + "BG": "Bulgaria", + "BH": "Bahrain", + "BI": "Burundi", + "BJ": "Benin", + "BL": "Saint Barthelemy", + "BM": "Bermuda", + "BN": "Brunei", + "BO": "Bolivia", + "BQ": "Bonaire, Saint Eustatius and Saba ", + "BR": "Brazil", + "BS": "Bahamas", + "BT": "Bhutan", + "BV": "Bouvet Island", + "BW": "Botswana", + "BY": "Belarus", + "BZ": "Belize", + "CA": "Canada", + "CC": "Cocos Islands", + "CD": "Democratic Republic of the Congo", + "CF": "Central African Republic", + "CG": "Republic of the Congo", + "CH": "Switzerland", + "CI": "Ivory Coast", + "CK": "Cook Islands", + "CL": "Chile", + "CM": "Cameroon", + "CN": "China", + "CO": "Colombia", + "CR": "Costa Rica", + "CU": "Cuba", + "CV": "Cape Verde", + "CW": "Curacao", + "CX": "Christmas Island", + "CY": "Cyprus", + "CZ": "Czech Republic", + "DE": "Germany", + "DJ": "Djibouti", + "DK": "Denmark", + "DM": "Dominica", + "DO": "Dominican Republic", + "DZ": "Algeria", + "EC": "Ecuador", + "EE": "Estonia", + "EG": "Egypt", + "EH": "Western Sahara", + "ER": "Eritrea", + "ES": "Spain", + "ET": "Ethiopia", + "FI": "Finland", + "FJ": "Fiji", + "FK": "Falkland Islands", + "FM": "Micronesia", + "FO": "Faroe Islands", + "FR": "France", + "GA": "Gabon", + "GB": "United Kingdom", + "GD": "Grenada", + "GE": "Georgia", + "GF": "French Guiana", + "GG": "Guernsey", + "GH": "Ghana", + "GI": "Gibraltar", + "GL": "Greenland", + "GM": "Gambia", + "GN": "Guinea", + "GP": "Guadeloupe", + "GQ": "Equatorial Guinea", + "GR": "Greece", + "GS": "South Georgia and the South Sandwich Islands", + "GT": "Guatemala", + "GU": "Guam", + "GW": "Guinea-Bissau", + "GY": "Guyana", + "HK": "Hong Kong", + "HM": "Heard Island and McDonald Islands", + "HN": "Honduras", + "HR": "Croatia", + "HT": "Haiti", + "HU": "Hungary", + "ID": "Indonesia", + "IE": "Ireland", + "IL": "Israel", + "IM": "Isle of Man", + "IN": "India", + "IO": "British Indian Ocean Territory", + "IQ": "Iraq", + "IR": "Iran", + "IS": "Iceland", + "IT": "Italy", + "JE": "Jersey", + "JM": "Jamaica", + "JO": "Jordan", + "JP": "Japan", + "KE": "Kenya", + "KG": "Kyrgyzstan", + "KH": "Cambodia", + "KI": "Kiribati", + "KM": "Comoros", + "KN": "Saint Kitts and Nevis", + "KP": "North Korea", + "KR": "South Korea", + "KW": "Kuwait", + "KY": "Cayman Islands", + "KZ": "Kazakhstan", + "LA": "Laos", + "LB": "Lebanon", + "LC": "Saint Lucia", + "LI": "Liechtenstein", + "LK": "Sri Lanka", + "LR": "Liberia", + "LS": "Lesotho", + "LT": "Lithuania", + "LU": "Luxembourg", + "LV": "Latvia", + "LY": "Libya", + "MA": "Morocco", + "MC": "Monaco", + "MD": "Moldova", + "ME": "Montenegro", + "MF": "Saint Martin", + "MG": "Madagascar", + "MH": "Marshall Islands", + "MK": "Macedonia", + "ML": "Mali", + "MM": "Myanmar", + "MN": "Mongolia", + "MO": "Macao", + "MP": "Northern Mariana Islands", + "MQ": "Martinique", + "MR": "Mauritania", + "MS": "Montserrat", + "MT": "Malta", + "MU": "Mauritius", + "MV": "Maldives", + "MW": "Malawi", + "MX": "Mexico", + "MY": "Malaysia", + "MZ": "Mozambique", + "NA": "Namibia", + "NC": "New Caledonia", + "NE": "Niger", + "NF": "Norfolk Island", + "NG": "Nigeria", + "NI": "Nicaragua", + "NL": "Netherlands", + "NO": "Norway", + "NP": "Nepal", + "NR": "Nauru", + "NU": "Niue", + "NZ": "New Zealand", + "OM": "Oman", + "PA": "Panama", + "PE": "Peru", + "PF": "French Polynesia", + "PG": "Papua New Guinea", + "PH": "Philippines", + "PK": "Pakistan", + "PL": "Poland", + "PM": "Saint Pierre and Miquelon", + "PN": "Pitcairn", + "PR": "Puerto Rico", + "PS": "Palestinian Territory", + "PT": "Portugal", + "PW": "Palau", + "PY": "Paraguay", + "QA": "Qatar", + "RE": "Reunion", + "RO": "Romania", + "RS": "Serbia", + "RU": "Russia", + "RW": "Rwanda", + "SA": "Saudi Arabia", + "SB": "Solomon Islands", + "SC": "Seychelles", + "SD": "Sudan", + "SE": "Sweden", + "SG": "Singapore", + "SH": "Saint Helena", + "SI": "Slovenia", + "SJ": "Svalbard and Jan Mayen", + "SK": "Slovakia", + "SL": "Sierra Leone", + "SM": "San Marino", + "SN": "Senegal", + "SO": "Somalia", + "SR": "Suriname", + "SS": "South Sudan", + "ST": "Sao Tome and Principe", + "SV": "El Salvador", + "SX": "Sint Maarten", + "SY": "Syria", + "SZ": "Swaziland", + "TC": "Turks and Caicos Islands", + "TD": "Chad", + "TF": "French Southern Territories", + "TG": "Togo", + "TH": "Thailand", + "TJ": "Tajikistan", + "TK": "Tokelau", + "TL": "East Timor", + "TM": "Turkmenistan", + "TN": "Tunisia", + "TO": "Tonga", + "TR": "Turkey", + "TT": "Trinidad and Tobago", + "TV": "Tuvalu", + "TW": "Taiwan", + "TZ": "Tanzania", + "UA": "Ukraine", + "UG": "Uganda", + "UM": "United States Minor Outlying Islands", + "US": "United States", + "UY": "Uruguay", + "UZ": "Uzbekistan", + "VA": "Vatican", + "VC": "Saint Vincent and the Grenadines", + "VE": "Venezuela", + "VG": "British Virgin Islands", + "VI": "U.S. Virgin Islands", + "VN": "Vietnam", + "VU": "Vanuatu", + "WF": "Wallis and Futuna", + "WS": "Samoa", + "XK": "Kosovo", + "YE": "Yemen", + "YT": "Mayotte", + "ZA": "South Africa", + "ZM": "Zambia", + "ZW": "Zimbabwe" } \ No newline at end of file diff --git a/ui/src/assets/img/gifs/backing-up.gif b/ui/src/assets/img/gifs/backing-up.gif new file mode 100644 index 000000000..8d94b97aa Binary files /dev/null and b/ui/src/assets/img/gifs/backing-up.gif differ diff --git a/ui/src/assets/img/gifs/cube.gif b/ui/src/assets/img/gifs/cube.gif new file mode 100644 index 000000000..1eab84be7 Binary files /dev/null and b/ui/src/assets/img/gifs/cube.gif differ diff --git a/ui/src/assets/img/icons/wifi-1.png b/ui/src/assets/img/icons/wifi-1.png new file mode 100644 index 000000000..73bb84820 Binary files /dev/null and b/ui/src/assets/img/icons/wifi-1.png differ diff --git a/ui/src/assets/img/icons/wifi-2.png b/ui/src/assets/img/icons/wifi-2.png new file mode 100644 index 000000000..8f5268d7b Binary files /dev/null and b/ui/src/assets/img/icons/wifi-2.png differ diff --git a/ui/src/assets/img/icons/wifi-3.png b/ui/src/assets/img/icons/wifi-3.png new file mode 100644 index 000000000..93c084f4d Binary files /dev/null and b/ui/src/assets/img/icons/wifi-3.png differ diff --git a/ui/src/global.scss b/ui/src/global.scss index 95be02836..8618386da 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -39,6 +39,10 @@ $subheader-height: 48px; color: var(--ion-color-warning) } +.wide-alert { + --min-width: 400px; +} + .break-all { word-break: break-all; } @@ -73,6 +77,10 @@ $subheader-height: 48px; height: 100%; } +ion-action-sheet { + --button-color: var(--ion-color-dark) !important; +} + ion-toast { --background: var(--ion-color-light); --button-color: var(--ion-color-dark);