mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-13 13:11:43 +00:00
Add OS upgrade features (#311)
* Add failed and success banner after image uploaded Signed-off-by: Andy Lee <andy.lee@suse.com> * add delete image feature Signed-off-by: Andy Lee <andy.lee@suse.com> * add skip checking single-replica detached volumes checkbox Signed-off-by: Andy Lee <andy.lee@suse.com> * change delete image flow Signed-off-by: Andy Lee <andy.lee@suse.com> * Reuse ConfirmRelatedToRemoveDialog Signed-off-by: Andy Lee <andy.lee@suse.com> --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
b689e3aacf
commit
dbbad01b0f
@ -43,7 +43,7 @@ export default {
|
||||
{{ t('harvester.upgradePage.upgradeInfo.tip') }}
|
||||
</p>
|
||||
|
||||
<p class="mb-5">
|
||||
<p>
|
||||
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a
|
||||
:href="releaseVersion"
|
||||
target="_blank"
|
||||
|
||||
@ -51,7 +51,7 @@ export default {
|
||||
},
|
||||
|
||||
warningMessage() {
|
||||
if (this.modalData.warningMessage) return this.t(this.modalData.warningMessage);
|
||||
if (this.modalData.warningMessage) return this.modalData.warningMessage;
|
||||
|
||||
const isPlural = this.type.endsWith('s');
|
||||
const thisOrThese = isPlural ? 'these' : 'this';
|
||||
@ -87,6 +87,12 @@ export default {
|
||||
return this.resources[0].nameDisplay;
|
||||
},
|
||||
|
||||
needConfirmation() {
|
||||
const { needConfirmation = true } = this.modalData ;
|
||||
|
||||
return needConfirmation === true;
|
||||
},
|
||||
|
||||
plusMore() {
|
||||
const remaining = this.resources.length - this.names.length;
|
||||
|
||||
@ -114,6 +120,10 @@ export default {
|
||||
},
|
||||
|
||||
deleteDisabled() {
|
||||
if (!this.needConfirmation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.confirmName !== this.nameToMatch;
|
||||
},
|
||||
|
||||
@ -164,24 +174,29 @@ export default {
|
||||
v-clean-html="warningMessage"
|
||||
></span>
|
||||
|
||||
<div class="mt-10 mb-10">
|
||||
<span
|
||||
v-clean-html="t('dialog.promptRemove.confirmName', {
|
||||
type: formattedType,
|
||||
nameToMatch: escapeHtml(nameToMatch)
|
||||
}, true)"
|
||||
></span>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<CopyToClipboardText :text="nameToMatch" />
|
||||
</div>
|
||||
<input
|
||||
id="confirm"
|
||||
v-model="confirmName"
|
||||
type="text"
|
||||
/>
|
||||
<div class="text-info mt-20">
|
||||
{{ protip }}
|
||||
<div
|
||||
v-if="needConfirmation"
|
||||
class="mt-20"
|
||||
>
|
||||
<div class="mt-10 mb-10">
|
||||
<span
|
||||
v-clean-html="t('dialog.promptRemove.confirmName', {
|
||||
type: formattedType,
|
||||
nameToMatch: escapeHtml(nameToMatch)
|
||||
}, true)"
|
||||
></span>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<CopyToClipboardText :text="nameToMatch" />
|
||||
</div>
|
||||
<input
|
||||
id="confirm"
|
||||
v-model="confirmName"
|
||||
type="text"
|
||||
/>
|
||||
<div class="text-info mt-20">
|
||||
{{ protip }}
|
||||
</div>
|
||||
</div>
|
||||
<Banner
|
||||
v-for="(error, i) in errors"
|
||||
|
||||
@ -291,7 +291,7 @@ export default {
|
||||
|
||||
const res = await this.value.save();
|
||||
|
||||
res.uploadImage(file);
|
||||
await res.uploadImage(file);
|
||||
|
||||
buttonCb(true);
|
||||
this.done();
|
||||
|
||||
@ -66,6 +66,7 @@ harvester:
|
||||
tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB'
|
||||
productLabel: 'Harvester'
|
||||
modal:
|
||||
|
||||
backup:
|
||||
success: 'Backup { backUpName } has been initiated.'
|
||||
addBackup: Add Backup
|
||||
@ -104,6 +105,9 @@ harvester:
|
||||
tip: Please enter a virtual machine name!
|
||||
success: 'Virtual machine { name } cloned successfully.'
|
||||
failed: 'Failed clone virtual machine!'
|
||||
osImage:
|
||||
title: Delete Image
|
||||
message: Are you sure you want to delete the image { name } ?
|
||||
downloadImage:
|
||||
title: Download Image
|
||||
banner: 'This action takes a while depending on the image size ({ size }). Please be patient.'
|
||||
@ -878,14 +882,18 @@ harvester:
|
||||
upgradeNode: Upgrading Node
|
||||
upgradeSysService: Upgrading System Service
|
||||
upgradeImage: Download Upgrade Image
|
||||
osUpgrade: OS Upgrade
|
||||
osUpgrade: Cluster Upgrade
|
||||
uploadNew: Upload New Image
|
||||
deleteHeader: Please select an image to delete.
|
||||
deleteExisting: Delete Existing Image
|
||||
selectExisting: Select Existing Image
|
||||
createRepository: Creating Upgrade Repository
|
||||
succeeded: Succeeded
|
||||
releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Notes</a>.
|
||||
checkReady: I have read and understood the upgrade instructions related to this Harvester version.
|
||||
pending: Pending
|
||||
upload:
|
||||
duplicatedFile: The file you are trying to upload already exists.
|
||||
repoInfo:
|
||||
upgradeStatus: Upgrade Status
|
||||
os: OS
|
||||
@ -1092,6 +1100,11 @@ harvester:
|
||||
imageUrl: Please input a valid image URL.
|
||||
chooseFile: Please select to upload an image.
|
||||
checksum: Checksum
|
||||
networkError: Unable to upload the image. Resolve network issues that may have occurred and try again.
|
||||
cancelUpload: Cancelled the image upload.
|
||||
uploadSuccess: "{name} uploaded successfully. Press Upgrade button to start the cluster upgrade process."
|
||||
deleteImage: Please select an image to delete.
|
||||
deleteSuccess: "{name} deleted successfully."
|
||||
harvesterMonitoring:
|
||||
label: Harvester Monitoring
|
||||
section:
|
||||
|
||||
@ -313,7 +313,7 @@ export default class HciVmImage extends HarvesterResource {
|
||||
try {
|
||||
this.$ctx.commit('harvester-common/uploadStart', this.metadata.name, { root: true });
|
||||
|
||||
await this.doAction('upload', formData, {
|
||||
const result = await this.doAction('upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'File-Size': file.size,
|
||||
@ -321,15 +321,15 @@ export default class HciVmImage extends HarvesterResource {
|
||||
params: { size: file.size },
|
||||
signal: opt.signal,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.$ctx.commit('harvester-common/uploadError', { name: this.name, message: err.message }, { root: true });
|
||||
|
||||
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
|
||||
|
||||
return Promise.reject(err);
|
||||
throw err;
|
||||
} finally {
|
||||
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
|
||||
}
|
||||
|
||||
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -11,10 +11,12 @@ import { HCI } from '../../../../types';
|
||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../../config/harvester';
|
||||
import ImagePercentageBar from '@shell/components/formatter/ImagePercentageBar';
|
||||
import { Banner } from '@components/Banner';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
const IMAGE_METHOD = {
|
||||
NEW: 'new',
|
||||
EXIST: 'exist'
|
||||
NEW: 'new',
|
||||
EXIST: 'exist',
|
||||
DELETE: 'delete'
|
||||
};
|
||||
|
||||
const DOWNLOAD = 'download';
|
||||
@ -40,23 +42,8 @@ export default {
|
||||
spec: { image: '' }
|
||||
});
|
||||
|
||||
const imageValue = await this.$store.dispatch('harvester/create', {
|
||||
type: HCI.IMAGE,
|
||||
metadata: {
|
||||
name: '',
|
||||
namespace: 'harvester-system',
|
||||
generateName: 'image-',
|
||||
annotations: {}
|
||||
},
|
||||
spec: {
|
||||
sourceType: UPLOAD,
|
||||
displayName: '',
|
||||
checksum: ''
|
||||
},
|
||||
});
|
||||
|
||||
await this.initImageValue();
|
||||
this.value = value;
|
||||
this.imageValue = imageValue;
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
@ -67,17 +54,20 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: null,
|
||||
file: {},
|
||||
uploadImageId: '',
|
||||
imageId: '',
|
||||
imageSource: IMAGE_METHOD.NEW,
|
||||
sourceType: UPLOAD,
|
||||
uploadController: null,
|
||||
imageValue: null,
|
||||
errors: [],
|
||||
enableLogging: true,
|
||||
IMAGE_METHOD
|
||||
value: null,
|
||||
file: {},
|
||||
uploadImageId: '',
|
||||
imageId: '',
|
||||
deleteImageId: '',
|
||||
imageSource: IMAGE_METHOD.NEW,
|
||||
sourceType: UPLOAD,
|
||||
uploadController: null,
|
||||
uploadResult: null,
|
||||
imageValue: null,
|
||||
enableLogging: true,
|
||||
IMAGE_METHOD,
|
||||
skipSingleReplicaDetachedVol: false,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
|
||||
@ -86,22 +76,45 @@ export default {
|
||||
return `${ HARVESTER_PRODUCT }-c-cluster-resource`;
|
||||
},
|
||||
|
||||
osImageOptions() {
|
||||
return this.$store.getters['harvester/all'](HCI.IMAGE)
|
||||
.filter((I) => I.isOSImage)
|
||||
.map((I) => {
|
||||
return {
|
||||
label: I.spec.displayName,
|
||||
value: I.id,
|
||||
disabled: !I.isReady
|
||||
};
|
||||
});
|
||||
skipSingleReplicaDetachedVolFeatureEnabled() {
|
||||
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
|
||||
},
|
||||
|
||||
uploadImage() {
|
||||
allOSImages() {
|
||||
return this.$store.getters['harvester/all'](HCI.IMAGE).filter((I) => I.isOSImage) || [];
|
||||
},
|
||||
|
||||
deleteOSImageOptions() {
|
||||
return this.allOSImages.map((I) => {
|
||||
return {
|
||||
label: I.spec.displayName,
|
||||
value: I.id,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
osImageOptions() {
|
||||
return this.allOSImages.map((I) => {
|
||||
return {
|
||||
label: I.spec.displayName,
|
||||
value: I.id,
|
||||
disabled: !I.isReady
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
createNewImage() {
|
||||
return this.imageSource === IMAGE_METHOD.NEW;
|
||||
},
|
||||
|
||||
selectExistImage() {
|
||||
return this.imageSource === IMAGE_METHOD.EXIST;
|
||||
},
|
||||
|
||||
deleteExistImage() {
|
||||
return this.imageSource === IMAGE_METHOD.DELETE;
|
||||
},
|
||||
|
||||
fileName() {
|
||||
return this.file?.name || '';
|
||||
},
|
||||
@ -116,7 +129,11 @@ export default {
|
||||
return image?.status?.progress;
|
||||
},
|
||||
|
||||
enableSave() {
|
||||
enableUpgrade() {
|
||||
if (this.deleteExistImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.sourceType === DOWNLOAD) {
|
||||
return true;
|
||||
}
|
||||
@ -128,16 +145,28 @@ export default {
|
||||
return true;
|
||||
},
|
||||
|
||||
showProgressBar() {
|
||||
return this.sourceType === UPLOAD && this.fileName !== '';
|
||||
},
|
||||
|
||||
showUploadingWarningBanner() {
|
||||
isUploading() {
|
||||
return this.fileName !== '' && this.uploadProgress !== 100;
|
||||
},
|
||||
|
||||
showProgressBar() {
|
||||
return this.createNewImage && this.sourceType === UPLOAD && this.isUploading;
|
||||
},
|
||||
|
||||
showUploadSuccessBanner() {
|
||||
return this.createNewImage && this.fileName !== '' && isEmpty(this.errors) && !this.showUploadingWarningBanner && this.uploadResult?._status === 200;
|
||||
},
|
||||
|
||||
showUploadingWarningBanner() {
|
||||
return this.createNewImage && this.isUploading;
|
||||
},
|
||||
|
||||
showUpgradeOptions() {
|
||||
return this.createNewImage || this.selectExistImage;
|
||||
},
|
||||
|
||||
disableUploadButton() {
|
||||
return this.sourceType === UPLOAD && this.fileName !== '' && this.uploadProgress !== 100;
|
||||
return this.sourceType === UPLOAD && this.isUploading;
|
||||
},
|
||||
},
|
||||
|
||||
@ -152,11 +181,30 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
async initImageValue() {
|
||||
this.imageValue = await this.$store.dispatch('harvester/create', {
|
||||
type: HCI.IMAGE,
|
||||
metadata: {
|
||||
name: '',
|
||||
namespace: 'harvester-system',
|
||||
generateName: 'image-',
|
||||
annotations: {}
|
||||
},
|
||||
spec: {
|
||||
sourceType: UPLOAD,
|
||||
displayName: '',
|
||||
checksum: ''
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async save(buttonCb) {
|
||||
let res = null;
|
||||
|
||||
this.file = {};
|
||||
this.errors = [];
|
||||
if (!this.imageValue.spec.displayName && this.uploadImage) {
|
||||
|
||||
if (!this.imageValue.spec.displayName && this.createNewImage) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('generic.name') }));
|
||||
buttonCb(false);
|
||||
|
||||
@ -164,6 +212,28 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.deleteExistImage) {
|
||||
// if not select image, show error
|
||||
if (!this.deleteImageId) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.deleteImage'));
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if select image, delete image
|
||||
const image = this.$store.getters['harvester/byId'](HCI.IMAGE, this.deleteImageId);
|
||||
|
||||
if (image) {
|
||||
this.handleImageDelete(image);
|
||||
buttonCb(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.imageSource === IMAGE_METHOD.NEW) {
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
|
||||
|
||||
@ -194,12 +264,15 @@ export default {
|
||||
if (this.canEnableLogging) {
|
||||
this.value.spec.logEnabled = this.enableLogging;
|
||||
}
|
||||
if (this.skipSingleReplicaDetachedVolFeatureEnabled) {
|
||||
this.value.metadata.annotations = { [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) };
|
||||
}
|
||||
|
||||
await this.value.save();
|
||||
this.done();
|
||||
buttonCb(true);
|
||||
} catch (e) {
|
||||
this.errors = exceptionToErrorsArray(e);
|
||||
this.errors = [e?.message] || exceptionToErrorsArray(e);
|
||||
buttonCb(false);
|
||||
}
|
||||
},
|
||||
@ -207,36 +280,71 @@ export default {
|
||||
async uploadFile(file) {
|
||||
const fileName = file.name;
|
||||
|
||||
this.imageValue.spec.sourceType = UPLOAD;
|
||||
this.imageValue.spec.displayName = fileName;
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
|
||||
|
||||
if (!fileName) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.unknownImageName'));
|
||||
|
||||
return;
|
||||
}
|
||||
const isDuplicatedFile = this.allOSImages.some((I) => I.spec.displayName === fileName);
|
||||
|
||||
this.imageValue.spec.url = '';
|
||||
if (isDuplicatedFile) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.upgradePage.upload.duplicatedFile'));
|
||||
this.file = {};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.errors = [];
|
||||
this.imageValue.spec.sourceType = UPLOAD;
|
||||
this.imageValue.spec.displayName = fileName;
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] = fileName;
|
||||
this.imageValue.spec.url = '';
|
||||
|
||||
try {
|
||||
const res = await this.imageValue.save();
|
||||
|
||||
this.uploadImageId = res.id;
|
||||
this.uploadController = new AbortController();
|
||||
|
||||
const signal = this.uploadController.signal;
|
||||
|
||||
await res.uploadImage(file, { signal });
|
||||
this.uploadResult = await res.uploadImage(file, { signal });
|
||||
} catch (e) {
|
||||
this.errors = exceptionToErrorsArray(e);
|
||||
if (e?.code === 'ERR_NETWORK') {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.networkError'));
|
||||
} else if (e?.code === 'ERR_CANCELED') {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.cancelUpload'));
|
||||
} else {
|
||||
this.errors = [e?.message] || exceptionToErrorsArray(e);
|
||||
}
|
||||
this.file = {};
|
||||
this.uploadImageId = '';
|
||||
}
|
||||
},
|
||||
|
||||
handleImageDelete(imageId) {
|
||||
const image = this.allOSImages.find((I) => I.id === imageId);
|
||||
|
||||
if (image) {
|
||||
this.$store.dispatch('harvester/promptModal', {
|
||||
resources: [image],
|
||||
component: 'ConfirmRelatedToRemoveDialog',
|
||||
needConfirmation: false,
|
||||
warningMessage: this.$store.getters['i18n/t']('harvester.modal.osImage.message', { name: image.displayName })
|
||||
});
|
||||
this.deleteImageId = '';
|
||||
}
|
||||
},
|
||||
|
||||
async handleFileUpload() {
|
||||
this.file = this.$refs.file.files[0];
|
||||
this.uploadImageId = '';
|
||||
this.errors = [];
|
||||
await this.uploadFile(this.file);
|
||||
this.file = this.$refs.file?.files[0];
|
||||
if (this.file) {
|
||||
await this.initImageValue();
|
||||
await this.uploadFile(this.file);
|
||||
}
|
||||
},
|
||||
|
||||
selectFile() {
|
||||
@ -246,6 +354,12 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
imageSource(neu) {
|
||||
if (neu !== IMAGE_METHOD.DELETE) {
|
||||
this.deleteImageId = '';
|
||||
}
|
||||
},
|
||||
|
||||
'imageValue.spec.url': {
|
||||
handler(neu) {
|
||||
const suffixName = neu?.split('/')?.pop();
|
||||
@ -283,10 +397,11 @@ export default {
|
||||
:errors="errors"
|
||||
:can-yaml="false"
|
||||
finish-button-mode="upgrade"
|
||||
:validation-passed="enableSave"
|
||||
:validation-passed="enableUpgrade"
|
||||
:cancel-event="true"
|
||||
@finish="save"
|
||||
@cancel="done"
|
||||
@error="e=>errors = e"
|
||||
>
|
||||
<RadioGroup
|
||||
v-model:value="imageSource"
|
||||
@ -295,20 +410,55 @@ export default {
|
||||
:options="[
|
||||
IMAGE_METHOD.NEW,
|
||||
IMAGE_METHOD.EXIST,
|
||||
IMAGE_METHOD.DELETE,
|
||||
]"
|
||||
:labels="[
|
||||
t('harvester.upgradePage.uploadNew'),
|
||||
t('harvester.upgradePage.selectExisting'),
|
||||
t('harvester.upgradePage.deleteExisting'),
|
||||
]"
|
||||
/>
|
||||
|
||||
<UpgradeInfo v-if="createNewImage || selectExistImage" />
|
||||
|
||||
<Banner
|
||||
v-if="showUploadSuccessBanner"
|
||||
color="success"
|
||||
class="mt-0 mb-30"
|
||||
:label="t('harvester.setting.upgrade.uploadSuccess', { name: file.name })"
|
||||
/>
|
||||
<Banner
|
||||
v-if="showUploadingWarningBanner"
|
||||
color="warning"
|
||||
class="mt-0 mb-30"
|
||||
:label="t('harvester.image.warning.osUpgrade.uploading', { name: file.name })"
|
||||
/>
|
||||
<UpgradeInfo />
|
||||
|
||||
<div v-if="uploadImage">
|
||||
<div
|
||||
v-if="showUpgradeOptions"
|
||||
class="mt-10 mb-10"
|
||||
>
|
||||
<Checkbox
|
||||
v-if="canEnableLogging"
|
||||
v-model:value="enableLogging"
|
||||
class="check mb-20"
|
||||
type="checkbox"
|
||||
:label="t('harvester.upgradePage.enableLogging')"
|
||||
/>
|
||||
<div
|
||||
v-if="skipSingleReplicaDetachedVolFeatureEnabled"
|
||||
class="mb-20"
|
||||
>
|
||||
<Checkbox
|
||||
v-model:value="skipSingleReplicaDetachedVol"
|
||||
class="check"
|
||||
type="checkbox"
|
||||
:label="t('harvester.upgradePage.skipSingleReplicaDetachedVol')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="createNewImage">
|
||||
<LabeledInput
|
||||
v-model:value.trim="imageValue.spec.displayName"
|
||||
class="mb-20"
|
||||
@ -322,14 +472,6 @@ export default {
|
||||
label-key="harvester.setting.upgrade.checksum"
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
v-if="canEnableLogging"
|
||||
v-model:value="enableLogging"
|
||||
class="check mb-20"
|
||||
type="checkbox"
|
||||
:label="t('harvester.upgradePage.enableLogging')"
|
||||
/>
|
||||
|
||||
<RadioGroup
|
||||
v-model:value="sourceType"
|
||||
class="mb-20 image-group"
|
||||
@ -386,15 +528,33 @@ export default {
|
||||
:value="uploadProgress"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LabeledSelect
|
||||
v-else
|
||||
v-if="selectExistImage"
|
||||
v-model:value="imageId"
|
||||
:options="osImageOptions"
|
||||
required
|
||||
class="mb-20"
|
||||
label-key="harvester.fields.image"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="deleteExistImage"
|
||||
class="mt-20"
|
||||
>
|
||||
<Banner
|
||||
color="info"
|
||||
class="mt-10 mb-30"
|
||||
:label="t('harvester.upgradePage.deleteHeader')"
|
||||
/>
|
||||
<LabeledSelect
|
||||
v-model:value="deleteImageId"
|
||||
:options="deleteOSImageOptions"
|
||||
required
|
||||
class="mb-20"
|
||||
label-key="harvester.fields.image"
|
||||
@update:value="handleImageDelete"
|
||||
/>
|
||||
</div>
|
||||
</CruResource>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user