mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-13 13:11:43 +00:00
Merge pull request #145 from a110605/issue_7572
External/third-party storage UI change
This commit is contained in:
commit
7c2317fae9
@ -51,7 +51,8 @@ const featuresV150 = [
|
|||||||
'tpmPersistentState',
|
'tpmPersistentState',
|
||||||
'efiPersistentState',
|
'efiPersistentState',
|
||||||
'untaggedNetworkSetting',
|
'untaggedNetworkSetting',
|
||||||
'skipSingleReplicaDetachedVol'
|
'skipSingleReplicaDetachedVol',
|
||||||
|
'thirdPartyStorage'
|
||||||
];
|
];
|
||||||
|
|
||||||
export const RELEASE_FEATURES = {
|
export const RELEASE_FEATURES = {
|
||||||
|
|||||||
@ -31,7 +31,8 @@ import {
|
|||||||
FINGERPRINT,
|
FINGERPRINT,
|
||||||
IMAGE_PROGRESS,
|
IMAGE_PROGRESS,
|
||||||
SNAPSHOT_TARGET_VOLUME,
|
SNAPSHOT_TARGET_VOLUME,
|
||||||
IMAGE_VIRTUAL_SIZE
|
IMAGE_VIRTUAL_SIZE,
|
||||||
|
IMAGE_STORAGE_CLASS
|
||||||
} from './table-headers';
|
} from './table-headers';
|
||||||
|
|
||||||
const TEMPLATE = HCI.VM_VERSION;
|
const TEMPLATE = HCI.VM_VERSION;
|
||||||
@ -221,6 +222,7 @@ export function init($plugin, store) {
|
|||||||
STATE,
|
STATE,
|
||||||
NAME_COL,
|
NAME_COL,
|
||||||
NAMESPACE_COL,
|
NAMESPACE_COL,
|
||||||
|
IMAGE_STORAGE_CLASS,
|
||||||
IMAGE_PROGRESS,
|
IMAGE_PROGRESS,
|
||||||
IMAGE_DOWNLOAD_SIZE,
|
IMAGE_DOWNLOAD_SIZE,
|
||||||
IMAGE_VIRTUAL_SIZE,
|
IMAGE_VIRTUAL_SIZE,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export const HCI = {
|
|||||||
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
|
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
|
||||||
CREATOR: 'harvesterhci.io/creator',
|
CREATOR: 'harvesterhci.io/creator',
|
||||||
OS: 'harvesterhci.io/os',
|
OS: 'harvesterhci.io/os',
|
||||||
|
GOLDEN_IMAGE: 'harvesterhci.io/goldenImage',
|
||||||
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
||||||
VM_NAME: 'harvesterhci.io/vmName',
|
VM_NAME: 'harvesterhci.io/vmName',
|
||||||
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
||||||
|
|||||||
@ -88,3 +88,13 @@ export const MACHINE_POOLS = {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100,
|
width: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The STORAGE_CLASS column in VM image list page
|
||||||
|
export const IMAGE_STORAGE_CLASS = {
|
||||||
|
name: 'imageStorageClass',
|
||||||
|
labelKey: 'harvester.tableHeaders.storageClass',
|
||||||
|
sort: 'imageStorageClass',
|
||||||
|
value: 'imageStorageClass',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
};
|
||||||
|
|||||||
@ -61,9 +61,12 @@ export default {
|
|||||||
images: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }),
|
images: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }),
|
||||||
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
||||||
});
|
});
|
||||||
|
|
||||||
this['storageClassName'] = this.storageClassName || this.defaultStorageClassName();
|
this['storageClassName'] = this.storageClassName || this.defaultStorageClassName();
|
||||||
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
||||||
|
|
||||||
|
this.storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
|
|
||||||
const { securityParameters } = this.value.spec;
|
const { securityParameters } = this.value.spec;
|
||||||
|
|
||||||
// edit and view mode should show the source image
|
// edit and view mode should show the source image
|
||||||
@ -98,14 +101,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedImage: null,
|
selectedImage: null,
|
||||||
images: [],
|
storageClasses: [],
|
||||||
url: this.value.spec.url,
|
images: [],
|
||||||
files: [],
|
url: this.value.spec.url,
|
||||||
resource: '',
|
files: [],
|
||||||
headers: {},
|
resource: '',
|
||||||
fileUrl: '',
|
headers: {},
|
||||||
file: '',
|
fileUrl: '',
|
||||||
|
file: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -158,9 +162,10 @@ export default {
|
|||||||
|
|
||||||
storageClassOptions() {
|
storageClassOptions() {
|
||||||
const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses;
|
const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses;
|
||||||
|
const filteredStorages = this.value.thirdPartyStorageFeatureEnabled ? storages.filter((s) => !s.parameters?.backingImage) : storages
|
||||||
|
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) ;
|
||||||
|
|
||||||
return storages
|
return filteredStorages
|
||||||
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
|
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||||
|
|
||||||
@ -178,6 +183,9 @@ export default {
|
|||||||
|
|
||||||
set(nue) {
|
set(nue) {
|
||||||
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
|
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
|
||||||
|
if (this.value.thirdPartyStorageFeatureEnabled) {
|
||||||
|
this.value.spec.targetStorageClassName = nue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sourceImageOptions() {
|
sourceImageOptions() {
|
||||||
@ -256,6 +264,13 @@ export default {
|
|||||||
} else { // URL / FILE / DECRYPT should use default storage class
|
} else { // URL / FILE / DECRYPT should use default storage class
|
||||||
this.storageClassName = this.defaultStorageClassName();
|
this.storageClassName = this.defaultStorageClassName();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'storageClassName'(neu) {
|
||||||
|
const storageClass = this.storages.find((s) => s.id === neu);
|
||||||
|
|
||||||
|
if (storageClass && this.value.thirdPartyStorageFeatureEnabled) {
|
||||||
|
this.value.spec.backend = storageClass.isLonghornV1 ? 'backingimage' : 'cdi';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -109,6 +109,10 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pvc.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
|
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
|
||||||
isBeingUsed = false;
|
isBeingUsed = false;
|
||||||
} else if (pvc.attachVM) {
|
} else if (pvc.attachVM) {
|
||||||
|
|||||||
@ -90,7 +90,7 @@ export default {
|
|||||||
imagesOption() {
|
imagesOption() {
|
||||||
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
|
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
|
||||||
return {
|
return {
|
||||||
label: `${ I.metadata.namespace }/${ I.spec.displayName }`,
|
label: this.imageOptionLabel(I),
|
||||||
value: I.id
|
value: I.id
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -116,8 +116,42 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
thirdPartyStorageEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
|
},
|
||||||
|
|
||||||
isLonghornV2() {
|
isLonghornV2() {
|
||||||
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
|
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedImage() {
|
||||||
|
return this.$store.getters['harvester/all'](HCI.IMAGE)?.find( (I) => this.value.image === I.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
imageVirtualSize() {
|
||||||
|
return this.selectedImage?.virtualSize;
|
||||||
|
},
|
||||||
|
|
||||||
|
diskSize() {
|
||||||
|
const size = this.value?.size || '0';
|
||||||
|
|
||||||
|
return `${ size.replace('Gi', '') } GB`;
|
||||||
|
},
|
||||||
|
|
||||||
|
imageVirtualSizeInByte() {
|
||||||
|
return Math.max(this.selectedImage?.status?.size, this.selectedImage?.status?.virtualSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
diskSizeInByte() {
|
||||||
|
return parseSi(this.value?.size || '0');
|
||||||
|
},
|
||||||
|
|
||||||
|
showDiskTooSmallError() {
|
||||||
|
if (!this.thirdPartyStorageEnabled ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.imageVirtualSizeInByte > this.diskSizeInByte;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -158,7 +192,17 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
imageOptionLabel(image) {
|
||||||
|
let label = `${ image.metadata.namespace }/${ image.spec.displayName }`;
|
||||||
|
|
||||||
|
if (this.thirdPartyStorageEnabled) {
|
||||||
|
label += ` (${ image.imageStorageClass } / ${ image.virtualSize })`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
},
|
||||||
update() {
|
update() {
|
||||||
|
this.value.hasDiskError = this.showDiskTooSmallError;
|
||||||
this.$emit('update');
|
this.$emit('update');
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -341,5 +385,10 @@ export default {
|
|||||||
class="mb-20"
|
class="mb-20"
|
||||||
:label="value.volumeBackups.error.message"
|
:label="value.volumeBackups.error.message"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="!isView && showDiskTooSmallError"
|
||||||
|
color="error"
|
||||||
|
:label="t('harvester.virtualMachine.volume.vmImageVolumeTip', {diskSize: diskSize, imageVirtualSize: imageVirtualSize})"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -195,7 +195,12 @@ export default {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
isValidationPassed() {
|
||||||
|
// check if any disk hasDiskError is true
|
||||||
|
const hasError = this.diskRows.some((disk) => disk.hasDiskError === true);
|
||||||
|
|
||||||
|
return !hasError;
|
||||||
|
},
|
||||||
showCpuPinningBanner() {
|
showCpuPinningBanner() {
|
||||||
if (!this.value.cpuPinningFeatureEnabled) {
|
if (!this.value.cpuPinningFeatureEnabled) {
|
||||||
return false;
|
return false;
|
||||||
@ -489,6 +494,7 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:cancel-event="true"
|
:cancel-event="true"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
:validation-passed="isValidationPassed"
|
||||||
:can-yaml="isSingle ? true : false"
|
:can-yaml="isSingle ? true : false"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
:generate-yaml="generateYaml"
|
:generate-yaml="generateYaml"
|
||||||
|
|||||||
@ -630,6 +630,7 @@ harvester:
|
|||||||
setFirst: Set as root volume
|
setFirst: Set as root volume
|
||||||
saveVolume: Update Volume
|
saveVolume: Update Volume
|
||||||
encryption: Encryption
|
encryption: Encryption
|
||||||
|
vmImageVolumeTip: Disk size ({diskSize}) should greater than selected image virtual size ({imageVirtualSize})
|
||||||
lockTooltip:
|
lockTooltip:
|
||||||
all: All volumes are encrypted.
|
all: All volumes are encrypted.
|
||||||
partial: Some volumes are encrypted.
|
partial: Some volumes are encrypted.
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import HarvesterVolumeState from '../formatters/HarvesterVolumeState';
|
|||||||
|
|
||||||
import { allSettled } from '../utils/promise';
|
import { allSettled } from '../utils/promise';
|
||||||
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
||||||
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
id: HCI.VOLUME,
|
id: HCI.VOLUME,
|
||||||
@ -58,8 +59,10 @@ export default {
|
|||||||
if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
|
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
|
||||||
}
|
}
|
||||||
|
// we only show the non golden image PVCs in the list
|
||||||
|
const pvcs = hash.pvcs.filter((pvc) => pvc.metadata.annotations[HCI_ANNOTATIONS.GOLDEN_IMAGE] !== 'true');
|
||||||
|
|
||||||
this.rows = hash.pvcs;
|
this.rows = pvcs;
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -92,7 +95,8 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'storageClass',
|
name: 'storageClass',
|
||||||
labelKey: 'tableHeaders.storageClass',
|
labelKey: 'tableHeaders.storageClass',
|
||||||
value: 'spec.storageClassName'
|
value: 'spec.storageClassName',
|
||||||
|
sort: 'spec.storageClassName'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'AttachedVM',
|
name: 'AttachedVM',
|
||||||
|
|||||||
@ -62,6 +62,10 @@ export default class HciStorageClass extends StorageClass {
|
|||||||
return this.parameters?.encrypted === 'true';
|
return this.parameters?.encrypted === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isLonghornV1() {
|
||||||
|
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V1;
|
||||||
|
}
|
||||||
|
|
||||||
get isLonghornV2() {
|
get isLonghornV2() {
|
||||||
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
|
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
|
||||||
}
|
}
|
||||||
@ -73,4 +77,8 @@ export default class HciStorageClass extends StorageClass {
|
|||||||
get volumeEncryptionFeatureEnabled() {
|
get volumeEncryptionFeatureEnabled() {
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get thirdPartyStorageFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,6 +199,10 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
return `${ this.metadata.namespace }/${ this.spec.displayName }`;
|
return `${ this.metadata.namespace }/${ this.spec.displayName }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get imageStorageClass() {
|
||||||
|
return this?.metadata?.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] || '';
|
||||||
|
}
|
||||||
|
|
||||||
get imageMessage() {
|
get imageMessage() {
|
||||||
if (this.uploadError) {
|
if (this.uploadError) {
|
||||||
return ucFirst(this.uploadError);
|
return ucFirst(this.uploadError);
|
||||||
@ -385,6 +389,10 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get thirdPartyStorageFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
|
}
|
||||||
|
|
||||||
download() {
|
download() {
|
||||||
window.location.href = this.links.download;
|
window.location.href = this.links.download;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1183,6 +1183,10 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get thirdPartyStorageFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
|
}
|
||||||
|
|
||||||
setInstanceLabels(val) {
|
setInstanceLabels(val) {
|
||||||
if ( !this.spec?.template?.metadata?.labels ) {
|
if ( !this.spec?.template?.metadata?.labels ) {
|
||||||
set(this, 'spec.template.metadata.labels', {});
|
set(this, 'spec.template.metadata.labels', {});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user