From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 65CBE1FF0E0 for ; Thu, 09 Jul 2026 11:21:06 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id BF69A215FA; Thu, 09 Jul 2026 11:19:39 +0200 (CEST) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH pve-manager v2 24/27] ui: sdn: microseg: add tag matcher Date: Thu, 9 Jul 2026 11:18:49 +0200 Message-ID: <20260709091852.538885-25-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260709091852.538885-1-h.laimer@proxmox.com> References: <20260709091852.538885-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1783588753232 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.280 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: NOBKSPUVE3N4HYBC5LY555Y2N45BHDXD X-Message-ID-Hash: NOBKSPUVE3N4HYBC5LY555Y2N45BHDXD X-MailFrom: h.laimer@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: Register the tag assignment type with its own input panel (tags multi-select, tag match, interface, groups, comment) and label it in the microseg tree. The add-assignment menu and tree filter pick it up from the schema registry. Signed-off-by: Hannes Laimer --- www/manager6/Utils.js | 5 + www/manager6/sdn/microseg/AssignmentEdit.js | 113 ++++++++++++++++++++ www/manager6/sdn/microseg/Base.js | 2 + www/manager6/sdn/microseg/Tree.js | 13 ++- 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 6d7ac5e3..52022a6a 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -980,6 +980,11 @@ Ext.define('PVE.Utils', { ipanel: 'GuestAssignmentInputPanel', urlType: 'assignment', }, + tagassignment: { + name: gettext('Tag Assignment'), + ipanel: 'TagAssignmentInputPanel', + urlType: 'assignment', + }, }, format_sdnmicroseg_type: function (value, md, record) { diff --git a/www/manager6/sdn/microseg/AssignmentEdit.js b/www/manager6/sdn/microseg/AssignmentEdit.js index 2a9c5cce..9fca4685 100644 --- a/www/manager6/sdn/microseg/AssignmentEdit.js +++ b/www/manager6/sdn/microseg/AssignmentEdit.js @@ -57,3 +57,116 @@ Ext.define('PVE.sdn.microseg.GuestAssignmentInputPanel', { me.callParent(); }, }); + +Ext.define('PVE.sdn.microseg.TagAssignmentInputPanel', { + extend: 'PVE.panel.SDNMicrosegBase', + + onlineHelp: 'pvesdn_microseg_assignment', + + autoId: true, + + onGetValues: function (values) { + let me = this; + if (me.isCreate) { + values.type = me.type; + } + if (values.iface === '' || values.iface === null || values.iface === undefined) { + delete values.iface; + } + return me.callParent([values]); + }, + + setValues: function (values) { + let me = this; + if (Ext.isArray(values.tags) && values.tags.length) { + let store = me.tagSelector.getStore(); + let known = {}; + store.each((rec) => { + known[rec.get('value')] = true; + }); + let missing = values.tags.filter((tag) => !known[tag]).map((tag) => ({ value: tag })); + if (missing.length) { + store.add(missing); + } + } + return me.callParent([values]); + }, + + initComponent: function () { + let me = this; + + let tagList = (PVE.UIOptions.tagList ?? []).map((tag) => ({ value: tag })); + + me.tagSelector = Ext.create({ + xtype: 'proxmoxComboGrid', + name: 'tags', + fieldLabel: gettext('Tags'), + emptyText: gettext('Select guest tags'), + editable: false, + multiSelect: true, + allowBlank: false, + disabled: !me.isCreate, + valueField: 'value', + displayField: 'value', + store: { + fields: ['value'], + data: tagList, + sorters: 'value', + }, + listConfig: { + userCls: 'proxmox-tags-full', + columns: [ + { + dataIndex: 'value', + flex: 1, + renderer: (value) => + PVE.Utils.renderTags(value, PVE.UIOptions.tagOverrides), + }, + ], + }, + }); + + me.items = [ + me.tagSelector, + { + xtype: 'proxmoxKVComboBox', + name: 'tag_match', + fieldLabel: gettext('Tag match'), + value: 'any', + disabled: !me.isCreate, + comboItems: [ + ['any', gettext('Any (shares a tag)')], + ['all', gettext('All (has every tag)')], + ['exact', gettext('Exact (equals)')], + ], + }, + { + xtype: 'proxmoxintegerfield', + name: 'iface', + fieldLabel: gettext('Network interface'), + minValue: 0, + maxValue: 31, + allowBlank: true, + emptyText: gettext('All NICs'), + disabled: !me.isCreate, + }, + { + xtype: 'pveMicrosegGroupSelector', + name: 'groups', + fieldLabel: gettext('Groups'), + multiSelect: true, + allowBlank: false, + }, + { + xtype: 'proxmoxtextfield', + name: 'comment', + fieldLabel: gettext('Comment'), + allowBlank: true, + maxLength: 256, + deleteEmpty: !me.isCreate, + }, + ]; + + me.callParent(); + }, +}); diff --git a/www/manager6/sdn/microseg/Base.js b/www/manager6/sdn/microseg/Base.js index 6e226ce2..a2c263f9 100644 --- a/www/manager6/sdn/microseg/Base.js +++ b/www/manager6/sdn/microseg/Base.js @@ -17,6 +17,8 @@ Ext.define('pve-sdn-microseg', { 'vmid', 'iface', 'groups', + 'tags', + 'tag_match', 'direction', ], idProperty: 'id', diff --git a/www/manager6/sdn/microseg/Tree.js b/www/manager6/sdn/microseg/Tree.js index 9bfa7608..47c13dfa 100644 --- a/www/manager6/sdn/microseg/Tree.js +++ b/www/manager6/sdn/microseg/Tree.js @@ -18,9 +18,20 @@ Ext.define('PVE.sdn.MicrosegTree', { ifaceVal === undefined || ifaceVal === null || ifaceVal === '' ? '' : ` net${ifaceVal}`; + if (a.type === 'tagassignment') { + let match = pv(a, 'tag_match') || 'any'; + return `${match}{${(pv(a, 'tags') || []).join(', ')}}${iface}`; + } return `vm${pv(a, 'vmid')}${iface}`; }; + let assignmentIcon = (a) => { + if (a.type === 'tagassignment') { + return 'fa fa-fw fa-tag'; + } + return 'fa fa-fw fa-link'; + }; + let buildNodes = (objects, identities) => { let groups = (objects || []) .filter((o) => o.type === 'group') @@ -102,7 +113,7 @@ Ext.define('PVE.sdn.MicrosegTree', { return { id: assignId, text: assignmentLabel(a), - iconCls: 'fa fa-fw fa-link', + iconCls: assignmentIcon(a), microsegEditType: a.type, microsegId: a.id, microsegMode: 'membership', -- 2.47.3