Refactor config-new component and add form validation

Refactors the config-new component form to add input constraints
and validation.

Change-Id: I930e6bf50def29d894c8ecc9cd5db9e3ed13d0cf
This commit is contained in:
Matthew Fuller 2020-12-10 20:57:46 +00:00
parent 61b9b19be6
commit d0ccf65d33
7 changed files with 261 additions and 66 deletions

View File

@ -62,8 +62,10 @@ export class ConfigManifestComponent implements OnInit {
});
const checkout = this.getCheckoutRef(repo);
repoGroup.controls.checkoutLabel.setValue(checkout[0]);
repoGroup.controls.checkoutReference.setValue(checkout[1]);
if (checkout !== null) {
repoGroup.controls.checkoutLabel.setValue(checkout[0]);
repoGroup.controls.checkoutReference.setValue(checkout[1]);
}
repoArray.push(repoGroup);
this.selectArray.push(name);
}

View File

@ -19,3 +19,7 @@
.text-input {
width: 80%;
}
mat-form-field {
width: 80%;
}

View File

@ -1,18 +1,171 @@
<h1 mat-dialog-title>New {{data.formName}} configuration</h1>
<div mat-dialog-content class="form-content">
<form [formGroup]="group">
<div *ngFor="let key of keys">
<mat-form-field *ngIf="!isBool(dataObj[key])" appearance="fill">
<mat-label>{{key}}</mat-label>
<input class="text-input" formControlName="{{key}}" matInput>
<div mat-dialog-content class="form-content" [ngSwitch]="data.formName">
<div *ngSwitchCase="'management'" [formGroup]="group">
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input matInput formControlName="Name">
<mat-error *ngIf="group.controls.Name.hasError('required')">
Name is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Type</mat-label>
<input matInput formControlName="type">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>System Action Retries</mat-label>
<input matInput formControlName="systemActionRetries">
<mat-error *ngIf="group.controls.systemActionRetries.hasError('pattern')">
Value must be a number
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>System Reboot Delay</mat-label>
<input matInput formControlName="systemRebootDelay">
<mat-error *ngIf="group.controls.systemRebootDelay.hasError('pattern')">
Value must be a number
</mat-error>
</mat-form-field>
<p>
<mat-checkbox formControlName="useproxy" labelPosition="after">Use Proxy</mat-checkbox>
</p>
<p>
<mat-checkbox formControlName="insecure" labelPosition="after">Insecure</mat-checkbox>
</p>
</div>
<div *ngSwitchCase="'context'" [formGroup]="group">
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input formControlName="Name" matInput>
<mat-error *ngIf="group.controls.Name.hasError('required')">
Name is required
</mat-error>
</mat-form-field><br />
<mat-form-field>
<mat-label>Manifest</mat-label>
<mat-select formControlName="Manifest">
<mat-option *ngFor="let m of data.configs['manifests']" [value]="m">{{m}}</mat-option>
</mat-select>
</mat-form-field><br />
<mat-form-field>
<mat-label>Encryption Config</mat-label>
<mat-select formControlName="EncryptionConfig">
<!-- Encryption config isn't required, so allow a null option -->
<mat-option [value]="null">None</mat-option>
<mat-option *ngFor="let e of data.configs['encryption']" [value]="e">{{e}}</mat-option>
</mat-select>
</mat-form-field><br />
<mat-form-field>
<mat-label>Management Config</mat-label>
<mat-select formControlName="ManagementConfiguration">
<mat-option *ngFor="let m of data.configs['management']" [value]="m">{{m}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngSwitchCase="'encryption'" [formGroup]="group">
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input matInput formControlName="Name">
<mat-error *ngIf="group.controls.Name.hasError('required')">
Name is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Config Type</mat-label>
<mat-select [(value)]="encryptionType" (selectionChange)="onEncryptionChange($event)">
<mat-option value="encryption">Encrypt / Decrypt Key</mat-option>
<mat-option value="secret">Secret</mat-option>
</mat-select>
</mat-form-field>
<div *ngIf="encryptionType === 'encryption'">
<mat-form-field appearance="fill">
<mat-label>EncryptionKeyPath</mat-label>
<input matInput formControlName="EncryptionKeyPath">
<mat-error *ngIf="group.controls.EncryptionKeyPath.hasError('required')">
EncryptionKeyPath is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>DecryptionKeyPath</mat-label>
<input matInput formControlName="DecryptionKeyPath">
<mat-error *ngIf="group.controls.DecryptionKeyPath.hasError('required')">
DecryptionKeyPath is required
</mat-error>
</mat-form-field>
<p *ngIf="isBool(dataObj[key])">
<mat-checkbox formControlName="{{key}}" labelPosition="before">{{key}} </mat-checkbox>
</p>
</div>
</form>
<div *ngIf="encryptionType === 'secret'">
<mat-form-field appearance="fill">
<mat-label>KeySecretName</mat-label>
<input matInput formControlName="KeySecretName">
<mat-error *ngIf="group.controls.KeySecretName.hasError('required')">
KeySecretName is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>KeySecretNamespace</mat-label>
<input matInput formControlName="KeySecretNamespace">
<mat-error *ngIf="group.controls.KeySecretNamespace.hasError('required')">
KeySecretNamespace is required
</mat-error>
</mat-form-field>
</div>
</div>
<div *ngSwitchCase="'manifest'" [formGroup]="group">
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input matInput formControlName="Name">
<mat-error *ngIf="group.controls.Name.hasError('required')">
Name is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Target Path</mat-label>
<input matInput formControlName="TargetPath">
<mat-error *ngIf="group.controls.TargetPath.hasError('required')">
TargetPath is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Metadata Path</mat-label>
<input matInput formControlName="MetadataPath">
<mat-error *ngIf="group.controls.MetadataPath.hasError('required')">
MetadataPath is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Repository Name</mat-label>
<input matInput formControlName="RepoName" readonly>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>URL</mat-label>
<input matInput formControlName="URL">
<mat-error *ngIf="group.controls.URL.hasError('required')">
URL is required
</mat-error>
</mat-form-field>
<mat-label>
<mat-select [(value)]="checkoutType">
<mat-option *ngFor="let type of checkoutTypes" [value]="type">{{type}}</mat-option>
</mat-select>
</mat-label>
<mat-form-field appearance="fill" *ngIf="checkoutType === 'Branch'">
<input matInput formControlName="Branch">
</mat-form-field>
<mat-form-field appearance="fill" *ngIf="checkoutType === 'Tag'">
<input matInput formControlName="Tag">
</mat-form-field>
<mat-form-field appearance="fill" *ngIf="checkoutType === 'CommitHash'">
<input matInput formControlName="CommitHash">
</mat-form-field>
<p>
<mat-checkbox formControlName="Force" labelPosition="after">Force</mat-checkbox>
</p>
<p>
<mat-checkbox formControlName="IsPhase" labelPosition="after">Is Phase</mat-checkbox>
</p>
</div>
</div>
<div mat-dialog-actions>
<button mat-raised-button (click)="closeDialog()">Cancel</button>
<button mat-raised-button color="primary" (click)="setConfig(data.formName)">Save</button>
<button mat-raised-button color="primary" [disabled]="!group.valid" (click)="setConfig()">Save</button>
</div>

View File

@ -18,6 +18,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
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 { ConfigNewComponent } from './config-new.component';
@ -36,6 +37,7 @@ describe('ConfigNewComponent', () => {
MatInputModule,
MatDialogModule,
MatCheckboxModule,
MatSelectModule,
ToastrModule.forRoot(),
],
declarations: [ ConfigNewComponent ],
@ -52,6 +54,11 @@ describe('ConfigNewComponent', () => {
component = fixture.componentInstance;
component.data.formName = 'context';
component.data.configs = {
manifests: ['default'],
encryption: ['default'],
management: ['default']
};
fixture.detectChanges();
});

View File

@ -13,11 +13,10 @@
*/
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { WsService } from 'src/services/ws/ws.service';
import { FormControl } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ContextOptions, EncryptionConfigOptions, ManagementConfig, ManifestOptions } from '../config.models';
import { WsConstants, WsMessage } from 'src/services/ws/ws.models';
@Component({
@ -27,77 +26,108 @@ import { WsConstants, WsMessage } from 'src/services/ws/ws.models';
})
export class ConfigNewComponent implements OnInit {
group: FormGroup;
dataObj: any;
keys: string[] = [];
dataObjs = {
context: new ContextOptions(),
manifest: new ManifestOptions(),
encryption: new EncryptionConfigOptions(),
management: new ManagementConfig()
};
subComponent: string;
encryptionType: string;
checkoutTypes = ['Branch', 'Tag', 'CommitHash'];
checkoutType = 'Branch';
constructor(private websocketService: WsService,
private fb: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: {formName: string},
@Inject(MAT_DIALOG_DATA) public data: {
formName: string,
configs: {}
},
public dialogRef: MatDialogRef<ConfigNewComponent>) { }
ngOnInit(): void {
const grp = {};
this.dataObj = this.dataObjs[this.data.formName];
for (const [key, val] of Object.entries(this.dataObj)) {
this.keys.push(key);
grp[key] = new FormControl(val);
}
this.group = new FormGroup(grp);
}
setConfig(type: string): void {
let subComponent = '';
switch (type) {
switch (this.data.formName) {
case 'context':
subComponent = WsConstants.SET_CONTEXT;
this.group = this.fb.group({
Name: new FormControl('', Validators.required),
Manifest: new FormControl(''),
EncryptionConfig: new FormControl(''),
ManagementConfiguration: new FormControl('')
});
this.subComponent = WsConstants.SET_CONTEXT;
break;
case 'manifest':
subComponent = WsConstants.SET_MANIFEST;
this.group = this.fb.group({
Name: new FormControl('', Validators.required),
TargetPath: new FormControl('', Validators.required),
MetadataPath: new FormControl('', Validators.required),
// new manifests seem to get an auto-generated repo named 'primary'
// that won't get configured properly unless it's done here, so
// don't let users modify this field
RepoName: new FormControl({value: 'primary', disabled: true}),
URL: new FormControl('', Validators.required),
Tag: new FormControl(''),
CommitHash: new FormControl(''),
Branch: new FormControl(''),
IsPhase: new FormControl(false),
Force: new FormControl(false)
});
this.subComponent = WsConstants.SET_MANIFEST;
break;
case 'encryption':
subComponent = WsConstants.SET_ENCRYPTION_CONFIG;
this.group = this.fb.group({
Name: new FormControl('', Validators.required),
EncryptionKeyPath: new FormControl('', Validators.required),
DecryptionKeyPath: new FormControl('', Validators.required),
KeySecretName: new FormControl('', Validators.required),
KeySecretNamespace: new FormControl('', Validators.required),
});
this.subComponent = WsConstants.SET_ENCRYPTION_CONFIG;
break;
case 'management':
subComponent = WsConstants.SET_MANAGEMENT_CONFIG;
// NOTE: capitalizations are different for management config due to
// inconsistent json definitions in airshipctl
this.group = this.fb.group({
Name: new FormControl('', Validators.required),
type: new FormControl(''),
insecure: new FormControl(false),
useproxy: new FormControl(false),
systemActionRetries: new FormControl(0, Validators.pattern('^[0-9]*$')),
systemRebootDelay: new FormControl(0, Validators.pattern('^[0-9]*$'))
});
this.subComponent = WsConstants.SET_MANAGEMENT_CONFIG;
break;
}
}
for (const [key, control] of Object.entries(this.group.controls)) {
// TODO(mfuller): need to validate this within the form
if (typeof this.dataObj[key] === 'number') {
this.dataObj[key] = +control.value;
setConfig(): void {
const msg = new WsMessage(WsConstants.CTL, WsConstants.CONFIG, this.subComponent);
const opts = {};
for (const [key, val] of Object.entries(this.group.controls)) {
if (key === 'systemActionRetries' || key === 'systemRebootDelay') {
opts[key] = +val.value;
} else {
this.dataObj[key] = control.value;
opts[key] = val.value;
}
}
const msg = new WsMessage(WsConstants.CTL, WsConstants.CONFIG, subComponent);
msg.data = JSON.parse(JSON.stringify(this.dataObj));
msg.name = this.dataObj.Name;
const name = 'Name';
msg.name = opts[name];
msg.data = JSON.parse(JSON.stringify(opts));
this.websocketService.sendMessage(msg);
this.dialogRef.close();
this.closeDialog();
}
closeDialog(): void {
this.dialogRef.close();
}
// annoying helper method because apparently I can't just test this natively
// inside an *ngIf
isBool(val: any): boolean {
return typeof val === 'boolean';
onEncryptionChange(event: any): void {
if (this.encryptionType === 'encryption') {
this.group.controls.EncryptionKeyPath.enable();
this.group.controls.DecryptionKeyPath.enable();
this.group.controls.KeySecretName.disable();
this.group.controls.KeySecretNamespace.disable();
} else {
this.group.controls.EncryptionKeyPath.disable();
this.group.controls.DecryptionKeyPath.disable();
this.group.controls.KeySecretName.enable();
this.group.controls.KeySecretNamespace.enable();
}
}
}

View File

@ -19,8 +19,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { ContextOptions, EncryptionConfigOptions, ManagementConfig, ManifestOptions } from '../config.models';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
imports: [
@ -31,10 +30,7 @@ import { ContextOptions, EncryptionConfigOptions, ManagementConfig, ManifestOpti
ReactiveFormsModule,
MatCheckboxModule,
MatDialogModule,
ContextOptions,
ManifestOptions,
ManagementConfig,
EncryptionConfigOptions
MatSelectModule
],
declarations: [
],

View File

@ -183,7 +183,10 @@ export class ConfigComponent implements WsReceiver, OnInit {
const dialogRef = this.dialog.open(ConfigNewComponent, {
width: '550px',
height: '650px',
data: { formName: configType}
data: {
formName: configType,
configs: this.configs
}
});
}
}