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

Dominic Jäger d.jaeger at proxmox.com
Wed Sep 16 09:43:12 CEST 2020


If you only want to take a quick lock, I put a screenshot in Bugzilla
https://bugzilla.proxmox.com/show_bug.cgi?id=2886#c2

On Tue, Sep 15, 2020 at 01:33:24PM +0200, Dominic Jäger wrote:
> 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
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel





More information about the pve-devel mailing list