[UI] Allow single item deletion from multi-value fields (#7084)

* Support single endpoint deletion from multi-endpoints component

* Support single command deletion from multi-commands component

* Support single key-value item deletion from multi-key-value component

* Support single text item deletion from multi-text component

* Support single volume mount item deletion from multi-volume-mounts component

* Add Cypress test cases

* Git-ignore Cypress screenshots folder

* Generate static UI

* Update Delete icon and add tooltip to it

Co-authored-by: Philippe Martin <phmartin@redhat.com>

* Move the "delete endpoint" button closer to the element it is attached to

Co-authored-by: Philippe Martin <phmartin@redhat.com>

* Generate static UI

* Revert "Move the "delete endpoint" button closer to the element it is attached to"

This reverts commit 4bf895f272.

* Move the "delete endpoint" buttons closer to the elements they are attached to

Co-authored-by: Philippe Martin <phmartin@redhat.com>

* Generate static UI

---------

Co-authored-by: Philippe Martin <phmartin@redhat.com>
This commit is contained in:
Armel Soro
2023-09-18 10:17:32 +02:00
committed by GitHub
parent b20ceb619c
commit 9724292dfa
23 changed files with 448 additions and 91 deletions

3
ui/.gitignore vendored
View File

@@ -42,5 +42,6 @@ testem.log
Thumbs.db
/cypress/videos
/cypress/screenshots
.odo
.odo

View File

@@ -54,10 +54,10 @@ describe('devfile editor spec', () => {
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-0').type("VAR1");
cy.getByDataCy('container-env-value-0').type("val1");
cy.getByDataCy('container-env-plus').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-1').type("VAR2");
cy.getByDataCy('container-env-value-1').type("val2");
cy.getByDataCy('container-env-plus').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-2').type("VAR3");
cy.getByDataCy('container-env-value-2').type("val3");
@@ -79,13 +79,13 @@ describe('devfile editor spec', () => {
cy.getByDataCy('container-deploy-anno-add').click();
cy.getByDataCy('container-deploy-anno-name-0').type("DEPANNO1");
cy.getByDataCy('container-deploy-anno-value-0').type("depval1");
cy.getByDataCy('container-deploy-anno-plus').click();
cy.getByDataCy('container-deploy-anno-add').click();
cy.getByDataCy('container-deploy-anno-name-1').type("DEPANNO2");
cy.getByDataCy('container-deploy-anno-value-1').type("depval2");
cy.getByDataCy('container-svc-anno-add').click();
cy.getByDataCy('container-svc-anno-name-0').type("SVCANNO1");
cy.getByDataCy('container-svc-anno-value-0').type("svcval1");
cy.getByDataCy('container-svc-anno-plus').click();
cy.getByDataCy('container-svc-anno-add').click();
cy.getByDataCy('container-svc-anno-name-1').type("SVCANNO2");
cy.getByDataCy('container-svc-anno-value-1').type("svcval2");
@@ -117,7 +117,7 @@ describe('devfile editor spec', () => {
});
it.only('displays a modified container', () => {
it('displays a modified container', () => {
cy.init();
cy.selectTab(TAB_VOLUMES);
@@ -132,10 +132,10 @@ describe('devfile editor spec', () => {
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-0').type("VAR1");
cy.getByDataCy('container-env-value-0').type("val1");
cy.getByDataCy('container-env-plus').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-1').type("VAR2");
cy.getByDataCy('container-env-value-1').type("val2");
cy.getByDataCy('container-env-plus').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-2').type("VAR3");
cy.getByDataCy('container-env-value-2').type("val3");
@@ -157,13 +157,13 @@ describe('devfile editor spec', () => {
cy.getByDataCy('container-deploy-anno-add').click();
cy.getByDataCy('container-deploy-anno-name-0').type("DEPANNO1");
cy.getByDataCy('container-deploy-anno-value-0').type("depval1");
cy.getByDataCy('container-deploy-anno-plus').click();
cy.getByDataCy('container-deploy-anno-add').click();
cy.getByDataCy('container-deploy-anno-name-1').type("DEPANNO2");
cy.getByDataCy('container-deploy-anno-value-1').type("depval2");
cy.getByDataCy('container-svc-anno-add').click();
cy.getByDataCy('container-svc-anno-name-0').type("SVCANNO1");
cy.getByDataCy('container-svc-anno-value-0').type("svcval1");
cy.getByDataCy('container-svc-anno-plus').click();
cy.getByDataCy('container-svc-anno-add').click();
cy.getByDataCy('container-svc-anno-name-1').type("SVCANNO2");
cy.getByDataCy('container-svc-anno-value-1').type("svcval2");
@@ -172,7 +172,7 @@ describe('devfile editor spec', () => {
cy.getByDataCy('container-edit').click();
cy.getByDataCy('container-image').type('{selectAll}{del}another-image');
cy.getByDataCy('container-env-plus').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-3').type("VAR4");
cy.getByDataCy('container-env-value-3').type("val4");
@@ -190,12 +190,12 @@ describe('devfile editor spec', () => {
cy.getByDataCy('container-more-params').click();
cy.getByDataCy('container-deploy-anno-name-0').type("{selectAll}{del}DEPANNO1b");
cy.getByDataCy('container-deploy-anno-value-0').type("{selectAll}{del}depval1b");
cy.getByDataCy('container-deploy-anno-plus').click();
cy.getByDataCy('container-deploy-anno-add').click();
cy.getByDataCy('container-deploy-anno-name-2').type("DEPANNO3");
cy.getByDataCy('container-deploy-anno-value-2').type("depval3");
cy.getByDataCy('container-svc-anno-name-0').type("{selectAll}{del}SVCANNO1b");
cy.getByDataCy('container-svc-anno-value-0').type("{selectAll}{del}svcval1b");
cy.getByDataCy('container-svc-anno-plus').click();
cy.getByDataCy('container-svc-anno-add').click();
cy.getByDataCy('container-svc-anno-name-2').type("SVCANNO3");
cy.getByDataCy('container-svc-anno-value-2').type("svcval3");
@@ -767,4 +767,262 @@ describe('devfile editor spec', () => {
cy.selectTab(TAB_YAML);
cy.get('[data-cy="yaml-input"]').should("contain.value", "events: {}");
});
it('should update list of commands from multi-value field when adding and editing a composite command', () => {
cy.init();
cy.fixture('input/with-exec-command.yaml').then(yaml => {
cy.setDevfile(yaml);
});
cy.selectTab(TAB_COMMANDS);
cy.getByDataCy('add').click();
cy.getByDataCy('new-command-composite').click();
cy.getByDataCy('command-composite-name').type('my-new-composite-command');
cy.getByDataCy('add-command').click();
cy.getByDataCy('add-command').click();
cy.getByDataCy('add-command').click();
cy.getByDataCy('command-selector-0').click().get('mat-option').contains('command2').click();
cy.getByDataCy('command-selector-1').click().get('mat-option').contains('command3').click();
cy.getByDataCy('command-selector-2').click().get('mat-option').contains('command1').click();
cy.getByDataCy('command-minus-1').click();
cy.getByDataCy('command-composite-create').click();
cy.getByDataCy('command-info').last()
.should('contain.text', 'command2')
.should('contain.text', 'command1')
.should('not.contain.text', 'command3');
//Edit
cy.getByDataCy('command-edit').last().click();
cy.getByDataCy('command-selector-0').should('have.text', 'command2');
cy.getByDataCy('command-selector-1').should('have.text', 'command1');
cy.getByDataCy('add-command').click();
cy.getByDataCy('add-command').click();
cy.getByDataCy('add-command').click();
cy.getByDataCy('command-selector-2').click().get('mat-option').contains('command2').click();
cy.getByDataCy('command-minus-4').click();
cy.getByDataCy('command-minus-0').click();
cy.getByDataCy('command-selector-2').click().get('mat-option').contains('command3').click();
cy.getByDataCy('command-composite-save').click();
cy.getByDataCy('command-info').last()
.should('contain.text', 'command1')
.should('contain.text', 'command2')
.should('contain.text', 'command3');
});
it('should update list of build args from multi-value field when adding and editing an image component', () => {
cy.init();
cy.selectTab(TAB_IMAGES);
cy.getByDataCy('image-name').type('my-new-image');
cy.getByDataCy('image-image-name').type('an-image-name');
cy.getByDataCy('image-dockerfile-uri').type('/path/to/dockerfile');
cy.getByDataCy('add-text').click();
cy.getByDataCy('add-text').click();
cy.getByDataCy('add-text').click();
cy.getByDataCy('image-arg-text-0').type('arg2');
cy.getByDataCy('image-arg-text-1').type('arg3');
cy.getByDataCy('image-arg-text-2').type('arg1');
cy.getByDataCy('image-arg-minus-1').click();
cy.getByDataCy('image-create').click();
cy.getByDataCy('image-info').last()
.should('contain.text', 'arg2')
.should('contain.text', 'arg1')
.should('not.contain.text', 'arg3');
//Edit
cy.getByDataCy('image-edit').last().click();
cy.getByDataCy('image-arg-text-0').should('have.value', 'arg2');
cy.getByDataCy('image-arg-text-1').should('have.value', 'arg1');
cy.getByDataCy('add-text').click();
cy.getByDataCy('add-text').click();
cy.getByDataCy('add-text').click();
cy.getByDataCy('image-arg-text-2').type('arg2');
cy.getByDataCy('image-arg-minus-4').click();
cy.getByDataCy('image-arg-minus-0').click();
cy.getByDataCy('image-arg-text-2').type('arg3');
cy.getByDataCy('image-save').click();
cy.getByDataCy('image-info').last()
.should('contain.text', 'arg1')
.should('contain.text', 'arg2')
.should('contain.text', 'arg3');
});
it('should update list of env vars from multi-value field when adding and editing a container', () => {
cy.init();
cy.selectTab(TAB_CONTAINERS);
cy.getByDataCy('container-name').type('my-new-container');
cy.getByDataCy('container-image').type('an-image');
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-0').type("VAR2");
cy.getByDataCy('container-env-value-0').type("val2");
cy.getByDataCy('container-env-name-1').type("VAR3");
cy.getByDataCy('container-env-value-1').type("val3");
cy.getByDataCy('container-env-name-2').type("VAR1");
cy.getByDataCy('container-env-value-2').type("val1");
cy.getByDataCy('container-env-minus-1').click();
cy.getByDataCy('container-create').click();
cy.getByDataCy('container-info').last()
.should('contain.text', 'my-new-container')
.should('contain.text', 'an-image')
.should('contain.text', 'VAR2: val2')
.should('contain.text', 'VAR1: val1')
.should('not.contain.text', 'VAR3: val3');
//Edit
cy.getByDataCy('container-edit').last().click();
cy.getByDataCy('container-env-name-0').should('have.value', 'VAR2');
cy.getByDataCy('container-env-value-0').should('have.value', 'val2');
cy.getByDataCy('container-env-name-1').should('have.value', 'VAR1');
cy.getByDataCy('container-env-value-1').should('have.value', 'val1');
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-add').click();
cy.getByDataCy('container-env-name-2').type("VAR2");
cy.getByDataCy('container-env-value-2').type("val2");
cy.getByDataCy('container-env-minus-4').click();
cy.getByDataCy('container-env-minus-0').click();
cy.getByDataCy('container-env-name-2').type("VAR3");
cy.getByDataCy('container-env-value-2').type("val3");
cy.getByDataCy('container-save').click();
cy.getByDataCy('container-info').last()
.should('contain.text', 'my-new-container')
.should('contain.text', 'an-image')
.should('contain.text', 'VAR2: val2')
.should('contain.text', 'VAR1: val1')
.should('contain.text', 'VAR3: val3');
});
it('should update list of endpoints from multi-value field when adding and editing a container', () => {
cy.init();
cy.selectTab(TAB_CONTAINERS);
cy.getByDataCy('container-name').type('my-new-container');
cy.getByDataCy('container-image').type('an-image');
cy.getByDataCy('endpoints-add').click();
cy.getByDataCy('endpoints-add').click();
cy.getByDataCy('endpoints-add').click();
cy.getByDataCy('endpoint-name-0').type("ep2");
cy.getByDataCy('endpoint-targetPort-0').clear().type("4002");
cy.getByDataCy('endpoint-name-1').type("ep3");
cy.getByDataCy('endpoint-targetPort-1').clear().type("4003");
cy.getByDataCy('endpoint-name-2').type("ep1");
cy.getByDataCy('endpoint-targetPort-2').clear().type("4001");
cy.getByDataCy('endpoint-minus-1').click();
cy.getByDataCy('container-create').click();
cy.getByDataCy('container-info').last()
.should('contain.text', 'my-new-container')
.should('contain.text', 'ep2')
.should('contain.text', '4002')
.should('contain.text', 'ep1')
.should('contain.text', '4001')
.should('not.contain.text', 'ep3')
.should('not.contain.text', '4003');
//Edit
cy.getByDataCy('container-edit').last().click();
cy.getByDataCy('endpoint-name-0').should('have.value', 'ep2');
cy.getByDataCy('endpoint-targetPort-0').should('have.value', '4002');
cy.getByDataCy('endpoint-name-1').should('have.value', 'ep1');
cy.getByDataCy('endpoint-targetPort-1').should('have.value', '4001');
cy.getByDataCy('endpoints-add').click();
cy.getByDataCy('endpoints-add').click();
cy.getByDataCy('endpoints-add').click();
cy.getByDataCy('endpoint-name-2').type("ep2");
cy.getByDataCy('endpoint-targetPort-2').clear().type("4002");
cy.getByDataCy('endpoint-minus-4').click();
cy.getByDataCy('endpoint-minus-0').click();
cy.getByDataCy('endpoint-name-2').type("ep3");
cy.getByDataCy('endpoint-targetPort-2').clear().type("4003");
cy.getByDataCy('container-save').click();
cy.getByDataCy('container-info').last()
.should('contain.text', 'my-new-container')
.should('contain.text', 'ep1')
.should('contain.text', '4001')
.should('contain.text', 'ep2')
.should('contain.text', '4002')
.should('contain.text', 'ep3')
.should('contain.text', '4003');
});
it('should update list of volume mounts from multi-value field when adding and editing a container', () => {
cy.init();
cy.fixture('input/with-volume.yaml').then(yaml => {
cy.setDevfile(yaml);
});
cy.selectTab(TAB_CONTAINERS);
cy.getByDataCy('container-name').type('my-new-container');
cy.getByDataCy('container-image').type('an-image');
cy.getByDataCy('volume-mount-add').click();
cy.getByDataCy('volume-mount-add').click();
cy.getByDataCy('volume-mount-add').click();
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol2", {force: true});
cy.getByDataCy('volume-mount-name-0').click().get('mat-option').contains('volume2').click();
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol3", {force: true});
cy.getByDataCy('volume-mount-name-1').click().get('mat-option').contains('volume3').click();
cy.getByDataCy('volume-mount-path-2').type("/mnt/vol1", {force: true});
cy.getByDataCy('volume-mount-name-2').click().get('mat-option').contains('volume1').click();
cy.getByDataCy('volume-mount-minus-1').click();
cy.getByDataCy('container-create').click();
cy.getByDataCy('container-info').last()
.should('contain.text', 'my-new-container')
.should('contain.text', 'volume2')
.should('contain.text', '/mnt/vol2')
.should('contain.text', 'volume1')
.should('contain.text', '/mnt/vol1')
.should('not.contain.text', 'volume3')
.should('not.contain.text', '/mnt/vol3');
//Edit
cy.getByDataCy('container-edit').last().click();
cy.getByDataCy('volume-mount-name-0').should('have.text', 'volume2');
cy.getByDataCy('volume-mount-path-0').should('have.value', '/mnt/vol2');
cy.getByDataCy('volume-mount-name-1').should('have.text', 'volume1');
cy.getByDataCy('volume-mount-path-1').should('have.value', '/mnt/vol1');
cy.getByDataCy('volume-mount-add').click();
cy.getByDataCy('volume-mount-add').click();
cy.getByDataCy('volume-mount-add').click();
cy.getByDataCy('volume-mount-path-2').type("/mnt/vol2", {force: true});
cy.getByDataCy('volume-mount-name-2').click().get('mat-option').contains('volume2').click();
cy.getByDataCy('volume-mount-minus-4').click();
cy.getByDataCy('volume-mount-minus-0').click();
cy.getByDataCy('volume-mount-path-2').type("/mnt/vol3", {force: true});
cy.getByDataCy('volume-mount-name-2').click().get('mat-option').contains('volume3').click();
cy.getByDataCy('container-save').click();
cy.getByDataCy('container-info').last()
.should('contain.text', 'my-new-container')
.should('contain.text', 'volume1')
.should('contain.text', '/mnt/vol1')
.should('contain.text', 'volume2')
.should('contain.text', '/mnt/vol2')
.should('contain.text', 'volume3')
.should('contain.text', '/mnt/vol3');
});
});

View File

@@ -7,6 +7,18 @@ commands:
hotReloadCapable: false
workingDir: /projects
id: command1
- exec:
commandLine: echo command2
component: container1
hotReloadCapable: true
workingDir: /projects
id: command2
- exec:
commandLine: echo command3
component: container1
hotReloadCapable: true
workingDir: /projects
id: command3
components:
- container:
args:

View File

@@ -0,0 +1,12 @@
schemaVersion: 2.2.0
metadata: {}
components:
- name: volume1
volume: {}
- name: volume2
volume:
size: 2Gi
- name: volume3
volume:
ephemeral: true
size: 3G

View File

@@ -1,2 +1,17 @@
.mid-width { width: 50%; }
.quart-width { width: 25%; }
mat-card{
display:flex;
flex-direction: row;
margin-bottom: 16px;
}
mat-card-content{
flex-grow: 1;
overflow: auto;
}
button.adjust-position {
right: 6px;
}

View File

@@ -1,43 +1,49 @@
<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]="'endpoint-name-'+i" matInput formControlName="name">
</mat-form-field>
<mat-form-field class="quart-width" appearance="outline">
<mat-label><span>Target Port</span></mat-label>
<input [attr.data-cy]="'endpoint-targetPort-'+i" type="number" matInput formControlName="targetPort">
</mat-form-field>
<mat-form-field class="quart-width" appearance="outline">
<mat-label>Exposure</mat-label>
<mat-select [attr.data-cy]="'endpoint-exposure-'+i" formControlName="exposure">
<mat-option value="">(default, public)</mat-option>
<mat-option value="public">public</mat-option>
<mat-option value="internal">internal</mat-option>
<mat-option value="none">none</mat-option>
</mat-select>
</mat-form-field>
<div class="group">
<mat-card *ngFor="let control of form.controls; index as i">
<mat-card-content [formGroup]="control">
<mat-form-field class="mid-width" appearance="outline">
<mat-label><span>Name</span></mat-label>
<input [attr.data-cy]="'endpoint-name-'+i" matInput formControlName="name">
</mat-form-field>
<mat-form-field class="quart-width" appearance="outline">
<mat-label><span>Target Port</span></mat-label>
<input [attr.data-cy]="'endpoint-targetPort-'+i" type="number" matInput formControlName="targetPort">
</mat-form-field>
<mat-form-field class="quart-width" appearance="outline">
<mat-label>Exposure</mat-label>
<mat-select [attr.data-cy]="'endpoint-exposure-'+i" formControlName="exposure">
<mat-option value="">(default, public)</mat-option>
<mat-option value="public">public</mat-option>
<mat-option value="internal">internal</mat-option>
<mat-option value="none">none</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mid-width" appearance="outline">
<mat-label><span>Path</span></mat-label>
<input [attr.data-cy]="'endpoint-path-'+i" matInput formControlName="path">
</mat-form-field>
<mat-form-field class="quart-width" appearance="outline">
<mat-label>Protocol</mat-label>
<mat-select [attr.data-cy]="'endpoint-protocol-'+i" formControlName="protocol">
<mat-option value="">(default, http)</mat-option>
<mat-option value="http">http</mat-option>
<mat-option value="https">https</mat-option>
<mat-option value="ws">ws</mat-option>
<mat-option value="wss">wss</mat-option>
<mat-option value="tcp">tcp</mat-option>
<mat-option value="udp">udp</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox [attr.data-cy]="'endpoint-secure-'+i" formControlName="secure">Protocol Is Secure</mat-checkbox>
</ng-container>
<mat-form-field class="mid-width" appearance="outline">
<mat-label><span>Path</span></mat-label>
<input [attr.data-cy]="'endpoint-path-'+i" matInput formControlName="path">
</mat-form-field>
<mat-form-field class="quart-width" appearance="outline">
<mat-label>Protocol</mat-label>
<mat-select [attr.data-cy]="'endpoint-protocol-'+i" formControlName="protocol">
<mat-option value="">(default, http)</mat-option>
<mat-option value="http">http</mat-option>
<mat-option value="https">https</mat-option>
<mat-option value="ws">ws</mat-option>
<mat-option value="wss">wss</mat-option>
<mat-option value="tcp">tcp</mat-option>
<mat-option value="udp">udp</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox [attr.data-cy]="'endpoint-secure-'+i" formControlName="secure">Protocol Is Secure</mat-checkbox>
</mat-card-content>
<mat-card-actions>
<button [attr.data-cy]="'endpoint-minus-'+i" class="adjust-position" mat-icon-button matTooltip="Delete endpoint" (click)="removeEndpoint(i)">
<mat-icon class="tab-icon material-icons-outlined">delete_forever</mat-icon>
</button>
</mat-card-actions>
</mat-card>
<div>
<button data-cy="endpoints-add" mat-flat-button (click)="addEndpoint()">Add an Endpoint</button>
</div>
</div>
<button data-cy="endpoints-plus" *ngIf="form.value.length > 0" mat-icon-button (click)="addEndpoint()">
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
</button>
<button data-cy="endpoints-add" *ngIf="form.value.length == 0" mat-flat-button (click)="addEndpoint()">Add an Endpoint</button>

View File

@@ -50,6 +50,10 @@ export class EndpointsComponent implements ControlValueAccessor, Validator {
}));
}
removeEndpoint(index: number) {
this.form.removeAt(index);
}
/* ControlValueAccessor implementation */
writeValue(value: Endpoint[]) {
value.forEach(ep => {

View File

@@ -1,2 +1,6 @@
h3 { margin-bottom: 0; }
div.group { margin-bottom: 16px; }
div.group { margin-bottom: 16px; }
button.adjust-position {
right: 6px;
}

View File

@@ -3,13 +3,15 @@
<span *ngFor="let control of form.controls; index as i">
<mat-form-field appearance="fill">
<mat-label><span>Command</span></mat-label>
<mat-select [formControl]="control">
<mat-select [attr.data-cy]="'command-selector-'+i" [formControl]="control">
<mat-option *ngFor="let commandElement of commandList" [value]="commandElement">{{commandElement}}</mat-option>
</mat-select>
</mat-form-field>
<button [attr.data-cy]="'command-minus-'+i" class="adjust-position" mat-icon-button matTooltip="Delete command" (click)="removeCommand(i)">
<mat-icon class="tab-icon material-icons-outlined">delete_forever</mat-icon>
</button>
</span>
<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="form.controls.length == 0" mat-flat-button (click)="addCommand('')">{{addLabel}}</button>
<div>
<button [attr.data-cy]="'add-command'" mat-flat-button (click)="addCommand('')">{{addLabel}}</button>
</div>
</div>

View File

@@ -61,6 +61,10 @@ export class MultiCommandComponent implements ControlValueAccessor, Validator {
this.form.push(this.newCommand(cmdName));
}
removeCommand(index: number) {
this.form.removeAt(index);
}
/* Validator implementation */
validate(control: AbstractControl): ValidationErrors | null {
if (!this.form.valid) {

View File

@@ -1,2 +1,8 @@
div.group { margin-bottom: 16px; }
.mid-width { width: 50%; }
.kv-width { width: 45%; }
button.adjust-position {
right: 6px;
top: 6px;
}

View File

@@ -1,18 +1,18 @@
<div class="group">
<div *ngFor="let control of form.controls; index as i">
<ng-container [formGroup]="control">
<mat-form-field class="mid-width" appearance="outline">
<mat-form-field class="kv-width" appearance="outline">
<mat-label><span>Name</span></mat-label>
<input [attr.data-cy]="dataCyPrefix+'-name-'+i" matInput formControlName="name">
</mat-form-field>
<mat-form-field class="mid-width" appearance="outline">
<mat-form-field class="kv-width" appearance="outline">
<mat-label><span>Value</span></mat-label>
<input [attr.data-cy]="dataCyPrefix+'-value-'+i" matInput formControlName="value">
</mat-form-field>
<button [attr.data-cy]="dataCyPrefix+'-minus-'+i" class="adjust-position" mat-icon-button [matTooltip]="deleteLabel" (click)="removeEntry(i)">
<mat-icon class="tab-icon material-icons-outlined">delete_forever</mat-icon>
</button>
</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="form.controls.length == 0" mat-flat-button (click)="addEntry('', '')">{{addLabel}}</button>
<button [attr.data-cy]="dataCyPrefix+'-add'" mat-flat-button (click)="addEntry('', '')">{{addLabel}}</button>
</div>

View File

@@ -38,6 +38,7 @@ export class MultiKeyValueComponent implements ControlValueAccessor, Validator {
@Input() dataCyPrefix: string = "";
@Input() addLabel: string = "";
@Input() deleteLabel: string = "";
form = new FormArray<FormGroup>([]);
@@ -71,6 +72,10 @@ export class MultiKeyValueComponent implements ControlValueAccessor, Validator {
this.form.push(this.newKeyValueForm({name, value}));
}
removeEntry(index: number) {
this.form.removeAt(index);
}
/* Validator implementation */
validate(control: AbstractControl): ValidationErrors | null {
if (!this.form.valid) {

View File

@@ -1,2 +1,7 @@
h3 { margin-bottom: 0; }
div.group { margin-bottom: 16px; }
div.group { margin-bottom: 16px; }
button.adjust-position {
right: 6px;
top: 6px;
}

View File

@@ -1,13 +1,20 @@
<h3 *ngIf="title">{{title}}</h3>
<div class="group">
<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 [formControl]="control">
</mat-form-field>
</span>
<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="form.controls.length == 0" mat-flat-button (click)="addText('')">{{addLabel}}</button>
<mat-card *ngIf="form.controls.length > 0">
<mat-card-content>
<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 [attr.data-cy]="dataCyPrefix+'-text-'+i" matInput [formControl]="control">
</mat-form-field>
<button [attr.data-cy]="dataCyPrefix+'-minus-'+i" class="adjust-position" mat-icon-button [matTooltip]="deleteLabel" (click)="removeText(i)">
<mat-icon class="tab-icon material-icons-outlined">delete_forever</mat-icon>
</button>
</span>
</mat-card-content>
<mat-card-actions>
<button [attr.data-cy]="'add-text'" mat-flat-button (click)="addText('')">{{addLabel}}</button>
</mat-card-actions>
</mat-card>
<button *ngIf="form.controls.length == 0" [attr.data-cy]="'add-text'" mat-flat-button (click)="addText('')">{{addLabel}}</button>
</div>

View File

@@ -30,8 +30,10 @@ import {
})
export class MultiTextComponent implements ControlValueAccessor, Validator {
@Input() dataCyPrefix: string = "";
@Input() label: string = "";
@Input() addLabel: string = "";
@Input() deleteLabel: string = "";
@Input() title: string = "";
onChange = (_: string[]) => {};
@@ -62,6 +64,10 @@ export class MultiTextComponent implements ControlValueAccessor, Validator {
this.form.push(this.newText(text));
}
removeText(index: number) {
this.form.removeAt(index);
}
/* Validator implementation */
validate(control: AbstractControl): ValidationErrors | null {
if (!this.form.valid) {

View File

@@ -1,2 +1,7 @@
h3 { margin-bottom: 0; }
div.group { margin-bottom: 16px; }
div.group { margin-bottom: 16px; }
button.adjust-position {
right: 6px;
top: 6px;
}

View File

@@ -13,15 +13,16 @@
<input formControlName="path" [attr.data-cy]="'volume-mount-path-'+i" matInput>
</mat-form-field>
<button [attr.data-cy]="'volume-mount-minus-'+i" class="adjust-position" mat-icon-button matTooltip="Delete Volume Mount" (click)="remove(i)">
<mat-icon class="tab-icon material-icons-outlined">delete_forever</mat-icon>
</button>
<app-volume
*ngIf="showNewVolume[i]"
(created)="onNewVolumeCreated(i, $event)"
></app-volume>
</ng-container>
</div>
<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="form.controls.length == 0" mat-flat-button (click)="add('', '')">Add Volume Mount</button>
<button data-cy="volume-mount-add" mat-flat-button (click)="add('', '')">Add Volume Mount</button>
</div>

View File

@@ -70,6 +70,10 @@ export class VolumeMountsComponent implements ControlValueAccessor, Validator {
this.form.push(this.newVolumeMount({name, path}));
}
remove(i: number) {
this.form.removeAt(i);
}
onNameChange(i: number, name: string) {
this.showNewVolume[i] = name == "!";
}

View File

@@ -14,12 +14,12 @@
</mat-form-field>
<h3>Command and Arguments</h3>
<div class="description">Command and Arguments can be used to override the entrypoint of the image</div>
<app-multi-text formControlName="command" label="Command" addLabel="Add command"></app-multi-text>
<app-multi-text formControlName="args" label="Arg" addLabel="Add arg"></app-multi-text>
<app-multi-text dataCyPrefix="container-command" formControlName="command" label="Command" addLabel="Add command" deleteLabel="Delete command"></app-multi-text>
<app-multi-text dataCyPrefix="container-arg" formControlName="args" label="Arg" addLabel="Add arg" deleteLabel="Delete arg"></app-multi-text>
<h3>Environment Variables</h3>
<div class="description">Environment Variables to define in the running container</div>
<app-multi-key-value dataCyPrefix="container-env" addLabel="Add Environment Variable" formControlName="env"></app-multi-key-value>
<app-multi-key-value dataCyPrefix="container-env" addLabel="Add Environment Variable" deleteLabel="Delete Environment Variable" formControlName="env"></app-multi-key-value>
<h3>Volume Mounts</h3>
<div class="description">Volumes to mount into the container's filesystem</div>
@@ -76,11 +76,11 @@
<h3>Deployment Annotations</h3>
<div class="description">Annotations added to the Kubernetes Deployment created for running this container</div>
<app-multi-key-value dataCyPrefix="container-deploy-anno" addLabel="Add Annotation" formControlName="deployAnnotations"></app-multi-key-value>
<app-multi-key-value dataCyPrefix="container-deploy-anno" addLabel="Add Annotation" deleteLabel="Delete Deployment Annotation" formControlName="deployAnnotations"></app-multi-key-value>
<h3>Service Annotations</h3>
<div class="description">Annotations added to the Kubernetes Service created for accessing this container</div>
<app-multi-key-value dataCyPrefix="container-svc-anno" addLabel="Add Annotation" formControlName="svcAnnotations"></app-multi-key-value>
<app-multi-key-value dataCyPrefix="container-svc-anno" addLabel="Add Annotation" deleteLabel="Delete Service Annotation" formControlName="svcAnnotations"></app-multi-key-value>
</div>
<div class="outbutton"><button data-cy="container-less-params" *ngIf="seeMore" mat-flat-button (click)="less()">Less parameters...</button></div>

View File

@@ -19,7 +19,7 @@
<mat-label><span>Image Name</span></mat-label>
<input placeholder="Reference to a container image" data-cy="image-image-name" matInput formControlName="imageName">
</mat-form-field>
<app-multi-text formControlName="args" title="Build Args" label="Arg" addLabel="Add Build Arg"></app-multi-text>
<app-multi-text dataCyPrefix="image-arg" formControlName="args" title="Build Args" label="Arg" addLabel="Add Build Arg" deleteLabel="Delete Build Args"></app-multi-text>
<mat-form-field appearance="outline" class="mid-width">
<mat-label><span>Build Context</span></mat-label>
<input placeholder="Directory from which the build will be executed" data-cy="image-build-context" matInput formControlName="buildContext">