From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id BD4C11FF138 for ; Wed, 22 Jul 2026 10:49:46 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id BF43D21618; Wed, 22 Jul 2026 10:48:38 +0200 (CEST) From: Daniel Kral To: pve-devel@lists.proxmox.com Subject: [PATCH manager v3 11/16] ui: ha: node affinity: allow setting affinity for node affinity rules Date: Wed, 22 Jul 2026 10:48:19 +0200 Message-ID: <20260722084824.108350-12-d.kral@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260722084824.108350-1-d.kral@proxmox.com> References: <20260722084824.108350-1-d.kral@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1784710082969 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.108 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: 6TM2ZRXQ2WRVF6UZ6RKONFPHIJUIKIZM X-Message-ID-Hash: 6TM2ZRXQ2WRVF6UZ6RKONFPHIJUIKIZM X-MailFrom: d.kral@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 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Since the node affinity rules can be either 'positive' or 'negative' now, allow users to define either affinity type for the node affinity rules in the web interface as well. Since the priority field does not have any semantic value for negative node affinity rules, do not use the field for the negative affinity type. As the affinity type is switched from positive to negative and vice versa, the node selection is inverted to make the (lossy) conversion easier. The column widths are changed to minimize movement while switching between the affinity types. The inversion is not done if the selection is empty, as preferring all nodes is rather uselessly verbose and avoiding all nodes is disallowed as this rule would not satisfiable. Signed-off-by: Daniel Kral --- changes since v2: - add (positive) and (negative) hints in combobox - remove unnecessary ?? ops - lower strict !== to != in applyUseNodePriority www/manager6/ha/NodePrioritySelector.js | 65 ++++++++++++++++++- www/manager6/ha/rules/NodeAffinityRuleEdit.js | 25 +++++++ www/manager6/ha/rules/NodeAffinityRules.js | 5 ++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/www/manager6/ha/NodePrioritySelector.js b/www/manager6/ha/NodePrioritySelector.js index 4637df40..93927449 100644 --- a/www/manager6/ha/NodePrioritySelector.js +++ b/www/manager6/ha/NodePrioritySelector.js @@ -9,6 +9,16 @@ Ext.define('PVE.forms.NodePrioritySelector', { allowBlank: true, isFormField: true, + config: { + useNodePriority: null, + }, + + publishes: ['useNodePriority'], + + viewModel: { + showNodePriority: null, + }, + store: { autoLoad: true, fields: ['node', 'cpu', 'mem', 'priority'], @@ -27,7 +37,7 @@ Ext.define('PVE.forms.NodePrioritySelector', { columns: [ { header: gettext('Node'), - flex: 1, + width: 150, dataIndex: 'node', }, { @@ -41,7 +51,7 @@ Ext.define('PVE.forms.NodePrioritySelector', { header: gettext('CPU usage'), renderer: Proxmox.Utils.render_cpu, sortable: true, - width: 150, + flex: 1, dataIndex: 'cpu', }, { @@ -62,6 +72,14 @@ Ext.define('PVE.forms.NodePrioritySelector', { record.commit(); }, }, + bind: { + hidden: '{!showNodePriority}', + disabled: '{!showNodePriority}', + }, + }, + bind: { + hidden: '{!showNodePriority}', + disabled: '{!showNodePriority}', }, }, ], @@ -80,6 +98,39 @@ Ext.define('PVE.forms.NodePrioritySelector', { }, }, + invertCheckboxSelection: function () { + let me = this; + + let sm = me.getSelectionModel(); + + let allNodeModels = new Set(sm.getStore().getRange()); + let selectedNodeModels = new Set(sm.getSelection()); + + if (!allNodeModels.size || !selectedNodeModels.size) { + return; + } + + sm.deselectAll(); + sm.select([...allNodeModels.difference(selectedNodeModels)]); + }, + + applyUseNodePriority: function (newValue) { + let me = this; + + let oldValue = me.getViewModel().get('showNodePriority'); + + if (newValue !== oldValue) { + me.getViewModel().set('showNodePriority', newValue); + + // Prevent inverting the selection during component initialization + if (oldValue != null) { + me.invertCheckboxSelection(); + } + } + + return newValue; + }, + getSubmitData: function () { let me = this; let res = {}; @@ -97,7 +148,15 @@ Ext.define('PVE.forms.NodePrioritySelector', { let sm = me.getSelectionModel(); let selectedNodeModels = sm.getSelection(); let nodes = selectedNodeModels - .map(({ data }) => data.node + (data.priority ? `:${data.priority}` : '')) + .map(({ data }) => { + let nodeEntry = data.node; + + if (me.useNodePriority && data.priority) { + nodeEntry += `:${data.priority}`; + } + + return nodeEntry; + }) .join(','); return nodes; diff --git a/www/manager6/ha/rules/NodeAffinityRuleEdit.js b/www/manager6/ha/rules/NodeAffinityRuleEdit.js index 77da18b1..310b894d 100644 --- a/www/manager6/ha/rules/NodeAffinityRuleEdit.js +++ b/www/manager6/ha/rules/NodeAffinityRuleEdit.js @@ -1,6 +1,15 @@ Ext.define('PVE.ha.rules.NodeAffinityInputPanel', { extend: 'PVE.ha.RuleInputPanel', + viewModel: { + data: { + affinity: 'positive', + }, + formulas: { + isPositiveNodeAffinity: (get) => get('affinity') === 'positive', + }, + }, + initComponent: function () { let me = this; @@ -18,6 +27,19 @@ Ext.define('PVE.ha.rules.NodeAffinityInputPanel', { uncheckedValue: 0, defaultValue: 0, }, + { + xtype: 'proxmoxKVComboBox', + name: 'affinity', + fieldLabel: gettext('Affinity'), + allowBlank: false, + comboItems: [ + ['positive', `${gettext('Prefer Nodes')} (positive)`], + ['negative', `${gettext('Avoid Nodes')} (negative)`], + ], + bind: { + value: '{affinity}', + }, + }, ]; me.columnB = [ @@ -25,6 +47,9 @@ Ext.define('PVE.ha.rules.NodeAffinityInputPanel', { xtype: 'pveNodePrioritySelector', name: 'nodes', allowBlank: false, + bind: { + useNodePriority: '{isPositiveNodeAffinity}', + }, }, ]; diff --git a/www/manager6/ha/rules/NodeAffinityRules.js b/www/manager6/ha/rules/NodeAffinityRules.js index 6fc42799..089dece8 100644 --- a/www/manager6/ha/rules/NodeAffinityRules.js +++ b/www/manager6/ha/rules/NodeAffinityRules.js @@ -11,6 +11,11 @@ Ext.define('PVE.ha.NodeAffinityRulesView', { stateId: 'grid-ha-node-affinity-rules', columns: [ + { + header: gettext('Affinity'), + width: 75, + dataIndex: 'affinity', + }, { header: gettext('Strict'), width: 75, -- 2.47.3