X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=www%2Fmanager6%2Fqemu%2FBootOrderEdit.js;h=c1f24ff4c9494d359d7c11478a1109dcfe37e245;hb=refs%2Fheads%2Fmaster;hp=783a3aa8910ccfcd33394a0aab1f28b3a72b5599;hpb=9e7b4d8dc5c25a2012669df973a2898f8e63a5ec;p=pve-manager.git diff --git a/www/manager6/qemu/BootOrderEdit.js b/www/manager6/qemu/BootOrderEdit.js index 783a3aa8..c1f24ff4 100644 --- a/www/manager6/qemu/BootOrderEdit.js +++ b/www/manager6/qemu/BootOrderEdit.js @@ -1,166 +1,273 @@ +Ext.define('pve-boot-order-entry', { + extend: 'Ext.data.Model', + fields: [ + { name: 'name', type: 'string' }, + { name: 'enabled', type: 'bool' }, + { name: 'desc', type: 'string' }, + ], +}); + Ext.define('PVE.qemu.BootOrderPanel', { - extend: 'PVE.panel.InputPanel', + extend: 'Proxmox.panel.InputPanel', alias: 'widget.pveQemuBootOrderPanel', - vmconfig: {}, // store loaded vm config - bootdisk: undefined, - selection: [], - list: [], - comboboxes: [], + onlineHelp: 'qm_bootorder', - setVMConfig: function(vmconfig) { - var me = this; - me.vmconfig = vmconfig; - var order = me.vmconfig.boot || 'cdn'; - me.bootdisk = me.vmconfig.bootdisk || undefined; + vmconfig: {}, // store loaded vm config + store: undefined, - // get the first 3 characters - // ignore the rest (there should never be more than 3) - me.selection = order.split('').slice(0,3); + inUpdate: false, + controller: { + xclass: 'Ext.app.ViewController', - // build bootdev list - me.list = []; - Ext.Object.each(me.vmconfig, function(key, value) { - if ((/^(ide|sata|scsi|virtio)\d+$/).test(key) && - !(/media=cdrom/).test(value)) { - me.list.push([key, "Disk '" + key + "'"]); - } - }); + init: function(view) { + let me = this; - me.list.push(['d', 'CD-ROM']); - me.list.push(['n', gettext('Network')]); - me.list.push(['__none__', PVE.Utils.noneText]); + let grid = me.lookup('grid'); + let marker = me.lookup('marker'); + let emptyWarning = me.lookup('emptyWarning'); - me.recomputeList(); + marker.originalValue = undefined; - me.comboboxes.forEach(function(box) { - box.resetOriginalValue(); - }); + view.store = Ext.create('Ext.data.Store', { + model: 'pve-boot-order-entry', + listeners: { + update: function() { + this.commitChanges(); + let val = view.calculateValue(); + if (marker.originalValue === undefined) { + marker.originalValue = val; + } + view.inUpdate = true; + marker.setValue(val); + view.inUpdate = false; + marker.checkDirty(); + emptyWarning.setHidden(val !== ''); + grid.getView().refresh(); + }, + }, + }); + grid.setStore(view.store); + }, }, - onGetValues: function(values) { - var me = this; - var order = me.selection.join(''); - var res = { boot: order }; + isCloudinit: (v) => v.match(/media=cdrom/) && v.match(/[:/]vm-\d+-cloudinit/), - if (me.bootdisk && order.indexOf('c') !== -1) { - res['bootdisk'] = me.bootdisk; - } else { - res['delete'] = 'bootdisk'; - } + isDisk: function(value) { + return PVE.Utils.bus_match.test(value); + }, - return res; + isBootdev: function(dev, value) { + return (this.isDisk(dev) && !this.isCloudinit(value)) || + (/^net\d+/).test(dev) || + (/^hostpci\d+/).test(dev) || + ((/^usb\d+/).test(dev) && !(/spice/).test(value)); }, - recomputeSelection: function(combobox, newVal, oldVal) { - var me = this.up('#inputpanel'); - me.selection = []; - me.comboboxes.forEach(function(item) { - var val = item.getValue(); - - // when selecting an already selected item, - // switch it around - if (val === newVal && - item.name !== combobox.name && - newVal !== '__none__') { - // swap items - val = oldVal; + setVMConfig: function(vmconfig) { + let me = this; + me.vmconfig = vmconfig; + + me.store.removeAll(); + + let boot = PVE.Parser.parsePropertyString(me.vmconfig.boot, "legacy"); + + let bootorder = []; + if (boot.order) { + bootorder = boot.order.split(';').map(dev => ({ name: dev, enabled: true })); + } else if (!(/^\s*$/).test(me.vmconfig.boot)) { + // legacy style, transform to new bootorder + let order = boot.legacy || 'cdn'; + let bootdisk = me.vmconfig.bootdisk || undefined; + + // get the first 4 characters (acdn) + // ignore the rest (there should never be more than 4) + let orderList = order.split('').slice(0, 4); + + // build bootdev list + for (let i = 0; i < orderList.length; i++) { + let list = []; + if (orderList[i] === 'c') { + if (bootdisk !== undefined && me.vmconfig[bootdisk]) { + list.push(bootdisk); + } + } else if (orderList[i] === 'd') { + Ext.Object.each(me.vmconfig, function(key, value) { + if (me.isDisk(key) && value.match(/media=cdrom/) && !me.isCloudinit(value)) { + list.push(key); + } + }); + } else if (orderList[i] === 'n') { + Ext.Object.each(me.vmconfig, function(key, value) { + if ((/^net\d+/).test(key)) { + list.push(key); + } + }); + } + + // Object.each iterates in random order, sort alphabetically + list.sort(); + list.forEach(dev => bootorder.push({ name: dev, enabled: true })); } + } - // push 'c','d' or 'n' in the array - if ((/^(ide|sata|scsi|virtio)\d+$/).test(val)) { - me.selection.push('c'); - me.bootdisk = val; - } else if (val === 'd' || - val === 'n') { - me.selection.push(val); + // add disabled devices as well + let disabled = []; + Ext.Object.each(me.vmconfig, function(key, value) { + if (me.isBootdev(key, value) && + !Ext.Array.some(bootorder, x => x.name === key)) { + disabled.push(key); } }); + disabled.sort(); + disabled.forEach(dev => bootorder.push({ name: dev, enabled: false })); + + // add descriptions + bootorder.forEach(entry => { + entry.desc = me.vmconfig[entry.name]; + }); - me.recomputeList(); + me.store.insert(0, bootorder); + me.store.fireEvent("update"); }, - recomputeList: function(){ - var me = this; - // set the correct values in the kvcomboboxes - var cnt = 0; - me.comboboxes.forEach(function(item) { - if (cnt === 0) { - // never show 'none' on first combobox - item.store.loadData(me.list.slice(0, me.list.length-1)); - } else { - item.store.loadData(me.list); - } - item.suspendEvent('change'); - if (cnt < me.selection.length) { - item.setValue((me.selection[cnt] !== 'c')?me.selection[cnt]:me.bootdisk); - } else if (cnt === 0){ - item.setValue(''); - } else { - item.setValue('__none__'); - } - cnt++; - item.resumeEvent('change'); - item.validate(); - }); + calculateValue: function() { + let me = this; + return me.store.getData().items + .filter(x => x.data.enabled) + .map(x => x.data.name) + .join(';'); }, - initComponent : function() { - var me = this; - - // this has to be done here, because of - // the way our inputPanel class handles items - me.comboboxes = [ - Ext.createWidget('pveKVComboBox', { - fieldLabel: gettext('Boot device') + " 1", - labelWidth: 120, - name: 'bd1', - allowBlank: false, - listeners: { - change: me.recomputeSelection, - } - }), - Ext.createWidget('pveKVComboBox', { - fieldLabel: gettext('Boot device') + " 2", - labelWidth: 120, - name: 'bd2', - allowBlank: false, - listeners: { - change: me.recomputeSelection, - } - }), - Ext.createWidget('pveKVComboBox', { - fieldLabel: gettext('Boot device') + " 3", - labelWidth: 120, - name: 'bd3', - allowBlank: false, - listeners: { - change: me.recomputeSelection, + onGetValues: function() { + let me = this; + // Note: we allow an empty value, so no 'delete' option + let val = { order: me.calculateValue() }; + let res = { boot: PVE.Parser.printPropertyString(val) }; + return res; + }, + + items: [ + { + xtype: 'grid', + reference: 'grid', + margin: '0 0 5 0', + minHeight: 150, + defaults: { + sortable: false, + hideable: false, + draggable: false, + }, + columns: [ + { + header: '#', + flex: 4, + renderer: (value, metaData, record, rowIndex) => { + let dragHandle = ""; + let idx = (rowIndex + 1).toString(); + if (record.get('enabled')) { + return dragHandle + idx; + } else { + return dragHandle + "" + idx + ""; + } + }, + }, + { + xtype: 'checkcolumn', + header: gettext('Enabled'), + dataIndex: 'enabled', + flex: 4, + }, + { + header: gettext('Device'), + dataIndex: 'name', + flex: 6, + renderer: (value, metaData, record, rowIndex) => { + let desc = record.get('desc'); + + let icon = '', iconCls; + if (value.match(/^net\d+$/)) { + iconCls = 'exchange'; + } else if (desc.match(/media=cdrom/)) { + metaData.tdCls = 'pve-itype-icon-cdrom'; + } else { + iconCls = 'hdd-o'; + } + if (iconCls !== undefined) { + metaData.tdCls += 'pve-itype-fa'; + icon = ``; + } + + return icon + value; + }, + }, + { + header: gettext('Description'), + dataIndex: 'desc', + flex: 20, + }, + ], + viewConfig: { + plugins: { + ptype: 'gridviewdragdrop', + dragText: gettext('Drag and drop to reorder'), + }, + }, + listeners: { + drop: function() { + // doesn't fire automatically on reorder + this.getStore().fireEvent("update"); + }, + }, + }, + { + xtype: 'component', + html: gettext('Drag and drop to reorder'), + }, + { + xtype: 'displayfield', + reference: 'emptyWarning', + userCls: 'pmx-hint', + value: gettext('Warning: No devices selected, the VM will probably not boot!'), + }, + { + // for dirty marking and 'reset' function + xtype: 'field', + reference: 'marker', + hidden: true, + setValue: function(val) { + let me = this; + let panel = me.up('pveQemuBootOrderPanel'); + + // on form reset, go back to original state + if (!panel.inUpdate) { + panel.setVMConfig(panel.vmconfig); } - }), - ]; - Ext.apply(me, { items: me.comboboxes }); - me.callParent(); - } + + // not a subclass, so no callParent; just do it manually + me.setRawValue(me.valueToRaw(val)); + return me.mixins.field.setValue.call(me, val); + }, + }, + ], }); Ext.define('PVE.qemu.BootOrderEdit', { - extend: 'PVE.window.Edit', + extend: 'Proxmox.window.Edit', - items: { + items: [{ xtype: 'pveQemuBootOrderPanel', itemId: 'inputpanel', - }, + }], subject: gettext('Boot Order'), + width: 640, - initComponent : function() { - var me = this; + initComponent: function() { + let me = this; me.callParent(); me.load({ - success: function(response, options) { - me.down('#inputpanel').setVMConfig(response.result.data); - } + success: ({ result }) => me.down('#inputpanel').setVMConfig(result.data), }); - } + }, });