-Ext.define('PVE.qemu.BootOrderPanel', {
- extend: 'PVE.panel.InputPanel',
-
- vmconfig: {}, // store loaded vm config
+Ext.define('pve-boot-order-entry', {
+ extend: 'Ext.data.Model',
+ fields: [
+ { name: 'name', type: 'string' },
+ { name: 'enabled', type: 'bool' },
+ { name: 'desc', type: 'string' },
+ ],
+});
- bootdisk: undefined,
- curSel1: '',
- curSel2: '',
- curSel3: '',
+Ext.define('PVE.qemu.BootOrderPanel', {
+ extend: 'Proxmox.panel.InputPanel',
+ alias: 'widget.pveQemuBootOrderPanel',
- onGetValues: function(values) {
- var me = this;
+ onlineHelp: 'qm_bootorder',
- var order = '';
+ vmconfig: {}, // store loaded vm config
+ store: undefined,
+
+ inUpdate: false,
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ init: function(view) {
+ let me = this;
+
+ let grid = me.lookup('grid');
+ let marker = me.lookup('marker');
+ let emptyWarning = me.lookup('emptyWarning');
+
+ marker.originalValue = undefined;
+
+ 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);
+ },
+ },
- if (me.curSel1) {
- order = order + me.curSel1;
- }
- if (me.curSel2) {
- order = order + me.curSel2;
- }
- if (me.curSel3) {
- order = order + me.curSel3;
- }
+ isCloudinit: (v) => v.match(/media=cdrom/) && v.match(/[:/]vm-\d+-cloudinit/),
- var res = { boot: order };
- if (me.bootdisk && (me.curSel1 === 'c' || me.curSel2 === 'c' || me.curSel3 === 'c') ) {
- 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));
},
setVMConfig: function(vmconfig) {
- var me = this;
-
+ let me = this;
me.vmconfig = vmconfig;
- var order = me.vmconfig.boot || 'cdn';
- me.bootdisk = me.vmconfig.bootdisk;
- if (!me.vmconfig[me.bootdisk]) {
- me.bootdisk = undefined;
- }
- me.curSel1 = order.substring(0, 1) || '';
- me.curSel2 = order.substring(1, 2) || '';
- me.curSel3 = order.substring(2, 3) || '';
-
- me.compute_sel1();
-
- me.kv1.resetOriginalValue();
- me.kv2.resetOriginalValue();
- me.kv3.resetOriginalValue();
- },
-
- genList: function(includeNone, sel1, sel2) {
- var me = this;
- var list = [];
-
- if (sel1 !== 'c' && (sel2 !== 'c')) {
- Ext.Object.each(me.vmconfig, function(key, value) {
- if ((/^(ide|sata|scsi|virtio)\d+$/).test(key) &&
- !(/media=cdrom/).test(value)) {
- list.push([key, "Disk '" + key + "'"]);
+ 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);
+ }
+ });
}
- });
- }
- if (sel1 !== 'd' && (sel2 !== 'd')) {
- list.push(['d', 'CD-ROM']);
- }
- if (sel1 !== 'n' && (sel2 !== 'n')) {
- list.push(['n', gettext('Network')]);
- }
- //if (sel1 !== 'a' && (sel2 !== 'a')) {
- // list.push(['a', 'Floppy']);
- //}
-
- if (includeNone) {
- list.push(['', PVE.Utils.noneText]);
+ // Object.each iterates in random order, sort alphabetically
+ list.sort();
+ list.forEach(dev => bootorder.push({ name: dev, enabled: true }));
+ }
}
- return list;
- },
-
- compute_sel3: function() {
- var me = this;
- var list = me.genList(true, me.curSel1, me.curSel2);
- me.kv3.store.loadData(list);
- me.kv3.setValue((me.curSel3 === 'c') ? me.bootdisk : me.curSel3);
- },
-
- compute_sel2: function() {
- var me = this;
- var list = me.genList(true, me.curSel1);
- me.kv2.store.loadData(list);
- me.kv2.setValue((me.curSel2 === 'c') ? me.bootdisk : me.curSel2);
- me.compute_sel3();
- },
-
- compute_sel1: function() {
- var me = this;
- var list = me.genList(false);
- me.kv1.store.loadData(list);
- me.kv1.setValue((me.curSel1 === 'c') ? me.bootdisk : me.curSel1);
- me.compute_sel2();
- },
-
- initComponent : function() {
- var me = this;
-
- me.kv1 = Ext.create('PVE.form.KVComboBox', {
- fieldLabel: gettext('Boot device') + " 1",
- labelWidth: 120,
- name: 'bd1',
- allowBlank: false,
- data: []
+ // 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 }));
- me.kv2 = Ext.create('PVE.form.KVComboBox', {
- fieldLabel: gettext('Boot device') + " 2",
- labelWidth: 120,
- name: 'bd2',
- allowBlank: false,
- data: []
+ // add descriptions
+ bootorder.forEach(entry => {
+ entry.desc = me.vmconfig[entry.name];
});
- me.kv3 = Ext.create('PVE.form.KVComboBox', {
- fieldLabel: gettext('Boot device') + " 3",
- labelWidth: 120,
- name: 'bd3',
- allowBlank: false,
- data: []
- });
+ me.store.insert(0, bootorder);
+ me.store.fireEvent("update");
+ },
- me.mon(me.kv1, 'change', function(t, value) {
- if ((/^(ide|sata|scsi|virtio)\d+$/).test(value)) {
- me.curSel1 = 'c';
- me.bootdisk = value;
- } else {
- me.curSel1 = value;
- }
- me.compute_sel2();
- });
+ calculateValue: function() {
+ let me = this;
+ return me.store.getData().items
+ .filter(x => x.data.enabled)
+ .map(x => x.data.name)
+ .join(';');
+ },
- me.mon(me.kv2, 'change', function(t, value) {
- if ((/^(ide|sata|scsi|virtio)\d+$/).test(value)) {
- me.curSel2 = 'c';
- me.bootdisk = value;
- } else {
- me.curSel2 = value;
- }
- me.compute_sel3();
- });
+ 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;
+ },
- me.mon(me.kv3, 'change', function(t, value) {
- if ((/^(ide|sata|scsi|virtio)\d+$/).test(value)) {
- me.curSel3 = 'c';
- me.bootdisk = value;
- } else {
- me.curSel3 = value;
- }
- });
+ 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 = "<i class='pve-grid-fa fa fa-fw fa-reorder cursor-move'></i>";
+ let idx = (rowIndex + 1).toString();
+ if (record.get('enabled')) {
+ return dragHandle + idx;
+ } else {
+ return dragHandle + "<span class='faded'>" + idx + "</span>";
+ }
+ },
+ },
+ {
+ 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 = `<i class="pve-grid-fa fa fa-fw fa-${iconCls}"></i>`;
+ }
+
+ 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.kv1, me.kv2, me.kv3 ]
- });
-
- 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',
- initComponent : function() {
- var me = this;
-
- var ipanel = Ext.create('PVE.qemu.BootOrderPanel', {});
+ items: [{
+ xtype: 'pveQemuBootOrderPanel',
+ itemId: 'inputpanel',
+ }],
- me.items = [ ipanel ];
-
- me.subject = gettext('Boot order');
+ subject: gettext('Boot Order'),
+ width: 640,
+ initComponent: function() {
+ let me = this;
me.callParent();
-
me.load({
- success: function(response, options) {
- ipanel.setVMConfig(response.result.data);
- }
+ success: ({ result }) => me.down('#inputpanel').setVMConfig(result.data),
});
- }
+ },
});