Frontend validation for config edits
Validates and enforces input constraints for edits to existing config sections for encryption configs, contexts, and management configs. This is first of several patches dealing with validation. Remaining work: validate edits to manifests, refactor New config component form to include validation for all new config items. Change-Id: If1b8dc1e0e1263f86f7bf0b7455ca3640ae6a127
This commit is contained in:
@@ -4,22 +4,32 @@
|
||||
<h4>{{context.name}}</h4>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
ContextKubeconf:
|
||||
<!-- Modifying the 'name' field makes Set treat it as a new context, which
|
||||
is why it's omitted from this form. New configs are handled with the 'New' button-->
|
||||
<mat-form-field appearance="fill">
|
||||
<input [formControl]="contextKubeconf" matInput>
|
||||
</mat-form-field>
|
||||
Manifest:
|
||||
<mat-form-field appearance="fill">
|
||||
<input [formControl]="manifest" matInput>
|
||||
</mat-form-field>
|
||||
EncryptionConfig:
|
||||
<mat-form-field appearance="fill">
|
||||
<input [formControl]="encryptionConfig" matInput>
|
||||
</mat-form-field>
|
||||
ManagementConfiguration:
|
||||
<mat-form-field appearance="fill">
|
||||
<input [formControl]="managementConfiguration" matInput>
|
||||
</mat-form-field>
|
||||
<mat-label>Kubeconfig Context</mat-label>
|
||||
<input [formControl]="contextKubeconf" matInput readonly>
|
||||
</mat-form-field><br />
|
||||
<mat-form-field>
|
||||
<mat-label>Manifest</mat-label>
|
||||
<mat-select [formControl]="manifest" [(value)]="context.manifest">
|
||||
<mat-option *ngFor="let m of configs.manifests" [value]="m">{{m}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field><br />
|
||||
<mat-form-field>
|
||||
<mat-label>Encryption Config</mat-label>
|
||||
<mat-select [formControl]="encryptionConfig" [(value)]="context.encryptionConfig">
|
||||
<!-- Encryption config isn't required, so allow a null option -->
|
||||
<mat-option [value]="null">None</mat-option>
|
||||
<mat-option *ngFor="let e of configs.encryption" [value]="e">{{e}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field><br />
|
||||
<mat-form-field>
|
||||
<mat-label>Management Config</mat-label>
|
||||
<mat-select [formControl]="managementConfiguration" [(value)]="context.managementConfiguration">
|
||||
<mat-option *ngFor="let m of configs.management" [value]="m">{{m}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field><br />
|
||||
<mat-action-row>
|
||||
<div class="edit-btn-container">
|
||||
<button mat-icon-button (click)="toggleLock()">
|
||||
|
@@ -12,6 +12,7 @@
|
||||
# limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -19,6 +20,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { Context } from '../config.models';
|
||||
@@ -42,6 +44,8 @@ describe('ConfigContextComponent', () => {
|
||||
ReactiveFormsModule,
|
||||
ToastrModule.forRoot(),
|
||||
MatExpansionModule,
|
||||
MatSelectModule,
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
@@ -52,6 +56,11 @@ describe('ConfigContextComponent', () => {
|
||||
component = fixture.componentInstance;
|
||||
|
||||
component.context = new Context();
|
||||
component.configs = {
|
||||
manifests: ['default'],
|
||||
encryption: ['default'],
|
||||
management: ['default']
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -25,6 +25,12 @@ import { WsMessage, WsConstants } from 'src/services/ws/ws.models';
|
||||
})
|
||||
export class ConfigContextComponent implements OnInit {
|
||||
@Input() context: Context;
|
||||
@Input() configs: {
|
||||
manifests: string[],
|
||||
encryption: string[],
|
||||
management: string[]
|
||||
};
|
||||
|
||||
type = WsConstants.CTL;
|
||||
component = WsConstants.CONFIG;
|
||||
|
||||
|
@@ -4,26 +4,28 @@
|
||||
<h4>{{config.name}}</h4>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div *ngIf="config.hasOwnProperty('encryptionKeyPath')">
|
||||
EncryptionKeyPath:
|
||||
<form [formGroup]="group">
|
||||
<div *ngIf="config.hasOwnProperty('encryptionKeyPath')">
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="encryptionKeyPath">
|
||||
<mat-label>EncryptionKeyPath</mat-label>
|
||||
<input matInput formControlName="encryptionKeyPath">
|
||||
</mat-form-field>
|
||||
DecryptionKeyPath:
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="decryptionKeyPath">
|
||||
<mat-label>DecryptionKeyPath</mat-label>
|
||||
<input matInput formControlName="decryptionKeyPath">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="config.hasOwnProperty('keySecretName')">
|
||||
KeySecretName:
|
||||
</div>
|
||||
<div *ngIf="config.hasOwnProperty('keySecretName')">
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="keySecretName">
|
||||
<mat-label>KeySecretName</mat-label>
|
||||
<input matInput formControlName="keySecretName">
|
||||
</mat-form-field>
|
||||
KeySecretNamespace:
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="keySecretNamespace">
|
||||
<mat-label>KeySecretNamespace</mat-label>
|
||||
<input matInput formControlName="keySecretNamespace">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<mat-action-row>
|
||||
<div class="edit-btn-container">
|
||||
<button mat-icon-button (click)="toggleLock()">
|
||||
@@ -33,7 +35,7 @@
|
||||
</ng-template>
|
||||
Edit</button>
|
||||
</div>
|
||||
<button mat-raised-button class="set-button" [disabled]="locked" (click)="setEncryptionConfig()" color="primary">Set</button>
|
||||
<button mat-raised-button class="set-button" [disabled]="locked || !group.valid" (click)="setEncryptionConfig()" color="primary">Set</button>
|
||||
</mat-action-row>
|
||||
</mat-expansion-panel>
|
||||
<br />
|
||||
|
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { FormControl, FormGroup, Validators, AbstractControl } from '@angular/forms';
|
||||
import { EncryptionConfig, EncryptionConfigOptions } from '../config.models';
|
||||
import { WsService } from 'src/services/ws/ws.service';
|
||||
import { WsMessage, WsConstants } from 'src/services/ws/ws.models';
|
||||
@@ -29,26 +29,35 @@ export class ConfigEncryptionComponent implements OnInit {
|
||||
component = WsConstants.CONFIG;
|
||||
|
||||
locked = true;
|
||||
name = new FormControl({value: '', disabled: true});
|
||||
encryptionKeyPath = new FormControl({value: '', disabled: true});
|
||||
decryptionKeyPath = new FormControl({value: '', disabled: true});
|
||||
keySecretName = new FormControl({value: '', disabled: true});
|
||||
keySecretNamespace = new FormControl({value: '', disabled: true});
|
||||
group: FormGroup;
|
||||
|
||||
controlsArray = [this.encryptionKeyPath, this.decryptionKeyPath, this.keySecretName, this.keySecretNamespace];
|
||||
configOptions: AbstractControl[] = [];
|
||||
|
||||
constructor(private websocketService: WsService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.name.setValue(this.config.name);
|
||||
this.encryptionKeyPath.setValue(this.config.encryptionKeyPath);
|
||||
this.decryptionKeyPath.setValue(this.config.decryptionKeyPath);
|
||||
this.keySecretName.setValue(this.config.keySecretName);
|
||||
this.keySecretNamespace.setValue(this.config.keySecretNamespace);
|
||||
this.group = new FormGroup({
|
||||
encryptionKeyPath: new FormControl({value: this.config.encryptionKeyPath, disabled: true},
|
||||
Validators.required),
|
||||
decryptionKeyPath: new FormControl({value: this.config.decryptionKeyPath, disabled: true},
|
||||
Validators.required),
|
||||
keySecretName: new FormControl({value: this.config.keySecretName, disabled: true},
|
||||
Validators.required),
|
||||
keySecretNamespace: new FormControl({value: this.config.keySecretNamespace, disabled: true},
|
||||
Validators.required)
|
||||
});
|
||||
|
||||
if (this.config.hasOwnProperty('encryptionKeyPath')) {
|
||||
this.configOptions.push(this.group.controls.encryptionKeyPath);
|
||||
this.configOptions.push(this.group.controls.decryptionKeyPath);
|
||||
} else {
|
||||
this.configOptions.push(this.group.controls.keySecretName);
|
||||
this.configOptions.push(this.group.controls.keySecretNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
toggleLock(): void {
|
||||
for (const control of this.controlsArray) {
|
||||
for (const control of this.configOptions) {
|
||||
if (this.locked) {
|
||||
control.enable();
|
||||
} else {
|
||||
@@ -61,16 +70,16 @@ export class ConfigEncryptionComponent implements OnInit {
|
||||
|
||||
setEncryptionConfig(): void {
|
||||
const opts: EncryptionConfigOptions = {
|
||||
Name: this.name.value,
|
||||
EncryptionKeyPath: this.encryptionKeyPath.value,
|
||||
DecryptionKeyPath: this.decryptionKeyPath.value,
|
||||
KeySecretName: this.keySecretName.value,
|
||||
KeySecretNamespace: this.keySecretNamespace.value,
|
||||
Name: this.config.name,
|
||||
EncryptionKeyPath: this.group.controls.encryptionKeyPath.value,
|
||||
DecryptionKeyPath: this.group.controls.decryptionKeyPath.value,
|
||||
KeySecretName: this.group.controls.keySecretName.value,
|
||||
KeySecretNamespace: this.group.controls.keySecretNamespace.value,
|
||||
};
|
||||
|
||||
const msg = new WsMessage(this.type, this.component, WsConstants.SET_ENCRYPTION_CONFIG);
|
||||
msg.data = JSON.parse(JSON.stringify(opts));
|
||||
msg.name = this.name.value;
|
||||
msg.name = this.config.name;
|
||||
|
||||
this.websocketService.sendMessage(msg);
|
||||
this.toggleLock();
|
||||
|
@@ -4,24 +4,26 @@
|
||||
<h4>{{config.Name}}</h4>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<form [formGroup]="group">
|
||||
<p>
|
||||
<mat-checkbox [formControl]="insecure" labelPosition="before">Insecure: </mat-checkbox>
|
||||
<mat-checkbox formControlName="insecure" labelPosition="after">Insecure</mat-checkbox>
|
||||
</p>
|
||||
SystemActionRetries:
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="systemActionRetries" pattern="[0-9]*">
|
||||
</mat-form-field>
|
||||
SystemRebootDelay:
|
||||
<mat-label>System Action Retries</mat-label>
|
||||
<input matInput formControlName="systemActionRetries" placeholder="Value in seconds, e.g. 30">
|
||||
</mat-form-field><br />
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="systemRebootDelay" pattern="[0-9]*">
|
||||
</mat-form-field>
|
||||
Type:
|
||||
<mat-label>System Reboot Delay</mat-label>
|
||||
<input matInput formControlName="systemRebootDelay" placeholder="Value in seconds, e.g. 30">
|
||||
</mat-form-field><br />
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput [formControl]="type">
|
||||
<mat-label>Type</mat-label>
|
||||
<input matInput formControlName="type">
|
||||
</mat-form-field>
|
||||
<p>
|
||||
<mat-checkbox [formControl]="useproxy" labelPosition="before">UseProxy: </mat-checkbox>
|
||||
<mat-checkbox formControlName="useproxy" labelPosition="after">UseProxy</mat-checkbox>
|
||||
</p>
|
||||
</form>
|
||||
<mat-action-row>
|
||||
<div class="edit-btn-container">
|
||||
<button mat-icon-button (click)="toggleLock()">
|
||||
@@ -31,7 +33,7 @@
|
||||
</ng-template>
|
||||
Edit</button>
|
||||
</div>
|
||||
<button mat-raised-button class="set-button" [disabled]="locked" (click)="setManagementConfig()" color="primary">Set</button>
|
||||
<button mat-raised-button class="set-button" [disabled]="locked || !group.valid" (click)="setManagementConfig()" color="primary">Set</button>
|
||||
</mat-action-row>
|
||||
</mat-expansion-panel>
|
||||
<br />
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ManagementConfig } from '../config.models';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { FormControl, FormGroup, Validators, AbstractControl } from '@angular/forms';
|
||||
import { WsService } from 'src/services/ws/ws.service';
|
||||
import { WsMessage, WsConstants } from 'src/services/ws/ws.models';
|
||||
|
||||
@@ -30,50 +30,43 @@ export class ConfigManagementComponent implements OnInit {
|
||||
|
||||
locked = true;
|
||||
|
||||
name = new FormControl({value: '', disabled: true});
|
||||
insecure = new FormControl({value: false, disabled: true});
|
||||
systemActionRetries = new FormControl({value: '', disabled: true}, Validators.pattern('[0-9]*'));
|
||||
systemRebootDelay = new FormControl({value: '', disabled: true}, Validators.pattern('[0-9]*'));
|
||||
type = new FormControl({value: '', disabled: true});
|
||||
useproxy = new FormControl({value: false, disabled: true});
|
||||
|
||||
controlsArray = [this.name, this.insecure, this.systemRebootDelay, this.systemActionRetries, this.type, this.useproxy];
|
||||
group: FormGroup;
|
||||
|
||||
constructor(private websocketService: WsService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.name.setValue(this.config.Name);
|
||||
this.insecure.setValue(this.config.insecure);
|
||||
this.systemActionRetries.setValue(this.config.systemActionRetries);
|
||||
this.systemRebootDelay.setValue(this.config.systemRebootDelay);
|
||||
this.type.setValue(this.config.type);
|
||||
this.useproxy.setValue(this.config.useproxy);
|
||||
this.group = new FormGroup({
|
||||
name: new FormControl({value: this.config.Name, disabled: true}),
|
||||
insecure: new FormControl({value: this.config.insecure, disabled: true}),
|
||||
systemActionRetries: new FormControl({value: this.config.systemActionRetries, disabled: true},
|
||||
Validators.pattern('^[0-9]*$')),
|
||||
systemRebootDelay: new FormControl({value: this.config.systemRebootDelay, disabled: true},
|
||||
Validators.pattern('^[0-9]*$')),
|
||||
type: new FormControl({value: this.config.type, disabled: true}),
|
||||
useproxy: new FormControl({value: this.config.useproxy, disabled: true})
|
||||
});
|
||||
}
|
||||
|
||||
toggleLock(): void {
|
||||
for (const control of this.controlsArray) {
|
||||
if (this.locked) {
|
||||
control.enable();
|
||||
} else {
|
||||
control.disable();
|
||||
}
|
||||
if (this.group.disabled) {
|
||||
this.group.enable();
|
||||
} else {
|
||||
this.group.disable();
|
||||
}
|
||||
|
||||
this.locked = !this.locked;
|
||||
}
|
||||
|
||||
setManagementConfig(): void {
|
||||
const msg = new WsMessage(this.msgType, this.component, WsConstants.SET_MANAGEMENT_CONFIG);
|
||||
msg.name = this.name.value;
|
||||
msg.name = this.group.controls.name.value;
|
||||
|
||||
const cfg: ManagementConfig = {
|
||||
Name: this.name.value,
|
||||
insecure: this.insecure.value,
|
||||
// TODO(mfuller): need to validate these are numerical values in the form
|
||||
systemActionRetries: +this.systemActionRetries.value,
|
||||
systemRebootDelay: +this.systemRebootDelay.value,
|
||||
type: this.type.value,
|
||||
useproxy: this.useproxy.value
|
||||
Name: this.group.controls.name.value,
|
||||
insecure: this.group.controls.insecure.value,
|
||||
systemActionRetries: +this.group.controls.systemActionRetries.value,
|
||||
systemRebootDelay: +this.group.controls.systemRebootDelay.value,
|
||||
type: this.group.controls.type.value,
|
||||
useproxy: this.group.controls.useproxy.value
|
||||
};
|
||||
|
||||
msg.data = JSON.parse(JSON.stringify(cfg));
|
||||
|
@@ -12,7 +12,7 @@
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-accordion *ngFor="let context of contexts">
|
||||
<app-config-context [context]="context"></app-config-context>
|
||||
<app-config-context [context]="context" [configs]="configs"></app-config-context>
|
||||
</mat-accordion>
|
||||
<button mat-icon-button (click)="newConfig('context')">
|
||||
<mat-icon class="grey-icon" svgIcon="add"></mat-icon>New Context
|
||||
|
@@ -39,6 +39,8 @@ export class ConfigComponent implements WsReceiver, OnInit {
|
||||
managementConfigs: ManagementConfig[] = [];
|
||||
encryptionConfigs: EncryptionConfig[] = [];
|
||||
|
||||
configs = {};
|
||||
|
||||
constructor(private websocketService: WsService,
|
||||
public dialog: MatDialog) {
|
||||
this.websocketService.registerFunctions(this);
|
||||
@@ -71,13 +73,13 @@ export class ConfigComponent implements WsReceiver, OnInit {
|
||||
Object.assign(this.contexts, message.data);
|
||||
break;
|
||||
case WsConstants.GET_MANIFESTS:
|
||||
Object.assign(this.manifests, message.data);
|
||||
this.handleGetManifests(message);
|
||||
break;
|
||||
case WsConstants.GET_ENCRYPTION_CONFIGS:
|
||||
Object.assign(this.encryptionConfigs, message.data);
|
||||
this.handleGetEncryptionConfigs(message);
|
||||
break;
|
||||
case WsConstants.GET_MANAGEMENT_CONFIGS:
|
||||
Object.assign(this.managementConfigs, message.data);
|
||||
this.handleGetManagementConfigs(message);
|
||||
break;
|
||||
case WsConstants.USE_CONTEXT:
|
||||
this.getCurrentContext();
|
||||
@@ -105,6 +107,33 @@ export class ConfigComponent implements WsReceiver, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
handleGetManifests(message: WsMessage): void {
|
||||
Object.assign(this.manifests, message.data);
|
||||
const manifests = 'manifests';
|
||||
this.configs[manifests] = [];
|
||||
for (const m of this.manifests) {
|
||||
this.configs[manifests].push(m.name);
|
||||
}
|
||||
}
|
||||
|
||||
handleGetEncryptionConfigs(message: WsMessage): void {
|
||||
Object.assign(this.encryptionConfigs, message.data);
|
||||
const encryption = 'encryption';
|
||||
this.configs[encryption] = [];
|
||||
for (const e of this.encryptionConfigs) {
|
||||
this.configs[encryption].push(e.name);
|
||||
}
|
||||
}
|
||||
|
||||
handleGetManagementConfigs(message: WsMessage): void {
|
||||
Object.assign(this.managementConfigs, message.data);
|
||||
const management = 'management';
|
||||
this.configs[management] = [];
|
||||
for (const m of this.managementConfigs) {
|
||||
this.configs[management].push(m.Name);
|
||||
}
|
||||
}
|
||||
|
||||
getConfig(): void {
|
||||
this.getAirshipConfigPath();
|
||||
this.getCurrentContext();
|
||||
|
@@ -29,6 +29,7 @@ import { ConfigInitComponent } from './config-init/config-init.component';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { ConfigNewComponent } from './config-new/config-new.component';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -41,7 +42,8 @@ import { ConfigNewComponent } from './config-new/config-new.component';
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatCheckboxModule,
|
||||
MatExpansionModule
|
||||
MatExpansionModule,
|
||||
MatSelectModule
|
||||
],
|
||||
declarations: [
|
||||
ConfigComponent,
|
||||
|
Reference in New Issue
Block a user