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 F16971FF0E1 for ; Mon, 27 Jul 2026 14:49:13 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 8B9A7213E7; Mon, 27 Jul 2026 14:49:12 +0200 (CEST) Message-ID: <3772ea7c-b974-4b07-891d-681b749dfb96@proxmox.com> Date: Mon, 27 Jul 2026 14:49:07 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: David Riley Subject: Re: [PATCH pve-manager] fix #7794: ui: dc summary: add per-node VM/CT count columns To: Erik Fastermann , pve-devel@lists.proxmox.com References: <20260724113506.150278-1-e.fastermann@proxmox.com> Content-Language: en-US In-Reply-To: <20260724113506.150278-1-e.fastermann@proxmox.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1785156513187 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.081 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_LOW -0.7 Sender listed at https://www.dnswl.org/, low 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: JN2BIB4A5XO3MVF4BV4MZIRQNP35MCVN X-Message-ID-Hash: JN2BIB4A5XO3MVF4BV4MZIRQNP35MCVN X-MailFrom: d.riley@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: Thanks for sending in this patch, this looks like a nice addition. some comments inline. On 7/24/26 1:34 PM, Erik Fastermann wrote: > The datacenter summary aggregates VM and CT counts cluster-wide but > gives no per-node breakdown. Add columns to the node list showing the > number of running and total VMs and CTs per node, so the workload > distribution across the cluster is visible at a glance. > > The running columns are shown by default, the totals are hidden and can > be enabled per profile. Templates are excluded from the totals, matching > the guest status panel that accounts for them separately. > > Combined guest columns (VMs plus CTs) were suggested but left out on > purpose, as one does not gain a lot of information from them and to > avoid column clutter. > > Signed-off-by: Erik Fastermann > --- > www/manager6/dc/NodeView.js | 26 ++++++++++++++++++++++++ > www/manager6/dc/Summary.js | 40 +++++++++++++++++++++++++++++++++++++ > 2 files changed, 66 insertions(+) > > diff --git a/www/manager6/dc/NodeView.js b/www/manager6/dc/NodeView.js > index 2c9574fb..4ea76bb7 100644 > --- a/www/manager6/dc/NodeView.js > +++ b/www/manager6/dc/NodeView.js > @@ -68,6 +68,32 @@ Ext.define( > criticalThreshold: 0.975, > }, > }, > + { > + header: gettext('VMs (running)'), > + width: 100, > + sortable: true, > + dataIndex: 'qemurunning', > + }, > + { > + header: gettext('VMs (total)'), > + width: 100, > + sortable: true, > + dataIndex: 'qemutotal', > + hidden: true, > + }, > + { > + header: gettext('CTs (running)'), > + width: 100, > + sortable: true, > + dataIndex: 'lxcrunning', > + }, > + { > + header: gettext('CTs (total)'), > + width: 100, > + sortable: true, > + dataIndex: 'lxctotal', > + hidden: true, > + }, > { > header: gettext('Uptime'), > sortable: true, > diff --git a/www/manager6/dc/Summary.js b/www/manager6/dc/Summary.js > index bf7e99c6..a27dfa3a 100644 > --- a/www/manager6/dc/Summary.js > +++ b/www/manager6/dc/Summary.js > @@ -146,12 +146,16 @@ Ext.define('PVE.dc.Summary', { > stopped: 0, > template: 0, > }; > + const qemuPerNodeRunning = {}; > + const qemuPerNodeTotal = {}; > let lxc = { > running: 0, > paused: 0, > stopped: 0, > template: 0, > }; > + const lxcPerNodeRunning = {}; > + const lxcPerNodeTotal = {}; > let error = 0; > > for (const { data } of results) { > @@ -194,12 +198,36 @@ Ext.define('PVE.dc.Summary', { > } > case 'qemu': > qemu[data.template ? 'template' : data.status]++; > + > + if (!(data.node in qemuPerNodeTotal)) { > + qemuPerNodeRunning[data.node] = 0; > + qemuPerNodeTotal[data.node] = 0; > + } > + if (data.status === 'running') { > + qemuPerNodeRunning[data.node] += 1; > + } > + if (!data.template) { > + qemuPerNodeTotal[data.node] += 1; > + } > + > if (data.hastate === 'error') { > error++; > } > break; > case 'lxc': > lxc[data.template ? 'template' : data.status]++; > + > + if (!(data.node in lxcPerNodeTotal)) { > + lxcPerNodeRunning[data.node] = 0; > + lxcPerNodeTotal[data.node] = 0; > + } > + if (data.status === 'running') { > + lxcPerNodeRunning[data.node] += 1; > + } > + if (!data.template) { > + lxcPerNodeTotal[data.node] += 1; > + } > + > if (data.hastate === 'error') { > error++; > } > @@ -228,6 +256,18 @@ Ext.define('PVE.dc.Summary', { > > gueststatus.updateValues(qemu, lxc, error); > > + gridstore.each((item) => { > + const node = item.getId().replace(/^node\//, ''); > + > + item.set('qemurunning', qemuPerNodeRunning[node] ?? 0); > + item.set('qemutotal', qemuPerNodeTotal[node] ?? 0); > + > + item.set('lxcrunning', lxcPerNodeRunning[node] ?? 0); > + item.set('lxctotal', lxcPerNodeTotal[node] ?? 0); > + > + item.commit(); > + }); nit: This could benefit from 'item.beginEdit()' [0] and 'endEdit()' [1] as well as passing an object to 'item.set()' [2] to avoid firing multiple field update events per record. item.beginEdit(); item.set({     qemurunning: qemuPerNodeRunning[node] ?? 0,     qemutotal: qemuPerNodeTotal[node] ?? 0,     lxcrunning: lxcPerNodeRunning[node] ?? 0,     lxctotal: lxcPerNodeTotal[node] ?? 0, }); item.commit(); item.endEdit(); Wrapping the loop with 'gridstore.beginUpdate()' [3] and 'gridstore.endUpdate()' [4] would also prevent the store from triggering grid redraws on every iteration. No hard feelings on this, though. [0] https://docs.sencha.com/extjs/7.0.0/classic/Ext.data.Model.html#method-beginEdit [1] https://docs.sencha.com/extjs/7.0.0/classic/Ext.data.Model.html#method-endEdit [2] https://docs.sencha.com/extjs/7.0.0/classic/Ext.data.Model.html#method-set [3] https://docs.sencha.com/extjs/7.0.0/classic/Ext.data.Store.html#method-beginUpdate [4] https://docs.sencha.com/extjs/7.0.0/classic/Ext.data.Store.html#method-endUpdate > + > me.suspendLayout = false; > me.updateLayout(true); > });