[pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk

Dominic Jäger d.jaeger at proxmox.com
Tue Sep 15 13:33:24 CEST 2020


Make importing single disks easier.
Required to import a whole VM via GUI.

Signed-off-by: Dominic Jäger <d.jaeger at proxmox.com>
---
v3->v4:
* Reuse propertyStringSet instead of building it myself
* More detailed permissions
* Reorder GUI elements such that source is first
* Assemble importdisk URL here instead of widget-toolkit & use regex for
  correct replacement
* Allow selecting images from PVE storages (Normal users + root) or all paths
  (root)

 www/manager6/qemu/HDEdit.js       | 134 ++++++++++++++++++++++++++----
 www/manager6/qemu/HardwareView.js |  24 ++++++
 2 files changed, 141 insertions(+), 17 deletions(-)

diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js
index e2a5b914..5e0a3981 100644
--- a/www/manager6/qemu/HDEdit.js
+++ b/www/manager6/qemu/HDEdit.js
@@ -67,7 +67,8 @@ Ext.define('PVE.qemu.HDInputPanel', {
 	if (me.unused) {
 	    me.drive.file = me.vmconfig[values.unusedId];
 	    confid = values.controller + values.deviceid;
-	} else if (me.isCreate) {
+	} else if (me.isCreate && !me.isImport) {
+	    // disk format & size should not be part of propertyString for import
 	    if (values.hdimage) {
 		me.drive.file = values.hdimage;
 	    } else {
@@ -83,16 +84,22 @@ Ext.define('PVE.qemu.HDInputPanel', {
 	PVE.Utils.propertyStringSet(me.drive, values.iothread, 'iothread', 'on');
 	PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache');
 
-        var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'];
-        Ext.Array.each(names, function(name) {
-            var burst_name = name + '_max';
+	var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'];
+	Ext.Array.each(names, function(name) {
+	    var burst_name = name + '_max';
 	    PVE.Utils.propertyStringSet(me.drive, values[name], name);
 	    PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name);
-        });
-
-
-	params[confid] = PVE.Parser.printQemuDrive(me.drive);
-
+	});
+	if (me.isImport) {
+	    params.device_options = PVE.Parser.printPropertyString(me.drive);
+	    params.source = values.sourceType === 'storage'
+		? values.sourceVolid : values.sourcePath;
+	    params.device = values.controller + values.deviceid;
+	    params.storage = values.hdstorage;
+	    if (values.diskformat) params.format = values.diskformat;
+	} else {
+	    params[confid] = PVE.Parser.printQemuDrive(me.drive);
+	}
 	return params;
     },
 
@@ -169,10 +176,14 @@ Ext.define('PVE.qemu.HDInputPanel', {
 	me.advancedColumn2 = [];
 
 	if (!me.confid || me.unused) {
+	    let controllerColumn = me.isImport ? me.column2 : me.column1;
 	    me.bussel = Ext.create('PVE.form.ControllerSelector', {
 		vmconfig: me.insideWizard ? {ide2: 'cdrom'} : {}
 	    });
-	    me.column1.push(me.bussel);
+	    if (me.isImport) {
+		me.bussel.fieldLabel = 'Target Device';
+	    }
+	    controllerColumn.push(me.bussel);
 
 	    me.scsiController = Ext.create('Ext.form.field.Display', {
 		fieldLabel: gettext('SCSI Controller'),
@@ -184,7 +195,7 @@ Ext.define('PVE.qemu.HDInputPanel', {
 		submitValue: false,
 		hidden: true
 	    });
-	    me.column1.push(me.scsiController);
+	    controllerColumn.push(me.scsiController);
 	}
 
 	if (me.unused) {
@@ -199,14 +210,21 @@ Ext.define('PVE.qemu.HDInputPanel', {
 		allowBlank: false
 	    });
 	    me.column1.push(me.unusedDisks);
-	} else if (me.isCreate) {
-	    me.column1.push({
+	} else if (me.isCreate || me.isImport) {
+	    let selector = {
 		xtype: 'pveDiskStorageSelector',
 		storageContent: 'images',
 		name: 'disk',
 		nodename: me.nodename,
-		autoSelect: me.insideWizard
-	    });
+		hideSize: me.isImport,
+		autoSelect: me.insideWizard || me.isImport,
+	    };
+	    if (me.isImport) {
+		selector.storageLabel = gettext('Target storage');
+		me.column2.push(selector);
+	    } else {
+		me.column1.push(selector);
+	    }
 	} else {
 	    me.column1.push({
 		xtype: 'textfield',
@@ -217,6 +235,12 @@ Ext.define('PVE.qemu.HDInputPanel', {
 	    });
 	}
 
+	if (me.isImport) {
+	    me.column2.push({
+		xtype: 'box',
+		autoEl: { tag: 'hr' },
+	    });
+	}
 	me.column2.push(
 	    {
 		xtype: 'CacheTypeSelector',
@@ -231,6 +255,74 @@ Ext.define('PVE.qemu.HDInputPanel', {
 		name: 'discard'
 	    }
 	);
+	if (me.isImport) {
+	    let show = (element, value) => {
+		element.setHidden(!value);
+		element.setDisabled(!value);
+	    };
+	    me.sourceRadioStorage = Ext.create('Ext.form.field.Radio', {
+		name: 'sourceType',
+		inputValue: 'storage',
+		boxLabel: gettext('Use a storage as source'),
+		checked: true,
+		hidden: Proxmox.UserName !== 'root at pam',
+		listeners: {
+		    added: () => show(me.sourcePathTextfield, false),
+		    change: (_, storageRadioChecked) => {
+			show(me.sourcePathTextfield, !storageRadioChecked);
+			let selectors = [
+			    me.sourceStorageSelector,
+			    me.sourceFileSelector,
+			];
+			for (const selector of selectors) {
+			    show(selector, storageRadioChecked);
+			}
+		    },
+		},
+	    });
+	    me.sourceStorageSelector = Ext.create('PVE.form.StorageSelector', {
+		name: 'inputImageStorage',
+		nodename: me.nodename,
+		fieldLabel: gettext('Source Storage'),
+		storageContent: 'images',
+		autoSelect: me.insideWizard,
+		listeners: {
+		    change: function(_, selectedStorage) {
+			me.sourceFileSelector.setStorage(selectedStorage);
+		    },
+		},
+	    });
+	    me.sourceFileSelector = Ext.create('PVE.form.FileSelector', {
+		name: 'sourceVolid',
+		nodename: me.nodename,
+		storageContent: 'images',
+		fieldLabel: gettext('Source Image'),
+	    });
+	    me.sourceRadioPath = Ext.create('Ext.form.field.Radio', {
+		name: 'sourceType',
+		inputValue: 'path',
+		boxLabel: gettext('Use an absolute path as source'),
+		hidden: Proxmox.UserName !== 'root at pam',
+	    });
+	    me.sourcePathTextfield = Ext.create('Ext.form.field.Text', {
+		xtype: 'textfield',
+		fieldLabel: gettext('Source Path'),
+		name: 'sourcePath',
+		emptyText: '/home/user/disk.qcow2',
+		hidden: Proxmox.UserName !== 'root at pam',
+		validator: function(insertedText) {
+		    return insertedText.startsWith('/') ||
+			gettext('Must be an absolute path');
+		},
+	    });
+	    me.column1.unshift(
+		me.sourceRadioStorage,
+		me.sourceStorageSelector,
+		me.sourceFileSelector,
+		me.sourceRadioPath,
+		me.sourcePathTextfield,
+	    );
+	}
 
 	me.advancedColumn1.push(
 	    {
@@ -372,14 +464,19 @@ Ext.define('PVE.qemu.HDEdit', {
 	    confid: me.confid,
 	    nodename: nodename,
 	    unused: unused,
-	    isCreate: me.isCreate
+	    isCreate: me.isCreate,
+	    isImport: me.isImport,
 	});
 
 	var subject;
 	if (unused) {
 	    me.subject = gettext('Unused Disk');
+	} else if (me.isImport) {
+	    me.subject = gettext('Import Disk');
+	    me.submitText = 'Import';
+	    me.backgroundDelay = undefined;
 	} else if (me.isCreate) {
-            me.subject = gettext('Hard Disk');
+	    me.subject = gettext('Hard Disk');
 	} else {
            me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
 	}
@@ -404,6 +501,9 @@ Ext.define('PVE.qemu.HDEdit', {
 		    ipanel.setDrive(drive);
 		    me.isValid(); // trigger validation
 		}
+		if (me.isImport) {
+		    me.url = me.url.replace(/\/config$/, "/importdisk");
+		}
 	    }
 	});
     }
diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js
index 40b3fe86..dc5e217e 100644
--- a/www/manager6/qemu/HardwareView.js
+++ b/www/manager6/qemu/HardwareView.js
@@ -436,6 +436,29 @@ Ext.define('PVE.qemu.HardwareView', {
 	    handler: run_move
 	});
 
+	var import_btn = new Proxmox.button.Button({
+	    text: gettext('Import disk'),
+	    hidden: !(
+		caps.storage['Datastore.Audit'] &&
+		caps.storage['Datastore.Allocate'] &&
+		caps.storage['Datastore.AllocateTemplate'] &&
+		caps.storage['Datastore.AllocateSpace'] &&
+		caps.vms['VM.Allocate'] &&
+		caps.vms['VM.Config.Disk'] &&
+		true
+	    ),
+	    handler: function() {
+		var win = Ext.create('PVE.qemu.HDEdit', {
+		    method: 'POST',
+		    url: `/api2/extjs/${baseurl}`,
+		    pveSelNode: me.pveSelNode,
+		    isImport: true,
+		});
+		win.on('destroy', me.reload, me);
+		win.show();
+	    },
+	});
+
 	var remove_btn = new Proxmox.button.Button({
 	    text: gettext('Remove'),
 	    defaultText: gettext('Remove'),
@@ -752,6 +775,7 @@ Ext.define('PVE.qemu.HardwareView', {
 		edit_btn,
 		resize_btn,
 		move_btn,
+		import_btn,
 		revert_btn
 	    ],
 	    rows: rows,
-- 
2.20.1





More information about the pve-devel mailing list