mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-01 11:46:06 +08:00
Compare commits
21
Commits
v1.7.0-rc2
...
v1.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fbe1943d8 | ||
|
|
ec6bc4d639 | ||
|
|
3824a14730 | ||
|
|
0fc8bece02 | ||
|
|
39764af627 | ||
|
|
bdc87bda0e | ||
|
|
e0dc77624b | ||
|
|
c3ba10bd22 | ||
|
|
5b7d54d0a3 | ||
|
|
99a216dfa0 | ||
|
|
e4c85f510e | ||
|
|
f391f018de | ||
|
|
4b2e92ea15 | ||
|
|
2f956d5946 | ||
|
|
5f8d556ea2 | ||
|
|
396ab48f1c | ||
|
|
6b8c079018 | ||
|
|
7f638e86c8 | ||
|
|
0c4955a766 | ||
|
|
3f4ff30275 | ||
|
|
8e0332a364 |
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harvester-ui-extension",
|
||||
"version": "1.7.0-rc2",
|
||||
"version": "1.7.0",
|
||||
"private": false,
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -24,9 +24,9 @@
|
||||
"glob": "7.2.3",
|
||||
"glob-parent": "6.0.2",
|
||||
"json5": "2.2.3",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/lodash": "4.17.21",
|
||||
"merge": "2.1.1",
|
||||
"node-forge": "1.3.1",
|
||||
"node-forge": "1.3.3",
|
||||
"nth-check": "2.1.1",
|
||||
"qs": "6.14.0",
|
||||
"roarr": "7.21.2",
|
||||
|
||||
@@ -18,16 +18,20 @@ export default {
|
||||
|
||||
await this.$store.dispatch('harvester/findAll', { type: NAMESPACE });
|
||||
|
||||
try {
|
||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl']('v1/harvester/namespaces?link=supportbundle');
|
||||
const response = await this.$store.dispatch('harvester/request', { url });
|
||||
if (this.customSupportBundleFeatureEnabled) {
|
||||
try {
|
||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl']('v1/harvester/namespaces?link=supportbundle');
|
||||
const response = await this.$store.dispatch('harvester/request', { url });
|
||||
|
||||
this.defaultNamespaces = response.data || [];
|
||||
} catch (error) {
|
||||
this.defaultNamespaces = response.data || [];
|
||||
} catch (error) {
|
||||
this.defaultNamespaces = [];
|
||||
}
|
||||
} else {
|
||||
this.defaultNamespaces = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -42,24 +46,38 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
customSupportBundleFeatureEnabled() {
|
||||
return this.$store.getters['harvester-common/getFeatureEnabled']('customSupportBundle');
|
||||
},
|
||||
|
||||
allNamespaces() {
|
||||
return this.$store.getters['harvester/all'](NAMESPACE).map((ns) => ns.id);
|
||||
},
|
||||
|
||||
filteredNamespaces() {
|
||||
if (!this.customSupportBundleFeatureEnabled) {
|
||||
return this.allNamespaces;
|
||||
}
|
||||
|
||||
const defaultIds = this.defaultNamespaces.map((ns) => ns.id);
|
||||
|
||||
return this.allNamespaces.filter((ns) => !defaultIds.includes(ns));
|
||||
},
|
||||
|
||||
namespaceOptions() {
|
||||
const mappedNamespaces = this.filteredNamespaces.map((ns) => ({ label: ns, value: ns }));
|
||||
|
||||
if (!this.customSupportBundleFeatureEnabled) {
|
||||
return mappedNamespaces;
|
||||
}
|
||||
|
||||
const allSelected =
|
||||
this.namespaces.length === this.filteredNamespaces.length &&
|
||||
this.filteredNamespaces.every((ns) => this.namespaces.includes(ns));
|
||||
|
||||
const controlOption = allSelected ? { label: this.t('harvester.modal.bundle.namespaces.unselectAll'), value: UNSELECT_ALL } : { label: this.t('harvester.modal.bundle.namespaces.selectAll'), value: SELECT_ALL };
|
||||
|
||||
return [controlOption, ...this.filteredNamespaces];
|
||||
return [controlOption, ...mappedNamespaces];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ export const HCI = {
|
||||
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
||||
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
||||
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
|
||||
ADDON_DISPLAYNAME: 'addon.harvesterhci.io/displayName',
|
||||
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
|
||||
VOLUME_FOR_VM: 'harvesterhci.io/volumeForVirtualMachine',
|
||||
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
|
||||
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
resources: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
...mapState('action-menu', ['modalData']),
|
||||
|
||||
title() {
|
||||
return this.modalData.title || 'dialog.promptRemove.title';
|
||||
return this.modalData?.title || 'dialog.promptRemove.title';
|
||||
},
|
||||
|
||||
formattedType() {
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
},
|
||||
|
||||
warningMessage() {
|
||||
if (this.modalData.warningMessage) return this.modalData.warningMessage;
|
||||
if (this.modalData?.warningMessage) return this.modalData.warningMessage;
|
||||
|
||||
const isPlural = this.type.endsWith('s');
|
||||
const thisOrThese = isPlural ? 'these' : 'this';
|
||||
@@ -145,6 +145,7 @@ export default {
|
||||
try {
|
||||
for (const resource of this.resources) {
|
||||
await resource.remove();
|
||||
if (this.modalData?.extraActionAfterRemove) await this.modalData.extraActionAfterRemove();
|
||||
}
|
||||
buttonDone(true);
|
||||
this.close();
|
||||
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !pvc.attachVM;
|
||||
return true;
|
||||
})
|
||||
.map((pvc) => {
|
||||
return {
|
||||
|
||||
@@ -371,7 +371,7 @@ export default {
|
||||
<div class="key">
|
||||
<input
|
||||
v-model="scope.row.value"
|
||||
:placeholder="t('harvester.setting.storageNetwork.exclude.placeholder')"
|
||||
:placeholder="t('harvester.subnet.excludeIPs.placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+6
-8
@@ -99,7 +99,6 @@ export default {
|
||||
this.allPVCs
|
||||
.filter( (pvc) => {
|
||||
let isAvailable = true;
|
||||
let isBeingUsed = false;
|
||||
|
||||
this.rows.forEach( (O) => {
|
||||
if (O.volumeName === pvc.metadata.name) {
|
||||
@@ -111,17 +110,16 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
// already used as image volume
|
||||
if (this.idx > 0 && pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pvc.isGoldenImageVolume) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
|
||||
isBeingUsed = false;
|
||||
} else if (pvc.attachVM) {
|
||||
isBeingUsed = true;
|
||||
}
|
||||
|
||||
return isAvailable && !isBeingUsed && pvc.isAvailable;
|
||||
return isAvailable && pvc.isAvailable;
|
||||
})
|
||||
.map((pvc) => {
|
||||
return {
|
||||
|
||||
@@ -96,7 +96,6 @@ export default {
|
||||
templateVersionId: '',
|
||||
namePrefix: '',
|
||||
isSingle: true,
|
||||
useTemplate: false,
|
||||
isOpen: false,
|
||||
hostname,
|
||||
isRestartImmediately,
|
||||
@@ -490,6 +489,7 @@ export default {
|
||||
if (this.isSingle) {
|
||||
if (!this.value.spec.template.spec.hostname) {
|
||||
this.value.spec.template.spec['hostname'] = this.value.metadata.name;
|
||||
this.spec.template.spec['hostname'] = this.value.metadata.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1089,6 +1089,8 @@ harvester:
|
||||
placeholder: e.g. 172.16.0.0/16
|
||||
excludeIPs:
|
||||
tooltip: The IP address list to reserve from automatic assignment. The gateway IP address is always excluded and will be automatically added to the list.
|
||||
placeholder: single IP or 192.168.0.100..192.168.0.200
|
||||
|
||||
acl:
|
||||
label: Access Control List
|
||||
tooltip: The ACL to apply to this Subnet. Must be one of the ACLs in the same namespace.
|
||||
@@ -1204,7 +1206,7 @@ harvester:
|
||||
vlan: VLAN ID
|
||||
exclude:
|
||||
label: Exclude IPs
|
||||
placeholder: e.g. 172.16.0.1
|
||||
placeholder: CIDR format, e.g. 172.16.0.10/32
|
||||
invalid: '"Exclude list" is invalid.'
|
||||
addIp: Add Exclude IP
|
||||
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
|
||||
@@ -1559,6 +1561,7 @@ harvester:
|
||||
'harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
|
||||
'harvester-system/harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
|
||||
'harvester-csi-driver-lvm': harvester-csi-driver-lvm is an add-on allowing users to create PVC through the LVM with local devices.
|
||||
'descheduler': 'The virtual machine auto balance optimizes workload scheduling by evicting pods that are not optimally placed according to administrator-defined policies.'
|
||||
vmImport:
|
||||
titles:
|
||||
basic: Basic
|
||||
|
||||
@@ -142,6 +142,7 @@ export default {
|
||||
showYaml: false,
|
||||
spec: null,
|
||||
osType: 'linux',
|
||||
useTemplate: false,
|
||||
sshKey: [],
|
||||
maintenanceStrategies,
|
||||
maintenanceStrategy: 'Migrate',
|
||||
@@ -273,7 +274,7 @@ export default {
|
||||
|
||||
needNewSecret() {
|
||||
// When creating a template it is always necessary to create a new secret.
|
||||
return this.isCreate ? true : this.showYaml ? false : this.resourceType === HCI.VM_VERSION;
|
||||
return this.isCreate || this.showYaml ? false : this.resourceType === HCI.VM_VERSION;
|
||||
},
|
||||
|
||||
defaultTerminationSetting() {
|
||||
@@ -709,16 +710,7 @@ export default {
|
||||
|
||||
disk.forEach( (R, index) => {
|
||||
const prefixName = this.value.metadata?.name || '';
|
||||
|
||||
let dataVolumeName = '';
|
||||
|
||||
if (R.source === SOURCE_TYPE.ATTACH_VOLUME) {
|
||||
dataVolumeName = R.volumeName;
|
||||
} else if (this.isClone || !this.hasCreateVolumes.includes(R.realName)) {
|
||||
dataVolumeName = `${ prefixName }-${ R.name }-${ randomStr(5).toLowerCase() }`;
|
||||
} else {
|
||||
dataVolumeName = R.realName;
|
||||
}
|
||||
const dataVolumeName = this.parseDataVolumeName(R, prefixName);
|
||||
|
||||
const _disk = this.parseDisk(R, index);
|
||||
const _volume = this.parseVolume(R, dataVolumeName);
|
||||
@@ -1013,6 +1005,20 @@ export default {
|
||||
this['cpuMemoryHotplugEnabled'] = cpuMemoryHotplugEnabled;
|
||||
},
|
||||
|
||||
parseDataVolumeName(R, prefixName) {
|
||||
let dataVolumeName = '';
|
||||
|
||||
if (R.source === SOURCE_TYPE.ATTACH_VOLUME) {
|
||||
dataVolumeName = R.volumeName;
|
||||
} else if (this.isClone || !this.hasCreateVolumes.includes(R.realName)) {
|
||||
dataVolumeName = `${ prefixName }-${ R.name }-${ randomStr(5).toLowerCase() }`;
|
||||
} else {
|
||||
dataVolumeName = R.realName;
|
||||
}
|
||||
|
||||
return dataVolumeName;
|
||||
},
|
||||
|
||||
parseDisk(R, index) {
|
||||
const out = { name: R.name };
|
||||
|
||||
@@ -1638,7 +1644,8 @@ export default {
|
||||
|
||||
secretRef: {
|
||||
handler(secret) {
|
||||
if (secret && this.resourceType !== HCI.BACKUP) {
|
||||
// we should not inherit the secret if it's from VM template.
|
||||
if (secret && this.resourceType !== HCI.BACKUP && !this.useTemplate) {
|
||||
this.secretName = secret?.metadata.name;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import SteveModel from '@shell/plugins/steve/steve-class';
|
||||
import { escapeHtml } from '@shell/utils/string';
|
||||
import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
|
||||
import HarvesterResource from './harvester';
|
||||
|
||||
/**
|
||||
* Class representing vGPU MIGConfiguration resource.
|
||||
* @extends SteveModal
|
||||
* @extends HarvesterResource
|
||||
*/
|
||||
export default class MIGCONFIGURATION extends SteveModel {
|
||||
export default class MIGCONFIGURATION extends HarvesterResource {
|
||||
get _availableActions() {
|
||||
let out = super._availableActions;
|
||||
|
||||
|
||||
@@ -113,8 +113,9 @@ export default class HciAddonConfig extends HarvesterResource {
|
||||
|
||||
get displayName() {
|
||||
const isExperimental = this.metadata?.labels?.[HCI_ANNOTATIONS.ADDON_EXPERIMENTAL] === 'true';
|
||||
const name = this.metadata?.labels?.[HCI_ANNOTATIONS.ADDON_DISPLAYNAME] || this.metadata.name;
|
||||
|
||||
return isExperimental ? `${ this.metadata.name } (${ this.t('generic.experimental') })` : this.metadata.name;
|
||||
return isExperimental ? `${ name } (${ this.t('generic.experimental') })` : name;
|
||||
}
|
||||
|
||||
get customValidationRules() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "harvester",
|
||||
"description": "Rancher UI Extension for Harvester",
|
||||
"version": "1.7.0-rc2",
|
||||
"version": "1.7.0",
|
||||
"private": false,
|
||||
"rancher": {
|
||||
"annotations": {
|
||||
|
||||
@@ -12,6 +12,7 @@ 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';
|
||||
import { STORAGE_CLASS } from '@shell/config/types';
|
||||
|
||||
const IMAGE_METHOD = {
|
||||
NEW: 'new',
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
|
||||
async fetch() {
|
||||
await this.$store.dispatch('harvester/findAll', { type: HCI.IMAGE });
|
||||
await this.$store.dispatch('harvester/findAll', { type: STORAGE_CLASS });
|
||||
|
||||
const value = await this.$store.dispatch('harvester/create', {
|
||||
type: HCI.UPGRADE,
|
||||
@@ -63,6 +65,7 @@ export default {
|
||||
sourceType: UPLOAD,
|
||||
uploadController: null,
|
||||
uploadResult: null,
|
||||
storageClassValue: null,
|
||||
imageValue: null,
|
||||
enableLogging: true,
|
||||
IMAGE_METHOD,
|
||||
@@ -79,7 +82,6 @@ export default {
|
||||
skipSingleReplicaDetachedVolFeatureEnabled() {
|
||||
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
|
||||
},
|
||||
|
||||
allOSImages() {
|
||||
return this.$store.getters['harvester/all'](HCI.IMAGE).filter((I) => I.isOSImage) || [];
|
||||
},
|
||||
@@ -116,7 +118,7 @@ export default {
|
||||
},
|
||||
|
||||
fileName() {
|
||||
return this.file?.name || '';
|
||||
return this.preprocessImageName(this.file?.name || '');
|
||||
},
|
||||
|
||||
canEnableLogging() {
|
||||
@@ -181,6 +183,38 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
async createImageStorageClass(imageName = '') {
|
||||
// delete related SC if existed
|
||||
await this.deleteImageStorageClass(imageName);
|
||||
|
||||
const storageClassPayload = {
|
||||
apiVersion: 'storage.k8s.io/v1',
|
||||
type: STORAGE_CLASS,
|
||||
metadata: { name: imageName },
|
||||
volumeBindingMode: 'Immediate',
|
||||
reclaimPolicy: 'Delete',
|
||||
allowVolumeExpansion: true, // must be boolean type
|
||||
provisioner: 'driver.longhorn.io',
|
||||
};
|
||||
|
||||
this.storageClassValue = await this.$store.dispatch('harvester/create', storageClassPayload);
|
||||
|
||||
if (this.storageClassValue && this.storageClassValue.save) {
|
||||
await this.storageClassValue.save();
|
||||
}
|
||||
},
|
||||
|
||||
async deleteImageStorageClass(imageName = '') {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const storageClasses = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||
|
||||
const targetSC = storageClasses.find((sc) => sc.id === imageName);
|
||||
|
||||
if (targetSC && targetSC.remove) {
|
||||
await targetSC.remove();
|
||||
}
|
||||
},
|
||||
|
||||
async initImageValue() {
|
||||
this.imageValue = await this.$store.dispatch('harvester/create', {
|
||||
type: HCI.IMAGE,
|
||||
@@ -191,6 +225,7 @@ export default {
|
||||
annotations: {}
|
||||
},
|
||||
spec: {
|
||||
backend: 'cdi',
|
||||
sourceType: UPLOAD,
|
||||
displayName: '',
|
||||
checksum: this.imageValue?.spec?.checksum || '',
|
||||
@@ -203,8 +238,9 @@ export default {
|
||||
|
||||
this.file = {};
|
||||
this.errors = [];
|
||||
const imageDisplayName = this.imageValue?.spec?.displayName || '';
|
||||
|
||||
if (!this.imageValue.spec.displayName && this.createNewImage) {
|
||||
if (!imageDisplayName && this.createNewImage) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('generic.name') }));
|
||||
buttonCb(false);
|
||||
|
||||
@@ -212,24 +248,31 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
// Save the image first if creating a new one
|
||||
if (this.imageSource === IMAGE_METHOD.NEW) {
|
||||
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
|
||||
|
||||
if (this.sourceType === UPLOAD && this.uploadImageId !== '') { // upload new image
|
||||
this.value.spec.image = this.uploadImageId;
|
||||
} else if (this.sourceType === DOWNLOAD) { // give URL to download new image
|
||||
this.imageValue.spec.sourceType = DOWNLOAD;
|
||||
// check if URL is provided
|
||||
if (!this.imageValue.spec.url) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl'));
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// create related image storage class first
|
||||
await this.createImageStorageClass(imageDisplayName);
|
||||
this.imageValue.spec.sourceType = DOWNLOAD;
|
||||
this.imageValue.spec.targetStorageClassName = imageDisplayName;
|
||||
|
||||
res = await this.imageValue.save();
|
||||
|
||||
this.value.spec.image = res.id;
|
||||
}
|
||||
} else if (this.imageSource === IMAGE_METHOD.EXIST) {
|
||||
} else if (this.imageSource === IMAGE_METHOD.EXIST) { // select existing image
|
||||
if (!this.imageId) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.chooseFile'));
|
||||
buttonCb(false);
|
||||
@@ -239,7 +282,7 @@ export default {
|
||||
|
||||
this.value.spec.image = this.imageId;
|
||||
}
|
||||
|
||||
// enable logging or skip single replica detection if checked
|
||||
if (this.canEnableLogging) {
|
||||
this.value.spec.logEnabled = this.enableLogging;
|
||||
}
|
||||
@@ -252,11 +295,13 @@ export default {
|
||||
} catch (e) {
|
||||
this.errors = [e?.message] || exceptionToErrorsArray(e);
|
||||
buttonCb(false);
|
||||
// if anything failed, delete the created image storage class
|
||||
await this.deleteImageStorageClass(imageDisplayName);
|
||||
}
|
||||
},
|
||||
|
||||
async uploadFile(file) {
|
||||
const fileName = file.name;
|
||||
const fileName = this.preprocessImageName(file.name);
|
||||
|
||||
if (!fileName) {
|
||||
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.unknownImageName'));
|
||||
@@ -280,6 +325,10 @@ export default {
|
||||
this.imageValue.spec.url = '';
|
||||
|
||||
try {
|
||||
// before uploading image, we need to create related image storage class first
|
||||
await this.createImageStorageClass(fileName);
|
||||
this.imageValue.spec.targetStorageClassName = fileName;
|
||||
|
||||
const res = await this.imageValue.save();
|
||||
|
||||
this.uploadImageId = res.id;
|
||||
@@ -296,20 +345,35 @@ export default {
|
||||
} else {
|
||||
this.errors = exceptionToErrorsArray(e);
|
||||
}
|
||||
// if upload failed, delete the created image storage class
|
||||
await this.deleteImageStorageClass(fileName);
|
||||
this.file = {};
|
||||
this.uploadImageId = '';
|
||||
}
|
||||
},
|
||||
|
||||
// replace _ to - to meet storage class name requirement
|
||||
preprocessImageName(name) {
|
||||
if (!name) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return name.toLowerCase().replace(/[_]/g, '-');
|
||||
},
|
||||
|
||||
handleImageDelete(imageId) {
|
||||
const image = this.allOSImages.find((I) => I.id === imageId);
|
||||
const imageDisplayName = image?.spec?.displayName || '';
|
||||
|
||||
if (image) {
|
||||
if (image && imageDisplayName) {
|
||||
this.$store.dispatch('harvester/promptModal', {
|
||||
resources: [image],
|
||||
component: 'ConfirmRelatedToRemoveDialog',
|
||||
needConfirmation: false,
|
||||
warningMessage: this.$store.getters['i18n/t']('harvester.modal.osImage.message', { name: image.displayName })
|
||||
resources: [image],
|
||||
component: 'ConfirmRelatedToRemoveDialog',
|
||||
needConfirmation: false,
|
||||
warningMessage: this.$store.getters['i18n/t']('harvester.modal.osImage.message', { name: imageDisplayName }),
|
||||
extraActionAfterRemove: async() => {
|
||||
await this.deleteImageStorageClass(imageDisplayName);
|
||||
}
|
||||
});
|
||||
this.deleteImageId = '';
|
||||
}
|
||||
@@ -419,13 +483,13 @@ export default {
|
||||
v-if="showUploadSuccessBanner"
|
||||
color="success"
|
||||
class="mt-0 mb-30"
|
||||
:label="t('harvester.setting.upgrade.uploadSuccess', { name: file.name })"
|
||||
:label="t('harvester.setting.upgrade.uploadSuccess', { name: fileName })"
|
||||
/>
|
||||
<Banner
|
||||
v-if="showUploadingWarningBanner"
|
||||
color="warning"
|
||||
class="mt-0 mb-30"
|
||||
:label="t('harvester.image.warning.osUpgrade.uploading', { name: file.name })"
|
||||
:label="t('harvester.image.warning.osUpgrade.uploading', { name: fileName })"
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user