mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
convert strigs to numbers for numberic form controls
This commit is contained in:
committed by
Aiden McClelland
parent
ef1cd70fbc
commit
2dc0f97d90
@@ -5,7 +5,14 @@
|
||||
<p class="input-label">{{ unionSpec.tag.name }}</p>
|
||||
<ion-item>
|
||||
<ion-label>{{ unionSpec.tag.name }}</ion-label>
|
||||
<ion-select [interfaceOptions]="{ message: getWarningText(unionSpec.warning) }" slot="end" placeholder="Select" [formControlName]="unionSpec.tag.id" [selectedText]="unionSpec.tag['variant-names'][entry.value.value]" (ionChange)="updateUnion($event)">
|
||||
<ion-select
|
||||
[interfaceOptions]="{ message: getWarningText(unionSpec.warning) }"
|
||||
slot="end"
|
||||
placeholder="Select"
|
||||
[formControlName]="unionSpec.tag.id"
|
||||
[selectedText]="unionSpec.tag['variant-names'][entry.value.value]"
|
||||
(ionChange)="updateUnion($event)"
|
||||
>
|
||||
<ion-select-option *ngFor="let option of Object.keys(unionSpec.variants)" [value]="option">
|
||||
{{ unionSpec.tag['variant-names'][option] }}
|
||||
</ion-select-option>
|
||||
@@ -23,17 +30,21 @@
|
||||
isEdited: entry.value.dirty
|
||||
}"></form-label>
|
||||
</h4>
|
||||
<!-- string -->
|
||||
<ion-item color="dark" *ngIf="spec.type === 'string'">
|
||||
<ion-input [type]="spec.masked && !unmasked[entry.key] ? 'password' : 'text'" [placeholder]="'Enter ' + spec.name" [formControlName]="entry.key" (ionFocus)="presentAlertChangeWarning(entry.key, spec)" (ionChange)="handleInputChange(spec)"></ion-input>
|
||||
<ion-button *ngIf="spec.masked" fill="clear" color="light" (click)="unmasked[entry.key] = !unmasked[entry.key]">
|
||||
<!-- string or number -->
|
||||
<ion-item color="dark" *ngIf="spec.type === 'string' || spec.type === 'number'">
|
||||
<ion-input
|
||||
[type]="spec.type === 'string' && spec.masked && !unmasked[entry.key] ? 'password' : 'text'"
|
||||
[inputmode]="spec.type === 'number' ? 'tel' : 'text'"
|
||||
[placeholder]="'Enter ' + spec.name"
|
||||
[formControlName]="entry.key"
|
||||
(ionFocus)="presentAlertChangeWarning(entry.key, spec)"
|
||||
(ionChange)="handleInputChange()"
|
||||
>
|
||||
</ion-input>
|
||||
<ion-button *ngIf="spec.type === 'string' && spec.masked" slot="end" fill="clear" color="light" (click)="unmasked[entry.key] = !unmasked[entry.key]">
|
||||
<ion-icon slot="icon-only" [name]="unmasked[entry.key] ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<!-- number -->
|
||||
<ion-item color="dark" *ngIf="spec.type === 'number'">
|
||||
<ion-input type="tel" [placeholder]="'Enter ' + spec.name" [formControlName]="entry.key" (ionFocus)="presentAlertChangeWarning(entry.key, spec)" (ionChange)="handleInputChange(spec)"></ion-input>
|
||||
<ion-note *ngIf="spec.units" slot="end" color="light" style="font-size: medium;">{{ spec.units }}</ion-note>
|
||||
<ion-note *ngIf="spec.type === 'number' && spec.units" slot="end" color="light" style="font-size: medium;">{{ spec.units }}</ion-note>
|
||||
</ion-item>
|
||||
<!-- boolean -->
|
||||
<ion-item *ngIf="spec.type === 'boolean'">
|
||||
@@ -43,7 +54,13 @@
|
||||
<!-- enum -->
|
||||
<ion-item *ngIf="spec.type === 'enum'">
|
||||
<ion-label>{{ spec.name }}</ion-label>
|
||||
<ion-select [interfaceOptions]="{ message: getWarningText(spec.warning) }" slot="end" placeholder="Select" [formControlName]="entry.key" [selectedText]="spec['value-names'][formGroup.get(entry.key).value]" (ionChange)="handleInputChange(spec)">
|
||||
<ion-select
|
||||
[interfaceOptions]="{ message: getWarningText(spec.warning) }"
|
||||
slot="end"
|
||||
placeholder="Select"
|
||||
[formControlName]="entry.key"
|
||||
[selectedText]="spec['value-names'][formGroup.get(entry.key).value]"
|
||||
>
|
||||
<ion-select-option *ngFor="let option of spec.values" [value]="option">
|
||||
{{ spec['value-names'][option] }}
|
||||
</ion-select-option>
|
||||
@@ -146,7 +163,13 @@
|
||||
<!-- string or number -->
|
||||
<ion-item-group *ngIf="spec.subtype === 'string' || spec.subtype === 'number'">
|
||||
<ion-item color="dark">
|
||||
<ion-input type="spec.spec.masked ? 'password' : 'text'" [placeholder]="'Enter ' + spec.name" [formControlName]="i"></ion-input>
|
||||
<ion-input
|
||||
[type]="$any(spec.spec).masked ? 'password' : 'text'"
|
||||
[inputmode]="spec.subtype === 'number' ? 'tel' : 'text'"
|
||||
[placeholder]="'Enter ' + spec.name"
|
||||
[formControlName]="i"
|
||||
>
|
||||
</ion-input>
|
||||
<ion-button slot="end" color="danger" (click)="presentAlertDelete(entry.key, i)">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
@@ -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(`<ion-text color="warning">${text}</ion-text>`)
|
||||
}
|
||||
|
||||
handleInputChange (spec: ValueSpec) {
|
||||
if (['string', 'number'].includes(spec.type)) {
|
||||
this.onInputChange.emit()
|
||||
}
|
||||
handleInputChange () {
|
||||
this.onInputChange.emit()
|
||||
}
|
||||
|
||||
handleBooleanChange (key: string, spec: ValueSpecBoolean) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<void> {
|
||||
convertToNumberRecursive(this.spec, this.formGroup)
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
this.formGroup.markAllAsTouched()
|
||||
document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<ion-item *ngIf="!interface.addresses['lan-address']">
|
||||
<ion-label>
|
||||
<h2>LAN Address</h2>
|
||||
<p>Service does not use a LAN Address</p>
|
||||
<p>N/A</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
@@ -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<S extends ListValueSpecType> (t: ValueSpecList
|
||||
}
|
||||
|
||||
export interface ListValueSpecString {
|
||||
// @TODO add masked?
|
||||
pattern?: string
|
||||
'pattern-description'?: string
|
||||
masked: boolean
|
||||
copyable: boolean
|
||||
}
|
||||
|
||||
export interface ListValueSpecNumber {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user