mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-05-30 17:34:55 +00:00

The new boot order property can express many more scenarios than the old one. Update the editor so it can handle it. Features a grid with all supported boot devices which can be reordered using drag-and-drop, as well as toggled on and off with an inline checkbox. Support for configs still using the old format is given, with the first write automatically updating the VM config to use the new one. The renderer for the Options panel is updated with support for the new format. Note that it is very well possible to disable all boot devices, in which case an empty 'boot: ' will be stored to the config file. I'm not sure what that would be useful for, but there's no reason to forbid it either, just warn the user that it's probably not what they want. Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
401 lines
10 KiB
JavaScript
401 lines
10 KiB
JavaScript
Ext.define('PVE.qemu.Options', {
|
|
extend: 'Proxmox.grid.PendingObjectGrid',
|
|
alias: ['widget.PVE.qemu.Options'],
|
|
|
|
onlineHelp: 'qm_options',
|
|
|
|
initComponent : function() {
|
|
var me = this;
|
|
var i;
|
|
|
|
var nodename = me.pveSelNode.data.node;
|
|
if (!nodename) {
|
|
throw "no node name specified";
|
|
}
|
|
|
|
var vmid = me.pveSelNode.data.vmid;
|
|
if (!vmid) {
|
|
throw "no VM ID specified";
|
|
}
|
|
|
|
var caps = Ext.state.Manager.get('GuiCap');
|
|
|
|
var rows = {
|
|
name: {
|
|
required: true,
|
|
defaultValue: me.pveSelNode.data.name,
|
|
header: gettext('Name'),
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Name'),
|
|
items: {
|
|
xtype: 'inputpanel',
|
|
items:{
|
|
xtype: 'textfield',
|
|
name: 'name',
|
|
vtype: 'DnsName',
|
|
value: '',
|
|
fieldLabel: gettext('Name'),
|
|
allowBlank: true
|
|
},
|
|
onGetValues: function(values) {
|
|
var params = values;
|
|
if (values.name === undefined ||
|
|
values.name === null ||
|
|
values.name === '') {
|
|
params = { 'delete':'name'};
|
|
}
|
|
return params;
|
|
}
|
|
}
|
|
} : undefined
|
|
},
|
|
onboot: {
|
|
header: gettext('Start at boot'),
|
|
defaultValue: '',
|
|
renderer: Proxmox.Utils.format_boolean,
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Start at boot'),
|
|
items: {
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'onboot',
|
|
uncheckedValue: 0,
|
|
defaultValue: 0,
|
|
deleteDefaultValue: true,
|
|
fieldLabel: gettext('Start at boot')
|
|
}
|
|
} : undefined
|
|
},
|
|
startup: {
|
|
header: gettext('Start/Shutdown order'),
|
|
defaultValue: '',
|
|
renderer: PVE.Utils.render_kvm_startup,
|
|
editor: caps.vms['VM.Config.Options'] && caps.nodes['Sys.Modify'] ?
|
|
{
|
|
xtype: 'pveWindowStartupEdit',
|
|
onlineHelp: 'qm_startup_and_shutdown'
|
|
} : undefined
|
|
},
|
|
ostype: {
|
|
header: gettext('OS Type'),
|
|
editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.OSTypeEdit' : undefined,
|
|
renderer: PVE.Utils.render_kvm_ostype,
|
|
defaultValue: 'other'
|
|
},
|
|
bootdisk: {
|
|
visible: false
|
|
},
|
|
boot: {
|
|
header: gettext('Boot Order'),
|
|
defaultValue: 'cdn',
|
|
editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.BootOrderEdit' : undefined,
|
|
multiKey: ['boot', 'bootdisk'],
|
|
renderer: function(order, metaData, record, rowIndex, colIndex, store, pending) {
|
|
if (/^\s*$/.test(order)) {
|
|
return gettext('(No boot device selected)');
|
|
}
|
|
let boot = PVE.Parser.parsePropertyString(order, "legacy");
|
|
if (boot.order) {
|
|
let list = boot.order.split(';');
|
|
let ret = '';
|
|
let i = 1;
|
|
list.forEach(dev => {
|
|
if (ret) {
|
|
ret += ', ';
|
|
}
|
|
ret += dev;
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
// legacy style and fallback
|
|
var i;
|
|
var text = '';
|
|
var bootdisk = me.getObjectValue('bootdisk', undefined, pending);
|
|
order = boot.legacy || 'cdn';
|
|
for (i = 0; i < order.length; i++) {
|
|
if (text) {
|
|
text += ', ';
|
|
}
|
|
var sel = order.substring(i, i + 1);
|
|
if (sel === 'c') {
|
|
if (bootdisk) {
|
|
text += bootdisk;
|
|
} else {
|
|
text += gettext('(no bootdisk)');
|
|
}
|
|
} else if (sel === 'n') {
|
|
text += gettext('any net');
|
|
} else if (sel === 'a') {
|
|
text += gettext('Floppy');
|
|
} else if (sel === 'd') {
|
|
text += gettext('any CD-ROM');
|
|
} else {
|
|
text += sel;
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
},
|
|
tablet: {
|
|
header: gettext('Use tablet for pointer'),
|
|
defaultValue: true,
|
|
renderer: Proxmox.Utils.format_boolean,
|
|
editor: caps.vms['VM.Config.HWType'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Use tablet for pointer'),
|
|
items: {
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'tablet',
|
|
checked: true,
|
|
uncheckedValue: 0,
|
|
defaultValue: 1,
|
|
deleteDefaultValue: true,
|
|
fieldLabel: gettext('Enabled')
|
|
}
|
|
} : undefined
|
|
},
|
|
hotplug: {
|
|
header: gettext('Hotplug'),
|
|
defaultValue: 'disk,network,usb',
|
|
renderer: PVE.Utils.render_hotplug_features,
|
|
editor: caps.vms['VM.Config.HWType'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Hotplug'),
|
|
items: {
|
|
xtype: 'pveHotplugFeatureSelector',
|
|
name: 'hotplug',
|
|
value: '',
|
|
multiSelect: true,
|
|
fieldLabel: gettext('Hotplug'),
|
|
allowBlank: true
|
|
}
|
|
} : undefined
|
|
},
|
|
acpi: {
|
|
header: gettext('ACPI support'),
|
|
defaultValue: true,
|
|
renderer: Proxmox.Utils.format_boolean,
|
|
editor: caps.vms['VM.Config.HWType'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('ACPI support'),
|
|
items: {
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'acpi',
|
|
checked: true,
|
|
uncheckedValue: 0,
|
|
defaultValue: 1,
|
|
deleteDefaultValue: true,
|
|
fieldLabel: gettext('Enabled')
|
|
}
|
|
} : undefined
|
|
},
|
|
kvm: {
|
|
header: gettext('KVM hardware virtualization'),
|
|
defaultValue: true,
|
|
renderer: Proxmox.Utils.format_boolean,
|
|
editor: caps.vms['VM.Config.HWType'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('KVM hardware virtualization'),
|
|
items: {
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'kvm',
|
|
checked: true,
|
|
uncheckedValue: 0,
|
|
defaultValue: 1,
|
|
deleteDefaultValue: true,
|
|
fieldLabel: gettext('Enabled')
|
|
}
|
|
} : undefined
|
|
},
|
|
freeze: {
|
|
header: gettext('Freeze CPU at startup'),
|
|
defaultValue: false,
|
|
renderer: Proxmox.Utils.format_boolean,
|
|
editor: caps.vms['VM.PowerMgmt'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Freeze CPU at startup'),
|
|
items: {
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'freeze',
|
|
uncheckedValue: 0,
|
|
defaultValue: 0,
|
|
deleteDefaultValue: true,
|
|
labelWidth: 140,
|
|
fieldLabel: gettext('Freeze CPU at startup')
|
|
}
|
|
} : undefined
|
|
},
|
|
localtime: {
|
|
header: gettext('Use local time for RTC'),
|
|
defaultValue: '__default__',
|
|
renderer: PVE.Utils.render_localtime,
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Use local time for RTC'),
|
|
width: 400,
|
|
items: {
|
|
xtype: 'proxmoxKVComboBox',
|
|
name: 'localtime',
|
|
value: '__default__',
|
|
comboItems: [
|
|
['__default__', PVE.Utils.render_localtime('__default__')],
|
|
[1, PVE.Utils.render_localtime(1)],
|
|
[0, PVE.Utils.render_localtime(0)],
|
|
],
|
|
labelWidth: 140,
|
|
fieldLabel: gettext('Use local time for RTC')
|
|
}
|
|
} : undefined
|
|
},
|
|
startdate: {
|
|
header: gettext('RTC start date'),
|
|
defaultValue: 'now',
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('RTC start date'),
|
|
items: {
|
|
xtype: 'proxmoxtextfield',
|
|
name: 'startdate',
|
|
deleteEmpty: true,
|
|
value: 'now',
|
|
fieldLabel: gettext('RTC start date'),
|
|
vtype: 'QemuStartDate',
|
|
allowBlank: true
|
|
}
|
|
} : undefined
|
|
},
|
|
smbios1: {
|
|
header: gettext('SMBIOS settings (type1)'),
|
|
defaultValue: '',
|
|
renderer: Ext.String.htmlEncode,
|
|
editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.Smbios1Edit' : undefined
|
|
},
|
|
agent: {
|
|
header: 'QEMU Guest Agent',
|
|
defaultValue: false,
|
|
renderer: PVE.Utils.render_qga_features,
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Qemu Agent'),
|
|
width: 350,
|
|
items: {
|
|
xtype: 'pveAgentFeatureSelector',
|
|
name: 'agent'
|
|
}
|
|
} : undefined
|
|
},
|
|
protection: {
|
|
header: gettext('Protection'),
|
|
defaultValue: false,
|
|
renderer: Proxmox.Utils.format_boolean,
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Protection'),
|
|
items: {
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'protection',
|
|
uncheckedValue: 0,
|
|
defaultValue: 0,
|
|
deleteDefaultValue: true,
|
|
fieldLabel: gettext('Enabled')
|
|
}
|
|
} : undefined
|
|
},
|
|
spice_enhancements: {
|
|
header: gettext('Spice Enhancements'),
|
|
defaultValue: false,
|
|
renderer: PVE.Utils.render_spice_enhancements,
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('Spice Enhancements'),
|
|
onlineHelp: 'qm_spice_enhancements',
|
|
items: {
|
|
xtype: 'pveSpiceEnhancementSelector',
|
|
name: 'spice_enhancements',
|
|
}
|
|
} : undefined
|
|
},
|
|
vmstatestorage: {
|
|
header: gettext('VM State storage'),
|
|
defaultValue: '',
|
|
renderer: val => val || gettext('Automatic'),
|
|
editor: caps.vms['VM.Config.Options'] ? {
|
|
xtype: 'proxmoxWindowEdit',
|
|
subject: gettext('VM State storage'),
|
|
onlineHelp: 'chapter_virtual_machines', // FIXME: use 'qm_vmstatestorage' once available
|
|
width: 350,
|
|
items: {
|
|
xtype: 'pveStorageSelector',
|
|
storageContent: 'images',
|
|
allowBlank: true,
|
|
emptyText: gettext("Automatic (Storage used by the VM, or 'local')"),
|
|
autoSelect: false,
|
|
deleteEmpty: true,
|
|
skipEmptyText: true,
|
|
nodename: nodename,
|
|
name: 'vmstatestorage',
|
|
}
|
|
} : undefined
|
|
},
|
|
hookscript: {
|
|
header: gettext('Hookscript')
|
|
}
|
|
};
|
|
|
|
var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
|
|
|
|
var edit_btn = new Ext.Button({
|
|
text: gettext('Edit'),
|
|
disabled: true,
|
|
handler: function() { me.run_editor(); }
|
|
});
|
|
|
|
var revert_btn = new PVE.button.PendingRevert();
|
|
|
|
var set_button_status = function() {
|
|
var sm = me.getSelectionModel();
|
|
var rec = sm.getSelection()[0];
|
|
|
|
if (!rec) {
|
|
edit_btn.disable();
|
|
return;
|
|
}
|
|
|
|
var key = rec.data.key;
|
|
var pending = rec.data['delete'] || me.hasPendingChanges(key);
|
|
var rowdef = rows[key];
|
|
|
|
edit_btn.setDisabled(!rowdef.editor);
|
|
revert_btn.setDisabled(!pending);
|
|
};
|
|
|
|
Ext.apply(me, {
|
|
url: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/pending",
|
|
interval: 5000,
|
|
cwidth1: 250,
|
|
tbar: [ edit_btn, revert_btn ],
|
|
rows: rows,
|
|
editorConfig: {
|
|
url: "/api2/extjs/" + baseurl
|
|
},
|
|
listeners: {
|
|
itemdblclick: me.run_editor,
|
|
selectionchange: set_button_status
|
|
}
|
|
});
|
|
|
|
me.callParent();
|
|
|
|
me.on('activate', () => me.rstore.startUpdate());
|
|
me.on('destroy', () => me.rstore.stopUpdate());
|
|
me.on('deactivate', () => me.rstore.stopUpdate());
|
|
|
|
me.mon(me.getStore(), 'datachanged', function() {
|
|
set_button_status();
|
|
});
|
|
}
|
|
});
|
|
|