diff --git a/pkg/harvester/dialog/HarvesterImageDownloader.vue b/pkg/harvester/dialog/HarvesterImageDownloader.vue
new file mode 100644
index 00000000..dd947d1e
--- /dev/null
+++ b/pkg/harvester/dialog/HarvesterImageDownloader.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ {{ t('harvester.modal.downloadImage.title') }}
+
+
+
+
+ {{ t('harvester.modal.downloadImage.banner', { size: imageVirtualSize }) }}
+
+ {{ t('harvester.modal.downloadImage.convertMessage', { name: imageDisplayName }) }}
+
+ {{ t('harvester.modal.downloadImage.startMessage') }}
+
+
+
+
+
+ {{ err }}
+
+
+
+
+
+
+
+
diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml
index b99f723a..9b0de058 100644
--- a/pkg/harvester/l10n/en-us.yaml
+++ b/pkg/harvester/l10n/en-us.yaml
@@ -99,6 +99,12 @@ 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.'
+ convertMessage: 'Harvester will convert { name } into qcow2 format.'
+ 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';