From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 46D161FF14C for ; Fri, 15 May 2026 10:54:18 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E9719110D0; Fri, 15 May 2026 10:53:58 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Subject: [PATCH manager v3 04/13] ui: add pending grid Date: Fri, 15 May 2026 10:44:22 +0200 Message-ID: <20260515085349.1123127-5-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260515085349.1123127-1-d.csapak@proxmox.com> References: <20260515085349.1123127-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.050 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 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: RRHLJ6YXT7EZ5APON2H5IBJO73VADRUB X-Message-ID-Hash: RRHLJ6YXT7EZ5APON2H5IBJO73VADRUB X-MailFrom: d.csapak@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: A new grid type that gets its data from a PendingObjectGrid and displays a parsed property string in various columns. Each column shows its own pending changes so that this can be seen more easily. Signed-off-by: Dominik Csapak --- www/manager6/Makefile | 1 + www/manager6/grid/PendingGrid.js | 134 +++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 www/manager6/grid/PendingGrid.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 280a9ca6..c2c1dffb 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -100,6 +100,7 @@ JSSRC= \ grid/FirewallAliases.js \ grid/FirewallOptions.js \ grid/FirewallRules.js \ + grid/PendingGrid.js \ grid/PoolMembers.js \ grid/Replication.js \ grid/ResourceGrid.js \ diff --git a/www/manager6/grid/PendingGrid.js b/www/manager6/grid/PendingGrid.js new file mode 100644 index 00000000..e2d7b6c3 --- /dev/null +++ b/www/manager6/grid/PendingGrid.js @@ -0,0 +1,134 @@ +/* It's main use is to be a 'sub grid' for a 'PendingObjectGrid, for qemu/HardwareView + * and lxc/Resources. + */ +Ext.define('PVE.grid.PendingGrid', { + extend: 'Ext.grid.Panel', + alias: 'widget.pvePendingGrid', + + /// the original store of the PendingObjectGrid, used for retrieving data + rstore: undefined, + + /// the filter function to filter only relevant data from rstore + storeFilter: function (_rec) { + return true; + }, + + /// the parser for the data, should return an object + recordParser: function (key, value) { + let parsed = PVE.Parser.parsePropertyString(value); + return { + key, + ...parsed, + }; + }, + + pendingRenderer: function (originalRenderer) { + originalRenderer ??= Ext.htmlEncode; + return function (val, mD, rec, rowIdx, colIdx, store, view) { + // add a ''pending parameter' + let txt = originalRenderer(val, mD, rec, rowIdx, colIdx, store, view, false); + let pendingTxt; + + let pending = rec.get('pending'); + if (pending) { + let dataIndex = view.up().getColumns()[colIdx].dataIndex; + let value = pending[dataIndex]; + pendingTxt = originalRenderer(value, mD, rec, rowIdx, colIdx, store, view, true); + } + + if (pendingTxt && txt && pendingTxt !== txt) { + txt += '
'; + txt += `${pendingTxt}`; + } + if (pending && pendingTxt && (!txt || !rec.data.raw)) { + txt = `${pendingTxt}`; + } + if (pending && !pendingTxt && txt) { + txt = `
${txt}
`; + } + if (rec.data.deleted && txt) { + txt = `
${txt}
`; + } + + return txt; + }; + }, + + updateData: function () { + let me = this; + let records = []; + let data = me.rstore.getData(); + (data.getSource() ?? data).each(function (rec) { + if (!me.storeFilter(rec)) { + return; + } + + let key = rec.get('key'); + let value = rec.get('value'); + let deleted = rec.get('delete'); + + let parsed = me.recordParser(key, value); + if (parsed) { + parsed.raw = value; + } + + let pending = rec.get('pending'); + let parsedPending; + if (pending) { + parsedPending = me.recordParser(key, pending); + parsedPending.id = key; + parsedPending.raw = pending; + } + + records.push({ + ...parsed, + id: key, + key, + pending: parsedPending, + deleted, + }); + }); + + me.getStore().setData(records); + }, + + store: {}, + + columns: [], + + initComponent: function () { + let me = this; + + if (!me.rstore) { + throw new 'no rstore defined'(); + } + + let columns = []; + + // overwrite renderer to be pending + // + // CAUTION: don't modify the original column objects, rather copy them + for (const column of me.columns ?? []) { + let originalRenderer = column.renderer; + // might be a string (named scope) + if (Ext.isString(originalRenderer)) { + let renderString = originalRenderer.toString(); + originalRenderer = function () { + return Ext.callback(renderString, me.scope, arguments, 0, me); + }; + } + + let renderer = me.pendingRenderer(originalRenderer); + columns.push({ + ...column, + renderer, + }); + } + + me.columns = columns; + + me.callParent(); + + me.mon(me.rstore, 'refresh', me.updateData, me); + }, +}); -- 2.47.3