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

Gilberto Nunes gilberto.nunes32 at gmail.com
Tue Sep 15 14:17:37 CEST 2020


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 at proxmox.com>
escreveu:

> 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