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 6C35D1FF13C for ; Thu, 19 Mar 2026 17:13:21 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 017D74AE0; Thu, 19 Mar 2026 17:13:35 +0100 (CET) From: Hannes Laimer To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v5 1/9] ui: show empty groups Date: Thu, 19 Mar 2026 17:13:17 +0100 Message-ID: <20260319161325.206846-2-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260319161325.206846-1-h.laimer@proxmox.com> References: <20260319161325.206846-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: 1773936765834 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.984 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.408 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.819 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.903 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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: QUNRBF5EU3OSZNGJNAJ7H6GF7JOUVOK6 X-Message-ID-Hash: QUNRBF5EU3OSZNGJNAJ7H6GF7JOUVOK6 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 Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Displays groups that do not contain any snapshots, currently deleting the last snapshot will also remove the parent group. So things like notes would be lost. This is needed for cleaning up partially failed moves in a datastore with a s3 backend as it allows for easy local and s3 cleanup. Without such a group showing up in the UI the only way to trigger a cleanup in the s3 storage would be trough our API. Signed-off-by: Hannes Laimer --- www/datastore/Content.js | 62 ++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/www/datastore/Content.js b/www/datastore/Content.js index a2aa1949..981acdc7 100644 --- a/www/datastore/Content.js +++ b/www/datastore/Content.js @@ -170,7 +170,8 @@ Ext.define('PBS.DataStoreContent', { return groups; }, - updateGroupNotes: async function (view) { + loadGroups: async function () { + let view = this.getView(); try { let url = `/api2/extjs/admin/datastore/${view.datastore}/groups`; if (view.namespace && view.namespace !== '') { @@ -179,19 +180,24 @@ Ext.define('PBS.DataStoreContent', { let { result: { data: groups }, } = await Proxmox.Async.api2({ url }); - let map = {}; - for (const group of groups) { - map[`${group['backup-type']}/${group['backup-id']}`] = group.comment; - } - view.getRootNode().cascade((node) => { - if (node.data.ty === 'group') { - let group = `${node.data.backup_type}/${node.data.backup_id}`; - node.set('comment', map[group], { dirty: false }); - } - }); + return groups; } catch (err) { console.debug(err); } + return []; + }, + + updateGroupNotes: function (view, groupList) { + let map = {}; + for (const group of groupList) { + map[`${group['backup-type']}/${group['backup-id']}`] = group.comment; + } + view.getRootNode().cascade((node) => { + if (node.data.ty === 'group') { + let group = `${node.data.backup_type}/${node.data.backup_id}`; + node.set('comment', map[group], { dirty: false }); + } + }); }, loadNamespaceFromSameLevel: async function () { @@ -215,7 +221,10 @@ Ext.define('PBS.DataStoreContent', { let me = this; let view = this.getView(); - let namespaces = await me.loadNamespaceFromSameLevel(); + let [namespaces, groupList] = await Promise.all([ + me.loadNamespaceFromSameLevel(), + me.loadGroups(), + ]); if (!success) { // TODO also check error code for != 403 ? @@ -232,6 +241,29 @@ Ext.define('PBS.DataStoreContent', { let groups = this.getRecordGroups(records); + for (const item of groupList) { + let btype = item['backup-type']; + let group = btype + '/' + item['backup-id']; + if (groups[group] !== undefined) { + continue; + } + let cls = PBS.Utils.get_type_icon_cls(btype); + if (cls === '') { + continue; + } + groups[group] = { + text: group, + leaf: true, + iconCls: 'fa ' + cls, + expanded: false, + backup_type: btype, + backup_id: item['backup-id'], + comment: item.comment, + owner: item.owner, + children: [], + }; + } + let selected; let expanded = {}; @@ -399,7 +431,7 @@ Ext.define('PBS.DataStoreContent', { ); } - this.updateGroupNotes(view); + this.updateGroupNotes(view, groupList); if (selected !== undefined) { let selection = view.getRootNode().findChildBy( @@ -985,7 +1017,7 @@ Ext.define('PBS.DataStoreContent', { flex: 1, renderer: (v, meta, record) => { let data = record.data; - if (!data || data.leaf || data.root) { + if (!data || (data.leaf && data.ty !== 'group') || data.root) { return ''; } @@ -1029,7 +1061,7 @@ Ext.define('PBS.DataStoreContent', { }, dblclick: function (tree, el, row, col, ev, rec) { let data = rec.data || {}; - if (data.leaf || data.root) { + if ((data.leaf && data.ty !== 'group') || data.root) { return; } let view = tree.up(); -- 2.47.3