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) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 07CB37409C for ; Thu, 8 Jul 2021 10:41:31 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id D689C158D9 for ; Thu, 8 Jul 2021 10:41:00 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 6F37E158CE for ; Thu, 8 Jul 2021 10:40:58 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 42FB940E9F for ; Thu, 8 Jul 2021 10:40:58 +0200 (CEST) From: =?UTF-8?q?Dominic=20J=C3=A4ger?= To: pve-devel@lists.proxmox.com Date: Thu, 8 Jul 2021 10:40:38 +0200 Message-Id: <20210708084038.40675-1-d.jaeger@proxmox.com> X-Mailer: git-send-email 2.30.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 1.319 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [result.data, me.drive, pveselnode.data] Subject: [pve-devel] [PATCH manager] ui: disk edit: Split out bandwidth limits 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: Thu, 08 Jul 2021 08:41:31 -0000 Signed-off-by: Dominic Jäger 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