[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