From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 7CC6D61FA5 for ; Tue, 15 Sep 2020 13:34:15 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6DDA41A05E for ; Tue, 15 Sep 2020 13:33:45 +0200 (CEST) Received: from dev.dominic.proxmox.com (212-186-127-178.static.upcbusiness.at [212.186.127.178]) by firstgate.proxmox.com (Proxmox) with ESMTP id E21BE1A005 for ; Tue, 15 Sep 2020 13:33:39 +0200 (CEST) Received: by dev.dominic.proxmox.com (Postfix, from userid 0) id B12EC220AE; Tue, 15 Sep 2020 13:33:39 +0200 (CEST) From: =?UTF-8?q?Dominic=20J=C3=A4ger?= To: pve-devel@lists.proxmox.com Date: Tue, 15 Sep 2020 13:33:24 +0200 Message-Id: <20200915113324.313395-3-d.jaeger@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200915113324.313395-1-d.jaeger@proxmox.com> References: <20200915113324.313395-1-d.jaeger@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 AWL -0.527 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods KHOP_HELO_FCRDNS 0.399 Relay HELO differs from its IP's reverse DNS NO_DNS_FOR_FROM 0.379 Envelope sender has no MX or A DNS records SPF_NONE 0.001 SPF: sender does not publish an SPF Record T_SPF_HELO_TEMPERROR 0.01 SPF: test of HELO record failed (temperror) URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [params.storage, caps.storage] Subject: [pve-devel] [PATCH manager v4 2/2] Hardware View: Add GUI for importdisk X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 15 Sep 2020 11:34:15 -0000 Make importing single disks easier. Required to import a whole VM via GUI. Signed-off-by: Dominic Jäger --- 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