[pve-devel] [PATCH manager] ui: disk edit: Split out bandwidth limits

Dominic Jäger d.jaeger at proxmox.com
Thu Jul 8 10:40:38 CEST 2021


Signed-off-by: Dominic Jäger <d.jaeger at proxmox.com>

Here two inputpanels
 1. diskData with volid, storage...
 2. diskBasicOptions with the checkboxes
are on the first tab. This required the least changes to get a working version
so that we can quickly get on the same page about what we want it to look like.
I can also put it in a single inputpanel.  This might make it slightly more
readable.

Other notable changes
- Return objects in onGetValues and put them into a string later
- Guarantee up-to-date confid as variable in the panels instead of using getters
- Make fields static as far as possible
---
 www/manager6/Makefile                         |   5 +-
 www/manager6/form/ControllerSelector.js       |   7 +
 www/manager6/qemu/CreateWizard.js             |   8 +-
 www/manager6/qemu/HDEdit.js                   | 409 ------------------
 www/manager6/qemu/HardwareView.js             |   9 +-
 www/manager6/qemu/disk/Disk.js                | 193 +++++++++
 .../qemu/disk/DiskBandwidthOptions.js         | 132 ++++++
 www/manager6/qemu/disk/DiskBasicOptions.js    | 102 +++++
 www/manager6/qemu/disk/DiskData.js            | 174 ++++++++
 9 files changed, 624 insertions(+), 415 deletions(-)
 delete mode 100644 www/manager6/qemu/HDEdit.js
 create mode 100644 www/manager6/qemu/disk/Disk.js
 create mode 100644 www/manager6/qemu/disk/DiskBandwidthOptions.js
 create mode 100644 www/manager6/qemu/disk/DiskBasicOptions.js
 create mode 100644 www/manager6/qemu/disk/DiskData.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 75d355a5..2bda765a 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -201,7 +201,10 @@ JSSRC= 							\
 	qemu/Config.js					\
 	qemu/CreateWizard.js				\
 	qemu/DisplayEdit.js				\
-	qemu/HDEdit.js					\
+	qemu/disk/Disk.js				\
+	qemu/disk/DiskData.js				\
+	qemu/disk/DiskBasicOptions.js			\
+	qemu/disk/DiskBandwidthOptions.js		\
 	qemu/HDEfi.js					\
 	qemu/HDMove.js					\
 	qemu/HDResize.js				\
diff --git a/www/manager6/form/ControllerSelector.js b/www/manager6/form/ControllerSelector.js
index daca2432..4b2be35d 100644
--- a/www/manager6/form/ControllerSelector.js
+++ b/www/manager6/form/ControllerSelector.js
@@ -72,6 +72,13 @@ Ext.define('PVE.form.ControllerSelector', {
 	deviceid.validate();
     },
 
+    // confid = controller + deviceid as string (e.g. virtio1)
+    getConfid: function() {
+	const names = ['controller', 'deviceid']; // order must be guaranteed
+	const values = names.map(n => this.down(`field[name=${n}]`).getValue());
+	return values.join('');
+    },
+
     initComponent: function() {
 	var me = this;
 
diff --git a/www/manager6/qemu/CreateWizard.js b/www/manager6/qemu/CreateWizard.js
index d4535c9d..4b23fea8 100644
--- a/www/manager6/qemu/CreateWizard.js
+++ b/www/manager6/qemu/CreateWizard.js
@@ -154,11 +154,12 @@ Ext.define('PVE.qemu.CreateWizard', {
 	    insideWizard: true,
 	},
 	{
-	    xtype: 'pveQemuHDInputPanel',
+	    xtype: 'pveQemuDisk',
 	    bind: {
 		nodename: '{nodename}',
 	    },
 	    title: gettext('Hard Disk'),
+	    plain: true,
 	    isCreate: true,
 	    insideWizard: true,
 	},
@@ -251,6 +252,11 @@ Ext.define('PVE.qemu.CreateWizard', {
 	    },
 	},
     ],
+
+    getValues: function() {
+	const values = this.callParent();
+	return PVE.qemu.Disk.mergeDiskValues(values);
+    },
 });
 
 
diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js
deleted file mode 100644
index 95a98b0b..00000000
--- a/www/manager6/qemu/HDEdit.js
+++ /dev/null
@@ -1,409 +0,0 @@
-/* 'change' property is assigned a string and then a function */
-Ext.define('PVE.qemu.HDInputPanel', {
-    extend: 'Proxmox.panel.InputPanel',
-    alias: 'widget.pveQemuHDInputPanel',
-    onlineHelp: 'qm_hard_disk',
-
-    insideWizard: false,
-
-    unused: false, // ADD usused disk imaged
-
-    vmconfig: {}, // used to select usused disks
-
-    viewModel: {},
-
-    controller: {
-
-	xclass: 'Ext.app.ViewController',
-
-	onControllerChange: function(field) {
-	    var value = field.getValue();
-
-	    var allowIOthread = value.match(/^(virtio|scsi)/);
-	    this.lookup('iothread').setDisabled(!allowIOthread);
-	    if (!allowIOthread) {
-		this.lookup('iothread').setValue(false);
-	    }
-
-	    var virtio = value.match(/^virtio/);
-	    this.lookup('ssd').setDisabled(virtio);
-	    if (virtio) {
-		this.lookup('ssd').setValue(false);
-	    }
-
-	    this.lookup('scsiController').setVisible(value.match(/^scsi/));
-	},
-
-	control: {
-	    'field[name=controller]': {
-		change: 'onControllerChange',
-		afterrender: 'onControllerChange',
-	    },
-	    'field[name=iothread]': {
-		change: function(f, value) {
-		    if (!this.getView().insideWizard) {
-			return;
-		    }
-		    var vmScsiType = value ? 'virtio-scsi-single': 'virtio-scsi-pci';
-		    this.lookupReference('scsiController').setValue(vmScsiType);
-		},
-	    },
-	},
-
-	init: function(view) {
-	    var vm = this.getViewModel();
-	    if (view.isCreate) {
-		vm.set('isIncludedInBackup', true);
-	    }
-	},
-    },
-
-    onGetValues: function(values) {
-	var me = this;
-
-	var params = {};
-	var confid = me.confid || values.controller + values.deviceid;
-
-	if (me.unused) {
-	    me.drive.file = me.vmconfig[values.unusedId];
-	    confid = values.controller + values.deviceid;
-	} else if (me.isCreate) {
-	    if (values.hdimage) {
-		me.drive.file = values.hdimage;
-	    } else {
-		me.drive.file = values.hdstorage + ":" + values.disksize;
-	    }
-	    me.drive.format = values.diskformat;
-	}
-
-	PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0');
-	PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no');
-	PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on');
-	PVE.Utils.propertyStringSet(me.drive, values.ssd, 'ssd', 'on');
-	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';
-	    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);
-
-	return params;
-    },
-
-    setVMConfig: function(vmconfig) {
-	var me = this;
-
-	me.vmconfig = vmconfig;
-
-	if (me.bussel) {
-	    me.bussel.setVMConfig(vmconfig);
-	    me.scsiController.setValue(vmconfig.scsihw);
-	}
-	if (me.unusedDisks) {
-	    var disklist = [];
-	    Ext.Object.each(vmconfig, function(key, value) {
-		if (key.match(/^unused\d+$/)) {
-		    disklist.push([key, value]);
-		}
-	    });
-	    me.unusedDisks.store.loadData(disklist);
-	    me.unusedDisks.setValue(me.confid);
-	}
-    },
-
-    setDrive: function(drive) {
-	var me = this;
-
-	me.drive = drive;
-
-	var values = {};
-	var match = drive.file.match(/^([^:]+):/);
-	if (match) {
-	    values.hdstorage = match[1];
-	}
-
-	values.hdimage = drive.file;
-	values.backup = PVE.Parser.parseBoolean(drive.backup, 1);
-	values.noreplicate = !PVE.Parser.parseBoolean(drive.replicate, 1);
-	values.diskformat = drive.format || 'raw';
-	values.cache = drive.cache || '__default__';
-	values.discard = drive.discard === 'on';
-	values.ssd = PVE.Parser.parseBoolean(drive.ssd);
-	values.iothread = PVE.Parser.parseBoolean(drive.iothread);
-
-	values.mbps_rd = drive.mbps_rd;
-	values.mbps_wr = drive.mbps_wr;
-	values.iops_rd = drive.iops_rd;
-	values.iops_wr = drive.iops_wr;
-	values.mbps_rd_max = drive.mbps_rd_max;
-	values.mbps_wr_max = drive.mbps_wr_max;
-	values.iops_rd_max = drive.iops_rd_max;
-	values.iops_wr_max = drive.iops_wr_max;
-
-	me.setValues(values);
-    },
-
-    setNodename: function(nodename) {
-	var me = this;
-	me.down('#hdstorage').setNodename(nodename);
-	me.down('#hdimage').setStorage(undefined, nodename);
-    },
-
-    initComponent: function() {
-	var me = this;
-
-	var labelWidth = 140;
-
-	me.drive = {};
-
-	me.column1 = [];
-	me.column2 = [];
-
-	me.advancedColumn1 = [];
-	me.advancedColumn2 = [];
-
-	if (!me.confid || me.unused) {
-	    me.bussel = Ext.create('PVE.form.ControllerSelector', {
-		vmconfig: me.insideWizard ? { ide2: 'cdrom' } : {},
-	    });
-	    me.column1.push(me.bussel);
-
-	    me.scsiController = Ext.create('Ext.form.field.Display', {
-		fieldLabel: gettext('SCSI Controller'),
-		reference: 'scsiController',
-		bind: me.insideWizard ? {
-		    value: '{current.scsihw}',
-		} : undefined,
-		renderer: PVE.Utils.render_scsihw,
-		submitValue: false,
-		hidden: true,
-	    });
-	    me.column1.push(me.scsiController);
-	}
-
-	if (me.unused) {
-	    me.unusedDisks = Ext.create('Proxmox.form.KVComboBox', {
-		name: 'unusedId',
-		fieldLabel: gettext('Disk image'),
-		matchFieldWidth: false,
-		listConfig: {
-		    width: 350,
-		},
-		data: [],
-		allowBlank: false,
-	    });
-	    me.column1.push(me.unusedDisks);
-	} else if (me.isCreate) {
-	    me.column1.push({
-		xtype: 'pveDiskStorageSelector',
-		storageContent: 'images',
-		name: 'disk',
-		nodename: me.nodename,
-		autoSelect: me.insideWizard,
-	    });
-	} else {
-	    me.column1.push({
-		xtype: 'textfield',
-		disabled: true,
-		submitValue: false,
-		fieldLabel: gettext('Disk image'),
-                name: 'hdimage',
-	    });
-	}
-
-	me.column2.push(
-	    {
-		xtype: 'CacheTypeSelector',
-		name: 'cache',
-		value: '__default__',
-		fieldLabel: gettext('Cache'),
-	    },
-	    {
-		xtype: 'proxmoxcheckbox',
-		fieldLabel: gettext('Discard'),
-		reference: 'discard',
-		name: 'discard',
-	    },
-	);
-
-	me.advancedColumn1.push(
-	    {
-		xtype: 'proxmoxcheckbox',
-		disabled: me.confid && me.confid.match(/^virtio/),
-		fieldLabel: gettext('SSD emulation'),
-		labelWidth: labelWidth,
-		name: 'ssd',
-		reference: 'ssd',
-	    },
-	    {
-		xtype: 'proxmoxcheckbox',
-		disabled: me.confid && !me.confid.match(/^(virtio|scsi)/),
-		fieldLabel: 'IO thread',
-		labelWidth: labelWidth,
-		reference: 'iothread',
-		name: 'iothread',
-	    },
-	    {
-		xtype: 'numberfield',
-		name: 'mbps_rd',
-		minValue: 1,
-		step: 1,
-		fieldLabel: gettext('Read limit') + ' (MB/s)',
-		labelWidth: labelWidth,
-		emptyText: gettext('unlimited'),
-	    },
-	    {
-		xtype: 'numberfield',
-		name: 'mbps_wr',
-		minValue: 1,
-		step: 1,
-		fieldLabel: gettext('Write limit') + ' (MB/s)',
-		labelWidth: labelWidth,
-		emptyText: gettext('unlimited'),
-	    },
-	    {
-		xtype: 'proxmoxintegerfield',
-		name: 'iops_rd',
-		minValue: 10,
-		step: 10,
-		fieldLabel: gettext('Read limit') + ' (ops/s)',
-		labelWidth: labelWidth,
-		emptyText: gettext('unlimited'),
-	    },
-	    {
-		xtype: 'proxmoxintegerfield',
-		name: 'iops_wr',
-		minValue: 10,
-		step: 10,
-		fieldLabel: gettext('Write limit') + ' (ops/s)',
-		labelWidth: labelWidth,
-		emptyText: gettext('unlimited'),
-	    },
-	);
-
-	me.advancedColumn2.push(
-	    {
-		xtype: 'proxmoxcheckbox',
-		fieldLabel: gettext('Backup'),
-		autoEl: {
-		    tag: 'div',
-		    'data-qtip': gettext('Include volume in backup job'),
-		},
-		labelWidth: labelWidth,
-		name: 'backup',
-		bind: {
-		    value: '{isIncludedInBackup}',
-		},
-	    },
-	    {
-		xtype: 'proxmoxcheckbox',
-		fieldLabel: gettext('Skip replication'),
-		labelWidth: labelWidth,
-		name: 'noreplicate',
-	    },
-	    {
-		xtype: 'numberfield',
-		name: 'mbps_rd_max',
-		minValue: 1,
-		step: 1,
-		fieldLabel: gettext('Read max burst') + ' (MB)',
-		labelWidth: labelWidth,
-		emptyText: gettext('default'),
-	    },
-	    {
-		xtype: 'numberfield',
-		name: 'mbps_wr_max',
-		minValue: 1,
-		step: 1,
-		fieldLabel: gettext('Write max burst') + ' (MB)',
-		labelWidth: labelWidth,
-		emptyText: gettext('default'),
-	    },
-	    {
-		xtype: 'proxmoxintegerfield',
-		name: 'iops_rd_max',
-		minValue: 10,
-		step: 10,
-		fieldLabel: gettext('Read max burst') + ' (ops)',
-		labelWidth: labelWidth,
-		emptyText: gettext('default'),
-	    },
-	    {
-		xtype: 'proxmoxintegerfield',
-		name: 'iops_wr_max',
-		minValue: 10,
-		step: 10,
-		fieldLabel: gettext('Write max burst') + ' (ops)',
-		labelWidth: labelWidth,
-		emptyText: gettext('default'),
-	    },
-	);
-
-	me.callParent();
-    },
-});
-
-Ext.define('PVE.qemu.HDEdit', {
-    extend: 'Proxmox.window.Edit',
-
-    isAdd: true,
-
-    backgroundDelay: 5,
-
-    initComponent: function() {
-	var me = this;
-
-	var nodename = me.pveSelNode.data.node;
-	if (!nodename) {
-	    throw "no node name specified";
-	}
-
-	var unused = me.confid && me.confid.match(/^unused\d+$/);
-
-	me.isCreate = me.confid ? unused : true;
-
-	var ipanel = Ext.create('PVE.qemu.HDInputPanel', {
-	    confid: me.confid,
-	    nodename: nodename,
-	    unused: unused,
-	    isCreate: me.isCreate,
-	});
-
-	if (unused) {
-	    me.subject = gettext('Unused Disk');
-	} else if (me.isCreate) {
-            me.subject = gettext('Hard Disk');
-	} else {
-           me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
-	}
-
-	me.items = [ipanel];
-
-	me.callParent();
-	/* 'data' is assigned an empty array in same file, and here we
-	 * use it like an object
-	 */
-	me.load({
-	    success: function(response, options) {
-		ipanel.setVMConfig(response.result.data);
-		if (me.confid) {
-		    var value = response.result.data[me.confid];
-		    var drive = PVE.Parser.parseQemuDrive(me.confid, value);
-		    if (!drive) {
-			Ext.Msg.alert(gettext('Error'), 'Unable to parse drive options');
-			me.close();
-			return;
-		    }
-		    ipanel.setDrive(drive);
-		    me.isValid(); // trigger validation
-		}
-	    },
-	});
-    },
-});
diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js
index bfe0a222..fde0938b 100644
--- a/www/manager6/qemu/HardwareView.js
+++ b/www/manager6/qemu/HardwareView.js
@@ -220,7 +220,7 @@ Ext.define('PVE.qemu.HardwareView', {
 	    rows[confid] = {
 		group: 10,
 		iconCls: 'hdd-o',
-		editor: 'PVE.qemu.HDEdit',
+		editor: 'PVE.qemu.DiskWindow',
 		isOnStorageBus: true,
 		header: gettext('Hard Disk') + ' (' + confid +')',
 		cdheader: gettext('CD/DVD Drive') + ' (' + confid +')',
@@ -290,7 +290,7 @@ Ext.define('PVE.qemu.HardwareView', {
 		order: i,
 		iconCls: 'hdd-o',
 		del_extra_msg: gettext('This will permanently erase all data.'),
-		editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
+		editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.DiskWindow' : undefined,
 		header: gettext('Unused Disk') + ' ' + i.toString(),
 	    };
 	}
@@ -629,9 +629,10 @@ Ext.define('PVE.qemu.HardwareView', {
 				iconCls: 'fa fa-fw fa-hdd-o black',
 				disabled: !caps.vms['VM.Config.Disk'],
 				handler: function() {
-				    let win = Ext.create('PVE.qemu.HDEdit', {
+				    let win = Ext.create('PVE.qemu.DiskWindow', {
 					url: '/api2/extjs/' + baseurl,
-					pveSelNode: me.pveSelNode,
+					nodename: me.pveSelNode.data.node,
+					isCreate: true,
 				    });
 				    win.on('destroy', me.reload, me);
 				    win.show();
diff --git a/www/manager6/qemu/disk/Disk.js b/www/manager6/qemu/disk/Disk.js
new file mode 100644
index 00000000..591ba5c3
--- /dev/null
+++ b/www/manager6/qemu/disk/Disk.js
@@ -0,0 +1,193 @@
+/* 'change' property is assigned a string and then a function */
+Ext.define('PVE.qemu.Disk', {
+    extend: 'Ext.tab.Panel',
+    alias: 'widget.pveQemuDisk',
+    onlineHelp: 'qm_hard_disk',
+
+    insideWizard: false,
+
+    isCreate: false,
+
+    bodyPadding: 10,
+
+    setDrive: function(drive) {
+	[
+	    'pveQemuDiskData',
+	    'pveQemuDiskBasicOptions',
+	    'pveQemuDiskBandwidthOptions',
+	].forEach(p => this.down(p).setDrive(drive));
+    },
+
+    setNodename: function(nodename) {
+	const me = this;
+	const hdstorage = me.down('#hdstorage');
+	if (hdstorage) {
+	    hdstorage.setNodename(nodename);
+	}
+	const hdimage = me.down('#hdimage');
+	if (hdimage) {
+	    hdimage.setStorage(undefined, nodename);
+	}
+    },
+
+    // going over the items with "down" is not yet possible in initComponent => use beforeRender
+    beforeRender: function() {
+	const me = this;
+	// any other panel because this has no height yet
+	if (me.insideWizard) {
+	    const panelHeight = me.up('#wizcontent').down('inputpanel').getHeight();
+	    me.setHeight(panelHeight);
+	}
+    },
+
+    initComponent: function() {
+	const me = this;
+
+	me.items = [
+	    {
+		title: 'Drive',
+		xtype: 'panel',
+		layout: {
+		    type: 'vbox',
+		},
+		defaults: {
+		    width: '100%',
+		    margin: '0 0 10 0',
+		},
+		items: [
+		    {
+			xtype: 'pveQemuDiskData',
+			isCreate: me.isCreate,
+			confid: me.confid,
+			unused: me.unused,
+			insideWizard: me.insideWizard,
+		    },
+		    {
+			xtype: 'pveQemuDiskBasicOptions',
+			isCreate: me.isCreate,
+			confid: me.confid,
+			unused: me.unused,
+			insideWizard: me.insideWizard,
+		    },
+		],
+	    },
+	    {
+		title: 'Bandwidth Limits',
+		xtype: 'pveQemuDiskBandwidthOptions',
+		isCreate: me.isCreate,
+		confid: me.confid,
+		unused: me.unused,
+		insideWizard: me.insideWizard,
+	    },
+	];
+
+	me.callParent();
+
+	const updateConfid = () => {
+	    const confid = me.down('pveQemuDiskData').getConfid();
+	    me.down('pveQemuDiskBasicOptions').confid = confid;
+	    me.down('pveQemuDiskBandwidthOptions').confid = confid;
+	};
+	const selector = me.down('pveQemuDiskData').down('pveControllerSelector');
+	if (selector) {
+	    // confid (controller + deviceid) is the key for which panels belong together
+	    // it is changed only in pveQemuDiskData => Always update confid in the other panels
+	    // see mergeDiskValues
+	    selector.query('field').forEach(f => f.on('change', updateConfid));
+	} else {
+	    //no confid change possible, e.g. in edit window for a disk that is already attached
+	    updateConfid();
+	}
+
+	me.setTabPosition(me.insideWizard ? 'bottom' : 'top');
+    },
+
+    setVMConfig: function(vmconfig) {
+	this.down('pveQemuDiskData').setVMConfig(vmconfig);
+    },
+
+    statics: {
+	// One tabpanel represents a whole drive/disk.
+	// Each panel in it has only some options.
+	// Values are collected by the wizard from inputpanels, ignoring tabpanels.
+	// But for disks (=> bus_match) we need values from all child inputpanels of the tabpanel.
+	// Each child panel prepares for this in onGetValues so that we can put it together here.
+	mergeDiskValues: function(values) {
+	    for (const [key, value] of Object.entries(values)) {
+		if (key.match(PVE.Utils.bus_match) && Array.isArray(value)) {
+		    const driveObj = value.reduce((acc, cur) => ({ ...acc, ...cur }));
+		    values[key] = PVE.Parser.printQemuDrive(driveObj);
+		}
+	    }
+	    return values;
+	},
+    },
+});
+
+Ext.define('PVE.qemu.DiskWindow', {
+    extend: 'Proxmox.window.Edit',
+
+    isAdd: true,
+
+    backgroundDelay: 5,
+
+    bodyPadding: 0,
+
+    initComponent: function() {
+	const me = this;
+
+	const selnode = me.pveSelNode && me.pveSelNode.data && me.pveSelNode.data.node;
+	if (selnode && !me.nodename) {
+	    me.nodename = selnode;
+	}
+	if (!me.nodename) {
+	    throw "no node name specified";
+	}
+
+	const unused = me.confid && me.confid.match(/^unused\d+$/);
+
+	me.isCreate = me.confid ? unused : true;
+
+	const ipanel = Ext.create('PVE.qemu.Disk', {
+	    confid: me.confid,
+	    unused: unused,
+	    isCreate: me.isCreate,
+	});
+	ipanel.setNodename(me.nodename);
+
+	if (unused) {
+	    me.subject = gettext('Unused Disk');
+	} else if (me.isCreate) {
+	    me.subject = gettext('Hard Disk');
+	} else {
+	    me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
+	}
+
+	me.items = [ipanel];
+
+	me.callParent();
+	/* 'data' is assigned an empty array in same file, and here we
+	 * use it like an object
+	 */
+	me.load({
+	    success: function(response, options) {
+		ipanel.setVMConfig(response.result.data);
+		if (me.confid) {
+		    const value = response.result.data[me.confid];
+		    const drive = PVE.Parser.parseQemuDrive(me.confid, value);
+		    if (!drive) {
+			Ext.Msg.alert(gettext('Error'), 'Unable to parse drive options');
+			me.close();
+			return;
+		    }
+		    ipanel.setDrive(drive);
+		    me.isValid(); // trigger validation
+		}
+	    },
+	});
+    },
+
+    getValues: function() {
+	return PVE.qemu.Disk.mergeDiskValues(this.callParent());
+    },
+});
diff --git a/www/manager6/qemu/disk/DiskBandwidthOptions.js b/www/manager6/qemu/disk/DiskBandwidthOptions.js
new file mode 100644
index 00000000..3834bb6a
--- /dev/null
+++ b/www/manager6/qemu/disk/DiskBandwidthOptions.js
@@ -0,0 +1,132 @@
+/* 'change' property is assigned a string and then a function */
+Ext.define('PVE.qemu.DiskBandwidthOptions', {
+    extend: 'Proxmox.panel.InputPanel',
+    alias: 'widget.pveQemuDiskBandwidthOptions',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: 'qm_hard_disk',
+
+    insideWizard: false,
+
+    unused: false, // ADD usused disk imaged
+
+    vmconfig: {}, // used to select usused disks
+
+    cbindData: {
+	labelWidth: 140,
+    },
+
+    onGetValues: function(values) {
+	const me = this;
+	const result = {};
+
+	const names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'];
+	Ext.Array.each(names, function(name) {
+	    const burstName = name + '_max';
+	    PVE.Utils.propertyStringSet(result, values[name], name);
+	    PVE.Utils.propertyStringSet(result, values[burstName], burstName);
+	});
+
+	if (!me.confid) {
+	    throw 'confid must be set by parent';
+	}
+	return { [me.confid]: result }; // see mergeDiskValues
+    },
+
+    setDrive: function(drive) {
+	this.setValues(drive); // non-existent values are ignored
+    },
+
+    column1: [
+	{
+	    xtype: 'numberfield',
+	    name: 'mbps_rd',
+	    minValue: 1,
+	    step: 1,
+	    fieldLabel: gettext('Read limit') + ' (MB/s)',
+	    emptyText: gettext('unlimited'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+	{
+	    xtype: 'numberfield',
+	    name: 'mbps_wr',
+	    minValue: 1,
+	    step: 1,
+	    fieldLabel: gettext('Write limit') + ' (MB/s)',
+	    emptyText: gettext('unlimited'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'iops_rd',
+	    minValue: 10,
+	    step: 10,
+	    fieldLabel: gettext('Read limit') + ' (ops/s)',
+	    emptyText: gettext('unlimited'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'iops_wr',
+	    minValue: 10,
+	    step: 10,
+	    fieldLabel: gettext('Write limit') + ' (ops/s)',
+	    emptyText: gettext('unlimited'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+    ],
+    column2: [
+	{
+	    xtype: 'numberfield',
+	    name: 'mbps_rd_max',
+	    minValue: 1,
+	    step: 1,
+	    fieldLabel: gettext('Read max burst') + ' (MB)',
+	    emptyText: gettext('default'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+	{
+	    xtype: 'numberfield',
+	    name: 'mbps_wr_max',
+	    minValue: 1,
+	    step: 1,
+	    fieldLabel: gettext('Write max burst') + ' (MB)',
+	    emptyText: gettext('default'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'iops_rd_max',
+	    minValue: 10,
+	    step: 10,
+	    fieldLabel: gettext('Read max burst') + ' (ops)',
+	    emptyText: gettext('default'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'iops_wr_max',
+	    minValue: 10,
+	    step: 10,
+	    fieldLabel: gettext('Write max burst') + ' (ops)',
+	    emptyText: gettext('default'),
+	    cbind: {
+		labelWidth: '{labelWidth}',
+	    },
+	},
+    ],
+});
diff --git a/www/manager6/qemu/disk/DiskBasicOptions.js b/www/manager6/qemu/disk/DiskBasicOptions.js
new file mode 100644
index 00000000..cee4da8c
--- /dev/null
+++ b/www/manager6/qemu/disk/DiskBasicOptions.js
@@ -0,0 +1,102 @@
+/* 'change' property is assigned a string and then a function */
+Ext.define('PVE.qemu.DiskBasicOptions', {
+    extend: 'Proxmox.panel.InputPanel',
+    alias: 'widget.pveQemuDiskBasicOptions',
+    onlineHelp: 'qm_hard_disk',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    insideWizard: false,
+
+    unused: false, // ADD usused disk imaged
+
+    vmconfig: {}, // used to select usused disks
+
+    onGetValues: function(values) {
+	const me = this;
+	const result = {};
+
+	PVE.Utils.propertyStringSet(result, !values.backup, 'backup', '0');
+	PVE.Utils.propertyStringSet(result, values.noreplicate, 'replicate', 'no');
+	PVE.Utils.propertyStringSet(result, values.ssd, 'ssd', 'on');
+	PVE.Utils.propertyStringSet(result, values.iothread, 'iothread', 'on');
+	PVE.Utils.propertyStringSet(result, values.discard, 'discard', 'on');
+
+	if (!me.confid) {
+	    throw 'confid must be set by parent';
+	}
+	return { [me.confid]: result }; // see mergeDiskValues
+    },
+
+    setDrive: function(drive) {
+	const me = this;
+
+	const values = {};
+	values.backup = PVE.Parser.parseBoolean(drive.backup, 1);
+	values.noreplicate = !PVE.Parser.parseBoolean(drive.replicate, 1);
+	values.ssd = PVE.Parser.parseBoolean(drive.ssd);
+	values.iothread = PVE.Parser.parseBoolean(drive.iothread);
+	values.discard = drive.discard === 'on';
+
+	me.setValues(values);
+    },
+
+    column1: [
+	{
+	    xtype: 'proxmoxcheckbox',
+	    fieldLabel: gettext('Discard'),
+	    reference: 'discard',
+	    name: 'discard',
+	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    fieldLabel: gettext('SSD emulation'),
+	    name: 'ssd',
+	    reference: 'ssd',
+	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    fieldLabel: 'IO thread',
+	    reference: 'iothread',
+	    name: 'iothread',
+	    listeners: {
+		change: function(field, value) {
+		    if (field.up('pveQemuDiskBasicOptions').insideWizard) {
+			const vmScsiType = value ? 'virtio-scsi-single' : 'virtio-scsi-pci';
+			const disk = field.up('pveQemuDisk');
+			disk.down('field[name=scsiController]').setValue(vmScsiType);
+		    }
+		},
+	    },
+	},
+    ],
+    column2: [
+	{
+	    xtype: 'proxmoxcheckbox',
+	    fieldLabel: gettext('Backup'),
+	    autoEl: {
+		tag: 'div',
+		'data-qtip': gettext('Include volume in backup job'),
+	    },
+	    name: 'backup',
+	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    fieldLabel: gettext('Skip replication'),
+	    name: 'noreplicate',
+	},
+    ],
+
+    listeners: {
+	beforerender: function() {
+	    // Cannot query fields in initComponent => beforerender.
+	    // Also those are all optional fields, so we don't need to run this right
+	    // when the window opens
+	    const me = this;
+	    if (me.isCreate) {
+		me.down('field[name=backup]').setValue(true); // else set by setDrive
+	    }
+	    me.down('field[name=ssd]').setDisabled(me.confid && me.confid.match(/^virtio/));
+	    me.down('field[name=iothread]').setDisabled(me.confid && !me.confid.match(/^(virtio|scsi)/));
+	},
+    },
+});
diff --git a/www/manager6/qemu/disk/DiskData.js b/www/manager6/qemu/disk/DiskData.js
new file mode 100644
index 00000000..d0fc0aa4
--- /dev/null
+++ b/www/manager6/qemu/disk/DiskData.js
@@ -0,0 +1,174 @@
+/* 'change' property is assigned a string and then a function */
+Ext.define('PVE.qemu.DiskData', {
+    extend: 'Proxmox.panel.InputPanel',
+    alias: 'widget.pveQemuDiskData',
+    onlineHelp: 'qm_hard_disk',
+
+    insideWizard: false,
+
+    unused: false,
+
+    vmconfig: {}, // used to select usused disks
+
+    getConfid() {
+	const me = this;
+	return me.isCreate ? this.down('pveControllerSelector').getConfid() : me.confid;
+    },
+
+    onGetValues: function(values) {
+	const me = this;
+	const result = {};
+
+	if (me.unused) {
+	    result.file = me.vmconfig[values.unusedId];
+	    // in this case we could also extract the confid from `values`
+	    // but getConfid() works always
+	} else if (me.isCreate) {
+	    if (values.hdimage) {
+		result.file = values.hdimage;
+	    } else {
+		result.file = values.hdstorage + ":" + values.disksize;
+	    }
+	    result.format = values.diskformat;
+	} else {
+	    // editing already attached disk
+	    result.file = me.down('field[name=hdimage]').getValue();
+	}
+
+	PVE.Utils.propertyStringSet(result, values.cache, 'cache');
+
+	return { [me.getConfid()]: result }; // see mergeDiskValues
+    },
+
+    setVMConfig: function(vmconfig) {
+	const me = this;
+
+	me.vmconfig = vmconfig;
+
+	if (me.bussel) {
+	    me.bussel.setVMConfig(vmconfig);
+	    me.scsiController.setValue(vmconfig.scsihw);
+	}
+	if (me.unusedDisks) {
+	    const disklist = [];
+	    Ext.Object.each(vmconfig, function(key, value) {
+		if (key.match(/^unused\d+$/)) {
+		    disklist.push([key, value]);
+		}
+	    });
+	    me.unusedDisks.store.loadData(disklist);
+	    me.unusedDisks.setValue(me.confid);
+	}
+    },
+
+    setDrive: function(drive) {
+	const me = this;
+
+	const values = {};
+	const match = drive.file.match(/^([^:]+):/);
+	if (match) {
+	    values.hdstorage = match[1];
+	}
+
+	values.hdimage = drive.file;
+	values.diskformat = drive.format || 'raw';
+	values.cache = drive.cache || '__default__';
+
+	me.setValues(values);
+    },
+
+    column2: [
+	{
+	    xtype: 'CacheTypeSelector',
+	    name: 'cache',
+	    value: '__default__',
+	    fieldLabel: gettext('Cache'),
+	},
+    ],
+
+    initComponent: function() {
+	const me = this;
+
+	me.column1 = [];
+	// scsiController & bussel must not be in every reference => work on copy
+	me.column2 = [...me.column2];
+
+	if (!me.confid || me.unused) {
+	    // Create now => easily set visible from bussel listener
+	    me.scsiController = Ext.create('Ext.form.field.Display', {
+		xtype: 'displayfield',
+		fieldLabel: gettext('SCSI Controller'),
+		reference: 'scsiController',
+		name: 'scsiController',
+		bind: me.insideWizard ? {
+		    value: '{current.scsihw}',
+		} : undefined,
+		renderer: PVE.Utils.render_scsihw,
+		submitValue: false,
+		hidden: true,
+	    });
+
+	    // Create now => Children initialized => setVMConfig possible
+	    me.bussel = Ext.create('PVE.form.ControllerSelector', {
+		xtype: 'pveControllerSelector',
+		itemId: 'bussel',
+		vmconfig: me.insideWizard ? { ide2: 'cdrom' } : {},
+	    });
+
+	    const changeFunction = (_, newValue) => {
+		const allowIOthread = newValue.match(/^(virtio|scsi)/);
+		const iothreadField = me.up('pveQemuDisk').down('field[name=iothread]');
+		iothreadField.setDisabled(!allowIOthread);
+		if (!allowIOthread) {
+		    iothreadField.setValue(false);
+		}
+
+		const virtio = newValue.match(/^virtio/);
+		const ssdField = me.up('pveQemuDisk').down('field[name=ssd]');
+		ssdField.setDisabled(virtio);
+		if (virtio) {
+		    ssdField.setValue(false);
+		}
+
+		me.scsiController.setVisible(newValue.match(/^scsi/));
+	    };
+	    me.bussel.down('field[name=controller]').addListener('change', changeFunction);
+
+	    me.column2.unshift(me.bussel, me.scsiController);
+	}
+
+	if (me.unused) {
+	    // Ext.create now => setVMConfig possible
+	    me.unusedDisks = Ext.create('Proxmox.form.KVComboBox', {
+		name: 'unusedId',
+		xtype: 'proxmoxKVComboBox',
+		fieldLabel: gettext('Disk image'),
+		matchFieldWidth: false,
+		listConfig: {
+		    width: 350,
+		},
+		data: [],
+		allowBlank: false,
+	    });
+	    me.column1.push(me.unusedDisks);
+	} else if (me.isCreate) {
+	    me.column1.push({
+		xtype: 'pveDiskStorageSelector',
+		storageContent: 'images',
+		storageLabel: gettext('Storage'),
+		name: 'disk',
+		autoSelect: me.insideWizard,
+	    });
+	} else {
+	    me.column1.push({
+		xtype: 'textfield',
+		disabled: true,
+		submitValue: false,
+		fieldLabel: gettext('Disk image'),
+		name: 'hdimage',
+	    });
+	}
+
+	me.callParent();
+    },
+});
-- 
2.30.2






More information about the pve-devel mailing list