mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-13 21:21:44 +00:00
add encrypt image and decrypt image actions
Signed-off-by: andy.lee <andy.lee@suse.com>
This commit is contained in:
parent
8117e99b54
commit
228369ad88
@ -2,7 +2,8 @@
|
|||||||
import CopyToClipboardText from '@shell/components/CopyToClipboardText';
|
import CopyToClipboardText from '@shell/components/CopyToClipboardText';
|
||||||
import LabelValue from '@shell/components/LabelValue';
|
import LabelValue from '@shell/components/LabelValue';
|
||||||
import { DESCRIPTION } from '@shell/config/labels-annotations';
|
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 Tabbed from '@shell/components/Tabbed';
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
import Tab from '@shell/components/Tabbed/Tab';
|
||||||
import { findBy } from '@shell/utils/array';
|
import { findBy } from '@shell/utils/array';
|
||||||
@ -31,10 +32,14 @@ export default {
|
|||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
this.secrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
|
this.secrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
|
||||||
|
this.images = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE });
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { secrets: [] };
|
return {
|
||||||
|
secrets: [],
|
||||||
|
images: []
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@ -64,6 +69,34 @@ export default {
|
|||||||
return this.value?.spec?.sourceType === 'upload';
|
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.displayNameWithNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
|
||||||
|
isEncryptedOrDecrypted() {
|
||||||
|
return ['encrypt', 'decrypt'].includes(this.value?.spec?.securityParameters?.cryptoOperation);
|
||||||
|
},
|
||||||
|
|
||||||
encryptionSecret() {
|
encryptionSecret() {
|
||||||
if (!this.value.isEncrypted) {
|
if (!this.value.isEncrypted) {
|
||||||
return '-';
|
return '-';
|
||||||
@ -80,7 +113,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
imageName() {
|
imageName() {
|
||||||
return this.value?.metadata?.annotations?.[HCI.IMAGE_NAME] || '-';
|
return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-';
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -144,7 +177,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="value.isEncrypted" class="row">
|
<div v-if="value.isEncrypted" class="row mb-20">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<div class="text-label">
|
<div class="text-label">
|
||||||
{{ t('harvester.image.encryptionSecret') }}
|
{{ t('harvester.image.encryptionSecret') }}
|
||||||
@ -161,6 +194,23 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isEncryptedOrDecrypted" class="row mb-20">
|
||||||
|
<div class="col span-12">
|
||||||
|
<div class="text-label">
|
||||||
|
{{ t('harvester.image.sourceImage') }}
|
||||||
|
</div>
|
||||||
|
<n-link v-if="sourceImageId && sourceImageLink" :to="sourceImageLink">
|
||||||
|
{{ sourceImageId }}
|
||||||
|
</n-link>
|
||||||
|
<span v-else-if="sourceImageId">
|
||||||
|
{{ sourceImageId }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-muted">
|
||||||
|
—
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="errorMessage !== '-'" class="row">
|
<div v-if="errorMessage !== '-'" class="row">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -62,12 +62,32 @@ export default {
|
|||||||
|
|
||||||
this['storageClassName'] = this.storageClassName || defaultStorage?.metadata?.name || 'longhorn';
|
this['storageClassName'] = this.storageClassName || defaultStorage?.metadata?.name || 'longhorn';
|
||||||
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
||||||
this.selectedImage = this.images.find(i => i.name === this.value.name) || null;
|
|
||||||
|
const { securityParameters } = this.value.spec;
|
||||||
|
|
||||||
|
// edit and view mode should show the source image
|
||||||
|
if (securityParameters) {
|
||||||
|
// image ns/name = image.id
|
||||||
|
const sourceImage = `${ securityParameters.sourceImageNamespace }/${ securityParameters.sourceImageName }`;
|
||||||
|
|
||||||
|
this.selectedImage = this.images.find(image => image.id === sourceImage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
// pass from Encrypt Image / Decrypt Image actions
|
||||||
|
const { image, sourceType, cryptoOperation } = this.$route.query || {};
|
||||||
|
|
||||||
if ( !this.value.spec ) {
|
if ( !this.value.spec ) {
|
||||||
this.value['spec'] = { sourceType: DOWNLOAD };
|
this.value['spec'] = { sourceType: sourceType || DOWNLOAD };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image && cryptoOperation) {
|
||||||
|
this.value.spec.securityParameters = {
|
||||||
|
cryptoOperation,
|
||||||
|
sourceImageName: image.metadata.name,
|
||||||
|
sourceImageNamespace: image.metadata.namespace
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.value.metadata.name) {
|
if (!this.value.metadata.name) {
|
||||||
@ -75,7 +95,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedImage: null,
|
selectedImage: image || null,
|
||||||
images: [],
|
images: [],
|
||||||
url: this.value.spec.url,
|
url: this.value.spec.url,
|
||||||
files: [],
|
files: [],
|
||||||
@ -147,22 +167,27 @@ export default {
|
|||||||
options = this.images.filter(image => image.isEncrypted);
|
options = this.images.filter(image => image.isEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
return options.map(image => image.spec.displayName);
|
return options.map(image => image.displayNameWithNamespace);
|
||||||
},
|
},
|
||||||
sourceImageName: {
|
sourceImage: {
|
||||||
get() {
|
get() {
|
||||||
return this.selectedImage?.spec.displayName;
|
if (this.selectedImage) {
|
||||||
|
return this.selectedImage.displayNameWithNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
},
|
},
|
||||||
set(imageDisplayName) {
|
set(neu) {
|
||||||
this.selectedImage = this.images.find(i => i.spec.displayName === imageDisplayName);
|
this.selectedImage = this.images.find(i => i.displayNameWithNamespace === neu);
|
||||||
// sourceImageName should bring the name of the image
|
// sourceImageName should bring the name of the image
|
||||||
this.value.spec.securityParameters.sourceImageName = this.selectedImage?.metadata.name || '';
|
this.value.spec.securityParameters.sourceImageName = this.selectedImage?.metadata.name || '';
|
||||||
|
this.value.spec.securityParameters.sourceImageNamespace = this.selectedImage?.metadata.namespace || '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sourceType: {
|
sourceType: {
|
||||||
get() {
|
get() {
|
||||||
if (this.value.spec.sourceType === CLONE) {
|
if (this.value.spec.sourceType === CLONE) {
|
||||||
return this.value.spec.securityParameters.cryptoOperation;
|
return this.value.spec?.securityParameters?.cryptoOperation;
|
||||||
} else {
|
} else {
|
||||||
return this.value.spec.sourceType;
|
return this.value.spec.sourceType;
|
||||||
}
|
}
|
||||||
@ -186,15 +211,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'value.metadata.namespace'(neu) {
|
|
||||||
if (this.value.spec.sourceType === CLONE) {
|
|
||||||
this.$set(this.value.spec, 'securityParameters', {
|
|
||||||
cryptoOperation: this.value.spec.securityParameters.cryptoOperation,
|
|
||||||
sourceImageName: '',
|
|
||||||
sourceImageNamespace: neu
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'value.spec.url'(neu) {
|
'value.spec.url'(neu) {
|
||||||
const url = neu.trim();
|
const url = neu.trim();
|
||||||
|
|
||||||
@ -451,7 +467,7 @@ export default {
|
|||||||
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-if="value.spec.sourceType === 'clone'"
|
v-if="value.spec.sourceType === 'clone'"
|
||||||
v-model="sourceImageName"
|
v-model="sourceImage"
|
||||||
:options="sourceImageOptions"
|
:options="sourceImageOptions"
|
||||||
:label="t('harvester.image.sourceImage')"
|
:label="t('harvester.image.sourceImage')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
|||||||
@ -153,7 +153,7 @@ harvester:
|
|||||||
warning: Warning
|
warning: Warning
|
||||||
error: Error
|
error: Error
|
||||||
action:
|
action:
|
||||||
createVM: Create a Virtual Machine
|
createVM: Create Virtual Machine
|
||||||
start: Start
|
start: Start
|
||||||
restart: Restart
|
restart: Restart
|
||||||
softreboot: Soft Reboot
|
softreboot: Soft Reboot
|
||||||
@ -162,6 +162,8 @@ harvester:
|
|||||||
deepClone: Clone
|
deepClone: Clone
|
||||||
shallowClone: Clone Template
|
shallowClone: Clone Template
|
||||||
unpause: Unpause
|
unpause: Unpause
|
||||||
|
encryptImage: Encrypt Image
|
||||||
|
decryptImage: Decrypt Image
|
||||||
ejectCDROM: Eject CD-ROM
|
ejectCDROM: Eject CD-ROM
|
||||||
editVMQuota: Edit VM Quota
|
editVMQuota: Edit VM Quota
|
||||||
launchFormTemplate: Launch instance from template
|
launchFormTemplate: Launch instance from template
|
||||||
@ -778,7 +780,7 @@ harvester:
|
|||||||
urlTip: 'Supports the <code>raw</code> and <code>qcow2</code> image formats which are supported by <a href="https://www.qemu.org/docs/master/system/images.html#disk-image-file-formats" target="_blank">qemu</a>. Bootable ISO images can also be used and are treated like <code>raw</code> images.'
|
urlTip: 'Supports the <code>raw</code> and <code>qcow2</code> image formats which are supported by <a href="https://www.qemu.org/docs/master/system/images.html#disk-image-file-formats" target="_blank">qemu</a>. Bootable ISO images can also be used and are treated like <code>raw</code> images.'
|
||||||
fileName: File Name
|
fileName: File Name
|
||||||
uploadFile: Upload File
|
uploadFile: Upload File
|
||||||
source: Source
|
source: Source Type
|
||||||
sourceType:
|
sourceType:
|
||||||
download: URL
|
download: URL
|
||||||
upload: File
|
upload: File
|
||||||
|
|||||||
@ -57,6 +57,20 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
label: this.t('harvester.action.createVM'),
|
label: this.t('harvester.action.createVM'),
|
||||||
disabled: !this.isReady,
|
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',
|
action: 'download',
|
||||||
enabled: this.links?.download,
|
enabled: this.links?.download,
|
||||||
@ -67,6 +81,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) {
|
applyDefaults(resources = this, realMode) {
|
||||||
if (realMode !== _CLONE) {
|
if (realMode !== _CLONE) {
|
||||||
this.metadata['labels'] = { [HCI_ANNOTATIONS.OS_TYPE]: '', [HCI_ANNOTATIONS.IMAGE_SUFFIX]: '' };
|
this.metadata['labels'] = { [HCI_ANNOTATIONS.OS_TYPE]: '', [HCI_ANNOTATIONS.IMAGE_SUFFIX]: '' };
|
||||||
@ -151,6 +195,10 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
!!this.spec.securityParameters?.sourceImageNamespace;
|
!!this.spec.securityParameters?.sourceImageNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get displayNameWithNamespace() {
|
||||||
|
return `${ this.metadata.namespace }/${ this.spec.displayName }`;
|
||||||
|
}
|
||||||
|
|
||||||
get imageMessage() {
|
get imageMessage() {
|
||||||
if (this.uploadError) {
|
if (this.uploadError) {
|
||||||
return ucFirst(this.uploadError);
|
return ucFirst(this.uploadError);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user