diff --git a/pkg/harvester/detail/harvesterhci.io.virtualmachineimage/index.vue b/pkg/harvester/detail/harvesterhci.io.virtualmachineimage/index.vue index 0776d940ebe..0416e6d5f9a 100644 --- a/pkg/harvester/detail/harvesterhci.io.virtualmachineimage/index.vue +++ b/pkg/harvester/detail/harvesterhci.io.virtualmachineimage/index.vue @@ -2,7 +2,8 @@ import CopyToClipboardText from '@shell/components/CopyToClipboardText'; import LabelValue from '@shell/components/LabelValue'; import { DESCRIPTION } from '@shell/config/labels-annotations'; -import { HCI } from '@pkg/harvester/config/labels-annotations'; +import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations'; +import { HCI } from '../../types'; import Tabbed from '@shell/components/Tabbed'; import Tab from '@shell/components/Tabbed/Tab'; import { findBy } from '@shell/utils/array'; @@ -31,10 +32,14 @@ export default { const inStore = this.$store.getters['currentProduct'].inStore; this.secrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }); + this.images = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }); }, data() { - return { secrets: [] }; + return { + secrets: [], + images: [] + }; }, computed: { @@ -64,6 +69,34 @@ export default { return this.value?.spec?.sourceType === 'upload'; }, + sourceImage() { + const { sourceImageName, sourceImageNamespace } = this.value?.spec?.securityParameters || {}; + + if (sourceImageNamespace && sourceImageName) { + const imageId = `${ sourceImageNamespace }/${ sourceImageName }`; + + return this.images.find(image => image.id === imageId); + } + + return null; + }, + + sourceImageLink() { + return this.sourceImage?.detailLocation; + }, + + sourceImageId() { + if (this.sourceImage) { + return `${ this.sourceImage.namespace }/${ this.sourceImage.displayName }`; + } + + return ''; + }, + + isEncryptedOrDecrypted() { + return ['encrypt', 'decrypt'].includes(this.value?.spec?.securityParameters?.cryptoOperation); + }, + encryptionSecret() { if (!this.value.isEncrypted) { return '-'; @@ -80,7 +113,7 @@ export default { }, imageName() { - return this.value?.metadata?.annotations?.[HCI.IMAGE_NAME] || '-'; + return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-'; }, } }; @@ -144,7 +177,7 @@ export default { -
+
{{ t('harvester.image.encryptionSecret') }} @@ -161,6 +194,23 @@ export default {
+
+
+
+ {{ t('harvester.image.sourceImage') }} +
+ + {{ sourceImageId }} + + + {{ sourceImageId }} + + + — + +
+
+
diff --git a/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue b/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue index 7a3c8c5e2ad..a573b59b5bd 100644 --- a/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue +++ b/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue @@ -60,12 +60,27 @@ export default { this.$set(this, 'storageClassName', this.storageClassName || defaultStorage?.metadata?.name || 'longhorn'); this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE); - this.selectedImage = this.images.find(i => i.name === this.value.name) || null; + + // edit and view mode should show the selected image + if (this.value.name) { + this.selectedImage = this.images.find(i => i.name === this.value.name); + } }, data() { + // pass from Encrypt Image / Decrypt Image actions + const { image, sourceType, cryptoOperation } = this.$route.query || {}; + if ( !this.value.spec ) { - this.$set(this.value, 'spec', { sourceType: DOWNLOAD }); + this.$set(this.value, 'spec', { sourceType: sourceType || DOWNLOAD }); + } + + if (image && cryptoOperation) { + this.$set(this.value.spec, 'securityParameters', { + cryptoOperation, + sourceImageName: image.metadata.name, + sourceImageNamespace: image.metadata.namespace + }); } if (!this.value.metadata.name) { @@ -73,7 +88,7 @@ export default { } return { - selectedImage: null, + selectedImage: image || null, images: [], url: this.value.spec.url, files: [], @@ -160,7 +175,7 @@ export default { sourceType: { get() { if (this.value.spec.sourceType === CLONE) { - return this.value.spec.securityParameters.cryptoOperation; + return this.value.spec?.securityParameters?.cryptoOperation; } else { return this.value.spec.sourceType; } diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index c639a01ab49..d71bf3075b1 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -126,7 +126,7 @@ harvester: warning: Warning error: Error action: - createVM: Create a Virtual Machine + createVM: Create Virtual Machine start: Start restart: Restart softreboot: Soft Reboot @@ -135,6 +135,8 @@ harvester: deepClone: Clone shallowClone: Clone Template unpause: Unpause + encryptImage: Encrypt Image + decryptImage: Decrypt Image ejectCDROM: Eject CD-ROM editVMQuota: Edit VM Quota launchFormTemplate: Launch instance from template @@ -734,7 +736,7 @@ harvester: urlTip: 'Supports the raw and qcow2 image formats which are supported by qemu. Bootable ISO images can also be used and are treated like raw images.' fileName: File Name uploadFile: Upload File - source: Source + source: Source Type sourceType: download: URL upload: File diff --git a/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js b/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js index 5f9e44a16cd..2db06826470 100644 --- a/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js +++ b/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js @@ -58,6 +58,20 @@ export default class HciVmImage extends HarvesterResource { label: this.t('harvester.action.createVM'), disabled: !this.isReady, }, + { + action: 'encryptImage', + enabled: !this.isEncrypted, + icon: 'icon icon-lock', + label: this.t('harvester.action.encryptImage'), + disabled: !this.isReady, + }, + { + action: 'decryptImage', + enabled: this.isEncrypted, + icon: 'icon icon-unlock', + label: this.t('harvester.action.decryptImage'), + disabled: !this.isReady, + }, { action: 'download', enabled: this.links?.download, @@ -68,6 +82,36 @@ export default class HciVmImage extends HarvesterResource { ]; } + encryptImage() { + const router = this.currentRouter(); + + router.push({ + name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`, + params: { resource: HCI.IMAGE }, + query: { + image: this, + fromPage: HCI.IMAGE, + sourceType: 'clone', + cryptoOperation: 'encrypt' + } + }); + } + + decryptImage() { + const router = this.currentRouter(); + + router.push({ + name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`, + params: { resource: HCI.IMAGE }, + query: { + image: this, + fromPage: HCI.IMAGE, + sourceType: 'clone', + cryptoOperation: 'decrypt' + } + }); + } + applyDefaults(resources = this, realMode) { if (realMode !== _CLONE) { Vue.set(this.metadata, 'labels', { [HCI_ANNOTATIONS.OS_TYPE]: '', [HCI_ANNOTATIONS.IMAGE_SUFFIX]: '' });