[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