mirror of https://github.com/rancher/dashboard.git
Vue3 fix workload storage (#12070)
* fix workload storage codemirror not rendering * workload storage default component yamleditor instead of codemirror * test editing projected vols * add container mount test * fix lint * refactor deployment tests to improve retry-ability * add to workoad storage tests and improve retry
This commit is contained in:
parent
3e89716a6e
commit
f28214f9c6
|
|
@ -25,6 +25,36 @@ export const createDeploymentBlueprint = {
|
|||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'test-vol',
|
||||
projected: {
|
||||
defaultMode: 420,
|
||||
sources: [
|
||||
{
|
||||
configMap: {
|
||||
items: [{ key: 'test-vol-key', path: 'test-vol-path' }],
|
||||
name: 'configmap-name'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'test-vol1',
|
||||
projected: {
|
||||
defaultMode: 420,
|
||||
sources: [
|
||||
{
|
||||
configMap: {
|
||||
items: [{ key: 'test-vol-key1', path: 'test-vol-path1' }],
|
||||
name: 'configmap-name1'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
metadata: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po';
|
||||
|
||||
export default class ButtonDropdownPo extends LabeledSelectPo {
|
||||
toggle() {
|
||||
return this.self().find('[data-testid="dropdown-button"]').click();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,9 @@ export default class LabeledSelectPo extends ComponentPo {
|
|||
}
|
||||
|
||||
clickLabel(label: string) {
|
||||
return this.getOptions().contains('li', label).click();
|
||||
const labelRegex = new RegExp(`^${ label } $`, 'g');
|
||||
|
||||
return this.getOptions().contains(labelRegex).click();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default class TabbedPo extends ComponentPo {
|
|||
}
|
||||
|
||||
clickTabWithSelector(selector: string) {
|
||||
return this.self().get(`${ selector }`).click();
|
||||
return this.self().find(`${ selector }`).click();
|
||||
}
|
||||
|
||||
allTabs() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import ComponentPo from '@/cypress/e2e/po/components/component.po';
|
||||
import ButtonDropdownPo from '@/cypress/e2e/po/components/button-dropdown.po';
|
||||
import InputPo from '@/cypress/e2e/po/components/input.po';
|
||||
|
||||
class ContainerMountPo extends ComponentPo {
|
||||
constructor(selector = '.dashboard-root') {
|
||||
super(selector);
|
||||
}
|
||||
|
||||
nthMountPoint(i: number) {
|
||||
return new InputPo(`[data-testid="mount-path-${ i }"] input:first-child`);
|
||||
}
|
||||
}
|
||||
|
||||
export default class ContainerMountPathPo extends ComponentPo {
|
||||
constructor(selector = '.dashboard-root') {
|
||||
super(selector);
|
||||
}
|
||||
|
||||
addVolumeButton() : ButtonDropdownPo {
|
||||
// return this.self().find('[data-testid="container-storage-add-button"]');
|
||||
return new ButtonDropdownPo('[data-testid="container-storage-add-button"]');
|
||||
}
|
||||
|
||||
addVolume(label: string) {
|
||||
this.addVolumeButton().toggle();
|
||||
this.addVolumeButton().clickOptionWithLabel(label);
|
||||
}
|
||||
|
||||
nthVolumeMount(i: number): ContainerMountPo {
|
||||
return new ContainerMountPo(`[data-testid="container-storage-mount-${ i }"]`);
|
||||
}
|
||||
|
||||
removeVolume(i: number) {
|
||||
this.self().find(`[data-testid="container-storage-array-list"] [data-testid="remove-item-${ i }"]`).click();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import ComponentPo from '@/cypress/e2e/po/components/component.po';
|
||||
import CodeMirrorPo from '@/cypress/e2e/po/components/code-mirror.po';
|
||||
|
||||
class WorkloadVolumePo extends ComponentPo {
|
||||
yamlEditor(): CodeMirrorPo {
|
||||
return CodeMirrorPo.bySelector(this.self(), '[data-testid="yaml-editor-code-mirror"]');
|
||||
}
|
||||
}
|
||||
|
||||
export default class WorkloadPodStoragePo extends ComponentPo {
|
||||
constructor(selector = '.dashboard-root') {
|
||||
super(selector);
|
||||
}
|
||||
|
||||
nthVolumeComponent(n: number) {
|
||||
return new WorkloadVolumePo(`[data-testid="volume-component-${ n }"]`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import CodeMirrorPo from '@/cypress/e2e/po/components/code-mirror.po';
|
||||
import ComponentPo from '@/cypress/e2e/po/components/component.po';
|
||||
|
||||
export default class YamlEditorPo extends ComponentPo {
|
||||
input(): CodeMirrorPo {
|
||||
return CodeMirrorPo.bySelector(this.self(), '[data-testid="yaml-editor-code-mirror"]');
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,11 @@ import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po';
|
|||
import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po';
|
||||
import WorkloadPagePo from '@/cypress/e2e/po/pages/explorer/workloads.po';
|
||||
import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po';
|
||||
import TabbedPo from '@/cypress/e2e/po/components/tabbed.po';
|
||||
import WorkloadPodStoragePo from '@/cypress/e2e/po/components/workloads/pod-storage.po';
|
||||
import ContainerMountPathPo from '@/cypress/e2e/po/components/workloads/container-mount-paths.po';
|
||||
import { WorkloadType } from '@shell/types/fleet';
|
||||
|
||||
export class workloadDetailsPageBasePo extends PagePo {
|
||||
static url: string;
|
||||
|
||||
|
|
@ -115,6 +119,10 @@ export class WorkloadsListPageBasePo extends PagePo {
|
|||
return this.sortableTable().rowActionMenuOpen(elemName).getMenuItem('Edit YAML').click();
|
||||
}
|
||||
|
||||
goToEditConfigPage(elemName: string) {
|
||||
return this.sortableTable().rowActionMenuOpen(elemName).getMenuItem('Edit Config').click();
|
||||
}
|
||||
|
||||
private workload() {
|
||||
return new WorkloadPagePo();
|
||||
}
|
||||
|
|
@ -164,6 +172,49 @@ export class WorkloadsCreatePageBasePo extends PagePo {
|
|||
return new AsyncButtonPo('[data-testid="form-save"]', this.self());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns po for the top level tabs in workloads ie general workload, pod, and one more per container
|
||||
*/
|
||||
horizontalTabs(): TabbedPo {
|
||||
return new TabbedPo('[data-testid="workload-horizontal-tabs"]');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns po for the vertical tabs within the first horizontal tab, ie non-pod workload configuration
|
||||
*/
|
||||
generalTabs(): TabbedPo {
|
||||
return new TabbedPo('[data-testid="workload-general-tabs"]');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns po for the vertical tabs within the pod tab
|
||||
*/
|
||||
podTabs(): TabbedPo {
|
||||
return new TabbedPo('[data-testid="workload-pod-tabs"]');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param containerIndex
|
||||
* @returns po for vertical tabs used to configure nth container
|
||||
*/
|
||||
nthContainerTabs(containerIndex: number) {
|
||||
this.horizontalTabs().clickTabWithSelector(`>ul>li:nth-child(${ containerIndex + 3 })`);
|
||||
|
||||
return new TabbedPo(`[data-testid="workload-container-tabs-${ containerIndex }"]`);
|
||||
}
|
||||
|
||||
podStorage(): WorkloadPodStoragePo {
|
||||
return new WorkloadPodStoragePo();
|
||||
}
|
||||
|
||||
containerStorage(): ContainerMountPathPo {
|
||||
return new ContainerMountPathPo();
|
||||
}
|
||||
|
||||
createWithUI(name: string, containerImage: string, namespace = 'default') {
|
||||
// NB: namespace is already selected by default
|
||||
this.selectNamespace(namespace);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ describe('Cluster Explorer', () => {
|
|||
let deploymentsListPage: WorkloadsDeploymentsListPagePo;
|
||||
let deploymentsCreatePage: WorkloadsDeploymentsCreatePagePo;
|
||||
|
||||
// collect name/namespace of all workloads created in this test suite & delete them afterwards
|
||||
// edit deployment tests each create a workload per run to improve their retryability
|
||||
const e2eWorkloads: { name: string; namespace: string; }[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -23,7 +25,10 @@ describe('Cluster Explorer', () => {
|
|||
});
|
||||
|
||||
it('should be able to create a new deployment with basic options', () => {
|
||||
const { name, namespace } = deploymentCreateRequest.metadata;
|
||||
const name = `e2e-deployment-${ Math.random().toString(36).substr(2, 6) }`;
|
||||
|
||||
deploymentCreateRequest.metadata.name = name;
|
||||
const { namespace } = deploymentCreateRequest.metadata;
|
||||
const containerImage = 'nginx';
|
||||
|
||||
deploymentsCreatePage.goTo();
|
||||
|
|
@ -31,7 +36,8 @@ describe('Cluster Explorer', () => {
|
|||
deploymentsCreatePage.createWithUI(name, containerImage, namespace);
|
||||
|
||||
cy.wait('@createDeployment').then(({ request, response }) => {
|
||||
expect(request.body).to.deep.eq(deploymentCreateRequest);
|
||||
// comparing pod spec instead of the entire request body to avoid needing to compare labels that include the dynamic test name
|
||||
expect(request.body.spec.template.spec).to.deep.eq(deploymentCreateRequest.spec.template.spec);
|
||||
expect(response.statusCode).to.eq(201);
|
||||
expect(response.body.metadata.name).to.eq(name);
|
||||
expect(response.body.metadata.namespace).to.eq(namespace);
|
||||
|
|
@ -43,16 +49,26 @@ describe('Cluster Explorer', () => {
|
|||
});
|
||||
|
||||
describe('Update: Deployments', () => {
|
||||
const { name: workloadName, namespace } = createDeploymentBlueprint.metadata;
|
||||
const workloadDetailsPage = new WorkloadsDeploymentsDetailsPagePo(workloadName);
|
||||
let workloadName;
|
||||
let workloadDetailsPage;
|
||||
|
||||
const { namespace } = createDeploymentBlueprint.metadata;
|
||||
let deploymentEditConfigPage;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('testWorkload');
|
||||
cy.intercept('GET', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('clonedPod');
|
||||
workloadName = `e2e-deployment-${ Math.random().toString(36).substr(2, 6) }`;
|
||||
const testDeployment = { ...createDeploymentBlueprint };
|
||||
|
||||
workloadDetailsPage = new WorkloadsDeploymentsDetailsPagePo(workloadName);
|
||||
|
||||
testDeployment.metadata.name = workloadName;
|
||||
deploymentsListPage.goTo();
|
||||
deploymentsListPage.createWithKubectl(createDeploymentBlueprint);
|
||||
|
||||
cy.intercept('PUT', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('editDeployment');
|
||||
deploymentsListPage.goTo();
|
||||
deploymentsListPage.goToEditConfigPage(workloadName);
|
||||
deploymentEditConfigPage = new WorkloadsDeploymentsCreatePagePo();
|
||||
// Collect the name of the workload for cleanup
|
||||
e2eWorkloads.push({ name: workloadName, namespace });
|
||||
});
|
||||
|
|
@ -61,10 +77,65 @@ describe('Cluster Explorer', () => {
|
|||
workloadDetailsPage.goTo();
|
||||
workloadDetailsPage.mastheadTitle().should('contain', workloadName);
|
||||
});
|
||||
|
||||
it('Should be able to view and edit configuration of pod volumes with no custom component', () => {
|
||||
// open the pod tab
|
||||
deploymentEditConfigPage.horizontalTabs().clickTabWithSelector('li#pod');
|
||||
|
||||
// open the pod storage tab
|
||||
deploymentEditConfigPage.podTabs().clickTabWithSelector('li#storage');
|
||||
|
||||
// check that there is a component rendered for each workload volume and that that component has rendered some information about the volume
|
||||
deploymentEditConfigPage.podStorage().nthVolumeComponent(0).yamlEditor().value()
|
||||
.should('contain', 'name: test-vol');
|
||||
deploymentEditConfigPage.podStorage().nthVolumeComponent(1).yamlEditor().value()
|
||||
.should('contain', 'name: test-vol1');
|
||||
|
||||
// now try editing
|
||||
deploymentEditConfigPage.podStorage().nthVolumeComponent(0).yamlEditor().set('name: test-vol-changed\nprojected:\n defaultMode: 420');
|
||||
|
||||
// verify that the list of volumes in the container tab has updated
|
||||
deploymentEditConfigPage.nthContainerTabs(0).clickTabWithSelector('li#storage');
|
||||
deploymentEditConfigPage.containerStorage().addVolumeButton().toggle();
|
||||
deploymentEditConfigPage.containerStorage().addVolumeButton().getOptions().should('contain', 'test-vol-changed (projected)');
|
||||
deploymentEditConfigPage.containerStorage().addVolumeButton().getOptions().should('not.contain', 'test-vol (projected)');
|
||||
|
||||
deploymentEditConfigPage.saveCreateForm().click();
|
||||
|
||||
cy.wait('@editDeployment').then(({ request, response }) => {
|
||||
expect(request.body.spec.template.spec.volumes[0]).to.deep.eq({ name: 'test-vol-changed', projected: { defaultMode: 420 } });
|
||||
expect(response.body.spec.template.spec.volumes[0]).to.deep.eq({ name: 'test-vol-changed', projected: { defaultMode: 420, sources: null } });
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to add container volume mounts', () => {
|
||||
deploymentEditConfigPage.nthContainerTabs(0).clickTabWithSelector('li#storage');
|
||||
|
||||
deploymentEditConfigPage.containerStorage().addVolume('test-vol1');
|
||||
|
||||
deploymentEditConfigPage.containerStorage().nthVolumeMount(0).nthMountPoint(0).set('test-123');
|
||||
|
||||
deploymentEditConfigPage.saveCreateForm().click();
|
||||
|
||||
cy.wait('@editDeployment').then(({ request, response }) => {
|
||||
expect(request.body.spec.template.spec.containers[0].volumeMounts).to.deep.eq([{ mountPath: 'test-123', name: 'test-vol1' }]);
|
||||
expect(response.body.spec.template.spec.containers[0].volumeMounts).to.deep.eq([{ mountPath: 'test-123', name: 'test-vol1' }]);
|
||||
});
|
||||
|
||||
// test removing volumes
|
||||
deploymentsListPage.goToEditConfigPage(workloadName);
|
||||
deploymentEditConfigPage.nthContainerTabs(0).clickTabWithSelector('li#storage');
|
||||
deploymentEditConfigPage.containerStorage().removeVolume(0);
|
||||
deploymentEditConfigPage.saveCreateForm().click();
|
||||
|
||||
cy.wait('@editDeployment').then(({ request, response }) => {
|
||||
expect(request.body.spec.template.spec.containers[0].volumeMounts).to.deep.eq([]);
|
||||
expect(response.body.spec.template.spec.containers[0].volumeMounts).to.eq(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('List: Deployments', () => {
|
||||
// To reduce test runtime, will use the same workload for all the tests
|
||||
it('Should list the workloads', () => {
|
||||
deploymentsListPage.goTo();
|
||||
e2eWorkloads.forEach(({ name }) => {
|
||||
|
|
@ -74,10 +145,9 @@ describe('Cluster Explorer', () => {
|
|||
});
|
||||
|
||||
describe('Delete: Deployments', () => {
|
||||
const deploymentName = deploymentCreateRequest.metadata.name;
|
||||
|
||||
// To reduce test runtime, will use the same workload for all the tests
|
||||
it('Should be able to delete a workload', () => {
|
||||
const deploymentName = e2eWorkloads[0].name;
|
||||
|
||||
deploymentsListPage.goTo();
|
||||
|
||||
deploymentsListPage.listElementWithName(deploymentName).should('exist');
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ export default {
|
|||
tabindex="-1"
|
||||
type="button"
|
||||
class="dropdown-button-two btn"
|
||||
data-testid="dropdown-button"
|
||||
@click="ddButtonAction(option)"
|
||||
@focus="focusSearch"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ export default {
|
|||
:show-tabs-add-remove="true"
|
||||
:default-tab="defaultTab"
|
||||
:flat="true"
|
||||
data-testid="workload-horizontal-tabs"
|
||||
@changed="changed"
|
||||
>
|
||||
<Tab
|
||||
|
|
@ -176,6 +177,7 @@ export default {
|
|||
<Tabbed
|
||||
:side-tabs="true"
|
||||
:weight="99"
|
||||
:data-testid="`workload-container-tabs-${i}`"
|
||||
>
|
||||
<Tab
|
||||
:label="t('workload.container.titles.general')"
|
||||
|
|
@ -347,13 +349,13 @@ export default {
|
|||
:weight="tabWeightMap['storage']"
|
||||
>
|
||||
<ContainerMountPaths
|
||||
v-model:value="podTemplateSpec"
|
||||
v-model:container="allContainers[i]"
|
||||
:value="podTemplateSpec"
|
||||
:namespace="value.metadata.namespace"
|
||||
:register-before-hook="registerBeforeHook"
|
||||
:mode="mode"
|
||||
:secrets="namespacedSecrets"
|
||||
:config-maps="namespacedConfigMaps"
|
||||
:container="allContainers[i]"
|
||||
:save-pvc-hook-name="savePvcHookName"
|
||||
@removePvcForm="clearPvcFormState"
|
||||
/>
|
||||
|
|
@ -366,7 +368,10 @@ export default {
|
|||
:name="nameDisplayFor(type)"
|
||||
:weight="99"
|
||||
>
|
||||
<Tabbed :side-tabs="true">
|
||||
<Tabbed
|
||||
data-testid="workload-general-tabs"
|
||||
:side-tabs="true"
|
||||
>
|
||||
<Tab
|
||||
name="labels"
|
||||
label-key="generic.labelsAndAnnotations"
|
||||
|
|
@ -404,13 +409,18 @@ export default {
|
|||
:name="'pod'"
|
||||
:weight="98"
|
||||
>
|
||||
<Tabbed :side-tabs="true">
|
||||
<Tabbed
|
||||
data-testid="workload-pod-tabs"
|
||||
:side-tabs="true"
|
||||
>
|
||||
<Tab
|
||||
:label="t('workload.storage.title')"
|
||||
name="storage"
|
||||
:weight="tabWeightMap['storage']"
|
||||
@active="$refs.storage.refresh()"
|
||||
>
|
||||
<Storage
|
||||
ref="storage"
|
||||
v-model:value="podTemplateSpec"
|
||||
:namespace="value.metadata.namespace"
|
||||
:register-before-hook="registerBeforeHook"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<script>
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { clone, set } from '@shell/utils/object';
|
||||
import { _VIEW } from '@shell/config/query-params';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
|
||||
import Mount from '@shell/edit/workload/storage/Mount';
|
||||
import ButtonDropdown from '@shell/components/ButtonDropdown';
|
||||
import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
||||
|
||||
export default {
|
||||
name: 'ContainerMountPaths',
|
||||
name: 'ContainerMountPaths',
|
||||
|
||||
emits: ['update:container'],
|
||||
|
||||
components: {
|
||||
ArrayListGrouped, ButtonDropdown, Mount
|
||||
},
|
||||
|
|
@ -36,13 +38,10 @@ export default {
|
|||
},
|
||||
|
||||
data() {
|
||||
// set volumeMount field
|
||||
this.initializeStorage();
|
||||
|
||||
return {
|
||||
containerVolumes: [],
|
||||
storageVolumes: this.getStorageVolumes(),
|
||||
selectedContainerVolumes: this.getSelectedContainerVolumes()
|
||||
};
|
||||
return { selectedContainerVolumes: this.getSelectedContainerVolumes() };
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -51,9 +50,9 @@ export default {
|
|||
},
|
||||
|
||||
availableVolumeOptions() {
|
||||
const containerVolumes = this.container.volumeMounts.map((item) => item.name);
|
||||
const containerVolumes = (this.container?.volumeMounts || []).map((item) => item.name);
|
||||
|
||||
return this.value.volumes.filter((vol) => !containerVolumes.includes(vol.name)).map((item) => {
|
||||
return (this.value?.volumes || []).filter((vol) => !containerVolumes.includes(vol.name)).map((item) => {
|
||||
return {
|
||||
label: `${ item.name } (${ this.headerFor(item) })`,
|
||||
action: this.selectVolume,
|
||||
|
|
@ -64,27 +63,13 @@ export default {
|
|||
},
|
||||
|
||||
watch: {
|
||||
value(neu, old) {
|
||||
this.selectedVolumes = this.getSelectedContainerVolumes();
|
||||
},
|
||||
storageVolumes(neu, old) {
|
||||
// removeObjects(this.value.volumes, old);
|
||||
// addObjects(this.value.volumes, neu);
|
||||
const names = neu.reduce((all, each) => {
|
||||
all.push(each.name);
|
||||
selectedContainerVolumes: {
|
||||
deep: true,
|
||||
handler(neu, old) {
|
||||
const names = neu.map((item) => item.name);
|
||||
|
||||
return all;
|
||||
}, []);
|
||||
|
||||
this.container.volumeMounts = this.container.volumeMounts.filter((mount) => names.includes(mount.name));
|
||||
},
|
||||
|
||||
selectedContainerVolumes(neu, old) {
|
||||
// removeObjects(this.value.volumes, old);
|
||||
// addObjects(this.value.volumes, neu);
|
||||
const names = neu.map((item) => item.name);
|
||||
|
||||
this.container.volumeMounts = this.container.volumeMounts.filter((mount) => names.includes(mount.name));
|
||||
this.container.volumeMounts = this.container.volumeMounts.filter((mount) => names.includes(mount.name));
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
|
@ -95,23 +80,9 @@ export default {
|
|||
*/
|
||||
initializeStorage() {
|
||||
if (!this.container.volumeMounts) {
|
||||
this.container['volumeMounts'] = [];
|
||||
set(this.container, 'volumeMounts', []);
|
||||
this.$emit('update:container', this.container);
|
||||
}
|
||||
if (!this.value.volumes) {
|
||||
this.value['volumes'] = [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get existing paired storage volumes
|
||||
*/
|
||||
getStorageVolumes() {
|
||||
// Extract volume mounts to map storage volumes
|
||||
const { volumeMounts = [] } = this.container;
|
||||
const names = volumeMounts.map(({ name }) => name);
|
||||
|
||||
// Extract storage volumes to allow mutation, if matches mount map
|
||||
return clone(this.value.volumes.filter((volume) => names.includes(volume.name)));
|
||||
},
|
||||
|
||||
getSelectedContainerVolumes() {
|
||||
|
|
@ -120,7 +91,7 @@ export default {
|
|||
const names = volumeMounts.map(({ name }) => name);
|
||||
|
||||
// Extract storage volumes to allow mutation, if matches mount map
|
||||
return clone(this.value.volumes.filter((volume) => names.includes(volume.name)));
|
||||
return clone((this.value?.volumes || []).filter((volume) => names.includes(volume.name)));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -133,39 +104,14 @@ export default {
|
|||
},
|
||||
|
||||
selectVolume(event) {
|
||||
const selectedVolume = this.value.volumes.find((vol) => vol.name === event.value);
|
||||
const selectedVolume = (this.value?.volumes || []).find((vol) => vol.name === event.value);
|
||||
|
||||
this.selectedContainerVolumes.push(selectedVolume);
|
||||
|
||||
const { name } = selectedVolume;
|
||||
|
||||
this.container.volumeMounts.push(name);
|
||||
},
|
||||
|
||||
addVolume(type) {
|
||||
const name = `vol-${ randomStr(5).toLowerCase() }`;
|
||||
|
||||
if (type === 'createPVC') {
|
||||
this.storageVolumes.push({
|
||||
_type: 'createPVC',
|
||||
persistentVolumeClaim: {},
|
||||
name,
|
||||
});
|
||||
} else if (type === 'csi') {
|
||||
this.storageVolumes.push({
|
||||
_type: type,
|
||||
csi: { volumeAttributes: {} },
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
this.storageVolumes.push({
|
||||
_type: type,
|
||||
[type]: {},
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
this.container.volumeMounts.push({ name });
|
||||
this.$emit('update:container', this.container);
|
||||
},
|
||||
|
||||
headerFor(value) {
|
||||
|
|
@ -182,13 +128,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
openPopover() {
|
||||
const button = this.$refs.buttonDropdown;
|
||||
|
||||
try {
|
||||
button.togglePopover();
|
||||
} catch (e) {}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -197,8 +136,11 @@ export default {
|
|||
<div>
|
||||
<!-- Storage Volumes -->
|
||||
<ArrayListGrouped
|
||||
:key="selectedContainerVolumes.length"
|
||||
v-model:value="selectedContainerVolumes"
|
||||
:add-allowed="false"
|
||||
:mode="mode"
|
||||
data-testid="container-storage-array-list"
|
||||
@remove="removeVolume"
|
||||
>
|
||||
<!-- Custom/default storage volume form -->
|
||||
|
|
@ -208,24 +150,22 @@ export default {
|
|||
:container="container"
|
||||
:name="props.row.value.name"
|
||||
:mode="mode"
|
||||
:data-testid="`container-storage-mount-${props.i}`"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Add Storage Volume -->
|
||||
<template #add>
|
||||
<ButtonDropdown
|
||||
v-if="!isView"
|
||||
id="add-volume"
|
||||
:button-label="t('workload.storage.selectVolume')"
|
||||
:dropdown-options="availableVolumeOptions"
|
||||
size="sm"
|
||||
@click-action="e=>selectVolume(e)"
|
||||
>
|
||||
<template #no-options>
|
||||
{{ t('workload.storage.noVolumes') }}
|
||||
</template>
|
||||
</ButtonDropdown>
|
||||
</template>
|
||||
</ArrayListGrouped>
|
||||
<ButtonDropdown
|
||||
v-if="!isView"
|
||||
id="add-volume"
|
||||
:button-label="t('workload.storage.selectVolume')"
|
||||
:dropdown-options="availableVolumeOptions"
|
||||
size="sm"
|
||||
data-testid="container-storage-add-button"
|
||||
@click-action="e=>selectVolume(e)"
|
||||
>
|
||||
<template #no-options>
|
||||
{{ t('workload.storage.noVolumes') }}
|
||||
</template>
|
||||
</ButtonDropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { _VIEW } from '@shell/config/query-params';
|
|||
import CodeMirror from '@shell/components/CodeMirror';
|
||||
import jsyaml from 'js-yaml';
|
||||
import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
||||
import YamlEditor, { EDITOR_MODES } from '@shell/components/YamlEditor.vue';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { uniq } from '@shell/utils/array';
|
||||
|
||||
|
|
@ -14,7 +15,11 @@ export default {
|
|||
emits: ['removePvcForm'],
|
||||
|
||||
components: {
|
||||
ArrayListGrouped, ButtonDropdown, Mount, CodeMirror
|
||||
ArrayListGrouped,
|
||||
ButtonDropdown,
|
||||
Mount,
|
||||
CodeMirror,
|
||||
YamlEditor
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
@ -110,21 +115,21 @@ export default {
|
|||
pvcNames() {
|
||||
return this.namespacedPvcs.map((pvc) => pvc.metadata.name);
|
||||
},
|
||||
|
||||
yamlEditorMode() {
|
||||
return this.isView ? EDITOR_MODES.VIEW_CODE : EDITOR_MODES.EDIT_CODE;
|
||||
}
|
||||
},
|
||||
|
||||
// watch: {
|
||||
// storageVolumes(neu, old) {
|
||||
// removeObjects(this.value.volumes, old);
|
||||
// addObjects(this.value.volumes, neu);
|
||||
// const names = neu.reduce((all, each) => {
|
||||
// all.push(each.name);
|
||||
|
||||
// return all;
|
||||
// }, []);
|
||||
|
||||
// this.container.volumeMounts = this.container.volumeMounts.filter(mount => names.includes(mount.name));
|
||||
// }
|
||||
// },
|
||||
// need to refresh codemirror when the tab is opened and hash change === tab change
|
||||
watch: {
|
||||
'$route.hash': {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
|
|
@ -223,14 +228,18 @@ export default {
|
|||
|
||||
// codemirror needs to refresh if it is in a tab that wasn't visible on page load
|
||||
refresh() {
|
||||
if (this.$refs.cm) {
|
||||
this.$refs.cm.forEach((component) => component.refresh());
|
||||
if (this.$refs) {
|
||||
// if a constant ref is assigned to the codemirror component in the template below, only the last instance of that codemirror component gets the ref
|
||||
const cmRefs = Object.keys(this.$refs).filter((ref) => ref.startsWith('cm-'));
|
||||
|
||||
cmRefs.forEach((r) => this.$refs[r].refresh());
|
||||
}
|
||||
},
|
||||
|
||||
removePvcForm(hookName) {
|
||||
this.$emit('removePvcForm', hookName);
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -260,13 +269,18 @@ export default {
|
|||
:register-before-hook="registerBeforeHook"
|
||||
:save-pvc-hook-name="savePvcHookName"
|
||||
:loading="loading"
|
||||
:data-testid="`volume-component-${props.i}`"
|
||||
@removePvcForm="removePvcForm"
|
||||
/>
|
||||
<div v-else-if="isView">
|
||||
<CodeMirror
|
||||
ref="cm"
|
||||
:value="yamlDisplay(props.row.value)"
|
||||
:options="{ readOnly: true, cursorBlinkRate: -1 }"
|
||||
<div
|
||||
v-else
|
||||
>
|
||||
<YamlEditor
|
||||
:ref="`cm-${props.i}`"
|
||||
v-model:value="props.row.value"
|
||||
:as-object="true"
|
||||
:data-testid="`volume-component-${props.i}`"
|
||||
:editor-mode="yamlEditorMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ export const defaultContainer = {
|
|||
readOnlyRootFilesystem: false,
|
||||
privileged: false,
|
||||
allowPrivilegeEscalation: false,
|
||||
}
|
||||
},
|
||||
volumeMounts: []
|
||||
};
|
||||
export default class Workload extends WorkloadService {
|
||||
// remove clone as yaml/edit as yaml until API supported
|
||||
|
|
|
|||
Loading…
Reference in New Issue