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);