From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 763F01FF13F for ; Thu, 12 Mar 2026 09:40:39 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 406879434; Thu, 12 Mar 2026 09:40:29 +0100 (CET) From: Arthur Bied-Charreton To: pve-devel@lists.proxmox.com Subject: [PATCH pve-manager 5/8] ui: Add CPU flag editor for custom models Date: Thu, 12 Mar 2026 09:40:18 +0100 Message-ID: <20260312084021.124465-6-a.bied-charreton@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260312084021.124465-1-a.bied-charreton@proxmox.com> References: <20260312084021.124465-1-a.bied-charreton@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.101 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: VJC64DJFFH35VO6AVZNPLQJTHAPV7EKF X-Message-ID-Hash: VJC64DJFFH35VO6AVZNPLQJTHAPV7EKF X-MailFrom: abied-charreton@jett.proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Stefan Reiter X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add CPU flag editor to the CPUTypeEdit component, using the VMCPUFlagSelector also used in the VM creation flow. By default, only show the CPU flags that are currently meant to be shown in the VM creation window, see [0]. When in CPUTypeEdit, show all available flags. For each flag in VMCPUFlagSelector, also display which node(s) it is available on to limit misconfigurations. Original patch: https://lore.proxmox.com/pve-devel/20211028114150.3245864-10-s.reiter@proxmox.com [0] https://git.proxmox.com/?p=qemu-server.git;a=blob;f=src/PVE/QemuServer/CPUConfig.pm;h=32ec495422791422f20caa928d6b632b3de4fcd9;hb=refs/heads/master#l349 Originally-by: Stefan Reiter Signed-off-by: Arthur Bied-Charreton --- www/manager6/dc/CPUTypeEdit.js | 11 ++- www/manager6/form/CPUModelSelector.js | 1 + www/manager6/form/VMCPUFlagSelector.js | 121 ++++++++++++++++++++++--- 3 files changed, 117 insertions(+), 16 deletions(-) diff --git a/www/manager6/dc/CPUTypeEdit.js b/www/manager6/dc/CPUTypeEdit.js index 8cf508b4..d6e06601 100644 --- a/www/manager6/dc/CPUTypeEdit.js +++ b/www/manager6/dc/CPUTypeEdit.js @@ -84,7 +84,16 @@ Ext.define('PVE.dc.CPUTypeEdit', { name: 'phys-bits', }, ], - + columnB: [ + { + xtype: 'vmcpuflagselector', + fieldLabel: gettext('Extra CPU flags'), + name: 'flags', + restrictToVMFlags: false, + height: 380, + hideHeaders: false, + }, + ], }, ], }); diff --git a/www/manager6/form/CPUModelSelector.js b/www/manager6/form/CPUModelSelector.js index 2154ff46..683fa469 100644 --- a/www/manager6/form/CPUModelSelector.js +++ b/www/manager6/form/CPUModelSelector.js @@ -17,6 +17,7 @@ Ext.define('PVE.form.CPUModelSelector', { anyMatch: true, forceSelection: true, autoSelect: false, + triggerAction: 'query', deleteEmpty: true, config: { diff --git a/www/manager6/form/VMCPUFlagSelector.js b/www/manager6/form/VMCPUFlagSelector.js index 74b1a2c4..06c9d9f1 100644 --- a/www/manager6/form/VMCPUFlagSelector.js +++ b/www/manager6/form/VMCPUFlagSelector.js @@ -1,3 +1,19 @@ +const VM_CPU_FLAGS_SUBSET = { + aes: true, + 'amd-no-ssb': true, + 'amd-ssbd': true, + 'hv-evmcs': true, + 'hv-tlbflush': true, + ibpb: true, + 'md-clear': true, + 'nested-virt': true, + pcid: true, + pdpe1gb: true, + 'spec-ctrl': true, + ssbd: true, + 'virt-ssbd': true, +}; + Ext.define('PVE.form.VMCPUFlagSelector', { extend: 'Ext.grid.Panel', alias: 'widget.vmcpuflagselector', @@ -6,6 +22,10 @@ Ext.define('PVE.form.VMCPUFlagSelector', { field: 'Ext.form.field.Field', }, + config: { + restrictToVMFlags: true, + }, + disableSelection: true, columnLines: false, selectable: false, @@ -17,27 +37,18 @@ Ext.define('PVE.form.VMCPUFlagSelector', { unknownFlags: [], store: { - type: 'store', - fields: ['name', { name: 'state', defaultValue: '=' }, 'description'], - autoLoad: true, + fields: ['name', { name: 'state', defaultValue: '=' }, 'description', 'supported-on'], + autoLoad: false, proxy: { type: 'proxmox', url: '/api2/json/nodes/localhost/capabilities/qemu/cpu-flags', + extraParams: { accel: 'kvm' }, }, listeners: { update: function () { this.commitChanges(); }, - refresh: function (store, eOpts) { - let me = this; - let view = me.view; - - if (store.adjustedForValue !== view.value) { - view.adjustStoreForValue(); - } - }, }, - adjustedForValue: undefined, }, getValue: function () { @@ -86,14 +97,18 @@ Ext.define('PVE.form.VMCPUFlagSelector', { let rec = store.findRecord('name', flagName, 0, false, true, true); if (rec !== null) { rec.set('state', sign); + rec.commit(); } else { me.unknownFlags.push(flag); } }); - store.adjustedForValue = value; + me.getView().refresh(); + }, + isDirty: function () { + let me = this; + return me.originalValue !== me.getValue(); }, - setValue: function (value) { let me = this; @@ -109,6 +124,7 @@ Ext.define('PVE.form.VMCPUFlagSelector', { }, columns: [ { + text: gettext('State'), dataIndex: 'state', renderer: function (v) { switch (v) { @@ -125,6 +141,7 @@ Ext.define('PVE.form.VMCPUFlagSelector', { width: 65, }, { + text: gettext('Set'), xtype: 'widgetcolumn', dataIndex: 'state', width: 95, @@ -171,22 +188,96 @@ Ext.define('PVE.form.VMCPUFlagSelector', { }, }, { + text: gettext('Flag'), dataIndex: 'name', width: 100, }, { + text: gettext('Description'), dataIndex: 'description', + sortable: false, + cellWrap: true, + flex: 3, + }, + { + text: gettext('Supported On'), + dataIndex: 'supported-on', cellWrap: true, flex: 1, + renderer: (v) => (Array.isArray(v) ? v.join(', ') : ''), }, ], initComponent: function () { let me = this; + me.unknownFlags = []; me.value = me.originalValue = ''; - me.store.view = me; + + me.dockedItems = [ + { + xtype: 'toolbar', + dock: 'top', + items: [ + { + xtype: 'tbtext', + text: gettext('Acceleration:'), + autoEl: { + tag: 'span', + 'data-qtip': gettext( + 'A custom CPU model using acceleration-specific flags should only be assigned to VMs configured with the matching acceleration type, i.e., `kvm: off` for TCG, or `kvm: on` for KVM.', + ), + }, + }, + { + xtype: 'radiogroup', + layout: 'hbox', + validateOnChange: false, + items: [ + { + boxLabel: 'KVM', + inputValue: 'kvm', + name: 'accel', + checked: true, + isFormField: false, + }, + { + boxLabel: 'TCG', + inputValue: 'tcg', + name: 'accel', + margin: '0 0 0 10', + isFormField: false, + }, + ], + listeners: { + change: function (_, value) { + let view = this.up('grid'); + let proxy = view.getStore().getProxy(); + let accel = value.accel; + if (accel) { + proxy.setExtraParam('accel', accel); + } else { + delete proxy.extraParams.accel; + } + view.getStore().load(); + }, + }, + }, + ], + }, + ]; me.callParent(arguments); + + me.getStore().on('load', function (store, _, success) { + if (success) { + if (me.restrictToVMFlags) { + store.filterBy((rec) => VM_CPU_FLAGS_SUBSET[rec.get('name')] === true); + } + me.adjustStoreForValue(); + me.checkDirty(); + } + }); + me.getStore().load(); }, }); -- 2.47.3