mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-01 03:36:04 +08:00
Latest changes from Harvester master - d7d9d4af8a88d677695d7aff47a81d52041dfcca
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
@@ -1,22 +1,35 @@
|
||||
<script>
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { CSI_DRIVER, LONGHORN, LONGHORN_DRIVER, LONGHORN_VERSION_V1, LONGHORN_VERSION_V2 } from '@shell/config/types';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabelValue from '@shell/components/LabelValue';
|
||||
import { BadgeState } from '@components/BadgeState';
|
||||
import { Banner } from '@components/Banner';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { RadioGroup, RadioButton } from '@components/Form/Radio';
|
||||
import HarvesterDisk from '../../mixins/harvester-disk';
|
||||
import Tags from '../../components/DiskTags';
|
||||
import { HCI } from '../../types';
|
||||
import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||
import { LONGHORN_SYSTEM } from './index';
|
||||
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
|
||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { LONGHORN_V2_DATA_ENGINE } from './index.vue';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
|
||||
const _NEW = '_NEW';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
LabelValue,
|
||||
BadgeState,
|
||||
Banner,
|
||||
RadioGroup,
|
||||
RadioButton,
|
||||
ModalWithCard,
|
||||
Tags,
|
||||
},
|
||||
|
||||
@@ -35,18 +48,95 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
node: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'edit',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
await allHash({
|
||||
csiDrivers: this.$store.dispatch(`${ inStore }/findAll`, { type: CSI_DRIVER }),
|
||||
lvmVolumeGroups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.LVM_VOLUME_GROUP }),
|
||||
});
|
||||
},
|
||||
|
||||
data() {
|
||||
let provisioner = `${ this.value.provisioner || LONGHORN_DRIVER }`;
|
||||
|
||||
if (provisioner === LONGHORN_DRIVER) {
|
||||
provisioner = `${ provisioner }_${ this.value.provisionerVersion || LONGHORN_VERSION_V1 }`;
|
||||
}
|
||||
|
||||
return {
|
||||
provisioner,
|
||||
volumeGroupDialog: null,
|
||||
randomStr: randomStr(10).toLowerCase(),
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
provisioners() {
|
||||
const out = [];
|
||||
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
|
||||
|
||||
csiDrivers.forEach(({ name }) => {
|
||||
switch (name) {
|
||||
case LONGHORN_DRIVER:
|
||||
out.push({
|
||||
label: `harvester.host.disk.storage.longhorn.${ LONGHORN_VERSION_V1 }.label`,
|
||||
value: `${ name }_${ LONGHORN_VERSION_V1 }`,
|
||||
});
|
||||
|
||||
if (this.longhornSystemVersion === LONGHORN_VERSION_V2 || this.value.provisionerVersion === LONGHORN_VERSION_V2) {
|
||||
out.push({
|
||||
label: `harvester.host.disk.storage.longhorn.${ LONGHORN_VERSION_V2 }.label`,
|
||||
value: `${ name }_${ LONGHORN_VERSION_V2 }`,
|
||||
disabled: this.forceLonghornV1
|
||||
});
|
||||
}
|
||||
break;
|
||||
case LVM_DRIVER:
|
||||
out.push({
|
||||
label: 'harvester.host.disk.storage.lvm.label',
|
||||
value: name,
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
lvmVolumeGroups() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const lvmVolumeGroups = this.$store.getters[`${ inStore }/all`](HCI.LVM_VOLUME_GROUP) || [];
|
||||
|
||||
const out = lvmVolumeGroups.filter(group => group.spec.nodeName === this.node.name).map(g => g.spec.vgName);
|
||||
|
||||
out.unshift({
|
||||
label: this.t('harvester.host.disk.lvmVolumeGroup.create'),
|
||||
value: _NEW,
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
targetDisk() {
|
||||
return this.disks.find(disk => disk.name === this.value.name);
|
||||
},
|
||||
|
||||
schedulableTooltipMessage() {
|
||||
const { name, path } = this.value;
|
||||
|
||||
@@ -56,6 +146,7 @@ export default {
|
||||
return this.schedulableCondition.message;
|
||||
}
|
||||
},
|
||||
|
||||
allowSchedulingOptions() {
|
||||
return [{
|
||||
label: this.t('generic.enabled'),
|
||||
@@ -87,7 +178,7 @@ export default {
|
||||
},
|
||||
|
||||
isProvisioned() {
|
||||
return this.blockDevice?.spec.fileSystem.provisioned;
|
||||
return this.blockDevice?.isProvisioned;
|
||||
},
|
||||
|
||||
forceFormattedDisabled() {
|
||||
@@ -153,8 +244,83 @@ export default {
|
||||
isFormatting() {
|
||||
return this.blockDevice.isFormatting;
|
||||
},
|
||||
|
||||
longhornSystemVersion() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const v2DataEngine = this.$store.getters[`${ inStore }/byId`](LONGHORN.SETTINGS, LONGHORN_V2_DATA_ENGINE) || {};
|
||||
|
||||
return v2DataEngine.value === 'true' ? LONGHORN_VERSION_V2 : LONGHORN_VERSION_V1;
|
||||
},
|
||||
|
||||
forceLonghornV1() {
|
||||
return this.node?.labels[HCI_LABELS_ANNOTATIONS.DISABLE_LONGHORN_V2_ENGINE] === 'true';
|
||||
},
|
||||
|
||||
isLvm() {
|
||||
return this.value.provisioner === LVM_DRIVER;
|
||||
},
|
||||
|
||||
isLonghorn() {
|
||||
return this.value.provisioner === LONGHORN_DRIVER;
|
||||
},
|
||||
|
||||
isLonghornV1() {
|
||||
return this.isLonghorn && this.value.provisionerVersion === LONGHORN_VERSION_V1;
|
||||
},
|
||||
|
||||
provisionerTooltip() {
|
||||
if (
|
||||
this.mode === _EDIT &&
|
||||
this.isLonghorn &&
|
||||
this.longhornSystemVersion === LONGHORN_VERSION_V2 &&
|
||||
this.forceLonghornV1
|
||||
) {
|
||||
return this.t('harvester.storage.storageClass.longhorn.versionTooltip');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
provisioner(value) {
|
||||
this.randomStr = randomStr(10).toLowerCase();
|
||||
|
||||
const [provisioner, provisionerVersion] = value?.split('_');
|
||||
|
||||
this.value.provisioner = provisioner;
|
||||
|
||||
if (provisioner === LONGHORN_DRIVER) {
|
||||
this.value.provisionerVersion = provisionerVersion || LONGHORN_VERSION_V1;
|
||||
} else {
|
||||
this.value.provisionerVersion = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
'value.lvmVolumeGroup'(neu) {
|
||||
if (neu === _NEW) {
|
||||
this.value.lvmVolumeGroup = null;
|
||||
this.showCreateVolumeGroup();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
showCreateVolumeGroup() {
|
||||
this.volumeGroupDialog = null;
|
||||
this.isOpen = true;
|
||||
},
|
||||
|
||||
hideCreateVolumeGroup() {
|
||||
this.isOpen = false;
|
||||
},
|
||||
|
||||
saveCreateVolumeGroup(buttonCb) {
|
||||
buttonCb(true);
|
||||
this.value.lvmVolumeGroup = this.volumeGroupDialog;
|
||||
this.hideCreateVolumeGroup();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.$emit('update:value', this.value);
|
||||
},
|
||||
@@ -176,7 +342,7 @@ export default {
|
||||
:label="t('harvester.host.disk.fileSystem.formatting')"
|
||||
/>
|
||||
<Banner
|
||||
v-else-if="isFormatted && !isCorrupted"
|
||||
v-else-if="isFormatted && isLonghornV1 && !isCorrupted"
|
||||
color="info"
|
||||
:label="formattedBannerLabel"
|
||||
/>
|
||||
@@ -256,8 +422,22 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(value.isNew && !isFormatted) || isCorrupted" class="row mt-10">
|
||||
<div class="col span-6">
|
||||
|
||||
<div class="row mt-10">
|
||||
<div :class="`col span-${ value.isNew ? '6': '12' }`">
|
||||
<LabeledSelect
|
||||
v-model:value="provisioner"
|
||||
:mode="mode"
|
||||
label-key="harvester.host.disk.provisioner"
|
||||
:localized-label="true"
|
||||
:searchable="true"
|
||||
:options="provisioners"
|
||||
:disabled="isProvisioned || !value.isNew"
|
||||
:tooltip="provisionerTooltip"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="(value.isNew && isLonghornV1 && !isFormatted) || isCorrupted" class="col span-6">
|
||||
<RadioGroup
|
||||
v-model:value="value.forceFormatted"
|
||||
:mode="mode"
|
||||
@@ -279,7 +459,44 @@ export default {
|
||||
</template>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div v-if="value.isNew && isLvm" class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="value.lvmVolumeGroup"
|
||||
:mode="mode"
|
||||
label-key="harvester.host.disk.lvmVolumeGroup.label"
|
||||
:localized-label="true"
|
||||
:searchable="false"
|
||||
:taggable="true"
|
||||
:multiple="false"
|
||||
:required="true"
|
||||
:disabled="isProvisioned"
|
||||
:options="lvmVolumeGroups"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ModalWithCard
|
||||
v-if="isOpen"
|
||||
:ref="randomStr"
|
||||
:name="randomStr"
|
||||
width="30%"
|
||||
@finish="saveCreateVolumeGroup"
|
||||
@close="hideCreateVolumeGroup"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('harvester.host.disk.lvmVolumeGroup.label') }}
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<LabeledInput
|
||||
v-model:value="volumeGroupDialog"
|
||||
:label="t('generic.name')"
|
||||
class="mb-20"
|
||||
required
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
/>
|
||||
</template>
|
||||
</ModalWithCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
||||
import ButtonDropdown from '@shell/components/ButtonDropdown';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||
import { LONGHORN, SECRET } from '@shell/config/types';
|
||||
import { LONGHORN, SECRET, LONGHORN_DRIVER, LONGHORN_VERSION_V1, LONGHORN_VERSION_V2 } from '@shell/config/types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { formatSi } from '@shell/utils/units';
|
||||
import { findBy } from '@shell/utils/array';
|
||||
@@ -29,9 +29,13 @@ import HarvesterDisk from './HarvesterDisk';
|
||||
import HarvesterSeeder from './HarvesterSeeder';
|
||||
import HarvesterKsmtuned from './HarvesterKsmtuned';
|
||||
import Tags from '../../components/DiskTags';
|
||||
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
export const LONGHORN_SYSTEM = 'longhorn-system';
|
||||
|
||||
export const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditNode',
|
||||
|
||||
@@ -66,10 +70,11 @@ export default {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const hash = {
|
||||
longhornNodes: this.$store.dispatch(`${ inStore }/findAll`, { type: LONGHORN.NODES }),
|
||||
blockDevices: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.BLOCK_DEVICE }),
|
||||
addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }),
|
||||
secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }),
|
||||
longhornNodes: this.$store.dispatch(`${ inStore }/findAll`, { type: LONGHORN.NODES }),
|
||||
blockDevices: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.BLOCK_DEVICE }),
|
||||
addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }),
|
||||
secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }),
|
||||
longhornV2DataEngine: this.$store.dispatch(`${ inStore }/find`, { type: LONGHORN.SETTINGS, id: LONGHORN_V2_DATA_ENGINE }),
|
||||
};
|
||||
|
||||
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.INVENTORY)) {
|
||||
@@ -80,23 +85,25 @@ export default {
|
||||
|
||||
const blockDevices = this.$store.getters[`${ inStore }/all`](HCI.BLOCK_DEVICE);
|
||||
const provisionedBlockDevices = blockDevices.filter((d) => {
|
||||
const provisioned = d?.spec?.fileSystem?.provisioned;
|
||||
const isCurrentNode = d?.spec?.nodeName === this.value.id;
|
||||
const isLonghornMounted = findBy(this.longhornDisks, 'name', d.metadata.name);
|
||||
|
||||
return provisioned && isCurrentNode && !isLonghornMounted;
|
||||
return d?.isProvisioned && isCurrentNode && !isLonghornMounted;
|
||||
})
|
||||
.map((d) => {
|
||||
const corrupted = d?.status?.deviceStatus?.fileSystem?.corrupted;
|
||||
|
||||
return {
|
||||
isNew: true,
|
||||
name: d?.metadata?.name,
|
||||
originPath: d?.spec?.fileSystem?.mountPoint,
|
||||
path: d?.spec?.fileSystem?.mountPoint,
|
||||
blockDevice: d,
|
||||
displayName: d?.displayName,
|
||||
forceFormatted: corrupted ? true : d?.spec?.fileSystem?.forceFormatted || false,
|
||||
isNew: true,
|
||||
name: d?.metadata?.name,
|
||||
originPath: d?.spec?.fileSystem?.mountPoint,
|
||||
path: d?.spec?.fileSystem?.mountPoint,
|
||||
blockDevice: d,
|
||||
displayName: d?.displayName,
|
||||
forceFormatted: corrupted ? true : d?.spec?.fileSystem?.forceFormatted || false,
|
||||
provisioner: d?.spec?.provisioner?.lvm ? LVM_DRIVER : LONGHORN_DRIVER,
|
||||
provisionerVersion: d?.spec?.provisioner?.longhorn?.engineVersion || LONGHORN_VERSION_V1,
|
||||
lvmVolumeGroup: d?.spec?.provisioner?.lvm?.vgName,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -160,6 +167,13 @@ export default {
|
||||
return out;
|
||||
},
|
||||
|
||||
longhornSystemVersion() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const v2DataEngine = this.$store.getters[`${ inStore }/byId`](LONGHORN.SETTINGS, LONGHORN_V2_DATA_ENGINE) || {};
|
||||
|
||||
return v2DataEngine.value === 'true' ? LONGHORN_VERSION_V2 : LONGHORN_VERSION_V1;
|
||||
},
|
||||
|
||||
longhornDisks() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `${ LONGHORN_SYSTEM }/${ this.value.id }`);
|
||||
@@ -180,16 +194,19 @@ export default {
|
||||
return {
|
||||
...diskStatus[key],
|
||||
...diskSpec?.[key],
|
||||
name: key,
|
||||
isNew: false,
|
||||
storageReserved: formatSi(diskSpec[key]?.storageReserved, formatOptions),
|
||||
storageAvailable: formatSi(diskStatus[key]?.storageAvailable, formatOptions),
|
||||
storageMaximum: formatSi(diskStatus[key]?.storageMaximum, formatOptions),
|
||||
storageScheduled: formatSi(diskStatus[key]?.storageScheduled, formatOptions),
|
||||
name: key,
|
||||
isNew: false,
|
||||
storageReserved: formatSi(diskSpec[key]?.storageReserved, formatOptions),
|
||||
storageAvailable: formatSi(diskStatus[key]?.storageAvailable, formatOptions),
|
||||
storageMaximum: formatSi(diskStatus[key]?.storageMaximum, formatOptions),
|
||||
storageScheduled: formatSi(diskStatus[key]?.storageScheduled, formatOptions),
|
||||
blockDevice,
|
||||
displayName: blockDevice?.displayName || key,
|
||||
forceFormatted: blockDevice?.spec?.fileSystem?.forceFormatted || false,
|
||||
tags: diskSpec?.[key]?.tags || [],
|
||||
displayName: blockDevice?.displayName || key,
|
||||
forceFormatted: blockDevice?.spec?.fileSystem?.forceFormatted || false,
|
||||
tags: diskSpec?.[key]?.tags || [],
|
||||
provisioner: blockDevice?.spec?.provisioner?.lvm ? LVM_DRIVER : LONGHORN_DRIVER,
|
||||
provisionerVersion: blockDevice?.spec?.provisioner?.longhorn?.engineVersion || LONGHORN_VERSION_V1,
|
||||
lvmVolumeGroup: blockDevice?.spec?.provisioner?.lvm?.vgName,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -197,7 +214,7 @@ export default {
|
||||
},
|
||||
|
||||
showFormattedWarning() {
|
||||
const out = this.newDisks.filter(d => d.forceFormatted && d.isNew) || [];
|
||||
const out = this.newDisks.filter(d => d.forceFormatted && d.isNew && d.provisionerVersion === LONGHORN_VERSION_V1) || [];
|
||||
|
||||
return out.length > 0;
|
||||
},
|
||||
@@ -316,15 +333,18 @@ export default {
|
||||
|
||||
this.newDisks.push({
|
||||
name,
|
||||
path: mountPoint,
|
||||
allowScheduling: false,
|
||||
evictionRequested: false,
|
||||
storageReserved: 0,
|
||||
isNew: true,
|
||||
originPath: disk?.spec?.fileSystem?.mountPoint,
|
||||
blockDevice: disk,
|
||||
displayName: disk?.displayName,
|
||||
path: mountPoint,
|
||||
allowScheduling: false,
|
||||
evictionRequested: false,
|
||||
storageReserved: 0,
|
||||
isNew: true,
|
||||
originPath: disk?.spec?.fileSystem?.mountPoint,
|
||||
blockDevice: disk,
|
||||
displayName: disk?.displayName,
|
||||
forceFormatted,
|
||||
provisioner: LONGHORN_DRIVER,
|
||||
provisionerVersion: LONGHORN_VERSION_V1,
|
||||
lvmVolumeGroup: null,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -338,13 +358,10 @@ export default {
|
||||
} else if (addDisks.length !== 0 && removeDisks.length === 0) {
|
||||
const updatedDisks = addDisks.filter((d) => {
|
||||
const blockDevice = this.$store.getters[`${ inStore }/byId`](HCI.BLOCK_DEVICE, `${ LONGHORN_SYSTEM }/${ d.name }`);
|
||||
const { provisioned, forceFormatted } = blockDevice.spec.fileSystem;
|
||||
const { forceFormatted } = blockDevice.spec.fileSystem;
|
||||
const { provisioner } = blockDevice.spec;
|
||||
|
||||
if (provisioned && forceFormatted === d.forceFormatted) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return !(blockDevice.isProvisioned && forceFormatted === d.forceFormatted && isEqual(provisioner, d.provisioner));
|
||||
});
|
||||
|
||||
if (updatedDisks.length === 0) {
|
||||
@@ -356,16 +373,25 @@ export default {
|
||||
await Promise.all(addDisks.map((d) => {
|
||||
const blockDevice = this.$store.getters[`${ inStore }/byId`](HCI.BLOCK_DEVICE, `${ LONGHORN_SYSTEM }/${ d.name }`);
|
||||
|
||||
blockDevice.spec.fileSystem.provisioned = true;
|
||||
blockDevice.spec.provision = true;
|
||||
blockDevice.spec.fileSystem.forceFormatted = d.forceFormatted;
|
||||
|
||||
switch (d.provisioner) {
|
||||
case LONGHORN_DRIVER:
|
||||
blockDevice.spec.provisioner = { longhorn: { engineVersion: d.provisionerVersion } };
|
||||
break;
|
||||
case LVM_DRIVER:
|
||||
blockDevice.spec.provisioner = { lvm: { vgName: d.lvmVolumeGroup } };
|
||||
break;
|
||||
}
|
||||
|
||||
return blockDevice.save();
|
||||
}));
|
||||
|
||||
await Promise.all(removeDisks.map((d) => {
|
||||
const blockDevice = this.$store.getters[`${ inStore }/byId`](HCI.BLOCK_DEVICE, `${ LONGHORN_SYSTEM }/${ d.name }`);
|
||||
|
||||
blockDevice.spec.fileSystem.provisioned = false;
|
||||
blockDevice.spec.provision = false;
|
||||
|
||||
return blockDevice.save();
|
||||
}));
|
||||
@@ -418,7 +444,7 @@ export default {
|
||||
if ((!findBy(this.disks || [], 'name', d.metadata.name) &&
|
||||
d?.spec?.nodeName === this.value.id &&
|
||||
(!addedToNodeCondition || addedToNodeCondition?.status === 'False') &&
|
||||
!d.spec?.fileSystem?.provisioned &&
|
||||
!d?.isProvisioned &&
|
||||
!isAdded) ||
|
||||
isRemoved
|
||||
) {
|
||||
@@ -573,6 +599,7 @@ export default {
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
:disks="disks"
|
||||
:node="value"
|
||||
/>
|
||||
</template>
|
||||
<template #add>
|
||||
|
||||
@@ -13,13 +13,23 @@ import Loading from '@shell/components/Loading';
|
||||
|
||||
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
||||
import { mapFeature, UNSUPPORTED_STORAGE_DRIVERS } from '@shell/store/features';
|
||||
import { STORAGE_CLASS, LONGHORN } from '@shell/config/types';
|
||||
import { STORAGE_CLASS, LONGHORN, LONGHORN_DRIVER, SECRET, NAMESPACE } from '@shell/config/types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { CSI_DRIVER } from '../../types';
|
||||
import Tags from '../../components/DiskTags';
|
||||
|
||||
const LONGHORN_DRIVER = 'driver.longhorn.io';
|
||||
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
|
||||
|
||||
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
||||
|
||||
export const DATA_ENGINE_V1 = 'v1';
|
||||
export const DATA_ENGINE_V2 = 'v2';
|
||||
|
||||
export const LVM_TOPOLOGY_LABEL = 'topology.lvm.csi/node';
|
||||
|
||||
const VOLUME_BINDING_MODE_IMMEDIATE = 'Immediate';
|
||||
const VOLUME_BINDING_MODE_WAIT = 'WaitForFirstConsumer';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterStorage',
|
||||
@@ -66,47 +76,63 @@ export default {
|
||||
const volumeBindingModeOptions = [
|
||||
{
|
||||
label: this.t('storageClass.customize.volumeBindingMode.now'),
|
||||
value: 'Immediate'
|
||||
value: VOLUME_BINDING_MODE_IMMEDIATE
|
||||
},
|
||||
{
|
||||
label: this.t('harvester.storage.customize.volumeBindingMode.later'),
|
||||
value: 'WaitForFirstConsumer'
|
||||
value: VOLUME_BINDING_MODE_WAIT
|
||||
}
|
||||
];
|
||||
|
||||
const allowedTopologies = clone(this.value.allowedTopologies?.[0]?.matchLabelExpressions || []);
|
||||
const allowedTopologies = clone(this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).filter(t => t.key !== LVM_TOPOLOGY_LABEL);
|
||||
|
||||
this.value['parameters'] = this.value.parameters || {};
|
||||
this.value['provisioner'] = this.value.provisioner || LONGHORN_DRIVER;
|
||||
this.value['allowVolumeExpansion'] = this.value.allowVolumeExpansion || allowVolumeExpansionOptions[0].value;
|
||||
this.value['reclaimPolicy'] = this.value.reclaimPolicy || reclaimPolicyOptions[0].value;
|
||||
this.value['volumeBindingMode'] = this.value.volumeBindingMode || volumeBindingModeOptions[0].value;
|
||||
|
||||
if (this.value.provisioner === LONGHORN_DRIVER) {
|
||||
this.value['parameters']['dataEngine'] = this.value.longhornVersion;
|
||||
this.value['volumeBindingMode'] = this.value.volumeBindingMode || VOLUME_BINDING_MODE_IMMEDIATE;
|
||||
}
|
||||
|
||||
if (this.value.provisioner === LVM_DRIVER) {
|
||||
this.value['volumeBindingMode'] = this.value.volumeBindingMode || VOLUME_BINDING_MODE_WAIT;
|
||||
}
|
||||
|
||||
let provisioner = `${ this.value.provisioner || LONGHORN_DRIVER }`;
|
||||
|
||||
if (provisioner === LONGHORN_DRIVER) {
|
||||
provisioner = `${ provisioner }_${ this.value.longhornVersion }`;
|
||||
}
|
||||
|
||||
return {
|
||||
LVM_DRIVER,
|
||||
reclaimPolicyOptions,
|
||||
allowVolumeExpansionOptions,
|
||||
volumeBindingModeOptions,
|
||||
mountOptions: [],
|
||||
provisioner: LONGHORN_DRIVER,
|
||||
STORAGE_CLASS,
|
||||
provisioner,
|
||||
allowedTopologies,
|
||||
defaultAddValue: {
|
||||
key: '',
|
||||
values: [],
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const hash = {
|
||||
storages: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
||||
longhornNodes: this.$store.dispatch(`${ inStore }/findAll`, { type: LONGHORN.NODES }),
|
||||
csiDrivers: this.$store.dispatch(`${ inStore }/findAll`, { type: CSI_DRIVER }),
|
||||
};
|
||||
|
||||
await allHash(hash);
|
||||
await allHash({
|
||||
namespaces: this.$store.dispatch(`${ inStore }/findAll`, { type: NAMESPACE }),
|
||||
secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }),
|
||||
storages: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
||||
longhornNodes: this.$store.dispatch(`${ inStore }/findAll`, { type: LONGHORN.NODES }),
|
||||
csiDrivers: this.$store.dispatch(`${ inStore }/findAll`, { type: CSI_DRIVER }),
|
||||
longhornV2DataEngine: this.$store.dispatch(`${ inStore }/find`, { type: LONGHORN.SETTINGS, id: LONGHORN_V2_DATA_ENGINE }),
|
||||
});
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -120,20 +146,37 @@ export default {
|
||||
return this.isCreate ? _CREATE : _VIEW;
|
||||
},
|
||||
|
||||
provisionerWatch() {
|
||||
return this.value.provisioner;
|
||||
},
|
||||
|
||||
provisioners() {
|
||||
const csiDrivers = this.$store.getters[`${ this.inStore }/all`](CSI_DRIVER) || [];
|
||||
const format = { [LONGHORN_DRIVER]: 'storageClass.longhorn.title' };
|
||||
const out = [];
|
||||
|
||||
return csiDrivers.map((provisioner) => {
|
||||
return {
|
||||
label: format[provisioner.name] || provisioner.name,
|
||||
value: provisioner.name,
|
||||
};
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
|
||||
|
||||
csiDrivers.forEach(({ name }) => {
|
||||
switch (name) {
|
||||
case LONGHORN_DRIVER:
|
||||
out.push({
|
||||
label: `harvester.storage.storageClass.longhorn.${ DATA_ENGINE_V1 }.label`,
|
||||
value: `${ name }_${ DATA_ENGINE_V1 }`,
|
||||
});
|
||||
|
||||
if (this.longhornSystemVersion === DATA_ENGINE_V2 || this.value.longhornVersion === DATA_ENGINE_V2) {
|
||||
out.push({
|
||||
label: `harvester.storage.storageClass.longhorn.${ DATA_ENGINE_V2 }.label`,
|
||||
value: `${ name }_${ DATA_ENGINE_V2 }`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case LVM_DRIVER:
|
||||
out.push({
|
||||
label: 'harvester.storage.storageClass.lvm.label',
|
||||
value: name,
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
schema() {
|
||||
@@ -141,11 +184,41 @@ export default {
|
||||
|
||||
return this.$store.getters[`${ inStore }/schemaFor`](STORAGE_CLASS);
|
||||
},
|
||||
|
||||
longhornSystemVersion() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const v2DataEngine = this.$store.getters[`${ inStore }/byId`](LONGHORN.SETTINGS, LONGHORN_V2_DATA_ENGINE) || {};
|
||||
|
||||
return v2DataEngine.value === 'true' ? DATA_ENGINE_V2 : DATA_ENGINE_V1;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
provisionerWatch() {
|
||||
this.value['parameters'] = {};
|
||||
provisioner(neu) {
|
||||
const [provisioner, dataEngine] = neu?.split('_');
|
||||
|
||||
let parameters = {};
|
||||
|
||||
if (provisioner === LVM_DRIVER) {
|
||||
const matchLabelExpressions = (this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).filter(t => t.key !== LVM_TOPOLOGY_LABEL);
|
||||
|
||||
if (matchLabelExpressions.length > 0) {
|
||||
this.value['allowedTopologies'] = [{ matchLabelExpressions }];
|
||||
} else {
|
||||
delete this.value.allowedTopologies;
|
||||
}
|
||||
|
||||
this.value['volumeBindingMode'] = VOLUME_BINDING_MODE_WAIT;
|
||||
}
|
||||
|
||||
if (provisioner === LONGHORN_DRIVER) {
|
||||
parameters = { dataEngine };
|
||||
this.value['volumeBindingMode'] = VOLUME_BINDING_MODE_IMMEDIATE;
|
||||
}
|
||||
|
||||
this.value['provisioner'] = provisioner;
|
||||
this.value['allowVolumeExpansion'] = this.value.provisioner === LONGHORN_DRIVER;
|
||||
this.value['parameters'] = parameters;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -162,11 +235,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
updateProvisioner(provisioner) {
|
||||
this.value['provisioner'] = provisioner;
|
||||
this.value['allowVolumeExpansion'] = provisioner === LONGHORN_DRIVER;
|
||||
},
|
||||
|
||||
willSave() {
|
||||
Object.keys(this.value.parameters).forEach((key) => {
|
||||
if (this.value.parameters[key] === null || this.value.parameters[key] === '') {
|
||||
@@ -178,10 +246,15 @@ export default {
|
||||
},
|
||||
|
||||
formatAllowedTopoloties() {
|
||||
const neu = this.allowedTopologies;
|
||||
const neu = this.allowedTopologies.filter(t => t.key !== LVM_TOPOLOGY_LABEL);
|
||||
const lvmMatchExpression = (this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).filter(t => t.key === LVM_TOPOLOGY_LABEL);
|
||||
|
||||
if (!neu || neu.length === 0) {
|
||||
delete this.value.allowedTopologies;
|
||||
if (lvmMatchExpression.length > 0) {
|
||||
this.value.allowedTopologies = [{ matchLabelExpressions: lvmMatchExpression }];
|
||||
} else {
|
||||
delete this.value.allowedTopologies;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +262,7 @@ export default {
|
||||
const matchLabelExpressions = neu.filter(R => !!R.key.trim() && (R.values.length > 0 && !R.values.find(V => !V.trim())));
|
||||
|
||||
if (matchLabelExpressions.length > 0) {
|
||||
this.value.allowedTopologies = [{ matchLabelExpressions }];
|
||||
this.value.allowedTopologies = [{ matchLabelExpressions: [...matchLabelExpressions, ...lvmMatchExpression] }];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +292,7 @@ export default {
|
||||
:register-before-hook="registerBeforeHook"
|
||||
/>
|
||||
<LabeledSelect
|
||||
:value="value.provisioner"
|
||||
v-model:value="provisioner"
|
||||
label="Provisioner"
|
||||
:options="provisioners"
|
||||
:localized-label="true"
|
||||
@@ -227,13 +300,12 @@ export default {
|
||||
:searchable="true"
|
||||
:taggable="true"
|
||||
class="mb-20"
|
||||
@update:value="updateProvisioner($event)"
|
||||
/>
|
||||
<Tabbed :side-tabs="true">
|
||||
<Tab name="parameters" :label="t('storageClass.parameters.label')" :weight="2">
|
||||
<component
|
||||
:is="getComponent(value.provisioner)"
|
||||
:key="value.provisioner"
|
||||
:is="getComponent(provisioner)"
|
||||
:key="provisioner"
|
||||
:value="value"
|
||||
:mode="modeOverride"
|
||||
:real-mode="realMode"
|
||||
@@ -268,6 +340,7 @@ export default {
|
||||
:label="t('storageClass.customize.volumeBindingMode.label')"
|
||||
:mode="modeOverride"
|
||||
:options="volumeBindingModeOptions"
|
||||
:disabled="provisioner === LVM_DRIVER"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+16
-13
@@ -3,11 +3,12 @@ import KeyValue from '@shell/components/form/KeyValue';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import RadioGroup from '@components/Form/Radio/RadioGroup';
|
||||
import { SECRET, NAMESPACE, LONGHORN } from '@shell/config/types';
|
||||
import { SECRET, LONGHORN } from '@shell/config/types';
|
||||
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
||||
import { CSI_SECRETS } from '@pkg/harvester/config/harvester-map';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { uniq } from '@shell/utils/array';
|
||||
import { DATA_ENGINE_V1 } from '../index.vue';
|
||||
|
||||
// UI components for Longhorn storage class parameters
|
||||
const DEFAULT_PARAMETERS = [
|
||||
@@ -17,6 +18,7 @@ const DEFAULT_PARAMETERS = [
|
||||
'nodeSelector',
|
||||
'migratable',
|
||||
'encrypted',
|
||||
'dataEngine',
|
||||
];
|
||||
|
||||
const {
|
||||
@@ -29,7 +31,7 @@ const {
|
||||
} = CSI_SECRETS;
|
||||
|
||||
export default {
|
||||
name: 'DriverLonghornIO',
|
||||
name: 'DriverLonghornIOV1',
|
||||
|
||||
components: {
|
||||
KeyValue,
|
||||
@@ -53,16 +55,6 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
await this.$store.dispatch(`${ inStore }/findAll`, { type: NAMESPACE });
|
||||
|
||||
const allSecrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
|
||||
|
||||
// only show non-system secret to user to select
|
||||
this.secrets = allSecrets.filter(secret => secret.isSystem === false);
|
||||
},
|
||||
data() {
|
||||
if (this.realMode === _CREATE) {
|
||||
this.value['parameters'] = {
|
||||
@@ -72,12 +64,23 @@ export default {
|
||||
nodeSelector: null,
|
||||
encrypted: 'false',
|
||||
migratable: 'true',
|
||||
dataEngine: DATA_ENGINE_V1
|
||||
};
|
||||
}
|
||||
|
||||
return { secrets: [] };
|
||||
return { };
|
||||
},
|
||||
|
||||
computed: {
|
||||
secrets() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const allSecrets = this.$store.getters[`${ inStore }/all`](SECRET);
|
||||
|
||||
// only show non-system secret to user to select
|
||||
return allSecrets.filter(secret => secret.isSystem === false);
|
||||
},
|
||||
|
||||
longhornNodes() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
<script>
|
||||
import KeyValue from '@shell/components/form/KeyValue';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import RadioGroup from '@components/Form/Radio/RadioGroup';
|
||||
import { SECRET, LONGHORN } from '@shell/config/types';
|
||||
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
||||
import { CSI_SECRETS } from '@pkg/harvester/config/harvester-map';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { uniq } from '@shell/utils/array';
|
||||
import { DATA_ENGINE_V2 } from '../index.vue';
|
||||
|
||||
// UI components for Longhorn storage class parameters
|
||||
const DEFAULT_PARAMETERS = [
|
||||
'numberOfReplicas',
|
||||
'staleReplicaTimeout',
|
||||
'diskSelector',
|
||||
'nodeSelector',
|
||||
'migratable',
|
||||
'encrypted',
|
||||
'dataEngine',
|
||||
];
|
||||
|
||||
const {
|
||||
CSI_PROVISIONER_SECRET_NAME,
|
||||
CSI_PROVISIONER_SECRET_NAMESPACE,
|
||||
CSI_NODE_PUBLISH_SECRET_NAME,
|
||||
CSI_NODE_PUBLISH_SECRET_NAMESPACE,
|
||||
CSI_NODE_STAGE_SECRET_NAME,
|
||||
CSI_NODE_STAGE_SECRET_NAMESPACE
|
||||
} = CSI_SECRETS;
|
||||
|
||||
export default {
|
||||
name: 'DriverLonghornIOV2',
|
||||
|
||||
components: {
|
||||
KeyValue,
|
||||
LabeledSelect,
|
||||
LabeledInput,
|
||||
RadioGroup,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
realMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
if (this.realMode === _CREATE) {
|
||||
this.value['parameters'] = {
|
||||
numberOfReplicas: '3',
|
||||
staleReplicaTimeout: '30',
|
||||
diskSelector: null,
|
||||
nodeSelector: null,
|
||||
encrypted: 'false',
|
||||
migratable: 'false',
|
||||
dataEngine: DATA_ENGINE_V2
|
||||
};
|
||||
}
|
||||
|
||||
return { };
|
||||
},
|
||||
|
||||
computed: {
|
||||
secrets() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const allSecrets = this.$store.getters[`${ inStore }/all`](SECRET);
|
||||
|
||||
// only show non-system secret to user to select
|
||||
return allSecrets.filter(secret => secret.isSystem === false);
|
||||
},
|
||||
|
||||
longhornNodes() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return this.$store.getters[`${ inStore }/all`](LONGHORN.NODES);
|
||||
},
|
||||
|
||||
nodeTags() {
|
||||
return (this.longhornNodes || []).reduce((sum, node) => {
|
||||
const tags = node.spec?.tags || [];
|
||||
|
||||
return uniq([...sum, ...tags]);
|
||||
}, []);
|
||||
},
|
||||
|
||||
diskTags() {
|
||||
return (this.longhornNodes || []).reduce((sum, node) => {
|
||||
const disks = node.spec?.disks;
|
||||
|
||||
const tagsOfNode = Object.keys(disks).reduce((sum, key) => {
|
||||
const tags = disks[key]?.tags || [];
|
||||
|
||||
return uniq([...sum, ...tags]);
|
||||
}, []);
|
||||
|
||||
return uniq([...sum, ...tagsOfNode]);
|
||||
}, []);
|
||||
},
|
||||
|
||||
isView() {
|
||||
return this.mode === _VIEW;
|
||||
},
|
||||
|
||||
migratableOptions() {
|
||||
return [{
|
||||
label: this.t('generic.yes'),
|
||||
value: 'true'
|
||||
}, {
|
||||
label: this.t('generic.no'),
|
||||
value: 'false'
|
||||
}];
|
||||
},
|
||||
|
||||
secretOptions() {
|
||||
return this.secrets.map(secret => secret.id);
|
||||
},
|
||||
|
||||
volumeEncryptionOptions() {
|
||||
return [{
|
||||
label: this.t('generic.yes'),
|
||||
value: 'true'
|
||||
}, {
|
||||
label: this.t('generic.no'),
|
||||
value: 'false'
|
||||
}];
|
||||
},
|
||||
|
||||
parameters: {
|
||||
get() {
|
||||
const parameters = clone(this.value?.parameters) || {};
|
||||
|
||||
DEFAULT_PARAMETERS.forEach((key) => {
|
||||
delete parameters[key];
|
||||
});
|
||||
|
||||
Object.values(CSI_SECRETS).forEach((key) => {
|
||||
delete parameters[key];
|
||||
});
|
||||
|
||||
return parameters;
|
||||
},
|
||||
|
||||
set(value) {
|
||||
Object.assign(this.value.parameters, value);
|
||||
}
|
||||
},
|
||||
|
||||
volumeEncryption: {
|
||||
set(neu) {
|
||||
this.value['parameters'] = {
|
||||
...this.value.parameters,
|
||||
encrypted: neu
|
||||
};
|
||||
},
|
||||
|
||||
get() {
|
||||
return this.value?.parameters?.encrypted || 'false';
|
||||
}
|
||||
},
|
||||
|
||||
secret: {
|
||||
get() {
|
||||
const selectedNs = this.value.parameters[CSI_PROVISIONER_SECRET_NAMESPACE];
|
||||
const selectedName = this.value.parameters[CSI_PROVISIONER_SECRET_NAME];
|
||||
|
||||
if (selectedNs && selectedName) {
|
||||
return `${ selectedNs }/${ selectedName }`;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
set(selectedSecret) {
|
||||
const [namespace, name] = selectedSecret.split('/');
|
||||
|
||||
this.value['parameters'] = {
|
||||
...this.value.parameters,
|
||||
[CSI_PROVISIONER_SECRET_NAME]: name,
|
||||
[CSI_NODE_PUBLISH_SECRET_NAME]: name,
|
||||
[CSI_NODE_STAGE_SECRET_NAME]: name,
|
||||
[CSI_PROVISIONER_SECRET_NAMESPACE]: namespace,
|
||||
[CSI_NODE_PUBLISH_SECRET_NAMESPACE]: namespace,
|
||||
[CSI_NODE_STAGE_SECRET_NAMESPACE]: namespace
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
nodeSelector: {
|
||||
get() {
|
||||
const nodeSelector = this.value?.parameters?.nodeSelector;
|
||||
|
||||
if ((nodeSelector || '').includes(',')) {
|
||||
return nodeSelector.split(',');
|
||||
} else if (nodeSelector) {
|
||||
return [nodeSelector];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
set(value) {
|
||||
this.value.parameters.nodeSelector = (value || []).join(',');
|
||||
}
|
||||
},
|
||||
|
||||
diskSelector: {
|
||||
get() {
|
||||
const diskSelector = this.value?.parameters?.diskSelector;
|
||||
|
||||
if ((diskSelector || '').includes(',')) {
|
||||
return diskSelector.split(',');
|
||||
} else if (diskSelector) {
|
||||
return [diskSelector];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
set(value) {
|
||||
this.value.parameters.diskSelector = (value || []).join(',');
|
||||
}
|
||||
},
|
||||
|
||||
numberOfReplicas: {
|
||||
get() {
|
||||
return this.value?.parameters?.numberOfReplicas;
|
||||
},
|
||||
|
||||
set(value) {
|
||||
if (value >= 1 && value <= 3) {
|
||||
this.value.parameters.numberOfReplicas = String(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model:value="numberOfReplicas"
|
||||
:label="t('harvester.storage.parameters.numberOfReplicas.label')"
|
||||
:required="true"
|
||||
:mode="mode"
|
||||
min="1"
|
||||
max="3"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model:value="value.parameters.staleReplicaTimeout"
|
||||
:label="t('harvester.storage.parameters.staleReplicaTimeout.label')"
|
||||
:required="true"
|
||||
:mode="mode"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-10">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="nodeSelector"
|
||||
:label="t('harvester.storage.parameters.nodeSelector.label')"
|
||||
:options="nodeTags"
|
||||
:taggable="true"
|
||||
:multiple="true"
|
||||
:mode="mode"
|
||||
>
|
||||
<template #no-options="{ searching }">
|
||||
<span v-if="!searching" class="text-muted">
|
||||
{{ t('harvester.storage.parameters.nodeSelector.no-options', null, true) }}
|
||||
</span>
|
||||
</template>
|
||||
</LabeledSelect>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="diskSelector"
|
||||
:label="t('harvester.storage.parameters.diskSelector.label')"
|
||||
:options="diskTags"
|
||||
:taggable="true"
|
||||
:multiple="true"
|
||||
:mode="mode"
|
||||
>
|
||||
<template #no-options="{ searching }">
|
||||
<span v-if="!searching" class="text-muted">
|
||||
{{ t('harvester.storage.parameters.diskSelector.no-options', null, true) }}
|
||||
</span>
|
||||
</template>
|
||||
</LabeledSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-20">
|
||||
<RadioGroup
|
||||
v-model:value="value.parameters.migratable"
|
||||
name="layer3NetworkMode"
|
||||
:label="t('harvester.storage.parameters.migratable.label')"
|
||||
:mode="mode"
|
||||
:options="migratableOptions"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="row mt-20">
|
||||
<RadioGroup
|
||||
v-model:value="volumeEncryption"
|
||||
name="volumeEncryption"
|
||||
:label="t('harvester.storage.volumeEncryption')"
|
||||
:mode="mode"
|
||||
:options="volumeEncryptionOptions"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="value.parameters.encrypted === 'true'" class="row mt-20">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="secret"
|
||||
:label="t('harvester.storage.secret')"
|
||||
:options="secretOptions"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<KeyValue
|
||||
v-model:value="parameters"
|
||||
:add-label="t('storageClass.longhorn.addLabel')"
|
||||
:read-allowed="false"
|
||||
:mode="mode"
|
||||
class="mt-10"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.labeled-input.compact-input {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
</style>
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<script>
|
||||
|
||||
import KeyValue from '@shell/components/form/KeyValue';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { HCI } from '../../../types';
|
||||
import { NODE } from '@shell/config/types';
|
||||
import { LVM_TOPOLOGY_LABEL } from '../index.vue';
|
||||
|
||||
const DEFAULT_PARAMETERS = [
|
||||
'type',
|
||||
'vgName'
|
||||
];
|
||||
|
||||
const DEFAULT_TOPOLOGIES = [{
|
||||
matchLabelExpressions: [{
|
||||
key: LVM_TOPOLOGY_LABEL,
|
||||
values: []
|
||||
}]
|
||||
}];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
KeyValue,
|
||||
LabeledSelect,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
realMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
await allHash({
|
||||
nodes: this.$store.dispatch(`${ inStore }/findAll`, { type: NODE }),
|
||||
lvmVolumeGroups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.LVM_VOLUME_GROUP }),
|
||||
});
|
||||
},
|
||||
|
||||
data() {
|
||||
const node = (this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).find(t => t.key === LVM_TOPOLOGY_LABEL)?.values[0];
|
||||
|
||||
return {
|
||||
volumeGroupTypes: ['striped', 'dm-thin'],
|
||||
node,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
node(value) {
|
||||
delete (this.value.parameters.vgName);
|
||||
|
||||
const allowedTopologies = [...DEFAULT_TOPOLOGIES];
|
||||
|
||||
allowedTopologies[0].matchLabelExpressions[0].values = [value];
|
||||
|
||||
this.value.allowedTopologies = allowedTopologies;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
nodes() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const nodes = this.$store.getters[`${ inStore }/all`](NODE) || [];
|
||||
|
||||
return nodes.filter(n => n.labels[LVM_TOPOLOGY_LABEL] === n.name).map(n => n.name);
|
||||
},
|
||||
|
||||
volumeGroups() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const lvmVolumeGroups = this.$store.getters[`${ inStore }/all`](HCI.LVM_VOLUME_GROUP) || [];
|
||||
|
||||
return lvmVolumeGroups
|
||||
.filter(group => group.spec.nodeName === this.node)
|
||||
.map(g => g.spec.vgName);
|
||||
},
|
||||
|
||||
parameters: {
|
||||
get() {
|
||||
const parameters = clone(this.value?.parameters) || {};
|
||||
|
||||
DEFAULT_PARAMETERS.map((key) => {
|
||||
delete parameters[key];
|
||||
});
|
||||
|
||||
return parameters;
|
||||
},
|
||||
|
||||
set(value) {
|
||||
Object.assign(this.value.parameters, value);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="row mt-10">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="node"
|
||||
:label="t('harvester.storage.parameters.node.label')"
|
||||
:options="nodes"
|
||||
:mode="mode"
|
||||
:required="true"
|
||||
>
|
||||
<template #no-options="{ searching }">
|
||||
<span v-if="!searching" class="text-muted">
|
||||
{{ t('harvester.storage.parameters.diskSelector.no-options', null, true) }}
|
||||
</span>
|
||||
</template>
|
||||
</LabeledSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-10">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="value.parameters.vgName"
|
||||
:label="t('harvester.storage.parameters.lvmVolumeGroup.label')"
|
||||
:options="volumeGroups"
|
||||
:mode="mode"
|
||||
:required="true"
|
||||
>
|
||||
<template #no-options="{ searching }">
|
||||
<span v-if="!searching" class="text-muted">
|
||||
{{ t('harvester.storage.parameters.lvmVolumeGroup.no-options', null, true) }}
|
||||
</span>
|
||||
</template>
|
||||
</LabeledSelect>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="value.parameters.type"
|
||||
:label="t('harvester.storage.parameters.lvmVolumeGroupType.label')"
|
||||
:options="volumeGroupTypes"
|
||||
:mode="mode"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<KeyValue
|
||||
v-model:value="parameters"
|
||||
:add-label="t('storageClass.longhorn.addLabel')"
|
||||
:read-allowed="false"
|
||||
:mode="mode"
|
||||
class="mt-10"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.labeled-input.compact-input {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@ import { STORAGE_CLASS } from '@shell/config/types';
|
||||
import { VM_IMAGE_FILE_FORMAT } from '../validators/vm-image';
|
||||
import { OS } from '../mixins/harvester-vm';
|
||||
import { HCI } from '../types';
|
||||
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
||||
|
||||
const ENCRYPT = 'encrypt';
|
||||
const DECRYPT = 'decrypt';
|
||||
@@ -138,16 +139,16 @@ export default {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||
|
||||
const out = storages.filter(s => !s.parameters?.backingImage).map((s) => {
|
||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||
return storages
|
||||
.filter(s => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
|
||||
.map((s) => {
|
||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||
|
||||
return {
|
||||
label,
|
||||
value: s.name,
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return out;
|
||||
return {
|
||||
label,
|
||||
value: s.name,
|
||||
};
|
||||
}) || [];
|
||||
},
|
||||
|
||||
storageClassName: {
|
||||
|
||||
@@ -267,7 +267,7 @@ export default {
|
||||
:label="t('workload.container.titles.nodeScheduling')"
|
||||
:weight="-3"
|
||||
>
|
||||
<template #default="{active}">
|
||||
<template #default>
|
||||
<NodeScheduling
|
||||
:mode="mode"
|
||||
:value="spec.template.spec"
|
||||
@@ -277,7 +277,7 @@ export default {
|
||||
</Tab>
|
||||
|
||||
<Tab :label="t('harvester.tab.vmScheduling')" name="vmScheduling" :weight="-4">
|
||||
<template #default="{active}">
|
||||
<template #default>
|
||||
<PodAffinity
|
||||
:mode="mode"
|
||||
:value="spec.template.spec"
|
||||
|
||||
@@ -7,18 +7,20 @@ import ResourceTabs from '@shell/components/form/ResourceTabs';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
||||
|
||||
import { Banner } from '@components/Banner';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { get } from '@shell/utils/object';
|
||||
import { STORAGE_CLASS, LONGHORN, PV } from '@shell/config/types';
|
||||
import { sortBy } from '@shell/utils/sort';
|
||||
import { saferDump } from '@shell/utils/create-yaml';
|
||||
import { _CREATE } from '@shell/config/query-params';
|
||||
import { _CREATE, _EDIT } from '@shell/config/query-params';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||
import { STATE, NAME, AGE, NAMESPACE } from '@shell/config/table-headers';
|
||||
import { InterfaceOption, VOLUME_DATA_SOURCE_KIND } from '../config/harvester-map';
|
||||
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
||||
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
||||
import { DATA_ENGINE_V2 } from './harvesterhci.io.storage/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterVolume',
|
||||
@@ -26,6 +28,7 @@ export default {
|
||||
emits: ['update:value'],
|
||||
|
||||
components: {
|
||||
Banner,
|
||||
Tab,
|
||||
UnitInput,
|
||||
CruResource,
|
||||
@@ -94,6 +97,10 @@ export default {
|
||||
isBlank() {
|
||||
return this.source === 'blank';
|
||||
},
|
||||
|
||||
isEdit() {
|
||||
return this.mode === _EDIT;
|
||||
},
|
||||
|
||||
isVMImage() {
|
||||
return this.source === 'url';
|
||||
@@ -160,11 +167,14 @@ export default {
|
||||
return VOLUME_DATA_SOURCE_KIND[this.value.spec?.dataSource?.kind];
|
||||
},
|
||||
|
||||
storageClassOptions() {
|
||||
storageClasses() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||
|
||||
const out = storages.filter(s => !s.parameters?.backingImage).map((s) => {
|
||||
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||
},
|
||||
|
||||
storageClassOptions() {
|
||||
return this.storageClasses.filter(s => !s.parameters?.backingImage).map((s) => {
|
||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||
|
||||
return {
|
||||
@@ -172,8 +182,6 @@ export default {
|
||||
value: s.name,
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
frontend() {
|
||||
@@ -220,6 +228,10 @@ export default {
|
||||
|
||||
rebuildStatus() {
|
||||
return this.value.longhornEngine?.status?.rebuildStatus;
|
||||
},
|
||||
|
||||
isLonghornV2() {
|
||||
return this.value.storageClass?.isLonghornV2;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -231,6 +243,10 @@ export default {
|
||||
let imageAnnotations = '';
|
||||
let storageClassName = this.value.spec.storageClassName;
|
||||
|
||||
const storageClass = this.storageClasses.find(sc => sc.name === storageClassName);
|
||||
const storageClassProvisioner = storageClass?.provisioner;
|
||||
const storageClassDataEngine = storageClass?.parameters?.dataEngine;
|
||||
|
||||
if (this.isVMImage && this.imageId) {
|
||||
const images = this.$store.getters['harvester/all'](HCI.IMAGE);
|
||||
|
||||
@@ -245,8 +261,9 @@ export default {
|
||||
|
||||
const spec = {
|
||||
...this.value.spec,
|
||||
resources: { requests: { storage: this.storage } },
|
||||
storageClassName
|
||||
resources: { requests: { storage: this.storage } },
|
||||
storageClassName,
|
||||
accessModes: storageClassProvisioner === LVM_DRIVER || storageClassDataEngine === DATA_ENGINE_V2 ? ['ReadWriteOnce'] : ['ReadWriteMany'],
|
||||
};
|
||||
|
||||
this.value.setAnnotations(imageAnnotations);
|
||||
@@ -335,10 +352,15 @@ export default {
|
||||
:output-modifier="true"
|
||||
:increment="1024"
|
||||
:mode="mode"
|
||||
:disabled="isLonghornV2 && isEdit"
|
||||
required
|
||||
class="mb-20"
|
||||
@update:value="update"
|
||||
/>
|
||||
|
||||
<Banner v-if="isLonghornV2 && isEdit" color="warning">
|
||||
<span>{{ t('harvester.volume.longhorn.disableResize') }}</span>
|
||||
</Banner>
|
||||
</Tab>
|
||||
<Tab v-if="!isCreate" name="details" :label="t('harvester.volume.tabs.details')" :weight="2.5" class="bordered-table">
|
||||
<LabeledInput v-model:value="frontendDisplay" class="mb-20" :mode="mode" :disabled="true" :label="t('harvester.volume.frontend')" />
|
||||
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
{{ deviceCRD.metadata.name }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(nodeName, i) in allNodeNames" :key="i" class="compat-cell"
|
||||
v-for="(nodeName, k) in allNodeNames" :key="k" class="compat-cell"
|
||||
:class="{'has-device': nodeHasDevice(nodeName, deviceCRD)}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,10 +8,10 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||
|
||||
import { PVC, STORAGE_CLASS } from '@shell/config/types';
|
||||
import { PVC } from '@shell/config/types';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { ucFirst, randomStr } from '@shell/utils/string';
|
||||
import { removeObject } from '@shell/utils/array';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
|
||||
import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
|
||||
import { SOURCE_TYPE } from '../../../config/harvester-map';
|
||||
@@ -82,11 +82,12 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
ucFirst,
|
||||
SOURCE_TYPE,
|
||||
rows: clone(this.value),
|
||||
nameIdx: 1,
|
||||
vol: null,
|
||||
isOpen: false,
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -175,10 +176,7 @@ export default {
|
||||
};
|
||||
|
||||
if (type === SOURCE_TYPE.NEW) {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find( O => O.isDefault);
|
||||
|
||||
neu.storageClassName = defaultStorage?.metadata?.name || 'longhorn';
|
||||
neu.storageClassName = this.defaultStorageClass?.metadata?.name || 'longhorn';
|
||||
}
|
||||
|
||||
this.rows.push(neu);
|
||||
@@ -258,6 +256,10 @@ export default {
|
||||
|
||||
getImageDisplayName(id) {
|
||||
return this.$store.getters['harvester/all'](HCI.IMAGE).find(image => image.id === id)?.spec?.displayName;
|
||||
},
|
||||
|
||||
isLonghornV2(volume) {
|
||||
return volume?.pvc?.storageClass?.isLonghornV2;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -371,12 +373,24 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Banner
|
||||
v-if="volume.volumeStatus && !isCreate"
|
||||
class="mt-15 volume-status"
|
||||
color="warning"
|
||||
:label="volume.volumeStatus"
|
||||
/>
|
||||
<div class="mt-15">
|
||||
<Banner
|
||||
v-if="volume.volumeStatus && !isCreate"
|
||||
class="volume-status"
|
||||
color="warning"
|
||||
:label="ucFirst(volume.volumeStatus)"
|
||||
/>
|
||||
<Banner
|
||||
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
|
||||
color="error"
|
||||
:label="ucFirst(value.volumeBackups.error.message)"
|
||||
/>
|
||||
<Banner
|
||||
v-if="isLonghornV2(volume) && !isView"
|
||||
color="warning"
|
||||
:label="t('harvester.volume.longhorn.disableResize')"
|
||||
/>
|
||||
</div>
|
||||
</InfoBox>
|
||||
</div>
|
||||
</template>
|
||||
@@ -497,4 +511,8 @@ export default {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -114,6 +114,10 @@ export default {
|
||||
return allPVCs.find((P) => {
|
||||
return this.namespace ? P.id === `${ this.namespace }/${ this.value.volumeName }` : true;
|
||||
});
|
||||
},
|
||||
|
||||
isLonghornV2() {
|
||||
return this.value.pvc?.storageClass?.isLonghornV2;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -279,6 +283,7 @@ export default {
|
||||
:label="t('harvester.fields.size')"
|
||||
:mode="mode"
|
||||
:required="validateRequired"
|
||||
:disable="isLonghornV2"
|
||||
suffix="GiB"
|
||||
@update:value="update"
|
||||
/>
|
||||
|
||||
+24
-13
@@ -4,13 +4,14 @@ import UnitInput from '@shell/components/form/UnitInput';
|
||||
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { PVC, STORAGE_CLASS } from '@shell/config/types';
|
||||
import { formatSi, parseSi } from '@shell/utils/units';
|
||||
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
||||
import { _VIEW } from '@shell/config/query-params';
|
||||
import LabelValue from '@shell/components/LabelValue';
|
||||
import { ucFirst } from '@shell/utils/string';
|
||||
import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storageclass';
|
||||
import { DATA_ENGINE_V2 } from '../../../../edit/harvesterhci.io.storage/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditVolume',
|
||||
@@ -18,7 +19,7 @@ export default {
|
||||
emits: ['update'],
|
||||
|
||||
components: {
|
||||
InputOrDisplay, Loading, LabeledInput, LabeledSelect, UnitInput, LabelValue, Banner
|
||||
InputOrDisplay, Loading, LabeledInput, LabeledSelect, UnitInput, LabelValue
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -88,10 +89,12 @@ export default {
|
||||
return !this.value.newCreateId && this.isEdit && this.isVirtualType;
|
||||
},
|
||||
|
||||
storageClassOptions() {
|
||||
const storages = this.$store.getters[`harvester/all`](STORAGE_CLASS) || [];
|
||||
storageClasses() {
|
||||
return this.$store.getters[`harvester/all`](STORAGE_CLASS) || [];
|
||||
},
|
||||
|
||||
const out = storages.filter(s => !s.parameters?.backingImage).map((s) => {
|
||||
storageClassOptions() {
|
||||
return this.storageClasses.filter(s => !s.parameters?.backingImage).map((s) => {
|
||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||
|
||||
return {
|
||||
@@ -99,12 +102,25 @@ export default {
|
||||
value: s.name,
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
isLonghornV2() {
|
||||
return this.value.pvc?.storageClass?.isLonghornV2;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'value.storageClassName': {
|
||||
immediate: true,
|
||||
handler(neu) {
|
||||
const storageClass = this.storageClasses.find(sc => sc.name === neu);
|
||||
const provisioner = storageClass?.provisioner;
|
||||
const engine = storageClass?.parameters?.dataEngine;
|
||||
|
||||
this.value.accessMode = provisioner === LVM_DRIVER || engine === DATA_ENGINE_V2 ? 'ReadWriteOnce' : 'ReadWriteMany';
|
||||
}
|
||||
},
|
||||
|
||||
'value.type'(neu) {
|
||||
if (neu === 'cd-rom') {
|
||||
this.value['bus'] = 'sata';
|
||||
@@ -222,6 +238,7 @@ export default {
|
||||
:mode="mode"
|
||||
:required="validateRequired"
|
||||
:label="t('harvester.fields.size')"
|
||||
:disabled="isLonghornV2"
|
||||
@update:value="update"
|
||||
/>
|
||||
</InputOrDisplay>
|
||||
@@ -261,11 +278,5 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Banner
|
||||
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
|
||||
color="error"
|
||||
class="mb-20"
|
||||
:label="value.volumeBackups.error.message"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -614,7 +614,7 @@ export default {
|
||||
:label="t('workload.container.titles.nodeScheduling')"
|
||||
:weight="-3"
|
||||
>
|
||||
<template #default="{active}">
|
||||
<template #default>
|
||||
<NodeScheduling
|
||||
:mode="mode"
|
||||
:value="spec.template.spec"
|
||||
@@ -628,7 +628,7 @@ export default {
|
||||
name="vmScheduling"
|
||||
:weight="-4"
|
||||
>
|
||||
<template #default="{active}">
|
||||
<template #default>
|
||||
<PodAffinity
|
||||
:mode="mode"
|
||||
:value="spec.template.spec"
|
||||
|
||||
Reference in New Issue
Block a user