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 08FC21FF133 for ; Mon, 11 May 2026 18:12:32 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 2A5D42077D; Mon, 11 May 2026 18:12:29 +0200 (CEST) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Mon, 11 May 2026 18:11:50 +0200 Message-Id: Subject: Re: [PATCH pve-manager v8 20/25] ui: sdn: prefix list: adapt to changed api structure From: "Lukas Sichert" To: "Stefan Hanreich" , References: <20260511133650.310040-1-s.hanreich@proxmox.com> <20260511133650.310040-21-s.hanreich@proxmox.com> In-Reply-To: <20260511133650.310040-21-s.hanreich@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1778515798749 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.521 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_MAILER 2 Automated Mailer Tag Left in Email POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [me.data,prefixlists.pm] Message-ID-Hash: LWOAEKR7PCX6TUSZL2RZF32TBDQ3S4VZ X-Message-ID-Hash: LWOAEKR7PCX6TUSZL2RZF32TBDQ3S4VZ X-MailFrom: l.sichert@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: On 2026-05-11 15:36, Stefan Hanreich wrote: > From: Dominik Csapak > > instead of having to parse out the prefix list entries from the overall > prefix list GET api call, the entries are now queried below: > > /sdn/cluster/prefix-list/{id}/entries > > so the entries grid can be simplified to use it's own store + url. > This makes it possible to move the stores out of the viewmodel and into > the respective grid views. > > Also use proxmoxStdRemoveButtons here to simplify the removal code. > > Signed-off-by: Dominik Csapak > [SH: fixed wrong url in edit window, made sequence number optional and > added empty text] > Signed-off-by: Stefan Hanreich > --- > www/manager6/sdn/PrefixListPanel.js | 279 ++++++++++------------------ > 1 file changed, 96 insertions(+), 183 deletions(-) > > diff --git a/www/manager6/sdn/PrefixListPanel.js b/www/manager6/sdn/Prefi= xListPanel.js > index cdb860f4f..0e5cc11b8 100644 > --- a/www/manager6/sdn/PrefixListPanel.js > +++ b/www/manager6/sdn/PrefixListPanel.js > @@ -1,16 +1,11 @@ > Ext.define('PVE.sdn.PrefixList', { > extend: 'Ext.data.Model', > - fields: ['id', 'entries', 'pending'], > - > - getId: function () { > - let me =3D this; > - return me.data.pending?.[me.idProperty] ?? me.data[me.idProperty= ]; > - }, > + fields: ['id', 'state'], > }); > =20 > Ext.define('PVE.sdn.PrefixListEntry', { > extend: 'Ext.data.Model', > - fields: ['id', 'action', 'prefix', 'le', 'ge', 'pending'], > + fields: ['id', 'action', 'seq', 'prefix', 'le', 'ge', 'pending'], > }); > =20 > Ext.define('PVE.sdn.EditPrefixListWindow', { > @@ -45,10 +40,11 @@ Ext.define('PVE.sdn.EditPrefixListWindow', { > =20 > Ext.define('PVE.sdn.EditPrefixListEntryWindow', { > extend: 'Proxmox.window.Edit', > + mixins: ['Proxmox.Mixin.CBind'], > =20 > subject: gettext('Prefix List Entry'), > =20 > - url: '/cluster/sdn/prefix-lists', > + baseUrl: '/cluster/sdn/prefix-lists', > =20 > config: { > entry: null, > @@ -57,6 +53,15 @@ Ext.define('PVE.sdn.EditPrefixListEntryWindow', { > isCreate: false, > =20 > items: [ > + { > + xtype: 'proxmoxintegerfield', > + name: 'seq', > + fieldLabel: gettext('Sequence Nr.'), > + emptyText: gettext('autogenerated'), > + cbind: { > + deleteEmpty: '{!isCreate}', > + } > + }, > { > xtype: 'proxmoxKVComboBox', > fieldLabel: gettext('Action'), > @@ -88,6 +93,17 @@ Ext.define('PVE.sdn.EditPrefixListEntryWindow', { > initComponent: function () { > let me =3D this; > me.method =3D me.isCreate ? 'POST' : 'PUT'; > + > + if (!me.prefixList) { > + throw new 'no prefixList given'(); > + } > + > + if (me.entry) { > + me.url =3D `${me.baseUrl}/${me.prefixList}/entries/${me.entr= y.seq}`; > + } else { > + me.url =3D `${me.baseUrl}/${me.prefixList}/entries`; > + } > + > me.callParent(); > =20 > me.setValues(me.getEntry()); > @@ -108,19 +124,28 @@ Ext.define('PVE.sdn.PrefixListView', { > }, > { > text: gettext('Remove'), > - xtype: 'button', > - handler: 'removePrefixList', > - bind: { > - disabled: '{!prefixListGrid.selection}', > - }, > + xtype: 'proxmoxStdRemoveButton', > + baseurl: '/cluster/sdn/prefix-lists/', > + dangerous: true, > + callback: 'reloadPrefixList', > }, > + '->', > { > text: gettext('Reload'), > xtype: 'button', > - handler: 'reload', > + handler: 'reloadPrefixList', > }, > ], > =20 > + store: { > + autoLoad: true, > + model: 'PVE.sdn.PrefixList', > + proxy: { > + type: 'proxmox', > + url: '/api2/json/cluster/sdn/prefix-lists?pending=3D1', > + }, > + }, > + > columns: [ > { > text: gettext('Name'), > @@ -139,6 +164,12 @@ Ext.define('PVE.sdn.PrefixListView', { > }, > }, > ], > + > + initComponent: function () { > + let me =3D this; > + me.callParent(); > + Proxmox.Utils.monStoreErrors(me, me.getStore()); > + }, > }); > =20 > Ext.define('PVE.sdn.PrefixListEntriesView', { > @@ -151,30 +182,22 @@ Ext.define('PVE.sdn.PrefixListEntriesView', { > prefixList: null, > }, > =20 > - viewConfig: { > - plugins: [ > - { > - ptype: 'gridviewdragdrop', > - }, > - ], > - }, > - > listeners: { > - drop: 'saveEntries', > itemdblclick: 'editPrefixListEntry', > }, > =20 > + store: { > + model: 'PVE.sdn.PrefixListEntry', > + proxy: { > + type: 'proxmox', > + }, > + }, > + > columns: [ > { > - width: 40, > - resizable: false, > - sortable: false, > - hideable: false, > - menuDisabled: true, > - renderer: function (value, metaData, record, rowIdx, colIdx)= { > - metaData.tdCls =3D Ext.baseCSSPrefix + 'grid-cell-specia= l'; > - return ""; > - }, > + text: gettext('Sequence Nr.'), > + dataIndex: 'seq', > + flex: 1, > }, > { > text: gettext('Action'), > @@ -209,21 +232,32 @@ Ext.define('PVE.sdn.PrefixListEntriesView', { > }, > { > text: gettext('Edit'), > - xtype: 'button', > + xtype: 'proxmoxButton', > + disabled: true, > handler: 'editPrefixListEntry', > - bind: { > - disabled: '{!prefixListEntriesGrid.selection}', > - }, > }, > { > text: gettext('Remove'), > - xtype: 'button', > - handler: 'removePrefixListEntry', > - bind: { > - disabled: '{!prefixListEntriesGrid.selection}', > + xtype: 'proxmoxStdRemoveButton', > + customConfirmationMessage: gettext( > + 'Are you sure you want to remove entry with sequence {0}= ', > + ), > + getRecordName: (rec) =3D> rec.data.seq, > + getUrl: function (rec) { > + let grid =3D this.up('grid'); > + let prefixList =3D grid.prefixList; > + let id =3D prefixList.getId(); > + let seq =3D rec.data.seq; > + return `/cluster/sdn/prefix-lists/${id}/entries/${seq}`; I am a bit confused here. I cannot find a Api Handler for '/entries' in the pve-network PrefixLists.pm File. Also if I want to look at the prefix-list entries in the Gui I get 'Method 'GET /cluster/sdn/prefix-lists//entries' not implemented (501)'. Is this somehow handled in a more obscure way or is it a bug that happened during rewriting the API interface? > }, > }, > ], > + > + initComponent: function () { > + let me =3D this; > + me.callParent(); > + Proxmox.Utils.monStoreErrors(me, me.getStore()); > + }, > }); > =20 > Ext.define('PVE.sdn.PrefixListPanel', { > @@ -233,30 +267,6 @@ Ext.define('PVE.sdn.PrefixListPanel', { > emptyText: gettext('No prefix list configured'), > =20 > viewModel: { > - stores: { > - prefixLists: { > - autoLoad: true, > - model: 'PVE.sdn.PrefixList', > - proxy: { > - type: 'proxmox', > - url: '/api2/json/cluster/sdn/prefix-lists?pending=3D= 1', > - }, > - }, > - prefixListEntries: { > - model: 'PVE.sdn.PrefixListEntry', > - proxy: { > - type: 'proxmox', > - reader: { > - transform: { > - fn: function (response) { > - let entries =3D response.data.entries ??= []; > - return entries.map(PVE.Parser.parsePrope= rtyString); > - }, > - }, > - }, > - }, > - }, > - }, > formulas: { > entryGridEmptyText: function (get) { > let selection =3D get('prefixListGrid.selection'); > @@ -269,64 +279,24 @@ Ext.define('PVE.sdn.PrefixListPanel', { > }, > =20 > controller: { > - reload: function () { > + reloadPrefixList: function () { > let me =3D this; > - > - let prefixList =3D me.getViewModel().get('prefixListGrid.sel= ection'); > - > - me.getViewModel() > - .getStore('prefixLists') > - .load((records, _operation, success) =3D> { > - if (!success || !prefixList) { > - return; > - } > - > - let newPrefixList =3D records.find((record) =3D> { > - return record.getId() =3D=3D=3D prefixList.getId= (); > - }); > - > - me.lookupReference('prefixListGrid').setSelection(ne= wPrefixList); > - }); > + me.lookup('prefixListGrid').getStore().load(); > + // reset entries grid > + let entriesGrid =3D me.lookup('prefixListEntriesGrid'); > + entriesGrid.getStore().setData([]); > + Proxmox.Utils.setErrorMask(entriesGrid, false); > }, > - saveEntries: function () { > + reloadPrefixEntries: function () { > let me =3D this; > - > - let prefixList =3D me.getViewModel().get('prefixListGrid.sel= ection'); > - > - let entries =3D me > - .getViewModel() > - .getStore('prefixListEntries') > - .getData() > - .items.map((item) =3D> { > - let data =3D item.data; > - delete data.id; > - > - return PVE.Parser.printPropertyString(data); > - }); > - > - let params =3D {}; > - > - if (entries.length > 0) { > - params.entries =3D entries; > - } else { > - params =3D { delete: ['entries'] }; > - } > - > - Proxmox.Async.api2({ > - url: `/api2/extjs/cluster/sdn/prefix-lists/${prefixList.= getId()}`, > - params, > - method: 'PUT', > - }) > - .catch(Proxmox.Utils.alertResponseFailure) > - .finally(() =3D> { > - me.reload(prefixList); > - }); > + me.lookup('prefixListEntriesGrid').getStore().load(); > }, > selectPrefixList: function (gridPanel, record, index, options) { > let me =3D this; > =20 > - let url =3D `/api2/extjs/cluster/sdn/prefix-lists/${record.g= etId()}`; > - let entryStore =3D me.getViewModel().getStore('prefixListEnt= ries'); > + let prefixEntriesGrid =3D me.lookup('prefixListEntriesGrid')= ; > + let entryStore =3D prefixEntriesGrid.getStore(); > + let url =3D `/api2/json/cluster/sdn/prefix-lists/${record.ge= tId()}/entries`; > =20 > entryStore.getProxy().setUrl(url); > entryStore.load(); > @@ -338,56 +308,28 @@ Ext.define('PVE.sdn.PrefixListPanel', { > autoShow: true, > isCreate: true, > listeners: { > - close: () =3D> me.reload(), > + close: () =3D> me.reloadPrefixList(), > }, > }); > }, > - removePrefixList: function () { > + addPrefixListEntry: function () { > let me =3D this; > let prefixList =3D me.getViewModel().get('prefixListGrid.sel= ection'); > =20 > - Ext.Msg.show({ > - title: gettext('Confirm'), > - icon: Ext.Msg.WARNING, > - message: Ext.String.format(gettext('Remove prefix list "= {0}"?'), prefixList.getId()), > - buttons: Ext.Msg.YESNO, > - defaultFocus: 'no', > - callback: function (btn) { > - if (btn !=3D=3D 'yes') { > - return; > - } > - > - Proxmox.Async.api2({ > - url: `/api2/extjs/cluster/sdn/prefix-lists/${pre= fixList.getId()}`, > - method: 'DELETE', > - }) > - .catch(Proxmox.Utils.alertResponseFailure) > - .finally(() =3D> { > - me.reload(prefixList); > - }); > - }, > - }); > - }, > - addPrefixListEntry: function () { > - let panel =3D this; > - > Ext.create('PVE.sdn.EditPrefixListEntryWindow', { > autoShow: true, > isCreate: true, > - submit: function () { > - let me =3D this; > - > - panel.getViewModel().getStore('prefixListEntries').a= dd(me.getValues()); > - panel.saveEntries(); > - > - me.close(); > + prefixList: prefixList.getId(), > + listeners: { > + close: () =3D> me.reloadPrefixEntries(), > }, > }); > }, > editPrefixListEntry: function () { > - let panel =3D this; > + let me =3D this; > =20 > - let entry =3D panel.getViewModel().get('prefixListEntriesGri= d.selection'); > + let entry =3D me.getViewModel().get('prefixListEntriesGrid.s= election'); > + let prefixList =3D me.getViewModel().get('prefixListGrid.sel= ection'); > =20 > if (!entry) { > console.warn('no prefix list entry selected!'); > @@ -397,35 +339,10 @@ Ext.define('PVE.sdn.PrefixListPanel', { > Ext.create('PVE.sdn.EditPrefixListEntryWindow', { > autoShow: true, > isCreate: false, > + prefixList: prefixList.getId(), > entry: entry.data, > - submit: function () { > - let me =3D this; > - entry.set(me.getValues()); > - > - panel.saveEntries(); > - > - me.close(); > - }, > - }); > - }, > - removePrefixListEntry: function () { > - let me =3D this; > - > - let entry =3D me.getViewModel().get('prefixListEntriesGrid.s= election'); > - > - Ext.Msg.show({ > - title: gettext('Confirm'), > - icon: Ext.Msg.WARNING, > - message: gettext('Remove prefix list entry?'), > - buttons: Ext.Msg.YESNO, > - defaultFocus: 'no', > - callback: function (btn) { > - if (btn !=3D=3D 'yes') { > - return; > - } > - > - me.getViewModel().getStore('prefixListEntries').remo= ve(entry); > - me.saveEntries(); > + listeners: { > + close: () =3D> me.reloadPrefixEntries(), > }, > }); > }, > @@ -441,9 +358,6 @@ Ext.define('PVE.sdn.PrefixListPanel', { > border: false, > split: true, > reference: 'prefixListGrid', > - bind: { > - store: '{prefixLists}', > - }, > listeners: { > select: 'selectPrefixList', > }, > @@ -454,7 +368,6 @@ Ext.define('PVE.sdn.PrefixListPanel', { > border: false, > bind: { > prefixList: '{prefixListGrid.selection}', > - store: '{prefixListEntries}', > emptyText: '{entryGridEmptyText}', > }, > reference: 'prefixListEntriesGrid', > --=20 > 2.47.3