* [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs
@ 2026-11-04 14:53 Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config Jonas Theisen
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
Goal of this patch series is to allow users to disable tasks
without having to remove their current schedule.
This was already possible for Prune jobs.
This can be used f.ex. to create jobs which are supposed to
be run only manually.
Backwards compatibiltiy was kept in mind so that existing
jobs wiithout schedules would not need manual intervention.
They will get updated and adapt the new scheme on the next
actual user change.
The "remove clear" patch is to be considered optional as it does not
interfere with the functionality of the checkboxes but was rather
added from a personal perspective that this would improve UX.
Thanks for the review and help off-list @c.ebner!
Depends-on: https://lore.proxmox.com/pbs-devel/20261014144357.771051-2-c.ebner@proxmox.com/
and https://lore.proxmox.com/all/20261022161612.331793-1-j.theisen@proxmox.com/
changes from v4:
* fixed commit message
* added code comments reasoning value parameter for checkboxes
* rebased on current master and fixed missing test parameter
proxmox:
Jonas Theisen (2):
pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and
datastore config
pbs-api-types: prune job: make schedule optional
pbs-api-types/src/datastore.rs | 13 ++++++++++
pbs-api-types/src/jobs.rs | 43 +++++++++++++++++++++++++++-------
2 files changed, 48 insertions(+), 8 deletions(-)
proxmox-backup:
Jonas Theisen (7):
server: schedules: add 'disable' parameter to
compute_schedule_status()
ui: prune job: align prune edit window to other "Enable" checkboxes
api: prune job: align prune job schedule to other jobs
fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs
fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit
ui: job overview: show "Disabled" as schedule of disabled jobs
ui: calendar-event: hide clear button if a schedule is required
src/api2/admin/datastore.rs | 9 +++-
src/api2/admin/prune.rs | 6 +--
src/api2/admin/sync.rs | 3 +-
src/api2/admin/verify.rs | 7 ++-
src/api2/config/datastore.rs | 11 +++-
src/api2/config/prune.rs | 16 ++++--
src/api2/config/sync.rs | 19 ++++++-
src/api2/config/tape_backup_job.rs | 17 +++++-
src/api2/config/verify.rs | 17 +++++-
src/api2/tape/backup.rs | 7 ++-
src/bin/proxmox-backup-proxy.rs | 25 ++++++++-
.../proxmox_backup_manager/migrate_config.rs | 2 +-
src/server/jobstate.rs | 4 +-
www/Utils.js | 22 ++++++++
www/config/GCView.js | 3 +-
www/config/PruneView.js | 1 +
www/config/SyncView.js | 2 +
www/config/VerifyView.js | 2 +
www/form/CalendarEvent.js | 19 +++++--
www/tape/BackupJobs.js | 2 +
www/tape/window/TapeBackupJob.js | 37 +++++++++++--
www/window/GCJobEdit.js | 52 +++++++++++++++++--
www/window/PruneJobEdit.js | 44 +++++++++++-----
www/window/SyncJobEdit.js | 34 ++++++++++--
www/window/VerifyJobEdit.js | 33 ++++++++++--
25 files changed, 338 insertions(+), 56 deletions(-)
Summary over all repositories:
27 files changed, 386 insertions(+), 64 deletions(-)
--
Generated by murpp 0.12.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH proxmox v5 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 2/9] pbs-api-types: prune job: make schedule optional Jonas Theisen
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
To be able to en-/disable the respective jobs this patch adds the
"disable" field to the job config and the needed schema.
This also adds the "gc_disable" parameter to the datastore config
to allow this type of job to be disabled.
"disable" was chosen over "enable" to be consistent with the already
existing config and API endpoints for prune jobs.
This commit also aligns the prune job config to make use of
the new schema but does not modify functionality.
Depends-on: https://lore.proxmox.com/pbs-devel/20261014144357.771051-2-c.ebner@proxmox.com/
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
pbs-api-types/src/datastore.rs | 13 ++++++++++++
pbs-api-types/src/jobs.rs | 39 ++++++++++++++++++++++++++++------
2 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs
index c545d05b..57918049 100644
--- a/pbs-api-types/src/datastore.rs
+++ b/pbs-api-types/src/datastore.rs
@@ -477,6 +477,11 @@ pub const MAX_SNAPSHOT_TIME_SKEW_SCHEMA: Schema =
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
+ "gc-disable": {
+ description: "Disable scheduled execution of garbage collection.",
+ optional: true,
+ type: Boolean,
+ },
"gc-schedule": {
optional: true,
schema: GC_SCHEDULE_SCHEMA,
@@ -545,6 +550,10 @@ pub struct DataStoreConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
+ #[serde(default, skip_serializing_if = "crate::is_false")]
+ #[updater(serde(skip_serializing_if = "Option::is_none"))]
+ pub gc_disable: bool,
+
#[serde(skip_serializing_if = "Option::is_none")]
pub gc_schedule: Option<String>,
@@ -630,6 +639,7 @@ impl DataStoreConfig {
name,
path,
comment: None,
+ gc_disable: Default::default(),
gc_schedule: None,
gc_on_unmount: None,
prune_schedule: None,
@@ -1730,6 +1740,9 @@ pub struct GarbageCollectionJobStatus {
pub store: String,
#[serde(flatten)]
pub status: GarbageCollectionStatus,
+ /// Disable state of the gc job
+ #[serde(default)]
+ pub disable: bool,
/// Schedule of the gc job
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
diff --git a/pbs-api-types/src/jobs.rs b/pbs-api-types/src/jobs.rs
index 9a5d2b77..6b725b75 100644
--- a/pbs-api-types/src/jobs.rs
+++ b/pbs-api-types/src/jobs.rs
@@ -32,6 +32,12 @@ pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
.max_length(32)
.schema();
+pub const DISABLE_JOB_SCHEMA: Schema = BooleanSchema::new(
+ "Disable the scheduled execution of this task.",
+ )
+ .default(false)
+ .schema();
+
pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new("Run sync job at specified schedule.")
.format(&ApiStringFormat::VerifyFn(
proxmox_time::verify_calendar_event,
@@ -228,6 +234,10 @@ pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema =
optional: true,
schema: VERIFICATION_SCHEDULE_SCHEMA,
},
+ disable: {
+ optional: true,
+ schema: DISABLE_JOB_SCHEMA,
+ },
ns: {
optional: true,
schema: BACKUP_NAMESPACE_SCHEMA,
@@ -267,6 +277,10 @@ pub struct VerificationJobConfig {
#[serde(skip_serializing_if = "Option::is_none")]
/// when to schedule this job in calendar event notation
pub schedule: Option<String>,
+ /// Disable this job.
+ #[serde(default, skip_serializing_if = "crate::is_false")]
+ #[updater(serde(skip_serializing_if = "Option::is_none"))]
+ pub disable: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
/// on which backup namespace to run the verification recursively
pub ns: Option<BackupNamespace>,
@@ -403,6 +417,10 @@ pub struct TapeBackupJobSetup {
optional: true,
schema: SYNC_SCHEDULE_SCHEMA,
},
+ disable: {
+ optional: true,
+ schema: DISABLE_JOB_SCHEMA,
+ },
}
)]
#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
@@ -417,6 +435,10 @@ pub struct TapeBackupJobConfig {
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
+ /// Disable this job.
+ #[serde(default, skip_serializing_if = "crate::is_false")]
+ #[updater(serde(skip_serializing_if = "Option::is_none"))]
+ pub disable: bool,
}
#[api(
@@ -642,6 +664,10 @@ pub const UNMOUNT_ON_SYNC_DONE_SCHEMA: Schema =
optional: true,
schema: SYNC_SCHEDULE_SCHEMA,
},
+ disable: {
+ optional: true,
+ schema: DISABLE_JOB_SCHEMA,
+ },
"group-filter": {
schema: GROUP_FILTER_LIST_SCHEMA,
optional: true,
@@ -717,6 +743,10 @@ pub struct SyncJobConfig {
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
+ /// Disable this job.
+ #[serde(default, skip_serializing_if = "crate::is_false")]
+ #[updater(serde(skip_serializing_if = "Option::is_none"))]
+ pub disable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_filter: Option<Vec<GroupFilter>>,
#[serde(flatten)]
@@ -886,9 +916,8 @@ impl PruneJobOptions {
#[api(
properties: {
disable: {
- type: Boolean,
optional: true,
- default: false,
+ schema: DISABLE_JOB_SCHEMA,
},
id: {
schema: JOB_ID_SCHEMA,
@@ -919,7 +948,7 @@ pub struct PruneJobConfig {
pub store: String,
/// Disable this job.
- #[serde(default, skip_serializing_if = "is_false")]
+ #[serde(default, skip_serializing_if = "crate::is_false")]
#[updater(serde(skip_serializing_if = "Option::is_none"))]
pub disable: bool,
@@ -938,10 +967,6 @@ impl PruneJobConfig {
}
}
-fn is_false(b: &bool) -> bool {
- !b
-}
-
#[api(
properties: {
config: {
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox v5 2/9] pbs-api-types: prune job: make schedule optional
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 3/9] server: schedules: add 'disable' parameter to compute_schedule_status() Jonas Theisen
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
To align the prune job API to the other job types and to make use of
the disable parameter the schedule should be optional.
If this is not the case a new prune job cannot be created to be
manually run i.e. disabled without a schedule.
This also means the schedule should be an Option<String> instead
of a plain String. This is based on the existing other jobs.
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
pbs-api-types/src/jobs.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/pbs-api-types/src/jobs.rs b/pbs-api-types/src/jobs.rs
index 6b725b75..9138596c 100644
--- a/pbs-api-types/src/jobs.rs
+++ b/pbs-api-types/src/jobs.rs
@@ -926,6 +926,7 @@ impl PruneJobOptions {
schema: DATASTORE_SCHEMA,
},
schedule: {
+ optional: true,
schema: PRUNE_SCHEDULE_SCHEMA,
},
comment: {
@@ -952,7 +953,8 @@ pub struct PruneJobConfig {
#[updater(serde(skip_serializing_if = "Option::is_none"))]
pub disable: bool,
- pub schedule: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub schedule: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 3/9] server: schedules: add 'disable' parameter to compute_schedule_status()
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 2/9] pbs-api-types: prune job: make schedule optional Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 4/9] ui: prune job: align prune edit window to other "Enable" checkboxes Jonas Theisen
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
To properly calculate the next_run for jobs the scheduler needs
to know if the job is currently disabled.
This commit adds the parameter to the function and sets the
next_run property to None if the job is disabled.
This is equivalent to no or an invalid schedule.
All callers are adapted to pass the disable flag of their job. For
prune jobs this replaces the manual reset of next_run for disabled
jobs.
Depends-on: https://lore.proxmox.com/all/20261022161612.331793-1-j.theisen@proxmox.com/
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
src/api2/admin/datastore.rs | 8 ++++++--
src/api2/admin/prune.rs | 6 ++----
src/api2/admin/sync.rs | 3 ++-
src/api2/admin/verify.rs | 7 ++++++-
src/api2/tape/backup.rs | 7 ++++++-
src/server/jobstate.rs | 4 +++-
6 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index cfe976811..b19a3fed7 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -1269,8 +1269,12 @@ pub(crate) fn garbage_collection_status_unchecked(
let datastore = DataStore::lookup_datastore(lookup_with(&store, Operation::Read))?;
let status_in_memory = datastore.last_gc_status();
- let computed_schedule: JobScheduleStatus =
- compute_schedule_status("garbage_collection", &store, info.schedule.as_deref())?;
+ let computed_schedule: JobScheduleStatus = compute_schedule_status(
+ "garbage_collection",
+ &store,
+ info.schedule.as_deref(),
+ info.disable,
+ )?;
if let Some(ref upid) = status_in_memory.upid {
if let Some(endtime) = computed_schedule.last_run_endtime {
diff --git a/src/api2/admin/prune.rs b/src/api2/admin/prune.rs
index 0846d8893..eb57b0167 100644
--- a/src/api2/admin/prune.rs
+++ b/src/api2/admin/prune.rs
@@ -73,10 +73,8 @@ pub fn list_prune_jobs(
let mut list = Vec::new();
for job in job_config_iter {
- let mut status = compute_schedule_status("prunejob", &job.id, Some(&job.schedule))?;
- if job.disable {
- status.next_run = None;
- }
+ let status =
+ compute_schedule_status("prunejob", &job.id, Some(&job.schedule), job.disable)?;
list.push(PruneJobStatus {
config: job,
diff --git a/src/api2/admin/sync.rs b/src/api2/admin/sync.rs
index 1799f51c0..9be86fbb0 100644
--- a/src/api2/admin/sync.rs
+++ b/src/api2/admin/sync.rs
@@ -112,7 +112,8 @@ pub fn list_config_sync_jobs(
continue;
}
- let status = compute_schedule_status("syncjob", &job.id, job.schedule.as_deref())?;
+ let status =
+ compute_schedule_status("syncjob", &job.id, job.schedule.as_deref(), job.disable)?;
list.push(SyncJobStatus {
config: job,
diff --git a/src/api2/admin/verify.rs b/src/api2/admin/verify.rs
index 80659ebe2..a60a44f6f 100644
--- a/src/api2/admin/verify.rs
+++ b/src/api2/admin/verify.rs
@@ -73,7 +73,12 @@ pub fn list_verification_jobs(
let mut list = Vec::new();
for job in job_config_iter {
- let status = compute_schedule_status("verificationjob", &job.id, job.schedule.as_deref())?;
+ let status = compute_schedule_status(
+ "verificationjob",
+ &job.id,
+ job.schedule.as_deref(),
+ job.disable,
+ )?;
list.push(VerificationJobStatus {
config: job,
diff --git a/src/api2/tape/backup.rs b/src/api2/tape/backup.rs
index c0a4ca65b..a92d032e1 100644
--- a/src/api2/tape/backup.rs
+++ b/src/api2/tape/backup.rs
@@ -81,7 +81,12 @@ pub fn list_tape_backup_jobs(
continue;
}
- let status = compute_schedule_status("tape-backup-job", &job.id, job.schedule.as_deref())?;
+ let status = compute_schedule_status(
+ "tape-backup-job",
+ &job.id,
+ job.schedule.as_deref(),
+ job.disable,
+ )?;
let next_run = status.next_run.unwrap_or(current_time);
diff --git a/src/server/jobstate.rs b/src/server/jobstate.rs
index 7add06779..23134c60c 100644
--- a/src/server/jobstate.rs
+++ b/src/server/jobstate.rs
@@ -340,6 +340,7 @@ pub fn compute_schedule_status(
jobtype: &str,
jobname: &str,
schedule: Option<&str>,
+ disable: bool,
) -> Result<JobScheduleStatus, Error> {
let job_state = match JobState::load(jobtype, jobname) {
Ok(job_state) => job_state,
@@ -385,7 +386,8 @@ pub fn compute_schedule_status(
..Default::default()
};
- if let Some(schedule) = schedule {
+ // a disabled job has no next run, just like a job without a schedule
+ if let Some(schedule) = schedule.filter(|_| !disable) {
if let Ok(event) = schedule.parse::<CalendarEvent>() {
// ignore errors
status.next_run = event.compute_next_event(last).unwrap_or(None);
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 4/9] ui: prune job: align prune edit window to other "Enable" checkboxes
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (2 preceding siblings ...)
2026-11-04 14:53 ` [PATCH proxmox-backup v5 3/9] server: schedules: add 'disable' parameter to compute_schedule_status() Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 5/9] api: prune job: align prune job schedule to other jobs Jonas Theisen
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
Align the already existing "Enabled" checkbox in the prune job edit
window to the "Enable" checkboxes of the other job types: rename it
to "Enable", as is more common throughout the UI, add the same
tooltip, and disable the schedule field while the checkbox is
unchecked.
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
www/Utils.js | 15 +++++++++++++
www/window/PruneJobEdit.js | 44 ++++++++++++++++++++++++++------------
2 files changed, 45 insertions(+), 14 deletions(-)
diff --git a/www/Utils.js b/www/Utils.js
index d6bfd459e..f4a27c13b 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -127,6 +127,21 @@ Ext.define('PBS.Utils', {
}
},
+ // jobs are disabled via a 'disable' flag in the API, but the UI shows an "Enable" checkbox
+ enableToDisable: function (values, prefix = '') {
+ values[`${prefix}disable`] = !values[`${prefix}enable`];
+ delete values[`${prefix}enable`];
+
+ return values;
+ },
+
+ disableToEnable: function (values, prefix = '') {
+ values[`${prefix}enable`] = !values[`${prefix}disable`];
+ delete values[`${prefix}disable`];
+
+ return values;
+ },
+
render_datetime_utc: function (datetime) {
let pad = (number) => (number < 10 ? '0' + number : number);
return (
diff --git a/www/window/PruneJobEdit.js b/www/window/PruneJobEdit.js
index a3dafde21..9f6c972ec 100644
--- a/www/window/PruneJobEdit.js
+++ b/www/window/PruneJobEdit.js
@@ -43,6 +43,21 @@ Ext.define('PBS.window.PruneJobEdit', {
},
},
+ viewModel: {
+ data: {
+ jobEnabled: true,
+ },
+ },
+
+ setValues: function (values) {
+ let me = this;
+
+ if (!values.schedule) {
+ values.disable = true;
+ }
+ me.callParent([values]);
+ },
+
items: {
xtype: 'inputpanel',
onGetValues: function (values) {
@@ -51,18 +66,10 @@ Ext.define('PBS.window.PruneJobEdit', {
if (!values.id && me.up('pbsPruneJobEdit').isCreate) {
values.id = 's-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
}
- values.disable = !values.enable;
- delete values.enable;
-
- return values;
- },
- onSetValues: function (values) {
- let _me = this;
- values.enable = !values.disable;
- delete values.disable;
- return values;
+ return PBS.Utils.enableToDisable(values);
},
+ onSetValues: (values) => PBS.Utils.disableToEnable(values),
column1: [
{
xtype: 'pmxDisplayEditField',
@@ -115,14 +122,23 @@ Ext.define('PBS.window.PruneJobEdit', {
cbind: {
value: '{scheduleValue}',
},
+ bind: {
+ disabled: '{!jobEnabled}',
+ },
},
{
xtype: 'proxmoxcheckbox',
- fieldLabel: gettext('Enabled'),
+ fieldLabel: gettext('Enable'),
name: 'enable',
- uncheckedValue: 0,
- defaultValue: 1,
- checked: true,
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Enable scheduled execution of this job.'),
+ },
+ bind: {
+ value: '{jobEnabled}',
+ },
+ uncheckedValue: false,
+ value: true, // value needs to be set despite bind for setValues to work
},
],
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 5/9] api: prune job: align prune job schedule to other jobs
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (3 preceding siblings ...)
2026-11-04 14:53 ` [PATCH proxmox-backup v5 4/9] ui: prune job: align prune edit window to other "Enable" checkboxes Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 6/9] fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs Jonas Theisen
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
To allow creation of prune jobs without a schedule the parameter
needs to be optional and be able to handle no content.
This is already the case for the schedule parameters of the other
job types, so this patch aligns the prune job schedules by using
an Option<String> instead of a plain String.
This patch also integrates the API check for disable = true if no
schedule is provided.
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
src/api2/admin/prune.rs | 2 +-
src/api2/config/datastore.rs | 2 +-
src/api2/config/prune.rs | 16 +++++++++++-----
src/bin/proxmox-backup-proxy.rs | 9 +++++++--
src/bin/proxmox_backup_manager/migrate_config.rs | 2 +-
5 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/src/api2/admin/prune.rs b/src/api2/admin/prune.rs
index eb57b0167..2ecca2c60 100644
--- a/src/api2/admin/prune.rs
+++ b/src/api2/admin/prune.rs
@@ -74,7 +74,7 @@ pub fn list_prune_jobs(
for job in job_config_iter {
let status =
- compute_schedule_status("prunejob", &job.id, Some(&job.schedule), job.disable)?;
+ compute_schedule_status("prunejob", &job.id, job.schedule.as_deref(), job.disable)?;
list.push(PruneJobStatus {
config: job,
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index dfce8ae50..4de4e3150 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -321,7 +321,7 @@ pub fn create_datastore(
store: config.name.clone(),
comment: None,
disable: false,
- schedule: schedule.clone(),
+ schedule: Some(schedule.clone()),
options: PruneJobOptions {
keep: config.keep.clone(),
max_depth: None,
diff --git a/src/api2/config/prune.rs b/src/api2/config/prune.rs
index 91d2324f9..60b123589 100644
--- a/src/api2/config/prune.rs
+++ b/src/api2/config/prune.rs
@@ -1,4 +1,4 @@
-use anyhow::Error;
+use anyhow::{Error, format_err};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@@ -112,6 +112,13 @@ pub fn create_prune_job(
user_info.check_privs(&auth_id, &config.acl_path(), PRIV_DATASTORE_MODIFY, true)?;
+ if !config.disable && config.schedule.is_none() {
+ param_bail!(
+ "schedule",
+ format_err!("job enabled but no schedule provided")
+ );
+ }
+
do_create_prune_job(config)
}
@@ -284,10 +291,9 @@ pub fn update_prune_job(
user_info.check_privs(&auth_id, &data.acl_path(), PRIV_DATASTORE_MODIFY, true)?;
}
- let mut schedule_changed = false;
- if let Some(schedule) = update.schedule {
- schedule_changed = data.schedule != schedule;
- data.schedule = schedule;
+ let schedule_changed = update.schedule.is_some() && data.schedule != update.schedule;
+ if update.schedule.is_some() {
+ data.schedule = update.schedule;
}
if let Some(max_depth) = update.options.max_depth {
diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index 22abfdbdb..c66cffeb4 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -641,9 +641,14 @@ async fn schedule_datastore_prune_jobs() {
continue;
}
+ let event_str = match job_config.schedule {
+ Some(event_str) => event_str,
+ None => continue,
+ };
+
let worker_type = "prunejob";
let auth_id = Authid::root_auth_id().clone();
- if check_schedule(worker_type, &job_config.schedule, &job_id) {
+ if check_schedule(worker_type, &event_str, &job_id) {
let job = match Job::new(worker_type, &job_id) {
Ok(job) => job,
Err(_) => continue, // could not get lock
@@ -653,7 +658,7 @@ async fn schedule_datastore_prune_jobs() {
job_config.options,
job_config.store,
&auth_id,
- Some(job_config.schedule),
+ Some(event_str),
) {
eprintln!("unable to start datastore prune job {job_id} - {err}");
}
diff --git a/src/bin/proxmox_backup_manager/migrate_config.rs b/src/bin/proxmox_backup_manager/migrate_config.rs
index 69f908dc5..b48d53104 100644
--- a/src/bin/proxmox_backup_manager/migrate_config.rs
+++ b/src/bin/proxmox_backup_manager/migrate_config.rs
@@ -84,7 +84,7 @@ pub(crate) fn update_to_prune_jobs_config() -> Result<(), Error> {
store: store.clone(),
disable: false,
comment: None,
- schedule,
+ schedule: Some(schedule),
options,
};
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 6/9] fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (4 preceding siblings ...)
2026-11-04 14:53 ` [PATCH proxmox-backup v5 5/9] api: prune job: align prune job schedule to other jobs Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 7/9] fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit Jonas Theisen
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
To allow users to disable jobs without having to remove the
schedule this patch introduces the "disable" parameter.
This can also be used to create jobs which are supposed to
be run only manually, since with the "disable" parameter
set to true, no schedule is required.
The create endpoint for the respective jobs now also raises
a parameter error if no schedule is set and the job is not
disabled. Updates are not checked, so that unrelated config
updates of existing jobs without a schedule are not prevented.
The value is inserted into the job config and honored by
the scheduler by skipping disabled jobs altogether.
For GC jobs the value is inserted into the datastore config.
If the value is not present in a job config it is assumed the job
is to be enabled for backwards compatibility and to reduce
config-bloat.
Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=7904
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
src/api2/admin/datastore.rs | 1 +
src/api2/config/datastore.rs | 9 +++++++++
src/api2/config/sync.rs | 19 ++++++++++++++++++-
src/api2/config/tape_backup_job.rs | 17 ++++++++++++++++-
src/api2/config/verify.rs | 17 ++++++++++++++++-
src/bin/proxmox-backup-proxy.rs | 16 ++++++++++++++++
6 files changed, 76 insertions(+), 3 deletions(-)
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index b19a3fed7..236f081ea 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -1263,6 +1263,7 @@ pub(crate) fn garbage_collection_status_unchecked(
let mut info = GarbageCollectionJobStatus {
store: store.clone(),
schedule: store_config.gc_schedule,
+ disable: store_config.gc_disable,
..Default::default()
};
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 4de4e3150..5db7d3044 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -406,6 +406,8 @@ pub fn read_datastore(
pub enum DeletableProperty {
/// Delete the comment property.
Comment,
+ /// Unset the disable flag.
+ GcDisable,
/// Delete the garbage collection schedule.
GcSchedule,
/// Delete the gc-on-unmount property.
@@ -495,6 +497,9 @@ pub fn update_datastore(
DeletableProperty::Comment => {
data.comment = None;
}
+ DeletableProperty::GcDisable => {
+ data.gc_disable = false;
+ }
DeletableProperty::GcSchedule => {
data.gc_schedule = None;
}
@@ -576,6 +581,10 @@ pub fn update_datastore(
data.gc_on_unmount = update.gc_on_unmount;
}
+ if let Some(value) = update.gc_disable {
+ data.gc_disable = value;
+ }
+
macro_rules! prune_disabled {
($(($param:literal, $($member:tt)+)),+) => {
$(
diff --git a/src/api2/config/sync.rs b/src/api2/config/sync.rs
index 1c322cc7e..6a34ce96e 100644
--- a/src/api2/config/sync.rs
+++ b/src/api2/config/sync.rs
@@ -1,5 +1,5 @@
use ::serde::{Deserialize, Serialize};
-use anyhow::{Error, bail};
+use anyhow::{Error, bail, format_err};
use pbs_api_types::SyncDirection;
use serde_json::Value;
@@ -306,6 +306,13 @@ pub fn create_sync_job(
}
}
+ if !config.disable && config.schedule.is_none() {
+ param_bail!(
+ "schedule",
+ format_err!("job enabled but no schedule provided")
+ );
+ }
+
let (mut section_config, _digest) = sync::config()?;
if section_config.sections.contains_key(&config.id) {
@@ -383,6 +390,8 @@ pub enum DeletableProperty {
Owner,
/// Delete the comment property.
Comment,
+ /// Unset the disable flag.
+ Disable,
/// Delete the job schedule.
Schedule,
/// Delete the remove-vanished flag.
@@ -526,6 +535,9 @@ fn do_update_sync_job_config(
DeletableProperty::Comment => {
data.comment = None;
}
+ DeletableProperty::Disable => {
+ data.disable = false;
+ }
DeletableProperty::Schedule => {
data.schedule = None;
}
@@ -729,6 +741,9 @@ fn do_update_sync_job_config(
if update.schedule.is_some() {
data.schedule = update.schedule;
}
+ if let Some(value) = update.disable {
+ data.disable = value;
+ }
if update.remove_vanished.is_some() {
data.remove_vanished = update.remove_vanished;
}
@@ -866,6 +881,7 @@ acl:1:/remote/remote1/remotestore1:write@pbs:RemoteSyncOperator
max_depth: None,
group_filter: None,
schedule: None,
+ disable: false,
limit: pbs_api_types::RateLimitConfig::default(), // no limit
transfer_last: None,
encrypted_only: None,
@@ -1060,6 +1076,7 @@ acl:1:/remote/remote1/remotealice1:alice@pbs:RemoteSyncOperator,RemoteSyncPushOp
remove_vanished: None,
max_depth: None,
group_filter: None,
+ disable: false,
schedule: None,
limit: pbs_api_types::RateLimitConfig::default(), // no limit
transfer_last: None,
diff --git a/src/api2/config/tape_backup_job.rs b/src/api2/config/tape_backup_job.rs
index 4bfc7f871..4df1137da 100644
--- a/src/api2/config/tape_backup_job.rs
+++ b/src/api2/config/tape_backup_job.rs
@@ -1,5 +1,5 @@
use ::serde::{Deserialize, Serialize};
-use anyhow::Error;
+use anyhow::{Error, format_err};
use serde_json::Value;
use proxmox_router::{Permission, Router, RpcEnvironment, http_bail};
@@ -79,6 +79,13 @@ pub fn create_tape_backup_job(
check_tape_backup_permission(&auth_id, &job.setup)?;
+ if !job.disable && job.schedule.is_none() {
+ param_bail!(
+ "schedule",
+ format_err!("job enabled but no schedule provided")
+ );
+ }
+
let _lock = pbs_config::tape_job::lock()?;
let (mut config, _digest) = pbs_config::tape_job::config()?;
@@ -130,6 +137,8 @@ pub fn read_tape_backup_job(
pub enum DeletableProperty {
/// Delete the comment property.
Comment,
+ /// Unset the disable flag.
+ Disable,
/// Delete the job schedule.
Schedule,
/// Delete the eject-media property
@@ -224,6 +233,9 @@ pub fn update_tape_backup_job(
DeletableProperty::Comment => {
data.comment = None;
}
+ DeletableProperty::Disable => {
+ data.disable = false;
+ }
DeletableProperty::GroupFilter => {
data.setup.group_filter = None;
}
@@ -286,6 +298,9 @@ pub fn update_tape_backup_job(
if update.schedule.is_some() {
data.schedule = update.schedule;
}
+ if let Some(value) = update.disable {
+ data.disable = value;
+ }
if let Some(comment) = update.comment {
let comment = comment.trim();
diff --git a/src/api2/config/verify.rs b/src/api2/config/verify.rs
index 35be9b175..588d4abf2 100644
--- a/src/api2/config/verify.rs
+++ b/src/api2/config/verify.rs
@@ -1,5 +1,5 @@
use ::serde::{Deserialize, Serialize};
-use anyhow::Error;
+use anyhow::{Error, format_err};
use serde_json::Value;
use proxmox_router::{Permission, Router, RpcEnvironment, http_bail};
@@ -80,6 +80,13 @@ pub fn create_verification_job(
user_info.check_privs(&auth_id, &config.acl_path(), PRIV_DATASTORE_VERIFY, false)?;
+ if !config.disable && config.schedule.is_none() {
+ param_bail!(
+ "schedule",
+ format_err!("job enabled but no schedule provided")
+ );
+ }
+
let _lock = verify::lock_config()?;
let (mut section_config, _digest) = verify::config()?;
@@ -140,6 +147,8 @@ pub enum DeletableProperty {
IgnoreVerified,
/// Delete the comment property.
Comment,
+ /// Unset the disable flag.
+ Disable,
/// Delete the job schedule.
Schedule,
/// Delete outdated after property.
@@ -220,6 +229,9 @@ pub fn update_verification_job(
DeletableProperty::Comment => {
data.comment = None;
}
+ DeletableProperty::Disable => {
+ data.disable = false;
+ }
DeletableProperty::Schedule => {
data.schedule = None;
}
@@ -262,6 +274,9 @@ pub fn update_verification_job(
if update.schedule.is_some() {
data.schedule = update.schedule;
}
+ if let Some(value) = update.disable {
+ data.disable = value;
+ }
if let Some(ns) = update.ns {
if !ns.is_root() {
data.ns = Some(ns);
diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index c66cffeb4..ce2629d0e 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -537,6 +537,10 @@ async fn schedule_datastore_garbage_collection() {
}
};
+ if store_config.gc_disable {
+ continue;
+ }
+
let event_str = match store_config.gc_schedule {
Some(event_str) => event_str,
None => continue,
@@ -689,6 +693,10 @@ async fn schedule_datastore_sync_jobs() {
None => continue,
};
+ if job_config.disable {
+ continue;
+ }
+
let worker_type = "syncjob";
if check_schedule(worker_type, &event_str, &job_id) {
let job = match Job::new(worker_type, &job_id) {
@@ -725,6 +733,10 @@ async fn schedule_datastore_verify_jobs() {
None => continue,
};
+ if job_config.disable {
+ continue;
+ }
+
let worker_type = "verificationjob";
let auth_id = Authid::root_auth_id().clone();
if check_schedule(worker_type, &event_str, &job_id) {
@@ -761,6 +773,10 @@ async fn schedule_tape_backup_jobs() {
None => continue,
};
+ if job_config.disable {
+ continue;
+ }
+
let worker_type = "tape-backup-job";
let auth_id = Authid::root_auth_id().clone();
if check_schedule(worker_type, &event_str, &job_id) {
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 7/9] fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (5 preceding siblings ...)
2026-11-04 14:53 ` [PATCH proxmox-backup v5 6/9] fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 9/9] ui: calendar-event: hide clear button if a schedule is required Jonas Theisen
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
This allows users to disable jobs from the UI using the "disable"
API parameter.
This also disallows the schedule from being empty, since disabling
jobs can now be done with the "disable" parameter.
To avoid user confusion and remain backwards compatible, the
setValues function automatically sets the "disable" parameter to
true if the schedule is currently empty, i.e. the job is disabled.
For this to work the value for the checkbox needs to be set
explicitely despite the bind, since the viewModel binds the value
asynchronously and such the GET could finish before that happend
which would lose the value from the config.
Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=7904
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
www/tape/window/TapeBackupJob.js | 37 +++++++++++++++++++++--
www/window/GCJobEdit.js | 52 +++++++++++++++++++++++++++++---
www/window/SyncJobEdit.js | 34 +++++++++++++++++++--
www/window/VerifyJobEdit.js | 33 ++++++++++++++++++--
4 files changed, 142 insertions(+), 14 deletions(-)
diff --git a/www/tape/window/TapeBackupJob.js b/www/tape/window/TapeBackupJob.js
index ad03b6076..4b431b8ad 100644
--- a/www/tape/window/TapeBackupJob.js
+++ b/www/tape/window/TapeBackupJob.js
@@ -46,6 +46,20 @@ Ext.define('PBS.TapeManagement.BackupJobEdit', {
},
},
+ viewModel: {
+ data: {
+ jobEnabled: true,
+ },
+ },
+
+ setValues: function (values) {
+ let me = this;
+ if (!values.schedule) {
+ values.disable = true;
+ }
+ me.callParent([values]);
+ },
+
items: {
xtype: 'tabpanel',
bodyPadding: 10,
@@ -66,8 +80,9 @@ Ext.define('PBS.TapeManagement.BackupJobEdit', {
delete values.delete;
}
- return values;
+ return PBS.Utils.enableToDisable(values);
},
+ onSetValues: (values) => PBS.Utils.disableToEnable(values),
cbind: {
isCreate: '{isCreate}', // pass it through
},
@@ -112,15 +127,31 @@ Ext.define('PBS.TapeManagement.BackupJobEdit', {
],
column2: [
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Enable'),
+ name: 'enable',
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Enable scheduled execution of this job.'),
+ },
+ bind: {
+ value: '{jobEnabled}',
+ },
+ uncheckedValue: false,
+ value: true, // value needs to be set despite bind for setValues to work
+ },
{
fieldLabel: gettext('Schedule'),
xtype: 'pbsCalendarEvent',
name: 'schedule',
- emptyText: gettext('none (disabled)'),
+ allowBlank: false,
cbind: {
- deleteEmpty: '{!isCreate}',
value: '{scheduleValue}',
},
+ bind: {
+ disabled: '{!jobEnabled}',
+ },
},
{
fieldLabel: gettext('Export Media-Set'),
diff --git a/www/window/GCJobEdit.js b/www/window/GCJobEdit.js
index 6afcf7de1..ae221726d 100644
--- a/www/window/GCJobEdit.js
+++ b/www/window/GCJobEdit.js
@@ -19,10 +19,52 @@ Ext.define('PBS.window.GCJobEdit', {
return {};
},
- items: {
- xtype: 'pbsCalendarEvent',
- name: 'gc-schedule',
- fieldLabel: gettext('GC Schedule'),
- emptyText: gettext('none (disabled)'),
+ viewModel: {
+ data: {
+ gcEnabled: true,
+ },
},
+
+ setValues: function (values) {
+ let me = this;
+ if (!values['gc-schedule']) {
+ values['gc-disable'] = true;
+ }
+
+ PBS.Utils.disableToEnable(values, 'gc-');
+ me.callParent([values]);
+ },
+
+ getValues: function (dirtyOnly) {
+ let me = this;
+ let values = me.callParent([dirtyOnly]);
+
+ return PBS.Utils.enableToDisable(values, 'gc-');
+ },
+
+ items: [
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Enable'),
+ name: 'gc-enable',
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Enable scheduled execution of this job.'),
+ },
+ bind: {
+ value: '{gcEnabled}',
+ },
+ uncheckedValue: false,
+ value: true, // value needs to be set despite bind for setValues to work
+ },
+ {
+ xtype: 'pbsCalendarEvent',
+ name: 'gc-schedule',
+ fieldLabel: gettext('GC Schedule'),
+ allowBlank: false,
+ bind: {
+ disabled: '{!gcEnabled}',
+ },
+ },
+ ],
});
diff --git a/www/window/SyncJobEdit.js b/www/window/SyncJobEdit.js
index 961ce2951..0f12c9d65 100644
--- a/www/window/SyncJobEdit.js
+++ b/www/window/SyncJobEdit.js
@@ -90,6 +90,12 @@ Ext.define('PBS.window.SyncJobEdit', {
},
},
+ viewModel: {
+ data: {
+ jobEnabled: true,
+ },
+ },
+
setValues: function (values) {
let me = this;
if (values.id && !values.remote) {
@@ -97,6 +103,10 @@ Ext.define('PBS.window.SyncJobEdit', {
} else {
values.location = 'remote';
}
+
+ if (!values.schedule) {
+ values.disable = true;
+ }
me.callParent([values]);
},
@@ -130,8 +140,10 @@ Ext.define('PBS.window.SyncJobEdit', {
PBS.Utils.delete_if_default(values, 'rate-out');
PBS.Utils.delete_if_default(values, 'remote');
}
- return values;
+
+ return PBS.Utils.enableToDisable(values);
},
+ onSetValues: (values) => PBS.Utils.disableToEnable(values),
cbind: {
isCreate: '{isCreate}', // pass it through
},
@@ -203,15 +215,31 @@ Ext.define('PBS.window.SyncJobEdit', {
deleteEmpty: '{!isCreate}',
},
},
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Enable'),
+ name: 'enable',
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Enable scheduled execution of this job.'),
+ },
+ bind: {
+ value: '{jobEnabled}',
+ },
+ uncheckedValue: false,
+ value: true, // value needs to be set despite bind for setValues to work
+ },
{
xtype: 'pbsCalendarEvent',
name: 'schedule',
fieldLabel: gettext('Sync Schedule'),
- emptyText: gettext('none'),
+ allowBlank: false,
cbind: {
- deleteEmpty: '{!isCreate}',
value: '{scheduleValue}',
},
+ bind: {
+ disabled: '{!jobEnabled}',
+ },
},
{
xtype: 'pmxBandwidthField',
diff --git a/www/window/VerifyJobEdit.js b/www/window/VerifyJobEdit.js
index b8d8b5810..4b3fdae87 100644
--- a/www/window/VerifyJobEdit.js
+++ b/www/window/VerifyJobEdit.js
@@ -34,6 +34,7 @@ Ext.define('PBS.window.VerifyJobEdit', {
ignoreVerified: true,
defaultReadThreads: null,
defaultVerifyThreads: null,
+ jobEnabled: true,
},
},
@@ -80,6 +81,14 @@ Ext.define('PBS.window.VerifyJobEdit', {
},
},
+ setValues: function (values) {
+ let me = this;
+ if (!values.schedule) {
+ values.disable = true;
+ }
+ me.callParent([values]);
+ },
+
items: {
xtype: 'inputpanel',
onGetValues: function (values) {
@@ -88,8 +97,10 @@ Ext.define('PBS.window.VerifyJobEdit', {
if (!values.id && me.up('pbsVerifyJobEdit').isCreate) {
values.id = 'v-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
}
- return values;
+
+ return PBS.Utils.enableToDisable(values);
},
+ onSetValues: (values) => PBS.Utils.disableToEnable(values),
column1: [
{
xtype: 'pmxDisplayEditField',
@@ -124,14 +135,30 @@ Ext.define('PBS.window.VerifyJobEdit', {
],
column2: [
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Enable'),
+ name: 'enable',
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Enable scheduled execution of this job.'),
+ },
+ bind: {
+ value: '{jobEnabled}',
+ },
+ uncheckedValue: false,
+ value: true, // value needs to be set despite bind for setValues to work
+ },
{
xtype: 'pbsCalendarEvent',
name: 'schedule',
fieldLabel: gettext('Schedule'),
- emptyText: gettext('none (disabled)'),
+ allowBlank: false,
cbind: {
value: '{scheduleValue}',
- deleteEmpty: '{!isCreate}',
+ },
+ bind: {
+ disabled: '{!jobEnabled}',
},
},
{
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (6 preceding siblings ...)
2026-11-04 14:53 ` [PATCH proxmox-backup v5 7/9] fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 9/9] ui: calendar-event: hide clear button if a schedule is required Jonas Theisen
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
This commit adds additional logic to prominently display if
a job was disabled on the respective overview page of sync,
verify, tape backup, GC and prune jobs by introducing a new
renderer.
If the job is disabled the text "Disabled" is shown instead of
the schedule which is still present in the config file.
For backwards compatibility if no schedule is defined the fallback
is still "none".
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
www/Utils.js | 7 +++++++
www/config/GCView.js | 3 ++-
www/config/PruneView.js | 1 +
www/config/SyncView.js | 2 ++
www/config/VerifyView.js | 2 ++
www/tape/BackupJobs.js | 2 ++
6 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/www/Utils.js b/www/Utils.js
index f4a27c13b..1202bc8fe 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -350,6 +350,13 @@ Ext.define('PBS.Utils', {
);
},
+ render_schedule: function (value, metaData, record) {
+ if (record.data.disable === true) {
+ return Proxmox.Utils.disabledText;
+ }
+ return value ? value : Proxmox.Utils.NoneText;
+ },
+
get_help_tool: function (blockid) {
let info = Proxmox.Utils.get_help_info(blockid);
if (info === undefined) {
diff --git a/www/config/GCView.js b/www/config/GCView.js
index 9fcc22344..533109bd7 100644
--- a/www/config/GCView.js
+++ b/www/config/GCView.js
@@ -5,6 +5,7 @@ Ext.define('pbs-gc-jobs-status', {
'upid',
'removed-bytes',
'pending-bytes',
+ 'disable',
'schedule',
'next-run',
'last-run-endtime',
@@ -181,7 +182,7 @@ Ext.define('PBS.config.GCJobView', {
header: gettext('Schedule'),
dataIndex: 'schedule',
hideable: false,
- renderer: (value) => (value ? value : Proxmox.Utils.NoneText),
+ renderer: PBS.Utils.render_schedule,
minWidth: 85,
flex: 1,
},
diff --git a/www/config/PruneView.js b/www/config/PruneView.js
index f38724687..c6fb750c7 100644
--- a/www/config/PruneView.js
+++ b/www/config/PruneView.js
@@ -234,6 +234,7 @@ Ext.define('PBS.config.PruneJobView', {
minWidth: 80,
flex: 1,
sortable: true,
+ renderer: PBS.Utils.render_schedule,
},
{
text: gettext('Keep'),
diff --git a/www/config/SyncView.js b/www/config/SyncView.js
index 386489120..499c5f2e2 100644
--- a/www/config/SyncView.js
+++ b/www/config/SyncView.js
@@ -8,6 +8,7 @@ Ext.define('pbs-sync-jobs-status', {
'remote-ns',
'store',
'ns',
+ 'disable',
'schedule',
'group-filter',
'next-run',
@@ -395,6 +396,7 @@ Ext.define('PBS.config.SyncJobView', {
minWidth: 80,
flex: 1,
sortable: true,
+ renderer: PBS.Utils.render_schedule,
},
{
header: gettext('Last Sync'),
diff --git a/www/config/VerifyView.js b/www/config/VerifyView.js
index 2c63f0d79..7b72e7a35 100644
--- a/www/config/VerifyView.js
+++ b/www/config/VerifyView.js
@@ -5,6 +5,7 @@ Ext.define('pbs-verify-jobs-status', {
'store',
'outdated-after',
'ignore-verified',
+ 'disable',
'schedule',
'next-run',
'last-run-upid',
@@ -253,6 +254,7 @@ Ext.define('PBS.config.VerifyJobView', {
maxWidth: 220,
minWidth: 80,
flex: 1,
+ renderer: PBS.Utils.render_schedule,
},
{
header: gettext('Last Verification'),
diff --git a/www/tape/BackupJobs.js b/www/tape/BackupJobs.js
index 68d808af5..7e36ad4d2 100644
--- a/www/tape/BackupJobs.js
+++ b/www/tape/BackupJobs.js
@@ -7,6 +7,7 @@ Ext.define('pbs-tape-backup-job-status', {
'drive',
'store',
'schedule',
+ 'disable',
'comment',
'group-filter',
{ name: 'eject-media', type: 'boolean' },
@@ -259,6 +260,7 @@ Ext.define('PBS.config.TapeBackupJobView', {
minWidth: 80,
flex: 1,
sortable: true,
+ renderer: PBS.Utils.render_schedule,
},
{
header: gettext('Last Backup'),
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH proxmox-backup v5 9/9] ui: calendar-event: hide clear button if a schedule is required
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (7 preceding siblings ...)
2026-11-04 14:53 ` [PATCH proxmox-backup v5 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs Jonas Theisen
@ 2026-11-04 14:53 ` Jonas Theisen
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-11-04 14:53 UTC (permalink / raw)
To: pbs-devel
Since the schedule fields of the job edit windows do not allow
empty inputs anymore, offering a clear button there is
unintuitive.
Only show the clear button if the field allows empty values. Fields
that may still be left empty, like the GC and prune schedules when
creating a datastore or the threshold reset schedule, keep it.
Also update the button's visibility when the value is set, not only
on change events. Initial values, like the default prune schedule
when creating a datastore, do not fire a change event, so the button
was previously only shown after the value had been changed manually.
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
www/form/CalendarEvent.js | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/www/form/CalendarEvent.js b/www/form/CalendarEvent.js
index b050da694..1ad0c0a87 100644
--- a/www/form/CalendarEvent.js
+++ b/www/form/CalendarEvent.js
@@ -59,17 +59,26 @@ Ext.define('PBS.form.CalendarEvent', {
weight: -1,
hidden: true,
handler: function () {
- this.triggers.clear.setVisible(false);
this.setValue('');
},
},
},
+ updateClearTrigger: function (value) {
+ let me = this;
+ let empty = Ext.isArray(value) ? !value.length : !value;
+ me.triggers.clear.setVisible(!empty && me.allowBlank);
+ },
+
+ // also called for the initial value, which does not fire a change event
+ setValue: function (value) {
+ let me = this;
+ me.updateClearTrigger(value);
+ return me.callParent([value]);
+ },
+
listeners: {
- change: function (field, value) {
- let canClear = (value ?? '') !== '';
- field.triggers.clear.setVisible(canClear);
- },
+ change: (field, value) => field.updateClearTrigger(value),
},
store: {
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-04 14:54 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 14:53 [PATCH proxmox{,-backup} v5 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox v5 2/9] pbs-api-types: prune job: make schedule optional Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 3/9] server: schedules: add 'disable' parameter to compute_schedule_status() Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 4/9] ui: prune job: align prune edit window to other "Enable" checkboxes Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 5/9] api: prune job: align prune job schedule to other jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 6/9] fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 7/9] fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs Jonas Theisen
2026-11-04 14:53 ` [PATCH proxmox-backup v5 9/9] ui: calendar-event: hide clear button if a schedule is required Jonas Theisen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.