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 159C01FF0A7 for ; Wed, 19 Aug 2026 14:44:38 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id B3A772159A; Wed, 19 Aug 2026 14:44:20 +0200 (CEST) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH pve-manager 2/2] ui: sdn: ipam: allow picking the nic instead of typing its MAC Date: Wed, 19 Aug 2026 14:44:07 +0200 Message-ID: <20260819124407.856577-3-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260819124407.856577-1-h.laimer@proxmox.com> References: <20260819124407.856577-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: 1787143432726 X-SPAM-LEVEL: Spam detection results: 0 AWL 1.286 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) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: 5OILBTAOWEFOH3R6BQDGQVUUN2G7IR2Z X-Message-ID-Hash: 5OILBTAOWEFOH3R6BQDGQVUUN2G7IR2Z 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: Instead of hand-copying a MAC from the guest's hardware panel, take a guest and one of its NICs on the assignments's VNet, and fill the MAC in from there. Setting a MAC without a guest stays possible, so this is purely a UX improvement. We could also restrict this, so our IPAM only manages IPs of guests in our SDN. Signed-off-by: Hannes Laimer --- www/manager6/sdn/IpamEdit.js | 112 ++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/www/manager6/sdn/IpamEdit.js b/www/manager6/sdn/IpamEdit.js index cdefe436..aeb21b2b 100644 --- a/www/manager6/sdn/IpamEdit.js +++ b/www/manager6/sdn/IpamEdit.js @@ -16,18 +16,48 @@ Ext.define('PVE.sdn.IpamEditInputPanel', { items: [ { - xtype: 'pmxDisplayEditField', name: 'vmid', - fieldLabel: 'VMID', + fieldLabel: gettext('Guest'), + allowBlank: true, + notFoundIsValid: true, + emptyText: Proxmox.Utils.NoneText, + cbind: { + xtype: (get) => (get('isCreate') ? 'vmComboSelector' : 'displayfield'), + }, + listeners: { + change: 'onGuestChange', + }, + }, + { + xtype: 'combobox', + reference: 'netid', + fieldLabel: gettext('Network Device'), + emptyText: gettext('Select a guest first'), + submitValue: false, allowBlank: false, editable: false, + disabled: true, + queryMode: 'local', + valueField: 'netid', + displayField: 'netid', + listConfig: { + itemTpl: '{netid} {mac:htmlEncode}', + }, + store: { + fields: ['netid', 'mac'], + data: [], + }, cbind: { - hidden: '{isCreate}', + hidden: '{!isCreate}', + }, + listeners: { + select: 'onDeviceSelect', }, }, { xtype: 'pmxDisplayEditField', name: 'mac', + reference: 'mac', fieldLabel: 'MAC', allowBlank: false, cbind: { @@ -58,11 +88,87 @@ Ext.define('PVE.sdn.IpamEdit', { return `${url}/${values.vnet}/ips`; }, + controller: { + xclass: 'Ext.app.ViewController', + + onGuestChange: function (f, vmid) { + if (!this.getView().isCreate) { + return; + } + this.setGuest(vmid ? f.getSelection() : undefined); + }, + + onDeviceSelect: function (f, record) { + this.lookup('mac').setValue(record?.data.mac ?? ''); + }, + + setGuest: function (record) { + let me = this; + let view = me.getView(); + + let selector = me.lookup('netid'); + let mac = me.lookup('mac'); + + selector.clearValue(); + selector.getStore().removeAll(); + selector.setDisabled(!record); + + mac.setValue(''); + mac.setEditable(!record); + + if (!record) { + selector.setEmptyText(gettext('Select a guest first')); + return; + } + + Proxmox.Utils.API2Request({ + url: `/nodes/${record.data.node}/${record.data.type}/${record.data.vmid}/config`, + method: 'GET', + failure: (response) => Ext.Msg.alert(gettext('Error'), response.htmlStatus), + success: function ({ result }) { + if (selector.destroyed) { + return; + } + + let vnet = view.extraRequestParams?.vnet; + let nics = []; + + for (const [key, value] of Object.entries(result.data)) { + if (!key.match(/^net\d+$/)) { + continue; + } + + let net = + record.data.type === 'qemu' + ? PVE.Parser.parseQemuNetwork(key, value) + : PVE.Parser.parseLxcNetwork(value); + + let mac = net?.macaddr ?? net?.hwaddr; + if (!mac || net.bridge !== vnet) { + continue; + } + + nics.push({ netid: key, mac }); + } + + selector.getStore().setData(nics); + selector.setEmptyText( + nics.length + ? gettext('Select a network device') + : Ext.String.format(gettext('No network device on {0}'), vnet), + ); + }, + }); + }, + }, + initComponent: function () { var me = this; me.method = me.isCreate ? 'POST' : 'PUT'; + me.defaultFocus = me.isCreate ? 'textfield[name=mac]' : 'textfield[name=ip]'; + let ipanel = Ext.create('PVE.sdn.IpamEditInputPanel', { isCreate: me.isCreate, }); -- 2.47.3