/* '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: { data: { isSCSI: false, isVirtIO: false, isSCSISingle: false, }, }, controller: { xclass: 'Ext.app.ViewController', onControllerChange: function(field) { let me = this; let vm = this.getViewModel(); let value = field.getValue(); vm.set('isSCSI', value.match(/^scsi/)); vm.set('isVirtIO', value.match(/^virtio/)); me.fireIdChange(); }, fireIdChange: function() { let view = this.getView(); view.fireEvent('diskidchange', view, view.bussel.getConfId()); }, control: { 'field[name=controller]': { change: 'onControllerChange', afterrender: 'onControllerChange', }, 'field[name=deviceid]': { change: 'fireIdChange', }, 'field[name=scsiController]': { change: function(f, value) { let vm = this.getViewModel(); vm.set('isSCSISingle', value === 'virtio-scsi-single'); }, }, }, init: function(view) { var vm = this.getViewModel(); if (view.isCreate) { vm.set('isIncludedInBackup', true); } if (view.confid) { vm.set('isSCSI', view.confid.match(/^scsi/)); vm.set('isVirtIO', view.confid.match(/^virtio/)); } }, }, 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.readOnly, 'ro', 'on'); PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache'); PVE.Utils.propertyStringSet(me.drive, values.aio, 'aio'); ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'].forEach(name => { let 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; }, updateVMConfig: function(vmconfig) { var me = this; me.vmconfig = vmconfig; me.bussel?.updateVMConfig(vmconfig); }, 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.readOnly = PVE.Parser.parseBoolean(drive.ro); values.aio = drive.aio || '__default__'; 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); }, hasAdvanced: true, initComponent: function() { var me = this; me.drive = {}; let column1 = []; let column2 = []; let advancedColumn1 = []; let advancedColumn2 = []; if (!me.confid || me.unused) { me.bussel = Ext.create('PVE.form.ControllerSelector', { vmconfig: me.vmconfig, selectFree: true, }); column1.push(me.bussel); me.scsiController = Ext.create('Ext.form.field.Display', { fieldLabel: gettext('SCSI Controller'), reference: 'scsiController', name: 'scsiController', bind: me.insideWizard ? { value: '{current.scsihw}', visible: '{isSCSI}', } : { visible: '{isSCSI}', }, renderer: PVE.Utils.render_scsihw, submitValue: false, hidden: true, }); 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, }); column1.push(me.unusedDisks); } else if (me.isCreate) { column1.push({ xtype: 'pveDiskStorageSelector', storageContent: 'images', name: 'disk', nodename: me.nodename, autoSelect: me.insideWizard, }); } else { column1.push({ xtype: 'textfield', disabled: true, submitValue: false, fieldLabel: gettext('Disk image'), name: 'hdimage', }); } column2.push( { xtype: 'CacheTypeSelector', name: 'cache', value: '__default__', fieldLabel: gettext('Cache'), }, { xtype: 'proxmoxcheckbox', fieldLabel: gettext('Discard'), reference: 'discard', name: 'discard', }, { xtype: 'proxmoxcheckbox', name: 'iothread', fieldLabel: 'IO thread', clearOnDisable: true, bind: me.insideWizard || me.isCreate ? { disabled: '{!isVirtIO && !isSCSI}', // Checkbox.setValue handles Arrays in a different way, therefore cast to bool value: '{!!isVirtIO || (isSCSI && isSCSISingle)}', } : { disabled: '{!isVirtIO && !isSCSI}', }, }, ); advancedColumn1.push( { xtype: 'proxmoxcheckbox', fieldLabel: gettext('SSD emulation'), name: 'ssd', clearOnDisable: true, bind: { disabled: '{isVirtIO}', }, }, { xtype: 'proxmoxcheckbox', name: 'readOnly', // `ro` in the config, we map in get/set values defaultValue: 0, fieldLabel: gettext('Read-only'), clearOnDisable: true, bind: { disabled: '{!isVirtIO && !isSCSI}', }, }, ); advancedColumn2.push( { xtype: 'proxmoxcheckbox', fieldLabel: gettext('Backup'), autoEl: { tag: 'div', 'data-qtip': gettext('Include volume in backup job'), }, name: 'backup', bind: { value: '{isIncludedInBackup}', }, }, { xtype: 'proxmoxcheckbox', fieldLabel: gettext('Skip replication'), name: 'noreplicate', }, { xtype: 'proxmoxKVComboBox', name: 'aio', fieldLabel: gettext('Async IO'), allowBlank: false, value: '__default__', comboItems: [ ['__default__', Proxmox.Utils.defaultText + ' (io_uring)'], ['io_uring', 'io_uring'], ['native', 'native'], ['threads', 'threads'], ], }, ); let labelWidth = 140; let bwColumn1 = [ { 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'), }, ]; let bwColumn2 = [ { 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.items = [ { xtype: 'tabpanel', plain: true, bodyPadding: 10, border: 0, items: [ { title: gettext('Disk'), xtype: 'inputpanel', reference: 'diskpanel', column1, column2, advancedColumn1, advancedColumn2, showAdvanced: me.showAdvanced, getValues: () => ({}), }, { title: gettext('Bandwidth'), xtype: 'inputpanel', reference: 'bwpanel', column1: bwColumn1, column2: bwColumn2, showAdvanced: me.showAdvanced, getValues: () => ({}), }, ], }, ]; me.callParent(); }, setAdvancedVisible: function(visible) { this.lookup('diskpanel').setAdvancedVisible(visible); this.lookup('bwpanel').setAdvancedVisible(visible); }, }); Ext.define('PVE.qemu.HDEdit', { extend: 'Proxmox.window.Edit', isAdd: true, backgroundDelay: 5, width: 600, bodyPadding: 0, 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 } }, }); }, });