From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id E8C561FF0E7 for ; Thu, 13 Aug 2026 19:11:39 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 4F61821B84; Thu, 13 Aug 2026 19:11:15 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup 28/28] ui: show retention in datastore contents Date: Thu, 13 Aug 2026 19:10:02 +0200 Message-ID: <20260813171002.809441-29-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260813171002.809441-1-c.ebner@proxmox.com> References: <20260813171002.809441-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1786641019287 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.195 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 RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS 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: AJSNIPIMNRXTJ7PVZP4VMAHXRQTYZHSH X-Message-ID-Hash: AJSNIPIMNRXTJ7PVZP4VMAHXRQTYZHSH X-MailFrom: c.ebner@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: Signed-off-by: Christian Ebner --- www/datastore/Content.js | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/www/datastore/Content.js b/www/datastore/Content.js index bd989e660..bf8b23187 100644 --- a/www/datastore/Content.js +++ b/www/datastore/Content.js @@ -46,6 +46,11 @@ Ext.define('pbs-data-store-snapshots', { type: 'boolean', defaultValue: true, }, + { + name: 'retain-until', + type: 'date', + dateFormat: 'timestamp', + }, ], }); @@ -842,6 +847,50 @@ Ext.define('PBS.DataStoreContent', { }); }, + onRetentionChange: function (table, rI, cI, item, e, { data }) { + let me = this; + let view = me.getView(); + + if ((data.ty !== 'group' && data.ty !== 'dir' && data.ty !== 'ns') || !view.datastore) { + return; + } + + Ext.Msg.show({ + title: gettext('Confirm'), + icon: Ext.Msg.WARNING, + message: Ext.String.format( + gettext('Are you sure you want to clear retention for snapshot {0}'), + `'${data.text}'`, + ), + buttons: Ext.Msg.YESNO, + defaultFocus: 'no', + callback: function (btn) { + if (btn !== 'yes') { + return; + } + let params = { + 'backup-type': data['backup-type'], + 'backup-id': data['backup-id'], + 'backup-time': (data['backup-time'].getTime() / 1000).toFixed(0), + }; + if (view.namespace && view.namespace !== '') { + params.ns = view.namespace; + } + + Proxmox.Utils.API2Request({ + url: `/api2/extjs/admin/datastore/${view.datastore}/retention`, + params, + method: 'PUT', + waitMsgTarget: view, + failure: function (response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + }, + callback: me.reload.bind(me), + }); + }, + }); + }, + onForget: function (table, rI, cI, item, e, { data }) { let me = this; let view = this.getView(); @@ -1036,6 +1085,7 @@ Ext.define('PBS.DataStoreContent', { onCopy: createControllerCallback('onCopy'), onVerify: createControllerCallback('onVerify'), onProtectionChange: createControllerCallback('onProtectionChange'), + onRetentionChange: createControllerCallback('onRetentionChange'), onForget: createControllerCallback('onForget'), }); } @@ -1196,6 +1246,23 @@ Ext.define('PBS.DataStoreContent', { }, isActionDisabled: (v, r, c, i, rec) => rec.data.ty !== 'dir', }, + { + handler: 'onRetentionChange', + getTip: (v, m, rec) => { + if (!rec.data['retain-until']) { + return; + } + return Ext.String.format(gettext("Clear Retention: '{0}'"), rec.data['retain-until']); + }, + getClass: (v, m, rec) => { + if (rec.data.ty === 'dir' && !!rec.data['retain-until'] > 0) { + return `fa fa-hourglass`; + } + return 'pmx-hidden'; + }, + isActionDisabled: (v, r, c, i, rec) => + rec.data.ty !== 'dir' || !rec.data['retain-until'], + }, { handler: 'onForget', getTip: (v, m, { data }) => { -- 2.47.3