mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
[UI] Make sure form validation displays non-valid fields as red in all forms (#7064)
* Add validation to multi-container component This covers the following forms: - Add commands when adding a Composite Command Co-authored-by: Philippe Martin <phmartin@redhat.com> * Add validation to multi-key-value component This covers the following forms: - Add Environment variables in Create Container - Add Deployment annotations in Create Container - Add Service annotations in Create Container Co-authored-by: Philippe Martin <phmartin@redhat.com> * Add validation to multi-text component This covers the following forms: - Add Command in Create Container - Add Args in Create Container - Add Args in Create Image Co-authored-by: Philippe Martin <phmartin@redhat.com> * Add validation to select-container component This covers the following forms: - Select or Create container in Add Exec Command - Select or create image component in Add Image Command - Select or create Resource in Add Apply command Co-authored-by: Philippe Martin <phmartin@redhat.com> * Add validation to volume-mounts component This covers the following forms: - Select or Create volume mount in Create container Co-authored-by: Philippe Martin <phmartin@redhat.com> * Add error helper message for invalid volume size quantities * Fix Cypress tests * Generate static UI * fixup! Add error helper message for invalid volume size quantities Co-authored-by: Philippe Martin <phmartin@redhat.com> * Generate static UI --------- Co-authored-by: Philippe Martin <phmartin@redhat.com>
This commit is contained in:
2
pkg/apiserver-impl/ui/index.html
generated
2
pkg/apiserver-impl/ui/index.html
generated
@@ -11,6 +11,6 @@
|
||||
<body class="mat-typography">
|
||||
<div id="loading">Loading, please wait...</div>
|
||||
<app-root></app-root>
|
||||
<script src="runtime.1289ea0acffcdc5e.js" type="module"></script><script src="polyfills.8b3b37cedaf377c3.js" type="module"></script><script src="main.4d8dc3ef32c88ca3.js" type="module"></script>
|
||||
<script src="runtime.1289ea0acffcdc5e.js" type="module"></script><script src="polyfills.8b3b37cedaf377c3.js" type="module"></script><script src="main.9400449aa2437590.js" type="module"></script>
|
||||
|
||||
</body></html>
|
||||
File diff suppressed because one or more lines are too long
@@ -62,7 +62,7 @@ describe('devfile editor spec', () => {
|
||||
cy.getByDataCy('container-env-value-2').type("val3");
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1");
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1", {force: true});
|
||||
cy.getByDataCy('volume-mount-name-0').click().get('mat-option').contains('volume1').click();
|
||||
|
||||
cy.getByDataCy('endpoints-add').click();
|
||||
@@ -70,7 +70,7 @@ describe('devfile editor spec', () => {
|
||||
cy.getByDataCy('endpoint-targetPort-0').type("4001");
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2");
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2", {force: true});
|
||||
cy.getByDataCy('volume-mount-name-1').click().get('mat-option').contains('(New Volume)').click();
|
||||
cy.getByDataCy('volume-name').type('volume2');
|
||||
cy.getByDataCy('volume-create').click();
|
||||
@@ -134,11 +134,11 @@ describe('devfile editor spec', () => {
|
||||
cy.getByDataCy('container-source-mapping').type('/mnt/sources');
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1");
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1", {force: true});
|
||||
cy.getByDataCy('volume-mount-name-0').click().get('mat-option').contains('volume1').click();
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2");
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2", {force: true});
|
||||
cy.getByDataCy('volume-mount-name-1').click().get('mat-option').contains('(New Volume)').click();
|
||||
cy.getByDataCy('volume-name').type('volume2');
|
||||
cy.getByDataCy('volume-create').click();
|
||||
@@ -397,11 +397,11 @@ describe('devfile editor spec', () => {
|
||||
cy.getByDataCy('container-image').type('an-image');
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1");
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1", {force: true});
|
||||
cy.getByDataCy('volume-mount-name-0').click().get('mat-option').contains('volume1').click();
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2");
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2", {force: true});
|
||||
cy.getByDataCy('volume-mount-name-1').click().get('mat-option').contains('(New Volume)').click();
|
||||
cy.getByDataCy('volume-name').type('volume2');
|
||||
cy.getByDataCy('volume-create').click();
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<h3>{{title}}</h3>
|
||||
<div class="group">
|
||||
<span *ngFor="let command of commands; let i=index">
|
||||
<span *ngFor="let control of form.controls; index as i">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-select [value]="command" (selectionChange)="onCommandChange(i, $event.value)">
|
||||
<mat-label><span>Command</span></mat-label>
|
||||
<mat-select [formControl]="control">
|
||||
<mat-option *ngFor="let commandElement of commandList" [value]="commandElement">{{commandElement}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</span>
|
||||
<button *ngIf="commands.length > 0" mat-icon-button (click)="addCommand()">
|
||||
<button *ngIf="form.controls.length > 0" mat-icon-button (click)="addCommand('')">
|
||||
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="commands.length == 0" mat-flat-button (click)="addCommand()">{{addLabel}}</button>
|
||||
<button *ngIf="form.controls.length == 0" mat-flat-button (click)="addCommand('')">{{addLabel}}</button>
|
||||
</div>
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import {Component, forwardRef, Input} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
FormArray,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR, ValidationErrors, Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-multi-command',
|
||||
@@ -10,10 +19,15 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: MultiCommandComponent
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MultiCommandComponent),
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
export class MultiCommandComponent {
|
||||
export class MultiCommandComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
@Input() addLabel: string = "";
|
||||
@Input() commandList: string[] = [];
|
||||
@@ -21,10 +35,16 @@ export class MultiCommandComponent {
|
||||
|
||||
onChange = (_: string[]) => {};
|
||||
|
||||
commands: string[] = [];
|
||||
form = new FormArray<FormControl>([]);
|
||||
|
||||
writeValue(value: any) {
|
||||
this.commands = value;
|
||||
constructor() {
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.onChange(value);
|
||||
});
|
||||
}
|
||||
|
||||
writeValue(value: string[]) {
|
||||
value.forEach(v => this.addCommand(v));
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
@@ -33,13 +53,19 @@ export class MultiCommandComponent {
|
||||
|
||||
registerOnTouched(_: any) {}
|
||||
|
||||
addCommand() {
|
||||
this.commands.push("");
|
||||
this.onChange(this.commands);
|
||||
newCommand(cmdName : string) {
|
||||
return new FormControl(cmdName, [Validators.required]);
|
||||
}
|
||||
|
||||
onCommandChange(i: number, cmd: string) {
|
||||
this.commands[i] = cmd;
|
||||
this.onChange(this.commands);
|
||||
addCommand(cmdName: string) {
|
||||
this.form.push(this.newCommand(cmdName));
|
||||
}
|
||||
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
if (!this.form.valid) {
|
||||
return {'internal': true};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<div class="group">
|
||||
<span *ngFor="let entry of entries; let i=index">
|
||||
<div *ngFor="let control of form.controls; index as i">
|
||||
<ng-container [formGroup]="control">
|
||||
<mat-form-field class="mid-width" appearance="outline">
|
||||
<mat-label><span>Name</span></mat-label>
|
||||
<input [attr.data-cy]="dataCyPrefix+'-name-'+i" matInput [value]="entry.name" (change)="onKeyChange(i, $event)" (input)="onKeyChange(i, $event)">
|
||||
<input [attr.data-cy]="dataCyPrefix+'-name-'+i" matInput formControlName="name">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="mid-width" appearance="outline">
|
||||
<mat-label><span>Value</span></mat-label>
|
||||
<input [attr.data-cy]="dataCyPrefix+'-value-'+i" matInput [value]="entry.value" (change)="onValueChange(i, $event)" (input)="onValueChange(i, $event)">
|
||||
<input [attr.data-cy]="dataCyPrefix+'-value-'+i" matInput formControlName="value">
|
||||
</mat-form-field>
|
||||
</span>
|
||||
<button [attr.data-cy]="dataCyPrefix+'-plus'" *ngIf="entries.length > 0" mat-icon-button (click)="addEntry()">
|
||||
</ng-container>
|
||||
</div>
|
||||
<button [attr.data-cy]="dataCyPrefix+'-plus'" *ngIf="form.controls.length > 0" mat-icon-button (click)="addEntry('', '')">
|
||||
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
<button [attr.data-cy]="dataCyPrefix+'-add'" *ngIf="entries.length == 0" mat-flat-button (click)="addEntry()">{{addLabel}}</button>
|
||||
<button [attr.data-cy]="dataCyPrefix+'-add'" *ngIf="form.controls.length == 0" mat-flat-button (click)="addEntry('', '')">{{addLabel}}</button>
|
||||
</div>
|
||||
@@ -1,5 +1,16 @@
|
||||
import { Component, Input, forwardRef } from '@angular/core';
|
||||
import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, Validators } from '@angular/forms';
|
||||
import {Component, forwardRef, Input} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
FormArray,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
|
||||
interface KeyValue {
|
||||
name: string;
|
||||
@@ -28,13 +39,19 @@ export class MultiKeyValueComponent implements ControlValueAccessor, Validator {
|
||||
@Input() dataCyPrefix: string = "";
|
||||
@Input() addLabel: string = "";
|
||||
|
||||
form = new FormArray<FormGroup>([]);
|
||||
|
||||
onChange = (_: KeyValue[]) => {};
|
||||
onValidatorChange = () => {};
|
||||
|
||||
entries: KeyValue[] = [];
|
||||
constructor() {
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.onChange(value);
|
||||
});
|
||||
}
|
||||
|
||||
writeValue(value: KeyValue[]) {
|
||||
this.entries = value;
|
||||
value.forEach(v => this.addEntry(v.name, v.value));
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
@@ -43,31 +60,22 @@ export class MultiKeyValueComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
registerOnTouched(_: any) {}
|
||||
|
||||
addEntry() {
|
||||
this.entries.push({name: "", value: ""});
|
||||
this.onChange(this.entries);
|
||||
newKeyValueForm(kv: KeyValue): FormGroup {
|
||||
return new FormGroup({
|
||||
name: new FormControl(kv.name, [Validators.required]),
|
||||
value: new FormControl(kv.value, [Validators.required]),
|
||||
});
|
||||
}
|
||||
|
||||
onKeyChange(i: number, e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this.entries[i].name = target.value;
|
||||
this.onChange(this.entries);
|
||||
}
|
||||
|
||||
onValueChange(i: number, e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this.entries[i].value = target.value;
|
||||
this.onChange(this.entries);
|
||||
addEntry(name: string, value: string) {
|
||||
this.form.push(this.newKeyValueForm({name, value}));
|
||||
}
|
||||
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
for (let i=0; i<this.entries.length; i++) {
|
||||
const entry = this.entries[i];
|
||||
if (entry.name == "" || entry.value == "") {
|
||||
if (!this.form.valid) {
|
||||
return {'internal': true};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<h3 *ngIf="title">{{title}}</h3>
|
||||
<div class="group">
|
||||
<span *ngFor="let text of texts; let i=index">
|
||||
<span *ngFor="let control of form.controls; index as i">
|
||||
<mat-form-field class="inline" appearance="outline">
|
||||
<mat-label><span>{{label}}</span></mat-label>
|
||||
<input matInput [value]="text" (change)="onTextChange(i, $event)">
|
||||
<input matInput [formControl]="control">
|
||||
</mat-form-field>
|
||||
</span>
|
||||
<button *ngIf="texts.length > 0" mat-icon-button (click)="addText()">
|
||||
<button *ngIf="form.controls.length > 0" mat-icon-button (click)="addText('')">
|
||||
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="texts.length == 0" mat-flat-button (click)="addText()">{{addLabel}}</button>
|
||||
<button *ngIf="form.controls.length == 0" mat-flat-button (click)="addText('')">{{addLabel}}</button>
|
||||
</div>
|
||||
@@ -1,5 +1,15 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import {Component, forwardRef, Input} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
FormArray,
|
||||
FormControl,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-multi-text',
|
||||
@@ -10,10 +20,15 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: MultiTextComponent
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MultiTextComponent),
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
export class MultiTextComponent implements ControlValueAccessor {
|
||||
export class MultiTextComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
@Input() label: string = "";
|
||||
@Input() addLabel: string = "";
|
||||
@@ -21,13 +36,20 @@ export class MultiTextComponent implements ControlValueAccessor {
|
||||
|
||||
onChange = (_: string[]) => {};
|
||||
|
||||
texts: string[] = [];
|
||||
form = new FormArray<FormControl>([]);
|
||||
|
||||
writeValue(value: any) {
|
||||
if (value == null) {
|
||||
value = [];
|
||||
constructor() {
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.onChange(value);
|
||||
});
|
||||
}
|
||||
this.texts = value;
|
||||
|
||||
newText(text: string): FormControl {
|
||||
return new FormControl(text, [Validators.required]);
|
||||
}
|
||||
|
||||
writeValue(value: string[]) {
|
||||
value?.forEach(v => this.addText(v));
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
@@ -36,14 +58,15 @@ export class MultiTextComponent implements ControlValueAccessor {
|
||||
|
||||
registerOnTouched(_: any) {}
|
||||
|
||||
addText() {
|
||||
this.texts.push("");
|
||||
this.onChange(this.texts);
|
||||
addText(text: string) {
|
||||
this.form.push(this.newText(text));
|
||||
}
|
||||
|
||||
onTextChange(i: number, e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this.texts[i] = target.value;
|
||||
this.onChange(this.texts);
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
if (!this.form.valid) {
|
||||
return {'internal': true};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>{{label}}</mat-label>
|
||||
<mat-select data-cy="select-container" [value]="container" (selectionChange)="onSelectChange($event.value)">
|
||||
<mat-select [formControl]="formCtrl" data-cy="select-container" (selectionChange)="onSelectChange($event.value)">
|
||||
<mat-option *ngFor="let container of containers" [value]="container">{{container}}</mat-option>
|
||||
<mat-option value="!">(New {{label}})</mat-option>
|
||||
</mat-select>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import {Component, EventEmitter, forwardRef, Input, Output} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
FormArray, FormControl,
|
||||
FormGroup, NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ValidationErrors, Validator, Validators
|
||||
} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-select-container',
|
||||
@@ -10,21 +17,30 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: SelectContainerComponent
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => SelectContainerComponent),
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
export class SelectContainerComponent implements ControlValueAccessor {
|
||||
export class SelectContainerComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
@Input() containers: string[] = [];
|
||||
@Input() label: string = "";
|
||||
@Output() createNew = new EventEmitter<boolean>();
|
||||
|
||||
container: string = "";
|
||||
formCtrl: FormControl;
|
||||
|
||||
onChange = (_: string) => {};
|
||||
|
||||
writeValue(value: any) {
|
||||
this.container = value;
|
||||
constructor() {
|
||||
this.formCtrl = new FormControl('', [Validators.required]);
|
||||
}
|
||||
|
||||
writeValue(value: string) {
|
||||
this.formCtrl.setValue(value);
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
@@ -39,4 +55,12 @@ export class SelectContainerComponent implements ControlValueAccessor {
|
||||
}
|
||||
this.createNew.emit(v == "!");
|
||||
}
|
||||
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
if (!this.formCtrl.valid) {
|
||||
return {'internal': true};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
<div class="group">
|
||||
<div *ngFor="let vm of volumeMounts; let i=index">
|
||||
<div *ngFor="let control of form.controls; index as i">
|
||||
<ng-container [formGroup]="control">
|
||||
<mat-form-field class="inline" appearance="outline">
|
||||
<mat-label><span>Volume</span></mat-label>
|
||||
<mat-select [attr.data-cy]="'volume-mount-name-'+i" [value]="vm.name" (selectionChange)="onNameChange(i, $event.value)">
|
||||
<mat-select formControlName="name" [attr.data-cy]="'volume-mount-name-'+i" (selectionChange)="onNameChange(i, $event.value)">
|
||||
<mat-option *ngFor="let volume of volumes" [value]="volume">{{volume}}</mat-option>
|
||||
<mat-option value="!">(New Volume)</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="inline" appearance="outline">
|
||||
<mat-label><span>Mount Path</span></mat-label>
|
||||
<input (input)="onPathChange(i, $event)" [attr.data-cy]="'volume-mount-path-'+i" matInput [value]="vm.path" (change)="onPathChange(i, $event)">
|
||||
<input formControlName="path" [attr.data-cy]="'volume-mount-path-'+i" matInput>
|
||||
</mat-form-field>
|
||||
|
||||
<app-volume
|
||||
*ngIf="showNewVolume[i]"
|
||||
(created)="onNewVolumeCreated(i, $event)"
|
||||
></app-volume>
|
||||
</ng-container>
|
||||
</div>
|
||||
<button data-cy="volume-mount-add" *ngIf="volumeMounts.length > 0" mat-icon-button (click)="add()">
|
||||
<button data-cy="volume-mount-add" *ngIf="form.controls.length > 0" mat-icon-button (click)="add('', '')">
|
||||
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
<button data-cy="volume-mount-add" *ngIf="volumeMounts.length == 0" mat-flat-button (click)="add()">Add Volume Mount</button>
|
||||
<button data-cy="volume-mount-add" *ngIf="form.controls.length == 0" mat-flat-button (click)="add('', '')">Add Volume Mount</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core';
|
||||
import { AbstractControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
|
||||
import { Volume, VolumeMount } from 'src/app/api-gen';
|
||||
import {Component, EventEmitter, forwardRef, Input, Output} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
FormArray,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import {Volume, VolumeMount} from 'src/app/api-gen';
|
||||
|
||||
@Component({
|
||||
selector: 'app-volume-mounts',
|
||||
@@ -19,20 +30,27 @@ import { Volume, VolumeMount } from 'src/app/api-gen';
|
||||
},
|
||||
]
|
||||
})
|
||||
export class VolumeMountsComponent implements Validator {
|
||||
export class VolumeMountsComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
@Input() volumes: string[] = [];
|
||||
|
||||
@Output() createNewVolume = new EventEmitter<Volume>();
|
||||
|
||||
volumeMounts: VolumeMount[] = [];
|
||||
form = new FormArray<FormGroup>([]);
|
||||
|
||||
showNewVolume: boolean[] = [];
|
||||
|
||||
onChange = (_: VolumeMount[]) => {};
|
||||
onValidatorChange = () => {};
|
||||
|
||||
writeValue(value: any) {
|
||||
this.volumeMounts = value;
|
||||
constructor() {
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.onChange(value);
|
||||
});
|
||||
}
|
||||
|
||||
writeValue(value: VolumeMount[]) {
|
||||
value.forEach(v => this.add(v.name, v.path));
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
@@ -41,29 +59,24 @@ export class VolumeMountsComponent implements Validator {
|
||||
|
||||
registerOnTouched(_: any) {}
|
||||
|
||||
add() {
|
||||
this.volumeMounts.push({name: "", path: ""});
|
||||
this.onChange(this.volumeMounts);
|
||||
newVolumeMount(vol: VolumeMount): FormGroup {
|
||||
return new FormGroup({
|
||||
name: new FormControl(vol.name, [Validators.required]),
|
||||
path: new FormControl(vol.path, [Validators.required]),
|
||||
});
|
||||
}
|
||||
|
||||
onPathChange(i: number, e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this.volumeMounts[i].path = target.value;
|
||||
this.onChange(this.volumeMounts);
|
||||
add(name: string, path: string) {
|
||||
this.form.push(this.newVolumeMount({name, path}));
|
||||
}
|
||||
|
||||
onNameChange(i: number, name: string) {
|
||||
if (name != "!") {
|
||||
this.volumeMounts[i].name = name;
|
||||
this.onChange(this.volumeMounts);
|
||||
}
|
||||
|
||||
this.showNewVolume[i] = name == "!";
|
||||
}
|
||||
|
||||
onNewVolumeCreated(i: number, v: Volume) {
|
||||
this.volumes.push(v.name);
|
||||
this.volumeMounts[i].name = v.name;
|
||||
this.form.at(i).get('name')?.setValue(v.name);
|
||||
this.createNewVolume.next(v);
|
||||
this.showNewVolume[i] = false;
|
||||
this.onValidatorChange();
|
||||
@@ -71,12 +84,9 @@ export class VolumeMountsComponent implements Validator {
|
||||
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
for (let i=0; i<this.volumeMounts.length; i++) {
|
||||
const vm = this.volumeMounts[i];
|
||||
if (vm.name == "" || vm.path == "") {
|
||||
if (!this.form.valid) {
|
||||
return {'internal': true};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>Size</span></mat-label>
|
||||
<mat-error>Example of valid quantities: 300k (300*1000), 30Mi(30*1024²), 3Gi (3*1024³), 3G (3*1000³)</mat-error>
|
||||
<input placeholder="Minimal size of the volume" data-cy="volume-size" matInput formControlName="size">
|
||||
</mat-form-field>
|
||||
<mat-checkbox data-cy="volume-ephemeral" formControlName="ephemeral">Volume is Ephemeral</mat-checkbox>
|
||||
|
||||
Reference in New Issue
Block a user