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)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 7BBE6953D5 for ; Wed, 1 Mar 2023 10:12:48 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5D52EFBC7 for ; Wed, 1 Mar 2023 10:12:48 +0100 (CET) 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)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 1 Mar 2023 10:12:47 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 0D01048D55 for ; Wed, 1 Mar 2023 10:12:47 +0100 (CET) From: Leo Nunner To: pve-devel@lists.proxmox.com Date: Wed, 1 Mar 2023 10:12:29 +0100 Message-Id: <20230301091229.53505-5-l.nunner@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230301091229.53505-1-l.nunner@proxmox.com> References: <20230301091229.53505-1-l.nunner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.131 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. [view.store] Subject: [pve-devel] [PATCH manager] fix #4068: expose fw_cfg through the GUI 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: Wed, 01 Mar 2023 09:12:48 -0000 Introduces a new UI element to set/edit/delete any number of fw_cfg arguments in a table-like manner. Signed-off-by: Leo Nunner --- www/manager6/Makefile | 1 + www/manager6/qemu/FirmwareCfgEdit.js | 224 +++++++++++++++++++++++++++ www/manager6/qemu/Options.js | 6 + 3 files changed, 231 insertions(+) create mode 100644 www/manager6/qemu/FirmwareCfgEdit.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 05afeda4..bf3c8b20 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -217,6 +217,7 @@ JSSRC= \ qemu/Config.js \ qemu/CreateWizard.js \ qemu/DisplayEdit.js \ + qemu/FirmwareCfgEdit.js \ qemu/HDEdit.js \ qemu/HDEfi.js \ qemu/HDTPM.js \ diff --git a/www/manager6/qemu/FirmwareCfgEdit.js b/www/manager6/qemu/FirmwareCfgEdit.js new file mode 100644 index 00000000..031042e3 --- /dev/null +++ b/www/manager6/qemu/FirmwareCfgEdit.js @@ -0,0 +1,224 @@ +Ext.define('pve-fw-cfg-option', { + extend: 'Ext.data.Model', + fields: [ + { name: 'opt', type: 'string' }, + { name: 'val', type: 'string' }, + ], +}); + +Ext.define('PVE.qemu.FirmwareCfgPanel', { + extend: 'Ext.form.FieldContainer', + alias: 'widget.pveQemuFirmwareCfgPanel', + + config: {}, // store loaded vm config + store: undefined, + + inUpdate: false, + controller: { + xclass: 'Ext.app.ViewController', + + init: function(view) { + let me = this; + let grid = me.lookup('grid'); + + view.store = Ext.create('Ext.data.Store', { + model: 'pve-fw-cfg-option', + }); + + grid.setStore(view.store); + }, + + addOption: function() { + let me = this; + me.lookup('grid').getStore().add({}); + }, + + removeOption: function(field) { + let me = this; + let record = field.getWidgetRecord(); + me.lookup('grid').getStore().remove(record); + me.setMarkerValue(); + }, + + onUpdate: function(record, property, value) { + let me = this; + + if (record === undefined) { + return; + } + + record.set(property, value); + record.commit(); + me.setMarkerValue(); + }, + + onUpdateOption: function(field, value) { + let me = this; + let record = field.getWidgetRecord(); + + me.onUpdate(record, "opt", value); + }, + + onUpdateValue: function(field, value) { + let me = this; + let record = field.getWidgetRecord(); + + me.onUpdate(record, "val", value); + }, + + setMarkerValue() { + let me = this; + let view = me.getView(); + + view.inUpdate = true; + me.lookup('marker').setValue(view.calculateValue()); + view.inUpdate = false; + }, + + control: { + "grid textfield[dest=opt]": { + change: "onUpdateOption", + }, + "grid textfield[dest=val]": { + change: "onUpdateValue", + }, + }, + }, + + loadConfig: function(config) { + let me = this; + let marker = me.lookup('marker'); + let list = PVE.Parser.parsePropertyString(config.fw_cfg); + let options = []; + + me.config = config; + me.store.removeAll(); + + for (const [key, value] of Object.entries(list)) { + options.push({ + opt: key, + val: value, + }); + } + + marker.originalValue = config.fw_cfg; + marker.value = config.fw_cfg; + me.store.setData(options); + }, + + calculateValue: function() { + let me = this; + let ret = []; + me.store.each((record) => { + ret.push(record.data.opt + "=" + record.data.val); + }); + return ret.join(","); + }, + + items: [ + { + xtype: 'grid', + reference: 'grid', + margin: '0 0 5 0', + height: 150, + defaults: { + sortable: false, + hideable: false, + draggable: false, + }, + columns: [ + { + xtype: 'widgetcolumn', + text: gettext('Option'), + dataIndex: 'opt', + flex: 1, + isFormField: false, + widget: { + xtype: 'textfield', + allowBlank: false, + dest: 'opt', + emptyText: 'opt/...', + }, + }, + { + xtype: 'widgetcolumn', + text: gettext('Value'), + dataIndex: 'val', + flex: 1, + isFormField: false, + widget: { + xtype: 'textfield', + allowBlank: false, + dest: 'val', + }, + }, + { + xtype: 'widgetcolumn', + width: 40, + widget: { + xtype: 'button', + iconCls: 'fa fa-trash-o', + handler: 'removeOption', + }, + }, + ], + }, + { + // for dirty marking and 'reset' function + xtype: 'hiddenfield', + reference: 'marker', + name: 'fw_cfg', + setValue: function(value) { + let me = this; + let panel = me.up('pveQemuFirmwareCfgPanel'); + + // Reset + if (!panel.inUpdate) { + panel.loadConfig(panel.config); + } + + me.value = value; + me.checkDirty(); + }, + getValue: function() { + return this.value; + }, + getSubmitValue: function() { + return this.value; + }, + }, + { + xtype: 'button', + text: gettext('Add'), + iconCls: 'fa fa-plus-circle', + handler: 'addOption', + }, + ], +}); + +Ext.define('PVE.qemu.FirmwareCfgEdit', { + extend: 'Proxmox.window.Edit', + + items: [{ + xtype: 'pveQemuFirmwareCfgPanel', + itemId: 'inputpanel', + }], + + subject: gettext('Firmware Configuration'), + onlineHelp: 'qm_fw_cfg', + width: 640, + + getValues: function() { + let me = this; + let values = me.callParent(); + return values.fw_cfg ? values : { 'delete': 'fw_cfg' }; + }, + + initComponent: function() { + let me = this; + me.callParent(); + me.load({ + success: ({ result }) => me.down('#inputpanel').loadConfig(result.data), + }); + }, +}); diff --git a/www/manager6/qemu/Options.js b/www/manager6/qemu/Options.js index 7b112400..80407010 100644 --- a/www/manager6/qemu/Options.js +++ b/www/manager6/qemu/Options.js @@ -338,6 +338,12 @@ Ext.define('PVE.qemu.Options', { }, } : undefined, }, + fw_cfg: { + header: gettext('QEMU Firmware Configuration'), + defaultValue: '', + renderer: val => val || Proxmox.Utils.noneText, + editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.FirmwareCfgEdit' : undefined, + }, hookscript: { header: gettext('Hookscript'), }, -- 2.30.2