public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk
@ 2020-09-15 15:37 Alexandre - H3TI
  0 siblings, 0 replies; 4+ messages in thread
From: Alexandre - H3TI @ 2020-09-15 15:37 UTC (permalink / raw)
  To: pve-devel

Hello good day !

First of all, I would like to congratulate Proxmox for the excellent
evolution of the solution, the possibility of being able to have via GUI
the option of being able to importdisk from other platforms such as ovf,
ova .......... will also be possible. browser button to perform disk image
search? of course if it works.

Thankful !

Att.


--


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk
  2020-09-15 11:33 ` [pve-devel] [PATCH manager v4 2/2] Hardware View: " Dominic Jäger
  2020-09-15 12:17   ` Gilberto Nunes
@ 2020-09-16  7:43   ` Dominic Jäger
  1 sibling, 0 replies; 4+ messages in thread
From: Dominic Jäger @ 2020-09-16  7:43 UTC (permalink / raw)
  To: pve-devel

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@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@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@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@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@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel




^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk
  2020-09-15 11:33 ` [pve-devel] [PATCH manager v4 2/2] Hardware View: " Dominic Jäger
@ 2020-09-15 12:17   ` Gilberto Nunes
  2020-09-16  7:43   ` Dominic Jäger
  1 sibling, 0 replies; 4+ messages in thread
From: Gilberto Nunes @ 2020-09-15 12:17 UTC (permalink / raw)
  To: Proxmox VE development discussion

I'm looking forward to test this new feature.
Sometimes is really annoying make importdisk from cli.
I also would like to recommend add some feature to import OVA/OVF images
using WEB interface, if this is possible, of course.

Thanks for such wonderful work!

---
Gilberto Nunes Ferreira



Em ter., 15 de set. de 2020 às 08:34, Dominic Jäger <d.jaeger@proxmox.com>
escreveu:

> Make importing single disks easier.
> Required to import a whole VM via GUI.
>
> Signed-off-by: Dominic Jäger <d.jaeger@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@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@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@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@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
>


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk
  2020-09-15 11:33 [pve-devel] [PATCH v4 0/2] Close #2886: " Dominic Jäger
@ 2020-09-15 11:33 ` Dominic Jäger
  2020-09-15 12:17   ` Gilberto Nunes
  2020-09-16  7:43   ` Dominic Jäger
  0 siblings, 2 replies; 4+ messages in thread
From: Dominic Jäger @ 2020-09-15 11:33 UTC (permalink / raw)
  To: pve-devel

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

Signed-off-by: Dominic Jäger <d.jaeger@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@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@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@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




^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2020-09-16  7:43 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-09-15 15:37 [pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk Alexandre - H3TI
  -- strict thread matches above, loose matches on Subject: below --
2020-09-15 11:33 [pve-devel] [PATCH v4 0/2] Close #2886: " Dominic Jäger
2020-09-15 11:33 ` [pve-devel] [PATCH manager v4 2/2] Hardware View: " Dominic Jäger
2020-09-15 12:17   ` Gilberto Nunes
2020-09-16  7:43   ` Dominic Jäger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal