diff --git a/ui/src/app/components/form-object/form-object.component.html b/ui/src/app/components/form-object/form-object.component.html
index 4c55d7d3e..61f08e1c0 100644
--- a/ui/src/app/components/form-object/form-object.component.html
+++ b/ui/src/app/components/form-object/form-object.component.html
@@ -5,7 +5,14 @@
{{ unionSpec.tag.name }}
{{ unionSpec.tag.name }}
-
+
{{ unionSpec.tag['variant-names'][option] }}
@@ -23,17 +30,21 @@
isEdited: entry.value.dirty
}">
-
-
-
-
+
+
+
+
+
-
-
-
-
- {{ spec.units }}
+ {{ spec.units }}
@@ -43,7 +54,13 @@
{{ spec.name }}
-
+
{{ spec['value-names'][option] }}
@@ -146,7 +163,13 @@
-
+
+
diff --git a/ui/src/app/components/form-object/form-object.component.ts b/ui/src/app/components/form-object/form-object.component.ts
index 0d4e233dd..e37409595 100644
--- a/ui/src/app/components/form-object/form-object.component.ts
+++ b/ui/src/app/components/form-object/form-object.component.ts
@@ -1,7 +1,7 @@
import { Component, Input, Output, SimpleChange, EventEmitter } from '@angular/core'
-import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms'
+import { AbstractControl, AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms'
import { AlertButton, AlertController, IonicSafeString, ModalController } from '@ionic/angular'
-import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types'
+import { ConfigSpec, ListValueSpecOf, ListValueSpecString, ValueSpec, ValueSpecBoolean, ValueSpecEnum, ValueSpecList, ValueSpecListOf, ValueSpecNumber, ValueSpecString, ValueSpecUnion } from 'src/app/pkg-config/config-types'
import { FormService } from 'src/app/services/form.service'
import { Range } from 'src/app/pkg-config/config-utilities'
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
@@ -109,10 +109,8 @@ export class FormObjectComponent {
if (text) return new IonicSafeString(`${text}`)
}
- handleInputChange (spec: ValueSpec) {
- if (['string', 'number'].includes(spec.type)) {
- this.onInputChange.emit()
- }
+ handleInputChange () {
+ this.onInputChange.emit()
}
handleBooleanChange (key: string, spec: ValueSpecBoolean) {
diff --git a/ui/src/app/modals/app-config/app-config.page.ts b/ui/src/app/modals/app-config/app-config.page.ts
index 550143de9..a0e7f45ff 100644
--- a/ui/src/app/modals/app-config/app-config.page.ts
+++ b/ui/src/app/modals/app-config/app-config.page.ts
@@ -4,12 +4,12 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { isEmptyObject, isObject, Recommendation } from 'src/app/util/misc.util'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
-import { ConfigSpec } from 'src/app/pkg-config/config-types'
+import { ConfigSpec, ListValueSpecObject, ListValueSpecUnion } from 'src/app/pkg-config/config-types'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
-import { FormGroup } from '@angular/forms'
-import { FormService } from 'src/app/services/form.service'
+import { FormArray, FormGroup } from '@angular/forms'
+import { convertToNumberRecursive, FormService } from 'src/app/services/form.service'
@Component({
selector: 'app-config',
@@ -71,37 +71,6 @@ export class AppConfigPage {
this.content.scrollToPoint(undefined, 1)
}
- setConfig (spec: ConfigSpec, config: object, depConfig?: object) {
- this.configSpec = spec
- this.current = config
- this.configForm = this.formService.createForm(spec, { ...config, ...depConfig })
- this.configForm.markAllAsTouched()
-
- if (depConfig) {
- this.alterConfigRecursive(this.configForm, depConfig)
- }
- }
-
- alterConfigRecursive (group: FormGroup, config: object) {
- Object.keys(config).forEach(key => {
- const next = group.get(key)
- if (!next) throw new Error('Dependency config not compatible with service version. Please contact support')
- const newVal = config[key]
- // check if val is an object
- if (isObject(newVal)) {
- this.alterConfigRecursive(next as FormGroup, newVal)
- } else {
- let val1 = group.get(key).value
- let val2 = config[key]
- if (Array.isArray(newVal)) {
- val1 = JSON.stringify(val1)
- val2 = JSON.stringify(val2)
- }
- if (val1 != val2) next.markAsDirty()
- }
- })
- }
-
resetDefaults () {
this.configForm = this.formService.createForm(this.configSpec)
this.alterConfigRecursive(this.configForm, this.current)
@@ -120,6 +89,10 @@ export class AppConfigPage {
}
async save () {
+ convertToNumberRecursive(this.configSpec, this.configForm)
+
+ console.log('SAVING', this.configForm.value)
+
if (this.configForm.invalid) {
document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
return
@@ -132,10 +105,11 @@ export class AppConfigPage {
})
await loader.present()
- const config = this.configForm.value
+ this.saving = true
try {
- this.saving = true
+ const config = this.configForm.value
+
const breakages = await this.embassyApi.drySetPackageConfig({
id: this.pkg.manifest.id,
config,
@@ -165,6 +139,37 @@ export class AppConfigPage {
}
}
+ private setConfig (spec: ConfigSpec, config: object, depConfig?: object) {
+ this.configSpec = spec
+ this.current = config
+ this.configForm = this.formService.createForm(spec, { ...config, ...depConfig })
+ this.configForm.markAllAsTouched()
+
+ if (depConfig) {
+ this.alterConfigRecursive(this.configForm, depConfig)
+ }
+ }
+
+ private alterConfigRecursive (group: FormGroup, config: object) {
+ Object.keys(config).forEach(key => {
+ const next = group.get(key)
+ if (!next) throw new Error('Dependency config not compatible with service version. Please contact support')
+ const newVal = config[key]
+ // check if val is an object
+ if (isObject(newVal)) {
+ this.alterConfigRecursive(next as FormGroup, newVal)
+ } else {
+ let val1 = group.get(key).value
+ let val2 = config[key]
+ if (Array.isArray(newVal)) {
+ val1 = JSON.stringify(val1)
+ val2 = JSON.stringify(val2)
+ }
+ if (val1 != val2) next.markAsDirty()
+ }
+ })
+ }
+
private async presentAlertUnsaved () {
const alert = await this.alertCtrl.create({
header: 'Unsaved Changes',
diff --git a/ui/src/app/modals/generic-form/generic-form.page.ts b/ui/src/app/modals/generic-form/generic-form.page.ts
index 001e534a9..c73b9c649 100644
--- a/ui/src/app/modals/generic-form/generic-form.page.ts
+++ b/ui/src/app/modals/generic-form/generic-form.page.ts
@@ -1,7 +1,7 @@
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 { convertToNumberRecursive, FormService } from 'src/app/services/form.service'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
export interface ActionButton {
@@ -40,6 +40,8 @@ export class GenericFormPage {
}
async handleClick (handler: ActionButton['handler']): Promise {
+ convertToNumberRecursive(this.spec, this.formGroup)
+
if (this.formGroup.invalid) {
this.formGroup.markAllAsTouched()
document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
diff --git a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html
index 346f7cb37..f96831c89 100644
--- a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html
+++ b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html
@@ -48,7 +48,7 @@
LAN Address
- Service does not use a LAN Address
+ N/A
\ No newline at end of file
diff --git a/ui/src/app/pkg-config/config-types.ts b/ui/src/app/pkg-config/config-types.ts
index 3c0c0403d..9158ece8b 100644
--- a/ui/src/app/pkg-config/config-types.ts
+++ b/ui/src/app/pkg-config/config-types.ts
@@ -19,8 +19,6 @@ export interface ValueSpecString extends ListValueSpecString, WithStandalone {
type: 'string'
default?: DefaultString
nullable: boolean
- masked: boolean
- copyable: boolean
}
export interface ValueSpecNumber extends ListValueSpecNumber, WithStandalone {
@@ -88,9 +86,10 @@ export function isValueSpecListOf (t: ValueSpecList
}
export interface ListValueSpecString {
- // @TODO add masked?
pattern?: string
'pattern-description'?: string
+ masked: boolean
+ copyable: boolean
}
export interface ListValueSpecNumber {
diff --git a/ui/src/app/services/api/api.fixures.ts b/ui/src/app/services/api/api.fixures.ts
index 3138588a9..84194e2ed 100644
--- a/ui/src/app/services/api/api.fixures.ts
+++ b/ui/src/app/services/api/api.fixures.ts
@@ -1135,7 +1135,7 @@ export module Mock {
'range': '(-100,100]',
'units': 'BTC',
},
- 'secondaryNumbers': {
+ 'unluckyNumbers': {
'name': 'Unlucky Numbers',
'type': 'list',
'subtype': 'number',
@@ -1345,6 +1345,8 @@ export module Mock {
'192.168.1.1',
],
'spec': {
+ 'masked': false,
+ 'copyable': false,
'pattern': '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))',
'pattern-description': 'must be a valid ipv4, ipv6, or domain name',
},
@@ -1356,7 +1358,10 @@ export module Mock {
'description': 'api keys that are authorized to access your Bitcoin node.',
'range': '[0,*)',
'default': [],
- 'spec': { },
+ 'spec': {
+ 'masked': false,
+ 'copyable': false,
+ },
},
},
// actual config
diff --git a/ui/src/app/services/form.service.ts b/ui/src/app/services/form.service.ts
index c2d0abf55..d50cc5e27 100644
--- a/ui/src/app/services/form.service.ts
+++ b/ui/src/app/services/form.service.ts
@@ -17,14 +17,6 @@ export class FormService {
return this.getFormGroup(config, [], current)
}
- getListItemValidators (spec: ValueSpecList) {
- if (isValueSpecListOf(spec, 'string')) {
- return this.stringValidators(spec.spec)
- } else if (isValueSpecListOf(spec, 'number')) {
- return this.numberValidators(spec.spec)
- }
- }
-
getUnionObject (spec: ValueSpecUnion | ListValueSpecUnion, selection: string, current?: { [key: string]: any }): FormGroup {
const { variants, tag } = spec
const { name, description, warning } = isFullUnion(spec) ? spec : { ...spec.tag, warning: undefined }
@@ -41,15 +33,6 @@ export class FormService {
return this.getFormGroup({ [spec.tag.id]: enumSpec, ...spec.variants[selection] }, [], current)
}
- getFormGroup (config: ConfigSpec, validators: ValidatorFn[] = [], current: { [key: string]: any } = { }): FormGroup {
- let group = { }
- Object.entries(config).map(([key, spec]) => {
- if (spec.type === 'pointer') return
- group[key] = this.getFormEntry(key, spec, current ? current[key] : { })
- })
- return this.formBuilder.group(group, { validators } )
- }
-
getListItem (spec: ValueSpecList, entry: any) {
const listItemValidators = this.getListItemValidators(spec)
if (isValueSpecListOf(spec, 'string')) {
@@ -65,6 +48,23 @@ export class FormService {
}
}
+ private getListItemValidators (spec: ValueSpecList) {
+ if (isValueSpecListOf(spec, 'string')) {
+ return this.stringValidators(spec.spec)
+ } else if (isValueSpecListOf(spec, 'number')) {
+ return this.numberValidators(spec.spec)
+ }
+ }
+
+ private getFormGroup (config: ConfigSpec, validators: ValidatorFn[] = [], current: { [key: string]: any } = { }): FormGroup {
+ let group = { }
+ Object.entries(config).map(([key, spec]) => {
+ if (spec.type === 'pointer') return
+ group[key] = this.getFormEntry(key, spec, current ? current[key] : { })
+ })
+ return this.formBuilder.group(group, { validators } )
+ }
+
private getFormEntry (key: string, spec: ValueSpec, currentValue: any): FormGroup | FormArray | FormControl {
let validators: ValidatorFn[]
let value: any
@@ -405,3 +405,36 @@ const sampleUniqueBy: UniqueBy = {
] },
],
}
+
+export function convertToNumberRecursive (configSpec: ConfigSpec, group: FormGroup) {
+ Object.entries(configSpec).forEach(([key, valueSpec]) => {
+ if (valueSpec.type === 'number') {
+ const control = group.get(key)
+ control.setValue(Number(control.value))
+ } else if (valueSpec.type === 'object') {
+ convertToNumberRecursive(valueSpec.spec, group.get(key) as FormGroup)
+ } else if (valueSpec.type === 'union') {
+ const control = group.get(key) as FormGroup
+ const spec = valueSpec.variants[control.controls[valueSpec.tag.id].value]
+ convertToNumberRecursive(spec, control)
+ } else if (valueSpec.type === 'list') {
+ const formArr = group.get(key) as FormArray
+ if (valueSpec.subtype === 'number') {
+ formArr.controls.forEach(control => {
+ control.setValue(Number(control.value))
+ })
+ } else if (valueSpec.subtype === 'object') {
+ formArr.controls.forEach((formGroup: FormGroup) => {
+ const objectSpec = valueSpec.spec as ListValueSpecObject
+ convertToNumberRecursive(objectSpec.spec, formGroup)
+ })
+ } else if (valueSpec.subtype === 'union') {
+ formArr.controls.forEach((formGroup: FormGroup) => {
+ const unionSpec = valueSpec.spec as ListValueSpecUnion
+ const spec = unionSpec.variants[formGroup.controls[unionSpec.tag.id].value]
+ convertToNumberRecursive(spec, formGroup)
+ })
+ }
+ }
+ })
+}