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 CD42879F67 for ; Thu, 28 Oct 2021 13:42:04 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B37FD1FAB5 for ; Thu, 28 Oct 2021 13:42:04 +0200 (CEST) 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 id DE6B11F95B for ; Thu, 28 Oct 2021 13:41:58 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id B79DD45FDF for ; Thu, 28 Oct 2021 13:41:58 +0200 (CEST) From: Stefan Reiter To: pve-devel@lists.proxmox.com Date: Thu, 28 Oct 2021 13:41:49 +0200 Message-Id: <20211028114150.3245864-9-s.reiter@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20211028114150.3245864-1-s.reiter@proxmox.com> References: <20211028114150.3245864-1-s.reiter@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.438 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 Subject: [pve-devel] [RFC manager 8/9] gui: add basic custom CPU model editor 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: Thu, 28 Oct 2021 11:42:04 -0000 Supports viewing, deleting, adding and editing existing custom CPU models. All properties except CPU flags are supported in the editor. A new control for selecting between different Phys-Bits (default, host, custom number) is added. This also seems to be the first use of a non-fa icon in the sidebar, so add a new class that correctly aligns pve-itype-icon-* elements. Signed-off-by: Stefan Reiter --- www/css/ext6-pve.css | 4 + www/manager6/Makefile | 3 + www/manager6/dc/CPUTypeEdit.js | 81 ++++++++++++++ www/manager6/dc/CPUTypeView.js | 148 ++++++++++++++++++++++++++ www/manager6/dc/Config.js | 6 ++ www/manager6/form/PhysBitsSelector.js | 128 ++++++++++++++++++++++ 6 files changed, 370 insertions(+) create mode 100644 www/manager6/dc/CPUTypeEdit.js create mode 100644 www/manager6/dc/CPUTypeView.js create mode 100644 www/manager6/form/PhysBitsSelector.js diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css index e5d792f8..bb9bd8c0 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -447,6 +447,10 @@ padding: 0; } +.pve-icon-sidebar { + margin-top: 5px; +} + /* displayfield minheight is wrong */ .x-form-display-field-default { min-height: 20px; diff --git a/www/manager6/Makefile b/www/manager6/Makefile index e5e85aed..abca0a05 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -48,6 +48,7 @@ JSSRC= \ form/NodeSelector.js \ form/PCISelector.js \ form/PermPathSelector.js \ + form/PhysBitsSelector.js \ form/PoolSelector.js \ form/PreallocationSelector.js \ form/PrivilegesSelector.js \ @@ -131,6 +132,8 @@ JSSRC= \ dc/ClusterEdit.js \ dc/Config.js \ dc/CorosyncLinkEdit.js \ + dc/CPUTypeView.js \ + dc/CPUTypeEdit.js \ dc/GroupEdit.js \ dc/GroupView.js \ dc/Guests.js \ diff --git a/www/manager6/dc/CPUTypeEdit.js b/www/manager6/dc/CPUTypeEdit.js new file mode 100644 index 00000000..3ad527c4 --- /dev/null +++ b/www/manager6/dc/CPUTypeEdit.js @@ -0,0 +1,81 @@ +Ext.define('PVE.dc.CPUTypeEdit', { + extend: 'Proxmox.window.Edit', + alias: ['widget.pveCpuTypeEdit'], + mixins: ['Proxmox.Mixin.CBind'], + + subject: gettext('CPU Type'), + + cbindData: { + cputype: '', + isCreate: (cfg) => !cfg.cputype, + }, + + cbind: { + autoLoad: get => !get('isCreate'), + url: get => `/api2/extjs/nodes/localhost/capabilities/qemu/cpu/model/${get('cputype')}`, + method: get => get('isCreate') ? 'POST' : 'PUT', + isCreate: get => get('isCreate'), + }, + + getValues: function() { + let me = this; + let values = me.callParent(); + + PVE.Utils.delete_if_default(values, 'reported-model', '', me.isCreate); + PVE.Utils.delete_if_default(values, 'hv-vendor-id', '', me.isCreate); + PVE.Utils.delete_if_default(values, 'phys-bits', '', me.isCreate); + PVE.Utils.delete_if_default(values, 'flags', '', me.isCreate); + + if (me.isCreate && !values.cputype.match(/^custom-/)) { + values.cputype = 'custom-' + values.cputype; + } + + return values; + }, + + items: [ + { + xtype: 'inputpanel', + column1: [ + { + xtype: 'pmxDisplayEditField', + fieldLabel: gettext('Name'), + cbind: { + editable: '{isCreate}', + value: '{cputype}', + }, + name: 'cputype', + allowBlank: false, + }, + { + xtype: 'CPUModelSelector', + fieldLabel: gettext('Reported Model'), + allowCustom: false, + name: 'reported-model', + }, + { + xtype: 'textfield', + fieldLabel: gettext('Hyper-V Vendor'), + name: 'hv-vendor-id', + allowBlank: true, + emptyText: gettext('None'), + maxLength: 12, + }, + ], + column2: [ + { + xtype: 'checkbox', + fieldLabel: gettext('Hidden'), + name: 'hidden', + inputValue: 1, + uncheckedValue: 0, + }, + { + xtype: 'PhysBitsSelector', + fieldLabel: gettext('Phys-Bits'), + name: 'phys-bits', + }, + ], + }, + ], +}); diff --git a/www/manager6/dc/CPUTypeView.js b/www/manager6/dc/CPUTypeView.js new file mode 100644 index 00000000..0d560369 --- /dev/null +++ b/www/manager6/dc/CPUTypeView.js @@ -0,0 +1,148 @@ +Ext.define('PVE.dc.CPUTypeView', { + extend: 'Ext.grid.GridPanel', + alias: ['widget.pveCPUTypeView'], + + onlineHelp: 'qm_cpu', + + store: { + model: 'pve-custom-cpu-type', + proxy: { + type: 'proxmox', + url: "/api2/json/nodes/localhost/capabilities/qemu/cpu/model", + root: 'data.ids', + }, + autoLoad: true, + sorters: ['cputype'], + }, + + controller: { + xclass: 'Ext.app.ViewController', + + getSelection: function() { + let me = this; + let grid = me.getView(); + let selection = grid.getSelection(); + if (selection.length === 1) { + return selection[0].data; + } + return null; + }, + + showEditor: function(cputype) { + let me = this; + let param = {}; + if (cputype) { + Ext.apply(param, { cputype: cputype }); + } + let win = Ext.create('PVE.dc.CPUTypeEdit', param); + win.on('destroy', () => me.reload()); + win.show(); + }, + + onAdd: function() { + let me = this; + me.showEditor(); + }, + + onEdit: function() { + let me = this; + let selection = me.getSelection(); + me.showEditor(selection.cputype); + }, + + reload: function() { + let me = this; + let grid = me.getView(); + let store = grid.store; + store.reload(); + }, + }, + + columns: [ + { + header: 'Name', + flex: 1, + sortable: true, + dataIndex: 'cputype', + renderer: val => val.replace(/^custom-/, ''), + }, + { + header: 'Reported Model', + width: '80px', + sortable: true, + dataIndex: 'reported-model', + }, + { + header: 'Phys-Bits', + width: '40px', + sortable: true, + dataIndex: 'phys-bits', + }, + { + header: 'Hidden', + width: '40px', + sortable: true, + dataIndex: 'hidden', + }, + { + header: 'HyperV-Vendor', + width: '80px', + sortable: true, + dataIndex: 'hv-vendor-id', + }, + { + header: 'Flags', + flex: 2, + sortable: true, + dataIndex: 'flags', + }, + ], + + tbar: [ + { + text: gettext('Add'), + handler: 'onAdd', + }, + '-', + { + xtype: 'proxmoxStdRemoveButton', + baseurl: '/api2/extjs/nodes/localhost/capabilities/qemu/cpu/model/', + getRecordName: (rec) => rec.data.cputype, + getUrl: function(rec) { + let me = this; + return me.baseurl + rec.data.cputype; + }, + callback: 'reload', + }, + { + text: gettext('Edit'), + handler: 'onEdit', + }, + ], + + selModel: { + xtype: 'Ext.selection.RowModel', + }, + + listeners: { + itemdblclick: function(_, rec) { + let me = this; + me.getController().showEditor(rec.data.cputype); + }, + }, + + initComponent: function() { + let me = this; + me.callParent(); + Proxmox.Utils.monStoreErrors(me, me.store); + }, + +}, function() { + Ext.define('pve-custom-cpu-type', { + extend: 'Ext.data.Model', + fields: [ + 'cputype', 'reported-model', 'hv-vendor-id', 'flags', 'phys-bits', + { name: 'hidden', type: 'boolean' }, + ], + }); +}); diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js index 934952d9..cbaabb9e 100644 --- a/www/manager6/dc/Config.js +++ b/www/manager6/dc/Config.js @@ -70,6 +70,12 @@ Ext.define('PVE.dc.Config', { title: gettext('Replication'), itemId: 'replication', }, + { + xtype: 'pveCPUTypeView', + iconCls: 'pve-itype-icon-processor pve-icon pve-icon-sidebar', + title: gettext('CPU Types'), + itemId: 'cputypes', + }, { xtype: 'pveACLView', title: gettext('Permissions'), diff --git a/www/manager6/form/PhysBitsSelector.js b/www/manager6/form/PhysBitsSelector.js new file mode 100644 index 00000000..a18b675f --- /dev/null +++ b/www/manager6/form/PhysBitsSelector.js @@ -0,0 +1,128 @@ +Ext.define('PVE.form.PhysBitsSelector', { + extend: 'Ext.form.FieldContainer', + alias: 'widget.PhysBitsSelector', + mixins: ['Ext.form.field.Field'], + + layout: 'vbox', + initialValue: '', + originalValue: '', + + controller: { + xclass: 'Ext.app.ViewController', + + updateNumberField: function() { + let me = this; + let modeCustom = me.lookupReference('modeCustom'); + let customNum = me.lookupReference('customNum'); + + customNum.setDisabled(!modeCustom.getValue()); + me.getView().validate(); + }, + + listen: { + component: { + '*': { + change: function() { + let me = this; + me.getView().checkChange(); + }, + }, + }, + }, + }, + + getValue: function() { + let me = this; + let ctrl = me.getController(); + if (ctrl.lookupReference('modeDefault').getValue()) { + return ''; + } else if (ctrl.lookupReference('modeHost').getValue()) { + return 'host'; + } else if (ctrl.lookupReference('modeCustom').getValue()) { + return ctrl.lookupReference('customNum').getValue(); + } + return ''; // shouldn't happen + }, + + setValue: function(value) { + let me = this; + let ctrl = me.getController(); + let modeField; + + if (!value) { + modeField = ctrl.lookupReference('modeDefault'); + } else if (value === 'host') { + modeField = ctrl.lookupReference('modeHost'); + } else { + let customNum = me.lookupReference('customNum'); + customNum.setValue(value); + modeField = ctrl.lookupReference('modeCustom'); + } + + modeField.setValue(true); + me.checkChange(); + + return value; + }, + + getErrors: function() { + let me = this; + let ctrl = me.getController(); + if (ctrl.lookupReference('modeCustom').getValue()) { + return ctrl.lookupReference('customNum').getErrors(); + } + return []; + }, + + isValid: function() { + let me = this; + let ctrl = me.getController(); + return ctrl.lookupReference('customNum').isValid(); + }, + + items: [ + { + xtype: 'radiofield', + boxLabel: gettext('Default'), + inputValue: 'default', + checked: true, + reference: 'modeDefault', + isFormField: false, + }, + { + xtype: 'radiofield', + boxLabel: gettext('Host'), + inputValue: 'host', + reference: 'modeHost', + isFormField: false, + }, + { + xtype: 'fieldcontainer', + layout: 'hbox', + items: [ + { + xtype: 'radiofield', + boxLabel: gettext('Custom'), + inputValue: 'custom', + listeners: { + change: 'updateNumberField', + }, + reference: 'modeCustom', + isFormField: false, + }, + { + xtype: 'numberfield', + width: '60px', + margin: '0 0 0 10px', + minValue: 8, + maxValue: 64, + reference: 'customNum', + allowBlank: false, + isFormField: false, + disabled: true, + }, + ], + }, + ], +}); + -- 2.30.2