mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
revamp wifi, fix error messaging in forms
This commit is contained in:
70
ui/package-lock.json
generated
70
ui/package-lock.json
generated
@@ -16,7 +16,7 @@
|
|||||||
"@angular/router": "12.2.0",
|
"@angular/router": "12.2.0",
|
||||||
"@ionic/angular": "5.6.13",
|
"@ionic/angular": "5.6.13",
|
||||||
"@ionic/storage-angular": "3.0.6",
|
"@ionic/storage-angular": "3.0.6",
|
||||||
"@start9labs/emver": "0.1.4",
|
"@start9labs/emver": "0.1.5",
|
||||||
"ajv": "6.12.6",
|
"ajv": "6.12.6",
|
||||||
"compare-versions": "3.6.0",
|
"compare-versions": "3.6.0",
|
||||||
"core-js": "3.16.1",
|
"core-js": "3.16.1",
|
||||||
@@ -54,16 +54,16 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "6.6.7",
|
||||||
"sorted-btree": "^1.5.0",
|
"sorted-btree": "1.5.0",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^15.0.0",
|
"@types/node": "16.4.13",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "8.3.1",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "10.2.0",
|
||||||
"tslint": "^6.1.0",
|
"tslint": "6.1.3",
|
||||||
"typescript": "4.1.5"
|
"typescript": "4.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@@ -2861,9 +2861,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@start9labs/emver": {
|
"node_modules/@start9labs/emver": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.5.tgz",
|
||||||
"integrity": "sha512-lWhc94tGhWjJYHTYlHydHESViBi8DqjIqdwtPKMeVkGp3RKZdcv8RRpvEatQHlnCoT5IfKuH/29BZtLnwGL4CQ=="
|
"integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg=="
|
||||||
},
|
},
|
||||||
"node_modules/@stencil/core": {
|
"node_modules/@stencil/core": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
@@ -16820,9 +16820,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
||||||
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {}
|
||||||
"ajv": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.16.0",
|
"version": "3.16.0",
|
||||||
@@ -16900,9 +16898,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
||||||
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {}
|
||||||
"ajv": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -16946,9 +16942,7 @@
|
|||||||
"integrity": "sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==",
|
"integrity": "sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"requires": {
|
"requires": {}
|
||||||
"ajv": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -17001,9 +16995,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
||||||
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {}
|
||||||
"ajv": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -17081,9 +17073,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
||||||
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {}
|
||||||
"ajv": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -18632,9 +18622,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz",
|
||||||
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
"integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {}
|
||||||
"ajv": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -18645,9 +18633,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@start9labs/emver": {
|
"@start9labs/emver": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@start9labs/emver/-/emver-0.1.5.tgz",
|
||||||
"integrity": "sha512-lWhc94tGhWjJYHTYlHydHESViBi8DqjIqdwtPKMeVkGp3RKZdcv8RRpvEatQHlnCoT5IfKuH/29BZtLnwGL4CQ=="
|
"integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg=="
|
||||||
},
|
},
|
||||||
"@stencil/core": {
|
"@stencil/core": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
@@ -24503,14 +24491,14 @@
|
|||||||
"patch-db-client": {
|
"patch-db-client": {
|
||||||
"version": "file:../../patch-db/client",
|
"version": "file:../../patch-db/client",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "^15.0.0",
|
"@types/node": "16.4.13",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "8.3.1",
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "6.6.7",
|
||||||
"sorted-btree": "^1.5.0",
|
"sorted-btree": "1.5.0",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "10.2.0",
|
||||||
"tslint": "^6.1.0",
|
"tslint": "6.1.3",
|
||||||
"typescript": "4.1.5",
|
"typescript": "4.3.5",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "8.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path-dirname": {
|
"path-dirname": {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"@angular/router": "12.2.0",
|
"@angular/router": "12.2.0",
|
||||||
"@ionic/angular": "5.6.13",
|
"@ionic/angular": "5.6.13",
|
||||||
"@ionic/storage-angular": "3.0.6",
|
"@ionic/storage-angular": "3.0.6",
|
||||||
"@start9labs/emver": "0.1.4",
|
"@start9labs/emver": "0.1.5",
|
||||||
"ajv": "6.12.6",
|
"ajv": "6.12.6",
|
||||||
"compare-versions": "3.6.0",
|
"compare-versions": "3.6.0",
|
||||||
"core-js": "3.16.1",
|
"core-js": "3.16.1",
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
<ion-icon name="cube-outline"></ion-icon>
|
<ion-icon name="cube-outline"></ion-icon>
|
||||||
<ion-icon name="desktop-outline"></ion-icon>
|
<ion-icon name="desktop-outline"></ion-icon>
|
||||||
<ion-icon name="download-outline"></ion-icon>
|
<ion-icon name="download-outline"></ion-icon>
|
||||||
|
<ion-icon name="earth-outline"></ion-icon>
|
||||||
<ion-icon name="ellipse"></ion-icon>
|
<ion-icon name="ellipse"></ion-icon>
|
||||||
<ion-icon name="eye-off-outline"></ion-icon>
|
<ion-icon name="eye-off-outline"></ion-icon>
|
||||||
<ion-icon name="eye-outline"></ion-icon>
|
<ion-icon name="eye-outline"></ion-icon>
|
||||||
@@ -87,6 +88,7 @@
|
|||||||
<ion-icon name="help-circle-outline"></ion-icon>
|
<ion-icon name="help-circle-outline"></ion-icon>
|
||||||
<ion-icon name="home-outline"></ion-icon>
|
<ion-icon name="home-outline"></ion-icon>
|
||||||
<ion-icon name="information-circle-outline"></ion-icon>
|
<ion-icon name="information-circle-outline"></ion-icon>
|
||||||
|
<ion-icon name="key-outline"></ion-icon>
|
||||||
<ion-icon name="list-outline"></ion-icon>
|
<ion-icon name="list-outline"></ion-icon>
|
||||||
<ion-icon name="logo-bitcoin"></ion-icon>
|
<ion-icon name="logo-bitcoin"></ion-icon>
|
||||||
<ion-icon name="mail-outline"></ion-icon>
|
<ion-icon name="mail-outline"></ion-icon>
|
||||||
|
|||||||
31
ui/src/app/components/form-object/form-error.component.html
Normal file
31
ui/src/app/components/form-object/form-error.component.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<div [hidden]="!control.dirty && !control.touched" class="validation-error">
|
||||||
|
<!-- primitive -->
|
||||||
|
<p *ngIf="control.hasError('required')">
|
||||||
|
{{ spec.name }} is required
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- string -->
|
||||||
|
<p *ngIf="control.hasError('pattern')">
|
||||||
|
{{ spec['pattern-description'] }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- number -->
|
||||||
|
<ng-container *ngIf="spec.type === 'number'">
|
||||||
|
<p *ngIf="control.hasError('numberNotInteger')">
|
||||||
|
{{ spec.name }} must be an integer
|
||||||
|
</p>
|
||||||
|
<p *ngIf="control.hasError('numberNotInRange')">
|
||||||
|
Number not in range
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- list -->
|
||||||
|
<ng-container *ngIf="spec.type === 'list'">
|
||||||
|
<p *ngIf="control.hasError('listNotInRange')">
|
||||||
|
List not in range
|
||||||
|
</p>
|
||||||
|
<p *ngIf="control.hasError('listNotUnique')">
|
||||||
|
{{ spec['pattern-description'] }}
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
@@ -151,11 +151,12 @@
|
|||||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ng-container *ngFor="let validation of formService.validationMessages[entry.key + '/' + i]">
|
<form-error
|
||||||
<p style="font-size: small;" *ngIf="abstractControl.hasError(validation.type) && (abstractControl.dirty || abstractControl.touched)">
|
*ngIf="abstractControl.errors"
|
||||||
<ion-text color="danger">{{ validation.message }}</ion-text>
|
[control]="abstractControl"
|
||||||
</p>
|
[spec]="spec.spec"
|
||||||
</ng-container>
|
>
|
||||||
|
</form-error>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,11 +184,12 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngFor="let validation of formService.validationMessages[entry.key]">
|
<form-error
|
||||||
<p class="validation-error" *ngIf="formGroup.get(entry.key).hasError(validation.type)">
|
*ngIf="formGroup.get(entry.key).errors"
|
||||||
<ion-text *ngIf="(formGroup.get(entry.key).dirty || formGroup.get(entry.key).touched)" color="danger">{{ spec.name }}: {{ validation.message }}</ion-text>
|
[control]="formGroup.get(entry.key)"
|
||||||
</p>
|
[spec]="spec"
|
||||||
</div>
|
>
|
||||||
|
</form-error>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
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 { IonicModule } from '@ionic/angular'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
@@ -10,6 +10,7 @@ import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
|
|||||||
declarations: [
|
declarations: [
|
||||||
FormObjectComponent,
|
FormObjectComponent,
|
||||||
FormLabelComponent,
|
FormLabelComponent,
|
||||||
|
FormErrorComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -22,6 +23,7 @@ import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
|
|||||||
exports: [
|
exports: [
|
||||||
FormObjectComponent,
|
FormObjectComponent,
|
||||||
FormLabelComponent,
|
FormLabelComponent,
|
||||||
|
FormErrorComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class FormObjectComponentModule { }
|
export class FormObjectComponentModule { }
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ ion-item-divider {
|
|||||||
.validation-error {
|
.validation-error {
|
||||||
p {
|
p {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
color: var(--ion-color-danger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, SimpleChange } from '@angular/core'
|
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 { AlertController, ModalController } from '@ionic/angular'
|
||||||
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types'
|
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types'
|
||||||
import { FormService } from 'src/app/services/form.service'
|
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)
|
// const validators = this.formService.getListItemValidators(this.objectSpec[key] as ValueSpecList, key, arr.length)
|
||||||
// arr.push(new FormControl(value, validators))
|
// arr.push(new FormControl(value, validators))
|
||||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
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()
|
newItem.markAllAsTouched()
|
||||||
arr.insert(0, newItem)
|
arr.insert(0, newItem)
|
||||||
if (['object', 'union'].includes(listSpec.subtype)) {
|
if (['object', 'union'].includes(listSpec.subtype)) {
|
||||||
@@ -222,3 +222,14 @@ export class FormLabelComponent {
|
|||||||
await alert.present()
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<void> {
|
|
||||||
this.modalCtrl.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
async save (): Promise<void> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module'
|
import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppActionInputPage],
|
declarations: [GenericFormPage],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
IonicModule,
|
IonicModule,
|
||||||
@@ -14,7 +14,7 @@ import { FormObjectComponentModule } from 'src/app/components/form-object/form-o
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FormObjectComponentModule,
|
FormObjectComponentModule,
|
||||||
],
|
],
|
||||||
entryComponents: [AppActionInputPage],
|
entryComponents: [GenericFormPage],
|
||||||
exports: [AppActionInputPage],
|
exports: [GenericFormPage],
|
||||||
})
|
})
|
||||||
export class AppActionInputPageModule { }
|
export class GenericFormPageModule { }
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>{{ action.name }}</ion-title>
|
<ion-title>{{ title }}</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<form [formGroup]="actionForm" (ngSubmit)="save()" novalidate>
|
<form [formGroup]="formGroup" (ngSubmit)="save()" novalidate>
|
||||||
<form-object
|
<form-object
|
||||||
[objectSpec]="action['input-spec']"
|
[objectSpec]="spec"
|
||||||
[formGroup]="actionForm"
|
[formGroup]="formGroup"
|
||||||
></form-object>
|
></form-object>
|
||||||
</form>
|
</form>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
<ion-footer>
|
<ion-footer>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-buttons slot="start" class="ion-padding-start">
|
<ion-buttons slot="start" class="ion-padding-start">
|
||||||
<ion-button fill="outline" (click)="dismiss()">
|
<ion-button fill="clear" (click)="dismiss()">
|
||||||
Cancel
|
Cancel
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-buttons slot="end" class="ion-padding-end">
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
<ion-button fill="outline" color="primary" (click)="save()">
|
<ion-button *ngFor="let button of buttons" fill="clear" (click)="handleClick(button)">
|
||||||
Execute
|
{{ button.text }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
46
ui/src/app/modals/generic-form/generic-form.page.ts
Normal file
46
ui/src/app/modals/generic-form/generic-form.page.ts
Normal file
@@ -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<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<void> {
|
||||||
|
this.modalCtrl.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleClick (button: ActionButton): Promise<void> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { IonicModule } from '@ionic/angular'
|
|||||||
import { AppActionsPage, AppActionsItemComponent } from './app-actions.page'
|
import { AppActionsPage, AppActionsItemComponent } from './app-actions.page'
|
||||||
import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
|
import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.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'
|
import { AppRestoreComponentModule } from 'src/app/modals/app-restore/app-restore.component.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -22,7 +22,7 @@ const routes: Routes = [
|
|||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
QRComponentModule,
|
QRComponentModule,
|
||||||
SharingModule,
|
SharingModule,
|
||||||
AppActionInputPageModule,
|
GenericFormPageModule,
|
||||||
AppRestoreComponentModule,
|
AppRestoreComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|||||||
@@ -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 { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { Subscription } from 'rxjs'
|
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 { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||||
import { AppRestoreComponent } from 'src/app/modals/app-restore/app-restore.component'
|
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['allowed-statuses'] as PackageMainStatus[]).includes(pkg.installed.status.main.status)) {
|
||||||
if (action.value['input-spec']) {
|
if (action.value['input-spec']) {
|
||||||
const modal = await this.modalCtrl.create({
|
const modal = await this.modalCtrl.create({
|
||||||
component: AppActionInputPage,
|
component: GenericFormPage,
|
||||||
componentProps: {
|
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()
|
await modal.present()
|
||||||
} else {
|
} else {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
@@ -135,7 +140,7 @@ export class AppActionsPage {
|
|||||||
return this.navCtrl.navigateRoot('/services')
|
return this.navCtrl.navigateRoot('/services')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeAction (pkgId: string, actionId: string, input?: object): Promise<void> {
|
private async executeAction (pkgId: string, actionId: string, input?: object): Promise<boolean> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
spinner: 'lines',
|
||||||
message: 'Executing action...',
|
message: 'Executing action...',
|
||||||
@@ -155,9 +160,12 @@ export class AppActionsPage {
|
|||||||
message: res.message.split('\n').join('</br ></br />'),
|
message: res.message.split('\n').join('</br ></br />'),
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
})
|
})
|
||||||
await successAlert.present()
|
|
||||||
|
setTimeout(() => successAlert.present(), 400)
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
loader.dismiss()
|
loader.dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,14 @@
|
|||||||
<ion-grid style="height: 100%;">
|
<ion-grid style="height: 100%;">
|
||||||
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
||||||
<ion-col>
|
<ion-col>
|
||||||
<ion-spinner name="lines" color="warning"></ion-spinner>
|
<ng-container *ngIf="status === ServerStatus.Updating">
|
||||||
<p *ngIf="status === ServerStatus.Updating">Embassy is updating</p>
|
<h1>Embassy Updating</h1>
|
||||||
<p *ngIf="status === ServerStatus.BackingUp">Embassy is backing up</p>
|
<img src="assets/img/gifs/cube.gif" />
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="status === ServerStatus.BackingUp">
|
||||||
|
<h1>Backing Up</h1>
|
||||||
|
<img src="assets/img/gifs/backing-up.gif" />
|
||||||
|
</ng-container>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
img {
|
||||||
|
width: 20%;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
@@ -22,9 +22,9 @@ export class SessionsPage {
|
|||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
try {
|
try {
|
||||||
this.sessionInfo = await this.embassyApi.getSessions({})
|
this.sessionInfo = await this.embassyApi.getSessions({ })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errToast.present(e.message)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
|
|
||||||
<!-- always -->
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
|
|
||||||
|
<!-- always -->
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<ion-item-divider>Saved Keys</ion-item-divider>
|
<ion-item-divider>Saved Keys</ion-item-divider>
|
||||||
|
|
||||||
<ion-item button detail="false" (click)="serverConfig.presentInputModal('ssh')">
|
<ion-item button detail="false" (click)="serverConfig.presentModalInput('ssh')">
|
||||||
<ion-icon slot="start" name="add" size="large"></ion-icon>
|
<ion-icon slot="start" name="add" size="large"></ion-icon>
|
||||||
<ion-label>Add new key</ion-label>
|
<ion-label>Add new key</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'wifi',
|
path: 'wifi',
|
||||||
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiListPageModule),
|
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiPageModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lan',
|
path: 'lan',
|
||||||
|
|||||||
@@ -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 { }
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<pwa-back-button></pwa-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>Add Network</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
|
||||||
<ion-item-group>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label>Select Country</ion-label>
|
|
||||||
<ion-select slot="end" placeholder="Select" [(ngModel)]="countryCode" [selectedText]="countryCode">
|
|
||||||
<ion-select-option *ngFor="let country of countries | keyvalue : asIsOrder" [value]="country.key">
|
|
||||||
{{ country.key }} - {{ country.value }}
|
|
||||||
</ion-select-option>
|
|
||||||
</ion-select>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item-divider>Network and Password</ion-item-divider>
|
|
||||||
<ion-item>
|
|
||||||
<ion-input placeholder="Network Name (SSID)" [(ngModel)]="ssid"></ion-input>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-input type="password" placeholder="Password" [(ngModel)]="password"></ion-input>
|
|
||||||
</ion-item>
|
|
||||||
</ion-item-group>
|
|
||||||
|
|
||||||
<ion-grid style="margin-top: 40px;">
|
|
||||||
<ion-row>
|
|
||||||
<ion-col size="6">
|
|
||||||
<ion-button class="ion-text-wrap" [disabled]="!ssid" expand="block" fill="outline" color="primary" (click)="save()">
|
|
||||||
<p>Save</p>
|
|
||||||
</ion-button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col size="6">
|
|
||||||
<ion-button class="ion-text-wrap" [disabled]="!ssid" expand="block" fill="outline" color="primary" (click)="saveAndConnect()">
|
|
||||||
<p>Save & Connect</p>
|
|
||||||
</ion-button>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
|
|
||||||
</ion-content>
|
|
||||||
@@ -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<void> {
|
|
||||||
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<void> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,13 @@ import { NgModule } from '@angular/core'
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
import { WifiListPage } from './wifi.page'
|
import { WifiPage } from './wifi.page'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: WifiListPage,
|
component: WifiPage,
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'add',
|
|
||||||
loadChildren: () => import('./wifi-add/wifi-add.module').then(m => m.WifiAddPageModule),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -23,6 +19,6 @@ const routes: Routes = [
|
|||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
SharingModule,
|
SharingModule,
|
||||||
],
|
],
|
||||||
declarations: [WifiListPage],
|
declarations: [WifiPage],
|
||||||
})
|
})
|
||||||
export class WifiListPageModule { }
|
export class WifiPageModule { }
|
||||||
|
|||||||
@@ -4,30 +4,70 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>WiFi Settings</ion-title>
|
<ion-title>WiFi Settings</ion-title>
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button [routerLink]="['add']">
|
|
||||||
<ion-icon slot="icon-only" name="add-outline"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
|
|
||||||
|
<!-- always -->
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p style="padding-bottom: 6px;">About</p>
|
<h2>
|
||||||
<h2>Embassy will automatically connect to saved WiFi networks when they are available, allowing you to remove the Ethernet cable.</h2>
|
Embassy will automatically connect to saved WiFi networks when they are available, allowing you to remove the Ethernet cable.
|
||||||
|
</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Saved Networks</ion-item-divider>
|
<ion-item-divider>Country</ion-item-divider>
|
||||||
<ng-container *ngIf="patch.data['server-info']?.wifi as wifi">
|
|
||||||
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids" (click)="presentAction(ssid, wifi)">
|
<!-- loading -->
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ion-item class="skeleton-parts">
|
||||||
|
<ion-button slot="start" fill="clear">
|
||||||
|
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
|
||||||
|
</ion-button>
|
||||||
|
<ion-label>
|
||||||
|
<ion-skeleton-text animated style="width: 150px;"></ion-skeleton-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- not loading -->
|
||||||
|
<ng-container *ngIf="!loading">
|
||||||
|
<ion-item button detail="false" (click)="presentAlertCountry()">
|
||||||
|
<ion-icon slot="start" name="earth-outline" size="large"></ion-icon>
|
||||||
|
<ion-label *ngIf="wifi.country">{{ wifi.country }} - {{ this.countries[wifi.country] }}</ion-label>
|
||||||
|
<ion-label *ngIf="!wifi.country">Select Country</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- loading -->
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ion-item-divider>Saved Networks</ion-item-divider>
|
||||||
|
<ion-item *ngFor="let entry of ['', '']" class="skeleton-parts">
|
||||||
|
<ion-button slot="start" fill="clear">
|
||||||
|
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
|
||||||
|
</ion-button>
|
||||||
|
<ion-label>
|
||||||
|
<ion-skeleton-text animated style="width: 18%;"></ion-skeleton-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- not loading -->
|
||||||
|
<ng-container *ngIf="!loading && wifi.country">
|
||||||
|
<ion-item-divider>Saved Networks</ion-item-divider>
|
||||||
|
<ion-item button detail="false" (click)="presentModalAdd()">
|
||||||
|
<ion-icon slot="start" name="add" size="large"></ion-icon>
|
||||||
|
<ion-label>Add new network</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids" (click)="presentAction(ssid)">
|
||||||
|
<div *ngIf="ssid !== wifi.connected" slot="start" style="padding-right: 32px;"></div>
|
||||||
|
<ion-icon *ngIf="ssid === wifi.connected" slot="start" size="large" name="checkmark" color="success"></ion-icon>
|
||||||
<ion-label>{{ ssid }}</ion-label>
|
<ion-label>{{ ssid }}</ion-label>
|
||||||
<ion-note slot="end" *ngIf="ssid === wifi.connected"><ion-text color="success">Connected</ion-text></ion-note>
|
<img *ngIf="ssid === wifi.connected" slot="end" [src]="getWifiIcon()" style="max-width: 32px;" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.skeleton-parts {
|
||||||
|
ion-button::part(native) {
|
||||||
|
padding-inline-start: 0;
|
||||||
|
padding-inline-end: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +1,124 @@
|
|||||||
import { Component } from '@angular/core'
|
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 { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ActionSheetButton } from '@ionic/core'
|
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 { 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({
|
@Component({
|
||||||
selector: 'wifi',
|
selector: 'wifi',
|
||||||
templateUrl: 'wifi.page.html',
|
templateUrl: 'wifi.page.html',
|
||||||
styleUrls: ['wifi.page.scss'],
|
styleUrls: ['wifi.page.scss'],
|
||||||
})
|
})
|
||||||
export class WifiListPage {
|
export class WifiPage {
|
||||||
subs: Subscription[] = []
|
loading = true
|
||||||
|
wifi: RR.GetWifiRes = { } as any
|
||||||
|
countries = require('../../../util/countries.json') as { [key: string]: string }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly embassyApi: ApiService,
|
private readonly api: ApiService,
|
||||||
|
private readonly toastCtrl: ToastController,
|
||||||
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly actionCtrl: ActionSheetController,
|
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<void> {
|
||||||
|
this.wifi = await this.api.getWifi({ }, timeout)
|
||||||
|
if (!this.wifi.country) {
|
||||||
|
await this.presentAlertCountry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async presentAlertCountry (): Promise<void> {
|
||||||
|
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[] = [
|
const buttons: ActionSheetButton[] = [
|
||||||
{
|
{
|
||||||
text: 'Forget',
|
text: 'Forget',
|
||||||
|
icon: 'trash',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.delete(ssid)
|
this.delete(ssid)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if (ssid !== wifi.connected) {
|
if (ssid !== this.wifi.connected) {
|
||||||
buttons.unshift(
|
buttons.unshift(
|
||||||
{
|
{
|
||||||
text: 'Connect',
|
text: 'Connect',
|
||||||
|
icon: 'wifi',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.connect(ssid)
|
this.connect(ssid)
|
||||||
},
|
},
|
||||||
@@ -47,14 +127,113 @@ export class WifiListPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const action = await this.actionCtrl.create({
|
const action = await this.actionCtrl.create({
|
||||||
|
header: ssid,
|
||||||
|
subHeader: 'Manage network',
|
||||||
|
mode: 'ios',
|
||||||
buttons,
|
buttons,
|
||||||
})
|
})
|
||||||
|
|
||||||
await action.present()
|
await action.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's add country code here
|
getWifiIcon (): string {
|
||||||
async connect (ssid: string): Promise<void> {
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
spinner: 'lines',
|
||||||
message: 'Connecting. This could take while...',
|
message: 'Connecting. This could take while...',
|
||||||
@@ -63,8 +242,8 @@ export class WifiListPage {
|
|||||||
await loader.present()
|
await loader.present()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.connectWifi({ ssid })
|
await this.api.connectWifi({ ssid })
|
||||||
this.wifiService.confirmWifi(ssid)
|
await this.confirmWifi(ssid)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -72,7 +251,7 @@ export class WifiListPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete (ssid: string): Promise<void> {
|
private async delete (ssid: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
spinner: 'lines',
|
||||||
message: 'Deleting...',
|
message: 'Deleting...',
|
||||||
@@ -81,7 +260,55 @@ export class WifiListPage {
|
|||||||
await loader.present()
|
await loader.present()
|
||||||
|
|
||||||
try {
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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) {
|
} catch (e) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<boolean> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 = {
|
export const Disks: RR.GetDisksRes = {
|
||||||
'/dev/sda': {
|
'/dev/sda': {
|
||||||
size: '32GB',
|
size: '32GB',
|
||||||
@@ -1516,7 +1524,6 @@ export module Mock {
|
|||||||
// 'tor-address': 'myveryownspecialtoraddress.onion',
|
// 'tor-address': 'myveryownspecialtoraddress.onion',
|
||||||
// wifi: {
|
// wifi: {
|
||||||
// ssids: ['Goosers', 'Goosers5G'],
|
// ssids: ['Goosers', 'Goosers5G'],
|
||||||
// selected: 'Goosers5G',
|
|
||||||
// connected: 'Goosers5G',
|
// connected: 'Goosers5G',
|
||||||
// },
|
// },
|
||||||
// 'eos-marketplace': 'https://registry.start9.com',
|
// 'eos-marketplace': 'https://registry.start9.com',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export module RR {
|
|||||||
export type LoginReq = { password: string, metadata: SessionMetadata } // auth.login - unauthed
|
export type LoginReq = { password: string, metadata: SessionMetadata } // auth.login - unauthed
|
||||||
export type loginRes = null
|
export type loginRes = null
|
||||||
|
|
||||||
export type LogoutReq = {} // auth.logout
|
export type LogoutReq = { } // auth.logout
|
||||||
export type LogoutRes = null
|
export type LogoutRes = null
|
||||||
|
|
||||||
// server
|
// server
|
||||||
@@ -30,26 +30,26 @@ export module RR {
|
|||||||
export type GetServerLogsReq = { before?: string } // server.logs
|
export type GetServerLogsReq = { before?: string } // server.logs
|
||||||
export type GetServerLogsRes = Log[]
|
export type GetServerLogsRes = Log[]
|
||||||
|
|
||||||
export type GetServerMetricsReq = {} // server.metrics
|
export type GetServerMetricsReq = { } // server.metrics
|
||||||
export type GetServerMetricsRes = Metrics
|
export type GetServerMetricsRes = Metrics
|
||||||
|
|
||||||
export type UpdateServerReq = WithExpire<{}> // server.update
|
export type UpdateServerReq = WithExpire<{ }> // server.update
|
||||||
export type UpdateServerRes = WithRevision<null>
|
export type UpdateServerRes = WithRevision<null>
|
||||||
|
|
||||||
export type RestartServerReq = {} // server.restart
|
export type RestartServerReq = { } // server.restart
|
||||||
export type RestartServerRes = null
|
export type RestartServerRes = null
|
||||||
|
|
||||||
export type ShutdownServerReq = {} // server.shutdown
|
export type ShutdownServerReq = { } // server.shutdown
|
||||||
export type ShutdownServerRes = null
|
export type ShutdownServerRes = null
|
||||||
|
|
||||||
// network
|
// network
|
||||||
|
|
||||||
export type RefreshLanReq = {} // network.lan.refresh
|
export type RefreshLanReq = { } // network.lan.refresh
|
||||||
export type RefreshLanRes = null
|
export type RefreshLanRes = null
|
||||||
|
|
||||||
// sessions
|
// sessions
|
||||||
|
|
||||||
export type GetSessionsReq = {} // sessions.list
|
export type GetSessionsReq = { } // sessions.list
|
||||||
export type GetSessionsRes = {
|
export type GetSessionsRes = {
|
||||||
current: string,
|
current: string,
|
||||||
sessions: { [hash: string]: Session }
|
sessions: { [hash: string]: Session }
|
||||||
@@ -67,6 +67,7 @@ export module RR {
|
|||||||
export type SetPackageMarketplaceRes = WithRevision<null>
|
export type SetPackageMarketplaceRes = WithRevision<null>
|
||||||
|
|
||||||
// password
|
// password
|
||||||
|
|
||||||
export type UpdatePasswordReq = { password: string } // password.set
|
export type UpdatePasswordReq = { password: string } // password.set
|
||||||
export type UpdatePasswordRes = null
|
export type UpdatePasswordRes = null
|
||||||
|
|
||||||
@@ -78,29 +79,40 @@ export module RR {
|
|||||||
export type DeleteNotificationReq = { id: string } // notification.delete
|
export type DeleteNotificationReq = { id: string } // notification.delete
|
||||||
export type DeleteNotificationRes = null
|
export type DeleteNotificationRes = null
|
||||||
|
|
||||||
export type DeleteAllNotificationsReq = {} // notification.delete.all
|
export type DeleteAllNotificationsReq = { } // notification.delete.all
|
||||||
export type DeleteAllNotificationsRes = null
|
export type DeleteAllNotificationsRes = null
|
||||||
|
|
||||||
// wifi
|
// 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
|
export type AddWifiReq = { // wifi.add
|
||||||
ssid: string
|
ssid: string
|
||||||
password: string
|
password: string
|
||||||
country: string
|
|
||||||
priority: number
|
priority: number
|
||||||
connect: boolean
|
connect: boolean
|
||||||
}
|
}
|
||||||
export type AddWifiRes = null
|
export type AddWifiRes = null
|
||||||
|
|
||||||
export type ConnectWifiReq = WithExpire<{ ssid: string }> // wifi.connect
|
export type ConnectWifiReq = { ssid: string } // wifi.connect
|
||||||
export type ConnectWifiRes = WithRevision<null>
|
export type ConnectWifiRes = null
|
||||||
|
|
||||||
export type DeleteWifiReq = WithExpire<{ ssid: string }> // wifi.delete
|
export type DeleteWifiReq = { ssid: string } // wifi.delete
|
||||||
export type DeleteWifiRes = WithRevision<null>
|
export type DeleteWifiRes = null
|
||||||
|
|
||||||
// ssh
|
// ssh
|
||||||
|
|
||||||
export type GetSSHKeysReq = {} // ssh.get
|
export type GetSSHKeysReq = { } // ssh.get
|
||||||
export type GetSSHKeysRes = SSHKeys
|
export type GetSSHKeysRes = SSHKeys
|
||||||
|
|
||||||
export type AddSSHKeyReq = { pubkey: string } // ssh.add
|
export type AddSSHKeyReq = { pubkey: string } // ssh.add
|
||||||
@@ -119,7 +131,7 @@ export module RR {
|
|||||||
|
|
||||||
// disk
|
// disk
|
||||||
|
|
||||||
export type GetDisksReq = {} // disk.list
|
export type GetDisksReq = { } // disk.list
|
||||||
export type GetDisksRes = DiskInfo
|
export type GetDisksRes = DiskInfo
|
||||||
|
|
||||||
export type EjectDisksReq = { logicalname: string } // disk.eject
|
export type EjectDisksReq = { logicalname: string } // disk.eject
|
||||||
@@ -178,10 +190,10 @@ export module RR {
|
|||||||
|
|
||||||
// marketplace
|
// marketplace
|
||||||
|
|
||||||
export type GetMarketplaceDataReq = {}
|
export type GetMarketplaceDataReq = { }
|
||||||
export type GetMarketplaceDataRes = MarketplaceData
|
export type GetMarketplaceDataRes = MarketplaceData
|
||||||
|
|
||||||
export type GetMarketplaceEOSReq = {}
|
export type GetMarketplaceEOSReq = { }
|
||||||
export type GetMarketplaceEOSRes = MarketplaceEOS
|
export type GetMarketplaceEOSRes = MarketplaceEOS
|
||||||
|
|
||||||
export type GetMarketplacePackagesReq = {
|
export type GetMarketplacePackagesReq = {
|
||||||
|
|||||||
@@ -96,17 +96,15 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
|||||||
|
|
||||||
// wifi
|
// wifi
|
||||||
|
|
||||||
|
abstract getWifi (params: RR.GetWifiReq, timeout: number): Promise<RR.GetWifiRes>
|
||||||
|
|
||||||
|
abstract setWifiCountry (params: RR.SetWifiCountryReq): Promise<RR.SetWifiCountryRes>
|
||||||
|
|
||||||
abstract addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes>
|
abstract addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes>
|
||||||
|
|
||||||
protected abstract connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
|
abstract connectWifi (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
|
||||||
connectWifi = (params: RR.ConnectWifiReq) => this.syncResponse(
|
|
||||||
() => this.connectWifiRaw(params),
|
|
||||||
)()
|
|
||||||
|
|
||||||
protected abstract deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.ConnectWifiRes>
|
abstract deleteWifi (params: RR.DeleteWifiReq): Promise<RR.ConnectWifiRes>
|
||||||
deleteWifi = (params: RR.DeleteWifiReq) => this.syncResponse(
|
|
||||||
() => this.deleteWifiRaw(params),
|
|
||||||
)()
|
|
||||||
|
|
||||||
// ssh
|
// ssh
|
||||||
|
|
||||||
|
|||||||
@@ -139,130 +139,138 @@ export class LiveApiService extends ApiService {
|
|||||||
|
|
||||||
// notification
|
// notification
|
||||||
|
|
||||||
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise < RR.GetNotificationsRes > {
|
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise <RR.GetNotificationsRes> {
|
||||||
return this.http.rpcRequest({ method: 'notification.list', params })
|
return this.http.rpcRequest({ method: 'notification.list', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteNotification (params: RR.DeleteNotificationReq): Promise < RR.DeleteNotificationRes > {
|
async deleteNotification (params: RR.DeleteNotificationReq): Promise <RR.DeleteNotificationRes> {
|
||||||
return this.http.rpcRequest({ method: 'notification.delete', params })
|
return this.http.rpcRequest({ method: 'notification.delete', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise < RR.DeleteAllNotificationsRes > {
|
async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise <RR.DeleteAllNotificationsRes> {
|
||||||
return this.http.rpcRequest({ method: 'notification.delete.all', params })
|
return this.http.rpcRequest({ method: 'notification.delete.all', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// wifi
|
// wifi
|
||||||
|
|
||||||
async addWifi (params: RR.AddWifiReq): Promise < RR.AddWifiRes > {
|
async getWifi (params: RR.GetWifiReq, timeout?: number): Promise <RR.GetWifiRes> {
|
||||||
|
return this.http.rpcRequest({ method: 'wifi.get', params, timeout })
|
||||||
|
}
|
||||||
|
|
||||||
|
async setWifiCountry (params: RR.SetWifiCountryReq): Promise <RR.SetWifiCountryRes> {
|
||||||
|
return this.http.rpcRequest({ method: 'wifi.country.set', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
async addWifi (params: RR.AddWifiReq): Promise <RR.AddWifiRes> {
|
||||||
return this.http.rpcRequest({ method: 'wifi.add', params })
|
return this.http.rpcRequest({ method: 'wifi.add', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectWifiRaw (params: RR.ConnectWifiReq): Promise < RR.ConnectWifiRes > {
|
async connectWifi (params: RR.ConnectWifiReq): Promise <RR.ConnectWifiRes> {
|
||||||
return this.http.rpcRequest({ method: 'wifi.connect', params })
|
return this.http.rpcRequest({ method: 'wifi.connect', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWifiRaw (params: RR.DeleteWifiReq): Promise < RR.DeleteWifiRes > {
|
async deleteWifi (params: RR.DeleteWifiReq): Promise <RR.DeleteWifiRes> {
|
||||||
return this.http.rpcRequest({ method: 'wifi.delete', params })
|
return this.http.rpcRequest({ method: 'wifi.delete', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ssh
|
// ssh
|
||||||
|
|
||||||
async getSshKeys (params: RR.GetSSHKeysReq): Promise < RR.GetSSHKeysRes > {
|
async getSshKeys (params: RR.GetSSHKeysReq): Promise <RR.GetSSHKeysRes> {
|
||||||
return this.http.rpcRequest({ method: 'ssh.get', params })
|
return this.http.rpcRequest({ method: 'ssh.get', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async addSshKey (params: RR.AddSSHKeyReq): Promise < RR.AddSSHKeyRes > {
|
async addSshKey (params: RR.AddSSHKeyReq): Promise <RR.AddSSHKeyRes> {
|
||||||
return this.http.rpcRequest({ method: 'ssh.add', params })
|
return this.http.rpcRequest({ method: 'ssh.add', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise < RR.DeleteSSHKeyRes > {
|
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise <RR.DeleteSSHKeyRes> {
|
||||||
return this.http.rpcRequest({ method: 'ssh.delete', params })
|
return this.http.rpcRequest({ method: 'ssh.delete', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// backup
|
// backup
|
||||||
|
|
||||||
async createBackupRaw (params: RR.CreateBackupReq): Promise < RR.CreateBackupRes > {
|
async createBackupRaw (params: RR.CreateBackupReq): Promise <RR.CreateBackupRes> {
|
||||||
return this.http.rpcRequest({ method: 'backup.create', params })
|
return this.http.rpcRequest({ method: 'backup.create', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreBackupRaw (params: RR.RestoreBackupReq): Promise < RR.RestoreBackupRes > {
|
async restoreBackupRaw (params: RR.RestoreBackupReq): Promise <RR.RestoreBackupRes> {
|
||||||
return this.http.rpcRequest({ method: 'backup.restore', params })
|
return this.http.rpcRequest({ method: 'backup.restore', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// disk
|
// disk
|
||||||
|
|
||||||
getDisks (params: RR.GetDisksReq): Promise < RR.GetDisksRes > {
|
getDisks (params: RR.GetDisksReq): Promise <RR.GetDisksRes> {
|
||||||
return this.http.rpcRequest({ method: 'disk.list', params })
|
return this.http.rpcRequest({ method: 'disk.list', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
ejectDisk (params: RR.EjectDisksReq): Promise < RR.EjectDisksRes > {
|
ejectDisk (params: RR.EjectDisksReq): Promise <RR.EjectDisksRes> {
|
||||||
return this.http.rpcRequest({ method: 'disk.eject', params })
|
return this.http.rpcRequest({ method: 'disk.eject', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// package
|
// package
|
||||||
|
|
||||||
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise < RR.GetPackagePropertiesRes < any > ['data'] > {
|
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise <RR.GetPackagePropertiesRes < any > ['data'] > {
|
||||||
return this.http.rpcRequest({ method: 'package.properties', params })
|
return this.http.rpcRequest({ method: 'package.properties', params })
|
||||||
.then(parsePropertiesPermissive)
|
.then(parsePropertiesPermissive)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPackageLogs (params: RR.GetPackageLogsReq): Promise < RR.GetPackageLogsRes > {
|
async getPackageLogs (params: RR.GetPackageLogsReq): Promise <RR.GetPackageLogsRes> {
|
||||||
return this.http.rpcRequest( { method: 'package.logs', params })
|
return this.http.rpcRequest( { method: 'package.logs', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise < RR.GetPackageMetricsRes > {
|
async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise <RR.GetPackageMetricsRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.metrics', params })
|
return this.http.rpcRequest({ method: 'package.metrics', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async installPackageRaw (params: RR.InstallPackageReq): Promise < RR.InstallPackageRes > {
|
async installPackageRaw (params: RR.InstallPackageReq): Promise <RR.InstallPackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.install', params })
|
return this.http.rpcRequest({ method: 'package.install', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise < RR.DryUpdatePackageRes > {
|
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise <RR.DryUpdatePackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.update.dry', params })
|
return this.http.rpcRequest({ method: 'package.update.dry', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPackageConfig (params: RR.GetPackageConfigReq): Promise < RR.GetPackageConfigRes > {
|
async getPackageConfig (params: RR.GetPackageConfigReq): Promise <RR.GetPackageConfigRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.config.get', params })
|
return this.http.rpcRequest({ method: 'package.config.get', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise < RR.DrySetPackageConfigRes > {
|
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise <RR.DrySetPackageConfigRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.config.set.dry', params })
|
return this.http.rpcRequest({ method: 'package.config.set.dry', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise < RR.SetPackageConfigRes > {
|
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise <RR.SetPackageConfigRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.config.set', params })
|
return this.http.rpcRequest({ method: 'package.config.set', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async restorePackageRaw (params: RR.RestorePackageReq): Promise < RR.RestorePackageRes > {
|
async restorePackageRaw (params: RR.RestorePackageReq): Promise <RR.RestorePackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.restore', params })
|
return this.http.rpcRequest({ method: 'package.restore', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async executePackageAction (params: RR.ExecutePackageActionReq): Promise < RR.ExecutePackageActionRes > {
|
async executePackageAction (params: RR.ExecutePackageActionReq): Promise <RR.ExecutePackageActionRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.action', params })
|
return this.http.rpcRequest({ method: 'package.action', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async startPackageRaw (params: RR.StartPackageReq): Promise < RR.StartPackageRes > {
|
async startPackageRaw (params: RR.StartPackageReq): Promise <RR.StartPackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.start', params })
|
return this.http.rpcRequest({ method: 'package.start', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryStopPackage (params: RR.DryStopPackageReq): Promise < RR.DryStopPackageRes > {
|
async dryStopPackage (params: RR.DryStopPackageReq): Promise <RR.DryStopPackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.stop.dry', params })
|
return this.http.rpcRequest({ method: 'package.stop.dry', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopPackageRaw (params: RR.StopPackageReq): Promise < RR.StopPackageRes > {
|
async stopPackageRaw (params: RR.StopPackageReq): Promise <RR.StopPackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.stop', params })
|
return this.http.rpcRequest({ method: 'package.stop', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryRemovePackage (params: RR.DryRemovePackageReq): Promise < RR.DryRemovePackageRes > {
|
async dryRemovePackage (params: RR.DryRemovePackageReq): Promise <RR.DryRemovePackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.remove.dry', params })
|
return this.http.rpcRequest({ method: 'package.remove.dry', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePackageRaw (params: RR.RemovePackageReq): Promise < RR.RemovePackageRes > {
|
async removePackageRaw (params: RR.RemovePackageReq): Promise <RR.RemovePackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.remove', params })
|
return this.http.rpcRequest({ method: 'package.remove', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise < RR.DryConfigureDependencyRes > {
|
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise <RR.DryConfigureDependencyRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params })
|
return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { pauseFor } from '../../util/misc.util'
|
import { pauseFor } from '../../util/misc.util'
|
||||||
import { ApiService } from './embassy-api.service'
|
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 { InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||||
import { RR, WithRevision } from './api.types'
|
import { RR, WithRevision } from './api.types'
|
||||||
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
||||||
@@ -223,47 +223,29 @@ export class MockApiService extends ApiService {
|
|||||||
|
|
||||||
// wifi
|
// wifi
|
||||||
|
|
||||||
|
async getWifi (params: RR.GetWifiReq): Promise < RR.GetWifiRes > {
|
||||||
|
await pauseFor(2000)
|
||||||
|
return Mock.Wifi
|
||||||
|
}
|
||||||
|
|
||||||
|
async setWifiCountry (params: RR.SetWifiCountryReq): Promise <RR.SetWifiCountryRes> {
|
||||||
|
await pauseFor(2000)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
|
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
|
async connectWifi (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
const patch = [
|
return null
|
||||||
{
|
|
||||||
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<WithRevision<null>>({ method: 'db.patch', params: { patch } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
|
async deleteWifi (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
const patch: Operation[] = [
|
return null
|
||||||
{
|
|
||||||
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<WithRevision<null>>({ method: 'db.patch', params: { patch } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ssh
|
// ssh
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ export class Emver {
|
|||||||
constructor () { }
|
constructor () { }
|
||||||
|
|
||||||
compare (lhs: string, rhs: string): number {
|
compare (lhs: string, rhs: string): number {
|
||||||
console.log('EMVER', emver)
|
|
||||||
const compare = emver.compare(lhs, rhs)
|
const compare = emver.compare(lhs, rhs)
|
||||||
console.log('COMPARE', compare)
|
|
||||||
return compare
|
return compare
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'
|
import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'
|
||||||
import { By } from '@angular/platform-browser'
|
import { ConfigSpec, isValueSpecListOf, ListValueSpecNumber, ListValueSpecObject, ListValueSpecString, ListValueSpecUnion, UniqueBy, ValueSpec, ValueSpecEnum, ValueSpecList, ValueSpecNumber, ValueSpecString, ValueSpecUnion } from '../pkg-config/config-types'
|
||||||
import { ConfigSpec, isValueSpecListOf, ListValueSpecNumber, ListValueSpecObject, ListValueSpecOf, ListValueSpecString, ListValueSpecUnion, UniqueBy, ValueSpec, ValueSpecEnum, ValueSpecList, ValueSpecNumber, ValueSpecObject, ValueSpecString, ValueSpecUnion } from '../pkg-config/config-types'
|
|
||||||
import { getDefaultString, Range } from '../pkg-config/config-utilities'
|
import { getDefaultString, Range } from '../pkg-config/config-utilities'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class FormService {
|
export class FormService {
|
||||||
validationMessages: { [key: string]: { type: string, message: string }[] } = { }
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly formBuilder: FormBuilder,
|
private readonly formBuilder: FormBuilder,
|
||||||
@@ -18,13 +16,11 @@ export class FormService {
|
|||||||
return this.getFormGroup(config, [], current)
|
return this.getFormGroup(config, [], current)
|
||||||
}
|
}
|
||||||
|
|
||||||
getListItemValidators (spec: ValueSpecList, key: string, index: number) {
|
getListItemValidators (spec: ValueSpecList) {
|
||||||
const listKey = `${key}/${index}`
|
|
||||||
this.validationMessages[listKey] = []
|
|
||||||
if (isValueSpecListOf(spec, 'string')) {
|
if (isValueSpecListOf(spec, 'string')) {
|
||||||
return this.stringValidators(listKey, spec.spec)
|
return this.stringValidators(spec.spec)
|
||||||
} else if (isValueSpecListOf(spec, 'number')) {
|
} 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 } )
|
return this.formBuilder.group(group, { validators } )
|
||||||
}
|
}
|
||||||
|
|
||||||
getListItem (key: string, index: number, spec: ValueSpecList, entry: any) {
|
getListItem (spec: ValueSpecList, entry: any) {
|
||||||
const listItemValidators = this.getListItemValidators(spec, key, index)
|
const listItemValidators = this.getListItemValidators(spec)
|
||||||
if (isValueSpecListOf(spec, 'string')) {
|
if (isValueSpecListOf(spec, 'string')) {
|
||||||
return this.formBuilder.control(entry, listItemValidators)
|
return this.formBuilder.control(entry, listItemValidators)
|
||||||
} else if (isValueSpecListOf(spec, 'number')) {
|
} else if (isValueSpecListOf(spec, 'number')) {
|
||||||
@@ -69,12 +65,11 @@ export class FormService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getFormEntry (key: string, spec: ValueSpec, currentValue: any): FormGroup | FormArray | FormControl {
|
private getFormEntry (key: string, spec: ValueSpec, currentValue: any): FormGroup | FormArray | FormControl {
|
||||||
this.validationMessages[key] = []
|
|
||||||
let validators: ValidatorFn[]
|
let validators: ValidatorFn[]
|
||||||
let value: any
|
let value: any
|
||||||
switch (spec.type) {
|
switch (spec.type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
validators = this.stringValidators(key, spec)
|
validators = this.stringValidators(spec)
|
||||||
if (currentValue !== undefined) {
|
if (currentValue !== undefined) {
|
||||||
value = currentValue
|
value = currentValue
|
||||||
} else {
|
} else {
|
||||||
@@ -82,7 +77,7 @@ export class FormService {
|
|||||||
}
|
}
|
||||||
return this.formBuilder.control(value, validators)
|
return this.formBuilder.control(value, validators)
|
||||||
case 'number':
|
case 'number':
|
||||||
validators = this.numberValidators(key, spec)
|
validators = this.numberValidators(spec)
|
||||||
if (currentValue !== undefined) {
|
if (currentValue !== undefined) {
|
||||||
value = currentValue
|
value = currentValue
|
||||||
} else {
|
} else {
|
||||||
@@ -92,9 +87,9 @@ export class FormService {
|
|||||||
case 'object':
|
case 'object':
|
||||||
return this.getFormGroup(spec.spec, [], currentValue)
|
return this.getFormGroup(spec.spec, [], currentValue)
|
||||||
case 'list':
|
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) => {
|
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)
|
return this.formBuilder.array(mapped, validators)
|
||||||
case 'union':
|
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[] = []
|
const validators: ValidatorFn[] = []
|
||||||
|
|
||||||
if (!(spec as ValueSpecString).nullable) {
|
if (!(spec as ValueSpecString).nullable) {
|
||||||
validators.push(Validators.required)
|
validators.push(Validators.required)
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'required',
|
|
||||||
message: 'Cannot be blank.',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec.pattern) {
|
if (spec.pattern) {
|
||||||
validators.push(Validators.pattern(spec.pattern))
|
validators.push(Validators.pattern(spec.pattern))
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'pattern',
|
|
||||||
message: spec['pattern-description'],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
}
|
}
|
||||||
|
|
||||||
private numberValidators (key: string, spec: ValueSpecNumber | ListValueSpecNumber): ValidatorFn[] {
|
private numberValidators (spec: ValueSpecNumber | ListValueSpecNumber): ValidatorFn[] {
|
||||||
const validators: ValidatorFn[] = []
|
const validators: ValidatorFn[] = []
|
||||||
|
|
||||||
if (!(spec as ValueSpecNumber).nullable) {
|
if (!(spec as ValueSpecNumber).nullable) {
|
||||||
validators.push(Validators.required)
|
validators.push(Validators.required)
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'required',
|
|
||||||
message: 'Cannot be blank.',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec.integral) {
|
if (spec.integral) {
|
||||||
validators.push(isInteger())
|
validators.push(isInteger())
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'numberNotInteger',
|
|
||||||
message: 'Number must be an integer.',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validators.push(numberInRange(spec.range))
|
validators.push(numberInRange(spec.range))
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'numberNotInRange',
|
|
||||||
message: 'Number not in range.',
|
|
||||||
})
|
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
}
|
}
|
||||||
|
|
||||||
private listValidators (key: string, spec: ValueSpecList): ValidatorFn[] {
|
private listValidators (spec: ValueSpecList): ValidatorFn[] {
|
||||||
const validators: ValidatorFn[] = []
|
const validators: ValidatorFn[] = []
|
||||||
|
|
||||||
validators.push(listInRange(spec.range))
|
validators.push(listInRange(spec.range))
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'listNotInRange',
|
|
||||||
message: 'List not in range.',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!isValueSpecListOf(spec, 'enum')) {
|
if (!isValueSpecListOf(spec, 'enum')) {
|
||||||
validators.push(listUnique(spec))
|
validators.push(listUnique(spec))
|
||||||
this.validationMessages[key].push({
|
|
||||||
type: 'listNotUnique',
|
|
||||||
message: 'List contains duplicate entries.',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ export class HttpService {
|
|||||||
async rpcRequest<T> (rpcOpts: RPCOptions): Promise<T> {
|
async rpcRequest<T> (rpcOpts: RPCOptions): Promise<T> {
|
||||||
const { url, version } = this.config.api
|
const { url, version } = this.config.api
|
||||||
rpcOpts.params = rpcOpts.params || { }
|
rpcOpts.params = rpcOpts.params || { }
|
||||||
const httpOpts = {
|
const httpOpts: HttpOptions = {
|
||||||
method: Method.POST,
|
method: Method.POST,
|
||||||
body: rpcOpts,
|
body: rpcOpts,
|
||||||
url: `/${url}/${version}`,
|
url: `/${url}/${version}`,
|
||||||
}
|
}
|
||||||
|
if (rpcOpts.timeout) httpOpts.timeout = rpcOpts.timeout
|
||||||
|
|
||||||
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
|
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
|
||||||
|
|
||||||
@@ -78,6 +79,7 @@ export class HttpService {
|
|||||||
case Method.PATCH: req = this.http.patch(url, httpOpts.body, options) as any; break
|
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
|
case Method.DELETE: req = this.http.delete(url, options) as any; break
|
||||||
}
|
}
|
||||||
|
console.log('REQUEST', options)
|
||||||
|
|
||||||
return (httpOpts.timeout ? withTimeout(req, httpOpts.timeout) : req)
|
return (httpOpts.timeout ? withTimeout(req, httpOpts.timeout) : req)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
@@ -139,6 +141,7 @@ export interface RPCOptions {
|
|||||||
params?: {
|
params?: {
|
||||||
[param: string]: string | number | boolean | object | string[] | number[];
|
[param: string]: string | number | boolean | object | string[] | number[];
|
||||||
}
|
}
|
||||||
|
timeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RPCBase {
|
interface RPCBase {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export interface ServerInfo {
|
|||||||
status: ServerStatus
|
status: ServerStatus
|
||||||
'eos-marketplace': URL
|
'eos-marketplace': URL
|
||||||
'package-marketplace': URL | null // uses EOS marketplace if null
|
'package-marketplace': URL | null // uses EOS marketplace if null
|
||||||
wifi: WiFiInfo
|
|
||||||
'unread-notification-count': number
|
'unread-notification-count': number
|
||||||
specs: {
|
specs: {
|
||||||
cpu: string
|
cpu: string
|
||||||
@@ -38,12 +37,6 @@ export enum ServerStatus {
|
|||||||
BackingUp = 'backing-up',
|
BackingUp = 'backing-up',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WiFiInfo {
|
|
||||||
ssids: string[]
|
|
||||||
selected: string | null
|
|
||||||
connected: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PackageDataEntry {
|
export interface PackageDataEntry {
|
||||||
state: PackageState
|
state: PackageState
|
||||||
'static-files': {
|
'static-files': {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class ServerConfigService {
|
|||||||
try {
|
try {
|
||||||
await this.saveFns[key](data)
|
await this.saveFns[key](data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errToast.present(e.message)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
loader.dismiss()
|
loader.dismiss()
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ export class ServerConfigService {
|
|||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentInputModal (key: string, current?: string) {
|
async presentModalInput (key: string, current?: string) {
|
||||||
const { name, description, masked } = serverConfig[key] as ValueSpecString
|
const { name, description, masked } = serverConfig[key] as ValueSpecString
|
||||||
|
|
||||||
const modal = await this.modalCtrl.create({
|
const modal = await this.modalCtrl.create({
|
||||||
@@ -102,14 +102,20 @@ export class ServerConfigService {
|
|||||||
await modal.present()
|
await modal.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
// async presentModalForm (key: string, current?: string) {
|
// async presentModalForm (key: string) {
|
||||||
// const modal = await this.modalCtrl.create({
|
// const modal = await this.modalCtrl.create({
|
||||||
// component: AppConfigValuePage,
|
// component: AppActionInputPage,
|
||||||
// componentProps: {
|
// componentProps: {
|
||||||
// cursor,
|
// title: serverConfig[key].name,
|
||||||
// saveFn: this.saveFns[key],
|
// spec: (serverConfig[key] as ValueSpecObject).spec,
|
||||||
// },
|
// },
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
// modal.onWillDismiss().then(res => {
|
||||||
|
// if (!res.data) return
|
||||||
|
// this.saveFns[key](res.data)
|
||||||
|
// })
|
||||||
|
|
||||||
// await modal.present()
|
// await modal.present()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|||||||
@@ -1,252 +1,252 @@
|
|||||||
{
|
{
|
||||||
"AD": "Andorra",
|
"AD": "Andorra",
|
||||||
"AE": "United Arab Emirates",
|
"AE": "United Arab Emirates",
|
||||||
"AF": "Afghanistan",
|
"AF": "Afghanistan",
|
||||||
"AG": "Antigua and Barbuda",
|
"AG": "Antigua and Barbuda",
|
||||||
"AI": "Anguilla",
|
"AI": "Anguilla",
|
||||||
"AL": "Albania",
|
"AL": "Albania",
|
||||||
"AM": "Armenia",
|
"AM": "Armenia",
|
||||||
"AO": "Angola",
|
"AO": "Angola",
|
||||||
"AQ": "Antarctica",
|
"AQ": "Antarctica",
|
||||||
"AR": "Argentina",
|
"AR": "Argentina",
|
||||||
"AS": "American Samoa",
|
"AS": "American Samoa",
|
||||||
"AT": "Austria",
|
"AT": "Austria",
|
||||||
"AU": "Australia",
|
"AU": "Australia",
|
||||||
"AW": "Aruba",
|
"AW": "Aruba",
|
||||||
"AX": "Aland Islands",
|
"AX": "Aland Islands",
|
||||||
"AZ": "Azerbaijan",
|
"AZ": "Azerbaijan",
|
||||||
"BA": "Bosnia and Herzegovina",
|
"BA": "Bosnia and Herzegovina",
|
||||||
"BB": "Barbados",
|
"BB": "Barbados",
|
||||||
"BD": "Bangladesh",
|
"BD": "Bangladesh",
|
||||||
"BE": "Belgium",
|
"BE": "Belgium",
|
||||||
"BF": "Burkina Faso",
|
"BF": "Burkina Faso",
|
||||||
"BG": "Bulgaria",
|
"BG": "Bulgaria",
|
||||||
"BH": "Bahrain",
|
"BH": "Bahrain",
|
||||||
"BI": "Burundi",
|
"BI": "Burundi",
|
||||||
"BJ": "Benin",
|
"BJ": "Benin",
|
||||||
"BL": "Saint Barthelemy",
|
"BL": "Saint Barthelemy",
|
||||||
"BM": "Bermuda",
|
"BM": "Bermuda",
|
||||||
"BN": "Brunei",
|
"BN": "Brunei",
|
||||||
"BO": "Bolivia",
|
"BO": "Bolivia",
|
||||||
"BQ": "Bonaire, Saint Eustatius and Saba ",
|
"BQ": "Bonaire, Saint Eustatius and Saba ",
|
||||||
"BR": "Brazil",
|
"BR": "Brazil",
|
||||||
"BS": "Bahamas",
|
"BS": "Bahamas",
|
||||||
"BT": "Bhutan",
|
"BT": "Bhutan",
|
||||||
"BV": "Bouvet Island",
|
"BV": "Bouvet Island",
|
||||||
"BW": "Botswana",
|
"BW": "Botswana",
|
||||||
"BY": "Belarus",
|
"BY": "Belarus",
|
||||||
"BZ": "Belize",
|
"BZ": "Belize",
|
||||||
"CA": "Canada",
|
"CA": "Canada",
|
||||||
"CC": "Cocos Islands",
|
"CC": "Cocos Islands",
|
||||||
"CD": "Democratic Republic of the Congo",
|
"CD": "Democratic Republic of the Congo",
|
||||||
"CF": "Central African Republic",
|
"CF": "Central African Republic",
|
||||||
"CG": "Republic of the Congo",
|
"CG": "Republic of the Congo",
|
||||||
"CH": "Switzerland",
|
"CH": "Switzerland",
|
||||||
"CI": "Ivory Coast",
|
"CI": "Ivory Coast",
|
||||||
"CK": "Cook Islands",
|
"CK": "Cook Islands",
|
||||||
"CL": "Chile",
|
"CL": "Chile",
|
||||||
"CM": "Cameroon",
|
"CM": "Cameroon",
|
||||||
"CN": "China",
|
"CN": "China",
|
||||||
"CO": "Colombia",
|
"CO": "Colombia",
|
||||||
"CR": "Costa Rica",
|
"CR": "Costa Rica",
|
||||||
"CU": "Cuba",
|
"CU": "Cuba",
|
||||||
"CV": "Cape Verde",
|
"CV": "Cape Verde",
|
||||||
"CW": "Curacao",
|
"CW": "Curacao",
|
||||||
"CX": "Christmas Island",
|
"CX": "Christmas Island",
|
||||||
"CY": "Cyprus",
|
"CY": "Cyprus",
|
||||||
"CZ": "Czech Republic",
|
"CZ": "Czech Republic",
|
||||||
"DE": "Germany",
|
"DE": "Germany",
|
||||||
"DJ": "Djibouti",
|
"DJ": "Djibouti",
|
||||||
"DK": "Denmark",
|
"DK": "Denmark",
|
||||||
"DM": "Dominica",
|
"DM": "Dominica",
|
||||||
"DO": "Dominican Republic",
|
"DO": "Dominican Republic",
|
||||||
"DZ": "Algeria",
|
"DZ": "Algeria",
|
||||||
"EC": "Ecuador",
|
"EC": "Ecuador",
|
||||||
"EE": "Estonia",
|
"EE": "Estonia",
|
||||||
"EG": "Egypt",
|
"EG": "Egypt",
|
||||||
"EH": "Western Sahara",
|
"EH": "Western Sahara",
|
||||||
"ER": "Eritrea",
|
"ER": "Eritrea",
|
||||||
"ES": "Spain",
|
"ES": "Spain",
|
||||||
"ET": "Ethiopia",
|
"ET": "Ethiopia",
|
||||||
"FI": "Finland",
|
"FI": "Finland",
|
||||||
"FJ": "Fiji",
|
"FJ": "Fiji",
|
||||||
"FK": "Falkland Islands",
|
"FK": "Falkland Islands",
|
||||||
"FM": "Micronesia",
|
"FM": "Micronesia",
|
||||||
"FO": "Faroe Islands",
|
"FO": "Faroe Islands",
|
||||||
"FR": "France",
|
"FR": "France",
|
||||||
"GA": "Gabon",
|
"GA": "Gabon",
|
||||||
"GB": "United Kingdom",
|
"GB": "United Kingdom",
|
||||||
"GD": "Grenada",
|
"GD": "Grenada",
|
||||||
"GE": "Georgia",
|
"GE": "Georgia",
|
||||||
"GF": "French Guiana",
|
"GF": "French Guiana",
|
||||||
"GG": "Guernsey",
|
"GG": "Guernsey",
|
||||||
"GH": "Ghana",
|
"GH": "Ghana",
|
||||||
"GI": "Gibraltar",
|
"GI": "Gibraltar",
|
||||||
"GL": "Greenland",
|
"GL": "Greenland",
|
||||||
"GM": "Gambia",
|
"GM": "Gambia",
|
||||||
"GN": "Guinea",
|
"GN": "Guinea",
|
||||||
"GP": "Guadeloupe",
|
"GP": "Guadeloupe",
|
||||||
"GQ": "Equatorial Guinea",
|
"GQ": "Equatorial Guinea",
|
||||||
"GR": "Greece",
|
"GR": "Greece",
|
||||||
"GS": "South Georgia and the South Sandwich Islands",
|
"GS": "South Georgia and the South Sandwich Islands",
|
||||||
"GT": "Guatemala",
|
"GT": "Guatemala",
|
||||||
"GU": "Guam",
|
"GU": "Guam",
|
||||||
"GW": "Guinea-Bissau",
|
"GW": "Guinea-Bissau",
|
||||||
"GY": "Guyana",
|
"GY": "Guyana",
|
||||||
"HK": "Hong Kong",
|
"HK": "Hong Kong",
|
||||||
"HM": "Heard Island and McDonald Islands",
|
"HM": "Heard Island and McDonald Islands",
|
||||||
"HN": "Honduras",
|
"HN": "Honduras",
|
||||||
"HR": "Croatia",
|
"HR": "Croatia",
|
||||||
"HT": "Haiti",
|
"HT": "Haiti",
|
||||||
"HU": "Hungary",
|
"HU": "Hungary",
|
||||||
"ID": "Indonesia",
|
"ID": "Indonesia",
|
||||||
"IE": "Ireland",
|
"IE": "Ireland",
|
||||||
"IL": "Israel",
|
"IL": "Israel",
|
||||||
"IM": "Isle of Man",
|
"IM": "Isle of Man",
|
||||||
"IN": "India",
|
"IN": "India",
|
||||||
"IO": "British Indian Ocean Territory",
|
"IO": "British Indian Ocean Territory",
|
||||||
"IQ": "Iraq",
|
"IQ": "Iraq",
|
||||||
"IR": "Iran",
|
"IR": "Iran",
|
||||||
"IS": "Iceland",
|
"IS": "Iceland",
|
||||||
"IT": "Italy",
|
"IT": "Italy",
|
||||||
"JE": "Jersey",
|
"JE": "Jersey",
|
||||||
"JM": "Jamaica",
|
"JM": "Jamaica",
|
||||||
"JO": "Jordan",
|
"JO": "Jordan",
|
||||||
"JP": "Japan",
|
"JP": "Japan",
|
||||||
"KE": "Kenya",
|
"KE": "Kenya",
|
||||||
"KG": "Kyrgyzstan",
|
"KG": "Kyrgyzstan",
|
||||||
"KH": "Cambodia",
|
"KH": "Cambodia",
|
||||||
"KI": "Kiribati",
|
"KI": "Kiribati",
|
||||||
"KM": "Comoros",
|
"KM": "Comoros",
|
||||||
"KN": "Saint Kitts and Nevis",
|
"KN": "Saint Kitts and Nevis",
|
||||||
"KP": "North Korea",
|
"KP": "North Korea",
|
||||||
"KR": "South Korea",
|
"KR": "South Korea",
|
||||||
"KW": "Kuwait",
|
"KW": "Kuwait",
|
||||||
"KY": "Cayman Islands",
|
"KY": "Cayman Islands",
|
||||||
"KZ": "Kazakhstan",
|
"KZ": "Kazakhstan",
|
||||||
"LA": "Laos",
|
"LA": "Laos",
|
||||||
"LB": "Lebanon",
|
"LB": "Lebanon",
|
||||||
"LC": "Saint Lucia",
|
"LC": "Saint Lucia",
|
||||||
"LI": "Liechtenstein",
|
"LI": "Liechtenstein",
|
||||||
"LK": "Sri Lanka",
|
"LK": "Sri Lanka",
|
||||||
"LR": "Liberia",
|
"LR": "Liberia",
|
||||||
"LS": "Lesotho",
|
"LS": "Lesotho",
|
||||||
"LT": "Lithuania",
|
"LT": "Lithuania",
|
||||||
"LU": "Luxembourg",
|
"LU": "Luxembourg",
|
||||||
"LV": "Latvia",
|
"LV": "Latvia",
|
||||||
"LY": "Libya",
|
"LY": "Libya",
|
||||||
"MA": "Morocco",
|
"MA": "Morocco",
|
||||||
"MC": "Monaco",
|
"MC": "Monaco",
|
||||||
"MD": "Moldova",
|
"MD": "Moldova",
|
||||||
"ME": "Montenegro",
|
"ME": "Montenegro",
|
||||||
"MF": "Saint Martin",
|
"MF": "Saint Martin",
|
||||||
"MG": "Madagascar",
|
"MG": "Madagascar",
|
||||||
"MH": "Marshall Islands",
|
"MH": "Marshall Islands",
|
||||||
"MK": "Macedonia",
|
"MK": "Macedonia",
|
||||||
"ML": "Mali",
|
"ML": "Mali",
|
||||||
"MM": "Myanmar",
|
"MM": "Myanmar",
|
||||||
"MN": "Mongolia",
|
"MN": "Mongolia",
|
||||||
"MO": "Macao",
|
"MO": "Macao",
|
||||||
"MP": "Northern Mariana Islands",
|
"MP": "Northern Mariana Islands",
|
||||||
"MQ": "Martinique",
|
"MQ": "Martinique",
|
||||||
"MR": "Mauritania",
|
"MR": "Mauritania",
|
||||||
"MS": "Montserrat",
|
"MS": "Montserrat",
|
||||||
"MT": "Malta",
|
"MT": "Malta",
|
||||||
"MU": "Mauritius",
|
"MU": "Mauritius",
|
||||||
"MV": "Maldives",
|
"MV": "Maldives",
|
||||||
"MW": "Malawi",
|
"MW": "Malawi",
|
||||||
"MX": "Mexico",
|
"MX": "Mexico",
|
||||||
"MY": "Malaysia",
|
"MY": "Malaysia",
|
||||||
"MZ": "Mozambique",
|
"MZ": "Mozambique",
|
||||||
"NA": "Namibia",
|
"NA": "Namibia",
|
||||||
"NC": "New Caledonia",
|
"NC": "New Caledonia",
|
||||||
"NE": "Niger",
|
"NE": "Niger",
|
||||||
"NF": "Norfolk Island",
|
"NF": "Norfolk Island",
|
||||||
"NG": "Nigeria",
|
"NG": "Nigeria",
|
||||||
"NI": "Nicaragua",
|
"NI": "Nicaragua",
|
||||||
"NL": "Netherlands",
|
"NL": "Netherlands",
|
||||||
"NO": "Norway",
|
"NO": "Norway",
|
||||||
"NP": "Nepal",
|
"NP": "Nepal",
|
||||||
"NR": "Nauru",
|
"NR": "Nauru",
|
||||||
"NU": "Niue",
|
"NU": "Niue",
|
||||||
"NZ": "New Zealand",
|
"NZ": "New Zealand",
|
||||||
"OM": "Oman",
|
"OM": "Oman",
|
||||||
"PA": "Panama",
|
"PA": "Panama",
|
||||||
"PE": "Peru",
|
"PE": "Peru",
|
||||||
"PF": "French Polynesia",
|
"PF": "French Polynesia",
|
||||||
"PG": "Papua New Guinea",
|
"PG": "Papua New Guinea",
|
||||||
"PH": "Philippines",
|
"PH": "Philippines",
|
||||||
"PK": "Pakistan",
|
"PK": "Pakistan",
|
||||||
"PL": "Poland",
|
"PL": "Poland",
|
||||||
"PM": "Saint Pierre and Miquelon",
|
"PM": "Saint Pierre and Miquelon",
|
||||||
"PN": "Pitcairn",
|
"PN": "Pitcairn",
|
||||||
"PR": "Puerto Rico",
|
"PR": "Puerto Rico",
|
||||||
"PS": "Palestinian Territory",
|
"PS": "Palestinian Territory",
|
||||||
"PT": "Portugal",
|
"PT": "Portugal",
|
||||||
"PW": "Palau",
|
"PW": "Palau",
|
||||||
"PY": "Paraguay",
|
"PY": "Paraguay",
|
||||||
"QA": "Qatar",
|
"QA": "Qatar",
|
||||||
"RE": "Reunion",
|
"RE": "Reunion",
|
||||||
"RO": "Romania",
|
"RO": "Romania",
|
||||||
"RS": "Serbia",
|
"RS": "Serbia",
|
||||||
"RU": "Russia",
|
"RU": "Russia",
|
||||||
"RW": "Rwanda",
|
"RW": "Rwanda",
|
||||||
"SA": "Saudi Arabia",
|
"SA": "Saudi Arabia",
|
||||||
"SB": "Solomon Islands",
|
"SB": "Solomon Islands",
|
||||||
"SC": "Seychelles",
|
"SC": "Seychelles",
|
||||||
"SD": "Sudan",
|
"SD": "Sudan",
|
||||||
"SE": "Sweden",
|
"SE": "Sweden",
|
||||||
"SG": "Singapore",
|
"SG": "Singapore",
|
||||||
"SH": "Saint Helena",
|
"SH": "Saint Helena",
|
||||||
"SI": "Slovenia",
|
"SI": "Slovenia",
|
||||||
"SJ": "Svalbard and Jan Mayen",
|
"SJ": "Svalbard and Jan Mayen",
|
||||||
"SK": "Slovakia",
|
"SK": "Slovakia",
|
||||||
"SL": "Sierra Leone",
|
"SL": "Sierra Leone",
|
||||||
"SM": "San Marino",
|
"SM": "San Marino",
|
||||||
"SN": "Senegal",
|
"SN": "Senegal",
|
||||||
"SO": "Somalia",
|
"SO": "Somalia",
|
||||||
"SR": "Suriname",
|
"SR": "Suriname",
|
||||||
"SS": "South Sudan",
|
"SS": "South Sudan",
|
||||||
"ST": "Sao Tome and Principe",
|
"ST": "Sao Tome and Principe",
|
||||||
"SV": "El Salvador",
|
"SV": "El Salvador",
|
||||||
"SX": "Sint Maarten",
|
"SX": "Sint Maarten",
|
||||||
"SY": "Syria",
|
"SY": "Syria",
|
||||||
"SZ": "Swaziland",
|
"SZ": "Swaziland",
|
||||||
"TC": "Turks and Caicos Islands",
|
"TC": "Turks and Caicos Islands",
|
||||||
"TD": "Chad",
|
"TD": "Chad",
|
||||||
"TF": "French Southern Territories",
|
"TF": "French Southern Territories",
|
||||||
"TG": "Togo",
|
"TG": "Togo",
|
||||||
"TH": "Thailand",
|
"TH": "Thailand",
|
||||||
"TJ": "Tajikistan",
|
"TJ": "Tajikistan",
|
||||||
"TK": "Tokelau",
|
"TK": "Tokelau",
|
||||||
"TL": "East Timor",
|
"TL": "East Timor",
|
||||||
"TM": "Turkmenistan",
|
"TM": "Turkmenistan",
|
||||||
"TN": "Tunisia",
|
"TN": "Tunisia",
|
||||||
"TO": "Tonga",
|
"TO": "Tonga",
|
||||||
"TR": "Turkey",
|
"TR": "Turkey",
|
||||||
"TT": "Trinidad and Tobago",
|
"TT": "Trinidad and Tobago",
|
||||||
"TV": "Tuvalu",
|
"TV": "Tuvalu",
|
||||||
"TW": "Taiwan",
|
"TW": "Taiwan",
|
||||||
"TZ": "Tanzania",
|
"TZ": "Tanzania",
|
||||||
"UA": "Ukraine",
|
"UA": "Ukraine",
|
||||||
"UG": "Uganda",
|
"UG": "Uganda",
|
||||||
"UM": "United States Minor Outlying Islands",
|
"UM": "United States Minor Outlying Islands",
|
||||||
"US": "United States",
|
"US": "United States",
|
||||||
"UY": "Uruguay",
|
"UY": "Uruguay",
|
||||||
"UZ": "Uzbekistan",
|
"UZ": "Uzbekistan",
|
||||||
"VA": "Vatican",
|
"VA": "Vatican",
|
||||||
"VC": "Saint Vincent and the Grenadines",
|
"VC": "Saint Vincent and the Grenadines",
|
||||||
"VE": "Venezuela",
|
"VE": "Venezuela",
|
||||||
"VG": "British Virgin Islands",
|
"VG": "British Virgin Islands",
|
||||||
"VI": "U.S. Virgin Islands",
|
"VI": "U.S. Virgin Islands",
|
||||||
"VN": "Vietnam",
|
"VN": "Vietnam",
|
||||||
"VU": "Vanuatu",
|
"VU": "Vanuatu",
|
||||||
"WF": "Wallis and Futuna",
|
"WF": "Wallis and Futuna",
|
||||||
"WS": "Samoa",
|
"WS": "Samoa",
|
||||||
"XK": "Kosovo",
|
"XK": "Kosovo",
|
||||||
"YE": "Yemen",
|
"YE": "Yemen",
|
||||||
"YT": "Mayotte",
|
"YT": "Mayotte",
|
||||||
"ZA": "South Africa",
|
"ZA": "South Africa",
|
||||||
"ZM": "Zambia",
|
"ZM": "Zambia",
|
||||||
"ZW": "Zimbabwe"
|
"ZW": "Zimbabwe"
|
||||||
}
|
}
|
||||||
BIN
ui/src/assets/img/gifs/backing-up.gif
Normal file
BIN
ui/src/assets/img/gifs/backing-up.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 MiB |
BIN
ui/src/assets/img/gifs/cube.gif
Normal file
BIN
ui/src/assets/img/gifs/cube.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 473 KiB |
BIN
ui/src/assets/img/icons/wifi-1.png
Normal file
BIN
ui/src/assets/img/icons/wifi-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
ui/src/assets/img/icons/wifi-2.png
Normal file
BIN
ui/src/assets/img/icons/wifi-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
ui/src/assets/img/icons/wifi-3.png
Normal file
BIN
ui/src/assets/img/icons/wifi-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -39,6 +39,10 @@ $subheader-height: 48px;
|
|||||||
color: var(--ion-color-warning)
|
color: var(--ion-color-warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wide-alert {
|
||||||
|
--min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
.break-all {
|
.break-all {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
@@ -73,6 +77,10 @@ $subheader-height: 48px;
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-action-sheet {
|
||||||
|
--button-color: var(--ion-color-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
ion-toast {
|
ion-toast {
|
||||||
--background: var(--ion-color-light);
|
--background: var(--ion-color-light);
|
||||||
--button-color: var(--ion-color-dark);
|
--button-color: var(--ion-color-dark);
|
||||||
|
|||||||
Reference in New Issue
Block a user