diff --git a/pkg/harvester/dialog/HarvesterImageDownloader.vue b/pkg/harvester/dialog/HarvesterImageDownloader.vue new file mode 100644 index 00000000..abfb5013 --- /dev/null +++ b/pkg/harvester/dialog/HarvesterImageDownloader.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index b99f723a..cf51ab48 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -99,6 +99,11 @@ harvester: tip: Please enter a virtual machine name! success: 'Virtual machine { name } cloned successfully.' failed: 'Failed clone virtual machine!' + downloadImage: + title: Download Image + banner: 'This action takes a while depending on the image size ({ size }). Please be patient.' + startMessage : 'The download process will auto start once the conversion is complete.' + download: Download exportImage: title: Export to Image name: Name diff --git a/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js b/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js index 9f9e6e94..71aac3ef 100644 --- a/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js +++ b/pkg/harvester/models/harvesterhci.io.virtualmachineimage.js @@ -73,7 +73,7 @@ export default class HciVmImage extends HarvesterResource { disabled: !this.isReady, }, { - action: 'download', + action: 'imageDownload', enabled: this.links?.download, icon: 'icon icon-download', label: this.t('asyncButton.download.action'), @@ -394,7 +394,19 @@ export default class HciVmImage extends HarvesterResource { return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage'); } - download() { + imageDownload(resources = this) { + // spec.backend is introduced in v1.5.0. If it's not set, it's an old image can be downloaded via link + if (this.spec?.backend === 'cdi') { + this.$dispatch('promptModal', { + resources, + component: 'HarvesterImageDownloader' + }); + } else { + this.downloadViaLink(); + } + } + + downloadViaLink() { window.location.href = this.links.download; } } diff --git a/pkg/harvester/store/harvester-common.js b/pkg/harvester/store/harvester-common.js index 03b149e7..3e1e4a02 100644 --- a/pkg/harvester/store/harvester-common.js +++ b/pkg/harvester/store/harvester-common.js @@ -5,16 +5,33 @@ import { featureEnabled, getVersion } from '../utils/feature-flags'; const state = function() { return { - latestBundleId: '', - bundlePending: false, - showBundleModal: false, - bundlePercentage: 0, - uploadingImages: [], - uploadingImageError: {}, + // support bundle + latestBundleId: '', + bundlePending: false, + showBundleModal: false, + bundlePercentage: 0, + uploadingImages: [], + uploadingImageError: {}, + // download cdi image + downloadImageId: '', + downloadImageInProgress: false, + isDownloadImageCancel: false, }; }; const mutations = { + setDownloadImageId(state, id) { + state.downloadImageId = id; + }, + + setDownloadImageCancel(state, value) { + state.isDownloadImageCancel = value; + }, + + setDownloadImageInProgress(state, value) { + state.downloadImageInProgress = value; + }, + setLatestBundleId(state, bundleId) { state.latestBundleId = bundleId; }, @@ -51,6 +68,14 @@ const getters = { return state.latestBundleId; }, + isDownloadImageCancel(state) { + return state.isDownloadImageCancel; + }, + + isDownloadImageInProgress(state) { + return state.downloadImageInProgress; + }, + isBundlePending(state) { return state.bundlePending; }, @@ -98,6 +123,70 @@ const getters = { }; const actions = { + async downloadImageProgress({ + state, dispatch, commit, rootGetters + }) { + const parse = Parse(window.history.href); + + const id = state.downloadImageId; // id is image_ns / image_name + + let imageCrd = await dispatch( + 'harvester/find', + { type: HCI.VM_IMAGE_DOWNLOADER, id }, + { root: true } + ); + + await commit('setDownloadImageInProgress', true); + + let count = 0; + + const timer = setInterval(async() => { + count = count + 1; + if (count % 3 === 0) { + // ws maybe disconnect, force to get the latest status + imageCrd = await dispatch( + 'harvester/find', + { + type: HCI.VM_IMAGE_DOWNLOADER, + id, + opt: { force: true } + }, + { root: true } + ); + } + + // If is cancel, clear the timer + if (state.isDownloadImageCancel === true) { + clearInterval(timer); + + return; + } + + // converting image status becomes ready + if (imageCrd?.status?.status === 'Ready') { + imageCrd = rootGetters['harvester/byId'](HCI.VM_IMAGE_DOWNLOADER, id); + + setTimeout(() => { + commit('setDownloadImageInProgress', false); + dispatch('promptModal'); // bring undefined data will close the promptModal + }, 600); + + if (rootGetters['isMultiCluster']) { + const clusterId = rootGetters['clusterId']; + const prefix = `/k8s/clusters/${ clusterId }`; + + window.location.href = `${ parse.origin }${ prefix }/v1/harvester/${ HCI.IMAGE }/${ id }/download`; + } else { + const link = `${ parse.origin }/v1/harvester/${ HCI.IMAGE }/${ id }/download`; + + window.location.href = link; + } + + clearInterval(timer); + } + }, 1000); + }, + async bundleProgress({ state, dispatch, commit, rootGetters }) { @@ -117,7 +206,7 @@ const actions = { const timer = setInterval(async() => { count = count + 1; if (count % 3 === 0) { - // ws mayby disconnect + // ws maybe disconnect bundleCrd = await dispatch( 'harvester/find', { diff --git a/pkg/harvester/types.ts b/pkg/harvester/types.ts index ef3afdd5..aab61607 100644 --- a/pkg/harvester/types.ts +++ b/pkg/harvester/types.ts @@ -1,56 +1,57 @@ export const HCI = { - VM: 'kubevirt.io.virtualmachine', - VMI: 'kubevirt.io.virtualmachineinstance', - VMIM: 'kubevirt.io.virtualmachineinstancemigration', - VM_TEMPLATE: 'harvesterhci.io.virtualmachinetemplate', - VM_VERSION: 'harvesterhci.io.virtualmachinetemplateversion', - IMAGE: 'harvesterhci.io.virtualmachineimage', - SSH: 'harvesterhci.io.keypair', - VOLUME: 'harvesterhci.io.volume', - USER: 'harvesterhci.io.user', - SETTING: 'harvesterhci.io.setting', - UPGRADE: 'harvesterhci.io.upgrade', - UPGRADE_LOG: 'harvesterhci.io.upgradelog', - SCHEDULE_VM_BACKUP: 'harvesterhci.io.schedulevmbackup', - BACKUP: 'harvesterhci.io.virtualmachinebackup', - RESTORE: 'harvesterhci.io.virtualmachinerestore', - NODE_NETWORK: 'network.harvesterhci.io.nodenetwork', - CLUSTER_NETWORK: 'network.harvesterhci.io.clusternetwork', - SUPPORT_BUNDLE: 'harvesterhci.io.supportbundle', - NETWORK_ATTACHMENT: 'harvesterhci.io.networkattachmentdefinition', - CLUSTER: 'harvesterhci.io.management.cluster', - DASHBOARD: 'harvesterhci.io.dashboard', - BLOCK_DEVICE: 'harvesterhci.io.blockdevice', - CLOUD_TEMPLATE: 'harvesterhci.io.cloudtemplate', - HOST: 'harvesterhci.io.host', - VERSION: 'harvesterhci.io.version', - SNAPSHOT: 'harvesterhci.io.volumesnapshot', - VM_SNAPSHOT: 'harvesterhci.io.vmsnapshot', - ALERTMANAGERCONFIG: 'harvesterhci.io.monitoring.alertmanagerconfig', - CLUSTER_FLOW: 'harvesterhci.io.logging.clusterflow', - CLUSTER_OUTPUT: 'harvesterhci.io.logging.clusteroutput', - FLOW: 'harvesterhci.io.logging.flow', - OUTPUT: 'harvesterhci.io.logging.output', - STORAGE: 'harvesterhci.io.storage', - RESOURCE_QUOTA: 'harvesterhci.io.resourcequota', - KSTUNED: 'node.harvesterhci.io.ksmtuned', - PCI_DEVICE: 'devices.harvesterhci.io.pcidevice', - PCI_CLAIM: 'devices.harvesterhci.io.pcideviceclaim', - SR_IOV: 'devices.harvesterhci.io.sriovnetworkdevice', - VGPU_DEVICE: 'devices.harvesterhci.io.vgpudevice', - SR_IOVGPU_DEVICE: 'devices.harvesterhci.io.sriovgpudevice', - USB_DEVICE: 'devices.harvesterhci.io.usbdevice', - USB_CLAIM: 'devices.harvesterhci.io.usbdeviceclaim', - VLAN_CONFIG: 'network.harvesterhci.io.vlanconfig', - VLAN_STATUS: 'network.harvesterhci.io.vlanstatus', - ADD_ONS: 'harvesterhci.io.addon', - LINK_MONITOR: 'network.harvesterhci.io.linkmonitor', - SECRET: 'harvesterhci.io.secret', - INVENTORY: 'metal.harvesterhci.io.inventory', - LB: 'loadbalancer.harvesterhci.io.loadbalancer', - IP_POOL: 'loadbalancer.harvesterhci.io.ippool', - HARVESTER_CONFIG: 'rke-machine-config.cattle.io.harvesterconfig', - LVM_VOLUME_GROUP: 'harvesterhci.io.lvmvolumegroup' + VM: 'kubevirt.io.virtualmachine', + VMI: 'kubevirt.io.virtualmachineinstance', + VMIM: 'kubevirt.io.virtualmachineinstancemigration', + VM_TEMPLATE: 'harvesterhci.io.virtualmachinetemplate', + VM_VERSION: 'harvesterhci.io.virtualmachinetemplateversion', + IMAGE: 'harvesterhci.io.virtualmachineimage', + SSH: 'harvesterhci.io.keypair', + VOLUME: 'harvesterhci.io.volume', + USER: 'harvesterhci.io.user', + SETTING: 'harvesterhci.io.setting', + UPGRADE: 'harvesterhci.io.upgrade', + UPGRADE_LOG: 'harvesterhci.io.upgradelog', + SCHEDULE_VM_BACKUP: 'harvesterhci.io.schedulevmbackup', + BACKUP: 'harvesterhci.io.virtualmachinebackup', + RESTORE: 'harvesterhci.io.virtualmachinerestore', + NODE_NETWORK: 'network.harvesterhci.io.nodenetwork', + CLUSTER_NETWORK: 'network.harvesterhci.io.clusternetwork', + VM_IMAGE_DOWNLOADER: 'harvesterhci.io.virtualmachineimagedownloader', + SUPPORT_BUNDLE: 'harvesterhci.io.supportbundle', + NETWORK_ATTACHMENT: 'harvesterhci.io.networkattachmentdefinition', + CLUSTER: 'harvesterhci.io.management.cluster', + DASHBOARD: 'harvesterhci.io.dashboard', + BLOCK_DEVICE: 'harvesterhci.io.blockdevice', + CLOUD_TEMPLATE: 'harvesterhci.io.cloudtemplate', + HOST: 'harvesterhci.io.host', + VERSION: 'harvesterhci.io.version', + SNAPSHOT: 'harvesterhci.io.volumesnapshot', + VM_SNAPSHOT: 'harvesterhci.io.vmsnapshot', + ALERTMANAGERCONFIG: 'harvesterhci.io.monitoring.alertmanagerconfig', + CLUSTER_FLOW: 'harvesterhci.io.logging.clusterflow', + CLUSTER_OUTPUT: 'harvesterhci.io.logging.clusteroutput', + FLOW: 'harvesterhci.io.logging.flow', + OUTPUT: 'harvesterhci.io.logging.output', + STORAGE: 'harvesterhci.io.storage', + RESOURCE_QUOTA: 'harvesterhci.io.resourcequota', + KSTUNED: 'node.harvesterhci.io.ksmtuned', + PCI_DEVICE: 'devices.harvesterhci.io.pcidevice', + PCI_CLAIM: 'devices.harvesterhci.io.pcideviceclaim', + SR_IOV: 'devices.harvesterhci.io.sriovnetworkdevice', + VGPU_DEVICE: 'devices.harvesterhci.io.vgpudevice', + SR_IOVGPU_DEVICE: 'devices.harvesterhci.io.sriovgpudevice', + USB_DEVICE: 'devices.harvesterhci.io.usbdevice', + USB_CLAIM: 'devices.harvesterhci.io.usbdeviceclaim', + VLAN_CONFIG: 'network.harvesterhci.io.vlanconfig', + VLAN_STATUS: 'network.harvesterhci.io.vlanstatus', + ADD_ONS: 'harvesterhci.io.addon', + LINK_MONITOR: 'network.harvesterhci.io.linkmonitor', + SECRET: 'harvesterhci.io.secret', + INVENTORY: 'metal.harvesterhci.io.inventory', + LB: 'loadbalancer.harvesterhci.io.loadbalancer', + IP_POOL: 'loadbalancer.harvesterhci.io.ippool', + HARVESTER_CONFIG: 'rke-machine-config.cattle.io.harvesterconfig', + LVM_VOLUME_GROUP: 'harvesterhci.io.lvmvolumegroup' }; export const VOLUME_SNAPSHOT = 'snapshot.storage.k8s.io.volumesnapshot';