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 3A6941FF0AF for ; Fri, 06 Nov 2026 18:24:50 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id BA7D221758; Fri, 06 Nov 2026 18:23:47 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v2 38/43] api/ui: expose default retention setting as datastore config option Date: Fri, 6 Nov 2026 18:22:30 +0100 Message-ID: <20261106172235.890542-39-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261106172235.890542-1-c.ebner@proxmox.com> References: <20261106172235.890542-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: 1793985780888 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.444 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 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: KOJW4AZZY3NXMDRPJ3TY5CJ337FYMBNT X-Message-ID-Hash: KOJW4AZZY3NXMDRPJ3TY5CJ337FYMBNT 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: Allows to set the default retention timespans for the datastore. Used as fallback for new backups and syncs when no explicit retention is requested by the client/sync job. The default is always required to be within the limits of the max. If no max is set, then no default can be set. This protects against potential misconfiguration. Signed-off-by: Christian Ebner --- src/api2/config/datastore.rs | 39 ++++++++++++++++++++-- www/Makefile | 1 + www/datastore/OptionView.js | 8 +++++ www/window/DefaultRetentionTimespanEdit.js | 25 ++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 www/window/DefaultRetentionTimespanEdit.js diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs index 03ff19fc3..d3e710015 100644 --- a/src/api2/config/datastore.rs +++ b/src/api2/config/datastore.rs @@ -444,6 +444,8 @@ pub enum DeletableProperty { MaxSnapshotTimeSkew, /// Delete the max-retention-timespan MaxRetentionTimespan, + /// Delete the default-retention-timespan + DefaultRetentionTimespan, } #[api( @@ -552,7 +554,20 @@ pub fn update_datastore( DeletableProperty::MaxSnapshotTimeSkew => { data.max_snapshot_time_skew = None; } + DeletableProperty::DefaultRetentionTimespan => { + data.default_retention_timespan = None; + } DeletableProperty::MaxRetentionTimespan => { + if update + .default_retention_timespan + .as_ref() + .or(data.default_retention_timespan.as_ref()) + .is_some() + { + bail!( + "Cannot remove max-retention-timespan while default-retention-timespan still set" + ); + } data.max_retention_timespan = None; } } @@ -660,8 +675,28 @@ pub fn update_datastore( data.max_snapshot_time_skew = update.max_snapshot_time_skew; } - if update.max_retention_timespan.is_some() { - data.max_retention_timespan = update.max_retention_timespan; + if let Some(max_retention) = update.max_retention_timespan.clone() { + let default_retention = update + .default_retention_timespan + .as_ref() + .or(data.default_retention_timespan.as_ref()); + if let Some(default_retention) = default_retention { + if default_retention.as_timespan().as_secs() > max_retention.as_timespan().as_secs() { + bail!("New max-retention-timespan smaller than default-retention-timespan"); + } + } + data.max_retention_timespan = Some(max_retention); + } + + if let Some(default_retention) = update.default_retention_timespan { + if let Some(max_retention) = &data.max_retention_timespan { + if default_retention.as_timespan().as_secs() > max_retention.as_timespan().as_secs() { + bail!("Cannot set default-retention-timespan exceeding max-retention-timespan"); + } + } else { + bail!("Cannot set default-retention-timespan without max-retention-timespan"); + } + data.default_retention_timespan = Some(default_retention); } config.set_data(&name, "datastore", &data)?; diff --git a/www/Makefile b/www/Makefile index 3b2c9a27f..e9696949d 100644 --- a/www/Makefile +++ b/www/Makefile @@ -92,6 +92,7 @@ JSSRC= \ window/TrafficControlEdit.js \ window/ThresholdResetScheduleEdit.js \ window/MaxRetentionTimespanEdit.js \ + window/DefaultRetentionTimespanEdit.js \ window/RetentionEdit.js \ window/NotifyOptions.js \ window/SyncJobEdit.js \ diff --git a/www/datastore/OptionView.js b/www/datastore/OptionView.js index cf23063d4..ab15ed47b 100644 --- a/www/datastore/OptionView.js +++ b/www/datastore/OptionView.js @@ -400,5 +400,13 @@ Ext.define('PBS.Datastore.Options', { xtype: 'pbsMaxRetentionTimespanEdit', }, }, + 'default-retention-timespan': { + required: true, + header: gettext('Default Retention'), + renderer: (timespan) => timespan ?? gettext('None'), + editor: { + xtype: 'pbsDefaultRetentionTimespanEdit', + }, + }, }, }); diff --git a/www/window/DefaultRetentionTimespanEdit.js b/www/window/DefaultRetentionTimespanEdit.js new file mode 100644 index 000000000..ae88a25d7 --- /dev/null +++ b/www/window/DefaultRetentionTimespanEdit.js @@ -0,0 +1,25 @@ +Ext.define('PBS.window.DefaultRetentionTimespanEdit', { + extend: 'Proxmox.window.Edit', + alias: 'widget.pbsDefaultRetentionTimespanEdit', + mixins: ['Proxmox.Mixin.CBind'], + + subject: gettext('Default Retention Timespan'), + + cbindData: function (initial) { + let me = this; + + me.datastore = encodeURIComponent(me.datastore); + me.url = `/api2/extjs/config/datastore/${me.datastore}`; + me.method = 'PUT'; + me.autoLoad = true; + return {}; + }, + + items: { + xtype: 'pbsRetentionTimespan', + name: 'default-retention-timespan', + fieldLabel: gettext('Retention Timespan'), + labelWidth: 150, + emptyText: gettext('None'), + }, +}); -- 2.47.3