mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-05-14 15:01:44 +00:00
feat: support advance option and creation from DataVolume (#776)
* feat: support advance option and creation from DataVolume
- advance option would let user change the accessMode/volumeMode
- creation from DataVolume could supports various scenario with
3rd-party storage
Signed-off-by: Vicente Cheng <freeze.bilsted@gmail.com>
* feat: add data migration action on volume page
Signed-off-by: Vicente Cheng <freeze.bilsted@gmail.com>
* refactor: use show advanced options link instead of checkbox
Signed-off-by: Andy Lee <andy.lee@suse.com>
* feat: add feature flag
Signed-off-by: Andy Lee <andy.lee@suse.com>
* feat: add feature flag for dataMigration action
Signed-off-by: Andy Lee <andy.lee@suse.com>
---------
Signed-off-by: Vicente Cheng <freeze.bilsted@gmail.com>
Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
42ddcfc1fe
commit
566e79eda5
@ -65,6 +65,7 @@ const FEATURE_FLAGS = {
|
|||||||
'vGPUAsPCIDevice',
|
'vGPUAsPCIDevice',
|
||||||
'instanceManagerResourcesSetting',
|
'instanceManagerResourcesSetting',
|
||||||
'rwxNetworkSetting',
|
'rwxNetworkSetting',
|
||||||
|
'createPVCWithDataVolume'
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -80,4 +80,5 @@ export const HCI = {
|
|||||||
VOLUME_SNAPSHOT_CLASS: 'cdi.harvesterhci.io/storageProfileVolumeSnapshotClass',
|
VOLUME_SNAPSHOT_CLASS: 'cdi.harvesterhci.io/storageProfileVolumeSnapshotClass',
|
||||||
MAC_ADDRESS: 'harvesterhci.io/mac-address',
|
MAC_ADDRESS: 'harvesterhci.io/mac-address',
|
||||||
NODE_UPGRADE_PAUSE_MAP: 'harvesterhci.io/node-upgrade-pause-map',
|
NODE_UPGRADE_PAUSE_MAP: 'harvesterhci.io/node-upgrade-pause-map',
|
||||||
|
CDI_POPULATOR_KIND: 'cdi.kubevirt.io/storage.populator.kind',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -41,3 +41,8 @@ export const VMIMPORT_SOURCE_KINDS = {
|
|||||||
OPENSTACK: 'OpenstackSource',
|
OPENSTACK: 'OpenstackSource',
|
||||||
OVA: 'OvaSource',
|
OVA: 'OvaSource',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CDI_POPULATOR_KIND = {
|
||||||
|
VOLUME_IMPORT_SOURCE: 'VolumeImportSource',
|
||||||
|
VOLUME_CLONE_SOURCE: 'VolumeCloneSource',
|
||||||
|
};
|
||||||
|
|||||||
183
pkg/harvester/dialog/HarvesterDataMigrationDialog.vue
Normal file
183
pkg/harvester/dialog/HarvesterDataMigrationDialog.vue
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
import { STORAGE_CLASS } from '@shell/config/types';
|
||||||
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
|
import { sortBy } from '@shell/utils/sort';
|
||||||
|
import { isInternalStorageClass } from '../utils/storage-class';
|
||||||
|
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HarvesterDataMigrationDialog',
|
||||||
|
|
||||||
|
emits: ['close'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AsyncButton, Banner, Card, LabeledInput, LabeledSelect
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
|
this.storageClasses = await this.$store.dispatch('harvester/findAll', { type: STORAGE_CLASS });
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
targetVolumeName: '',
|
||||||
|
targetStorageClassName: '',
|
||||||
|
errors: [],
|
||||||
|
storageClasses: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ t: 'i18n/t' }),
|
||||||
|
|
||||||
|
actionResource() {
|
||||||
|
return this.resources[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
storageClassOptions() {
|
||||||
|
return sortBy(
|
||||||
|
this.storageClasses
|
||||||
|
.filter((sc) => !isInternalStorageClass(sc.metadata?.name))
|
||||||
|
.map((sc) => ({
|
||||||
|
label: sc.metadata?.name,
|
||||||
|
value: sc.metadata?.name
|
||||||
|
})),
|
||||||
|
'label'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
disableSave() {
|
||||||
|
return !this.targetVolumeName || !this.targetStorageClassName;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.targetVolumeName = '';
|
||||||
|
this.targetStorageClassName = '';
|
||||||
|
this.errors = [];
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async apply(buttonDone) {
|
||||||
|
if (!this.actionResource) {
|
||||||
|
buttonDone(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.targetVolumeName) {
|
||||||
|
const name = this.t('harvester.modal.dataMigration.fields.targetVolumeName.label');
|
||||||
|
|
||||||
|
this['errors'] = [this.t('validation.required', { key: name })];
|
||||||
|
buttonDone(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.targetStorageClassName) {
|
||||||
|
const name = this.t('harvester.modal.dataMigration.fields.targetStorageClassName.label');
|
||||||
|
|
||||||
|
this['errors'] = [this.t('validation.required', { key: name })];
|
||||||
|
buttonDone(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.actionResource.doAction('dataMigration', {
|
||||||
|
targetVolumeName: this.targetVolumeName,
|
||||||
|
targetStorageClassName: this.targetStorageClassName
|
||||||
|
}, {}, false);
|
||||||
|
|
||||||
|
buttonDone(true);
|
||||||
|
this.close();
|
||||||
|
} catch (err) {
|
||||||
|
const error = err?.data || err;
|
||||||
|
|
||||||
|
this['errors'] = exceptionToErrorsArray(error);
|
||||||
|
buttonDone(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :show-highlight-border="false">
|
||||||
|
<template #title>
|
||||||
|
{{ t('harvester.modal.dataMigration.title') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="targetVolumeName"
|
||||||
|
:label="t('harvester.modal.dataMigration.fields.targetVolumeName.label')"
|
||||||
|
:placeholder="t('harvester.modal.dataMigration.fields.targetVolumeName.placeholder')"
|
||||||
|
class="mb-20"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="targetStorageClassName"
|
||||||
|
:label="t('harvester.modal.dataMigration.fields.targetStorageClassName.label')"
|
||||||
|
:placeholder="t('harvester.modal.dataMigration.fields.targetStorageClassName.placeholder')"
|
||||||
|
:options="storageClassOptions"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template
|
||||||
|
#actions
|
||||||
|
class="actions"
|
||||||
|
>
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary mr-10"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<AsyncButton
|
||||||
|
mode="apply"
|
||||||
|
:disabled="disableSave"
|
||||||
|
@click="apply"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -9,8 +9,11 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
|||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
||||||
import Conditions from '@shell/components/form/Conditions';
|
import Conditions from '@shell/components/form/Conditions';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
|
import jsyaml from 'js-yaml';
|
||||||
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import { get } from '@shell/utils/object';
|
import { clone, get } from '@shell/utils/object';
|
||||||
import { STORAGE_CLASS, LONGHORN, PV } from '@shell/config/types';
|
import { STORAGE_CLASS, LONGHORN, PV } from '@shell/config/types';
|
||||||
import { sortBy } from '@shell/utils/sort';
|
import { sortBy } from '@shell/utils/sort';
|
||||||
import { saferDump } from '@shell/utils/create-yaml';
|
import { saferDump } from '@shell/utils/create-yaml';
|
||||||
@ -33,6 +36,7 @@ export default {
|
|||||||
|
|
||||||
components: {
|
components: {
|
||||||
Banner,
|
Banner,
|
||||||
|
Checkbox,
|
||||||
Tab,
|
Tab,
|
||||||
UnitInput,
|
UnitInput,
|
||||||
CruResource,
|
CruResource,
|
||||||
@ -90,6 +94,8 @@ export default {
|
|||||||
source,
|
source,
|
||||||
storage,
|
storage,
|
||||||
imageId,
|
imageId,
|
||||||
|
showAdvanced: false,
|
||||||
|
createWithDataVolume: false,
|
||||||
snapshots: [],
|
snapshots: [],
|
||||||
images: [],
|
images: [],
|
||||||
GIBIBYTE
|
GIBIBYTE
|
||||||
@ -98,6 +104,24 @@ export default {
|
|||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.registerBeforeHook(this.willSave, 'willSave');
|
this.registerBeforeHook(this.willSave, 'willSave');
|
||||||
|
|
||||||
|
if (this.mode === _CREATE) {
|
||||||
|
const originalSaveYaml = this.value.saveYaml?.bind(this.value);
|
||||||
|
|
||||||
|
this.value.saveYaml = async(yaml) => {
|
||||||
|
if (this.createWithDataVolume && this.isBlank) {
|
||||||
|
const parsed = jsyaml.load(yaml);
|
||||||
|
const dvObj = { ...parsed, type: 'cdi.kubevirt.io.datavolume' };
|
||||||
|
const dataVolume = await this.$store.dispatch('harvester/create', dvObj);
|
||||||
|
|
||||||
|
await dataVolume.save();
|
||||||
|
|
||||||
|
return dataVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalSaveYaml(yaml);
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@ -135,6 +159,10 @@ export default {
|
|||||||
return Object.values(VOLUME_MODE);
|
return Object.values(VOLUME_MODE);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
accessModeOptions() {
|
||||||
|
return ['ReadWriteOnce', 'ReadWriteMany', 'ReadOnlyMany'];
|
||||||
|
},
|
||||||
|
|
||||||
imageOption() {
|
imageOption() {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
this.images
|
this.images
|
||||||
@ -275,6 +303,10 @@ export default {
|
|||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
|
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isCreatePVCWithDataVolumeFeatureEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('createPVCWithDataVolume');
|
||||||
|
},
|
||||||
|
|
||||||
isResizeDisabled() {
|
isResizeDisabled() {
|
||||||
return (
|
return (
|
||||||
!this.isLHV2VolExpansionFeatureEnabled &&
|
!this.isLHV2VolExpansionFeatureEnabled &&
|
||||||
@ -341,6 +373,58 @@ export default {
|
|||||||
|
|
||||||
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
|
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
|
||||||
},
|
},
|
||||||
|
buildDataVolumeObj() {
|
||||||
|
const storage = {
|
||||||
|
storageClassName: this.value.spec.storageClassName,
|
||||||
|
resources: { requests: { storage: this.storage } },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.showAdvanced && this.value.spec.accessModes?.length > 0) {
|
||||||
|
storage.accessModes = this.value.spec.accessModes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.showAdvanced && this.value.spec.volumeMode) {
|
||||||
|
storage.volumeMode = this.value.spec.volumeMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'cdi.kubevirt.io.datavolume',
|
||||||
|
apiVersion: 'cdi.kubevirt.io/v1beta1',
|
||||||
|
kind: 'DataVolume',
|
||||||
|
metadata: {
|
||||||
|
name: this.value.metadata.name,
|
||||||
|
namespace: this.value.metadata.namespace,
|
||||||
|
annotations: this.value.metadata.annotations || {},
|
||||||
|
labels: this.value.metadata.labels || {},
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
source: { blank: {} },
|
||||||
|
storage,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async save(buttonDone) {
|
||||||
|
if (this.isCreate && this.isBlank && this.createWithDataVolume) {
|
||||||
|
try {
|
||||||
|
this.update();
|
||||||
|
const dvObj = this.buildDataVolumeObj();
|
||||||
|
const dataVolume = await this.$store.dispatch('harvester/create', dvObj);
|
||||||
|
|
||||||
|
await dataVolume.save();
|
||||||
|
buttonDone(true);
|
||||||
|
this.done();
|
||||||
|
} catch (err) {
|
||||||
|
const error = err?.data || err;
|
||||||
|
|
||||||
|
this['errors'] = exceptionToErrorsArray(error);
|
||||||
|
buttonDone(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await CreateEditView.methods.save.call(this, buttonDone);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
willSave() {
|
willSave() {
|
||||||
this.update();
|
this.update();
|
||||||
},
|
},
|
||||||
@ -383,9 +467,17 @@ export default {
|
|||||||
this.update();
|
this.update();
|
||||||
},
|
},
|
||||||
generateYaml() {
|
generateYaml() {
|
||||||
const out = saferDump(this.value);
|
this.update();
|
||||||
|
|
||||||
return out;
|
if (this.isCreate && this.isBlank && this.createWithDataVolume) {
|
||||||
|
return saferDump(this.buildDataVolumeObj());
|
||||||
|
}
|
||||||
|
|
||||||
|
const plain = clone(this.value);
|
||||||
|
|
||||||
|
delete plain.saveYaml;
|
||||||
|
|
||||||
|
return saferDump(plain);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -458,18 +550,6 @@ export default {
|
|||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LabeledSelect
|
|
||||||
v-if="showVolumeMode"
|
|
||||||
v-model:value="value.spec.volumeMode"
|
|
||||||
:label="t('harvester.volume.volumeMode')"
|
|
||||||
:options="volumeModeOptions"
|
|
||||||
required
|
|
||||||
:disabled="!isCreate"
|
|
||||||
:mode="mode"
|
|
||||||
class="mb-20"
|
|
||||||
@update:value="update"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UnitInput
|
<UnitInput
|
||||||
v-model:value="storage"
|
v-model:value="storage"
|
||||||
:label="t('harvester.volume.size')"
|
:label="t('harvester.volume.size')"
|
||||||
@ -490,6 +570,44 @@ export default {
|
|||||||
>
|
>
|
||||||
<span>{{ t('harvester.volume.longhorn.disableResize') }}</span>
|
<span>{{ t('harvester.volume.longhorn.disableResize') }}</span>
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
|
<div class="row mb-20">
|
||||||
|
<Checkbox
|
||||||
|
v-if="isCreate && isBlank && isCreatePVCWithDataVolumeFeatureEnabled"
|
||||||
|
v-model:value="createWithDataVolume"
|
||||||
|
:label="t('harvester.volume.createWithDataVolume')"
|
||||||
|
tooltip-key="harvester.volume.createWithDataVolumeTooltip"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
v-if="isCreate && isCreatePVCWithDataVolumeFeatureEnabled"
|
||||||
|
role="button"
|
||||||
|
class="hand"
|
||||||
|
@click="showAdvanced = !showAdvanced"
|
||||||
|
>
|
||||||
|
{{ showAdvanced ? t('harvester.volume.hideAdvanced') : t('harvester.volume.showAdvanced') }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-if="showAdvanced"
|
||||||
|
v-model:value="value.spec.accessModes"
|
||||||
|
:label="t('harvester.volume.accessModes')"
|
||||||
|
:options="accessModeOptions"
|
||||||
|
:multiple="true"
|
||||||
|
:mode="mode"
|
||||||
|
class="mb-20 mt-20"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-if="showAdvanced"
|
||||||
|
v-model:value="value.spec.volumeMode"
|
||||||
|
:label="t('harvester.volume.volumeMode')"
|
||||||
|
:options="volumeModeOptions"
|
||||||
|
:mode="mode"
|
||||||
|
class="mb-20"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
v-if="!isCreate"
|
v-if="!isCreate"
|
||||||
|
|||||||
@ -132,6 +132,15 @@ harvester:
|
|||||||
targetVolume:
|
targetVolume:
|
||||||
label: Target Volume
|
label: Target Volume
|
||||||
placeholder: Select a target volume
|
placeholder: Select a target volume
|
||||||
|
dataMigration:
|
||||||
|
title: Data Migration
|
||||||
|
fields:
|
||||||
|
targetVolumeName:
|
||||||
|
label: Target Volume Name
|
||||||
|
placeholder: Enter a target volume name
|
||||||
|
targetStorageClassName:
|
||||||
|
label: Target Storage Class
|
||||||
|
placeholder: Select a storage class
|
||||||
migration:
|
migration:
|
||||||
failedMessage: Latest migration failed!
|
failedMessage: Latest migration failed!
|
||||||
title: Migration
|
title: Migration
|
||||||
@ -243,6 +252,7 @@ harvester:
|
|||||||
abortMigration: Abort Migration
|
abortMigration: Abort Migration
|
||||||
storageMigration: Storage Migration
|
storageMigration: Storage Migration
|
||||||
cancelStorageMigration: Cancel Storage Migration
|
cancelStorageMigration: Cancel Storage Migration
|
||||||
|
dataMigration: Data Migration
|
||||||
createTemplate: Generate Template
|
createTemplate: Generate Template
|
||||||
enableMaintenance: Enable Maintenance Mode
|
enableMaintenance: Enable Maintenance Mode
|
||||||
disableMaintenance: Disable Maintenance Mode
|
disableMaintenance: Disable Maintenance Mode
|
||||||
@ -912,6 +922,11 @@ harvester:
|
|||||||
conditions: Conditions
|
conditions: Conditions
|
||||||
size: Size
|
size: Size
|
||||||
volumeMode: Volume Mode
|
volumeMode: Volume Mode
|
||||||
|
accessModes: Access Modes
|
||||||
|
createWithDataVolume: Create with DataVolume
|
||||||
|
createWithDataVolumeTooltip: Create Volume with Kubevirt/Containerized Data Importer way. It can fill accessMode/volumeMode automatically.
|
||||||
|
showAdvanced: Show Advanced Options
|
||||||
|
hideAdvanced: Hide Advanced Options
|
||||||
source: Source
|
source: Source
|
||||||
kind: Kind
|
kind: Kind
|
||||||
sourceOptions:
|
sourceOptions:
|
||||||
|
|||||||
@ -70,8 +70,13 @@ export default {
|
|||||||
return schema;
|
return schema;
|
||||||
},
|
},
|
||||||
filterRows() {
|
filterRows() {
|
||||||
// we only show the non golden image PVCs in the list
|
return this.rows.filter((pvc) => {
|
||||||
return this.rows.filter((pvc) => !pvc?.isGoldenImageVolume);
|
if (pvc?.isGoldenImageVolume || pvc?.isCDIPopulatorVolume) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
headers() {
|
headers() {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
|
|||||||
import { HCI, VOLUME_SNAPSHOT } from '../../types';
|
import { HCI, VOLUME_SNAPSHOT } from '../../types';
|
||||||
import HarvesterResource from '../harvester';
|
import HarvesterResource from '../harvester';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
|
||||||
|
import { CDI_POPULATOR_KIND } from '../../config/types';
|
||||||
import { LVM_DRIVER } from './storage.k8s.io.storageclass';
|
import { LVM_DRIVER } from './storage.k8s.io.storageclass';
|
||||||
|
|
||||||
const DEGRADED_ERRORS = ['replica scheduling failed', 'precheck new replica failed'];
|
const DEGRADED_ERRORS = ['replica scheduling failed', 'precheck new replica failed'];
|
||||||
@ -44,7 +45,7 @@ export default class HciPv extends HarvesterResource {
|
|||||||
const exportImageAction = {
|
const exportImageAction = {
|
||||||
action: 'exportImage',
|
action: 'exportImage',
|
||||||
enabled: this.hasAction('export') && !this.isEncrypted,
|
enabled: this.hasAction('export') && !this.isEncrypted,
|
||||||
icon: 'icon icon-copy',
|
icon: 'icon icon-external-link',
|
||||||
label: this.t('harvester.action.exportImage')
|
label: this.t('harvester.action.exportImage')
|
||||||
};
|
};
|
||||||
const takeSnapshotAction = {
|
const takeSnapshotAction = {
|
||||||
@ -77,10 +78,23 @@ export default class HciPv extends HarvesterResource {
|
|||||||
icon: 'icon icon-backup',
|
icon: 'icon icon-backup',
|
||||||
label: this.t('harvester.action.cancelExpand')
|
label: this.t('harvester.action.cancelExpand')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: 'dataMigration',
|
||||||
|
enabled: this.hasAction('dataMigration') && this.createPVCWithDataVolumeFeatureEnabled,
|
||||||
|
icon: 'icon icon-copy',
|
||||||
|
label: this.t('harvester.action.dataMigration')
|
||||||
|
},
|
||||||
...out
|
...out
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataMigration(resources = this) {
|
||||||
|
this.$dispatch('promptModal', {
|
||||||
|
resources,
|
||||||
|
component: 'HarvesterDataMigrationDialog'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
exportImage(resources = this) {
|
exportImage(resources = this) {
|
||||||
this.$dispatch('promptModal', {
|
this.$dispatch('promptModal', {
|
||||||
resources,
|
resources,
|
||||||
@ -339,10 +353,20 @@ export default class HciPv extends HarvesterResource {
|
|||||||
return this?.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true';
|
return this?.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isCDIPopulatorVolume() {
|
||||||
|
const kind = this?.metadata?.annotations?.[HCI_ANNOTATIONS.CDI_POPULATOR_KIND];
|
||||||
|
|
||||||
|
return kind === CDI_POPULATOR_KIND.VOLUME_IMPORT_SOURCE || kind === CDI_POPULATOR_KIND.VOLUME_CLONE_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
get thirdPartyStorageFeatureEnabled() {
|
get thirdPartyStorageFeatureEnabled() {
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get createPVCWithDataVolumeFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('createPVCWithDataVolume');
|
||||||
|
}
|
||||||
|
|
||||||
get resourceExternalLink() {
|
get resourceExternalLink() {
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
const { params } = this.currentRoute();
|
const { params } = this.currentRoute();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user