* [PATCH proxmox v4 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox v4 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-10-22 16:43 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.
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 93ccaf07..cf661589 100644
--- a/pbs-api-types/src/datastore.rs
+++ b/pbs-api-types/src/datastore.rs
@@ -460,6 +460,11 @@ pub const COUNTER_RESET_SCHEDULE_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,
@@ -524,6 +529,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>,
@@ -605,6 +614,7 @@ impl DataStoreConfig {
name,
path,
comment: None,
+ gc_disable: Default::default(),
gc_schedule: None,
gc_on_unmount: None,
prune_schedule: None,
@@ -1704,6 +1714,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 v4 2/9] pbs-api-types: prune job: make schedule optional
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox v4 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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 v4 3/9] server: schedules: add 'disable' parameter to compute_schedule_status()
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox v4 1/9] pbs-api-types: add 'disable' to {sync,verify,tape,gc} job schema and datastore config Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox v4 2/9] pbs-api-types: prune job: make schedule optional Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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.
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 v4 4/9] ui: prune job: align prune edit window to other "Enable" checkboxes
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (2 preceding siblings ...)
2026-10-22 16:43 ` [PATCH proxmox-backup v4 3/9] server: schedules: add 'disable' parameter to compute_schedule_status() Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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/window/PruneJobEdit.js | 32 ++++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)
diff --git a/www/window/PruneJobEdit.js b/www/window/PruneJobEdit.js
index a3dafde21..63f736570 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) {
@@ -115,14 +130,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,
},
],
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH proxmox-backup v4 5/9] api: prune job: align prune job schedule to other jobs
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (3 preceding siblings ...)
2026-10-22 16:43 ` [PATCH proxmox-backup v4 4/9] ui: prune job: align prune edit window to other "Enable" checkboxes Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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 e7028480c..a8b003601 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 v4 6/9] fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (4 preceding siblings ...)
2026-10-22 16:43 ` [PATCH proxmox-backup v4 5/9] api: prune job: align prune job schedule to other jobs Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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 | 18 +++++++++++++++++-
src/api2/config/tape_backup_job.rs | 17 ++++++++++++++++-
src/api2/config/verify.rs | 17 ++++++++++++++++-
src/bin/proxmox-backup-proxy.rs | 16 ++++++++++++++++
6 files changed, 75 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 a8b003601..f3644149e 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.
@@ -493,6 +495,9 @@ pub fn update_datastore(
DeletableProperty::Comment => {
data.comment = None;
}
+ DeletableProperty::GcDisable => {
+ data.gc_disable = false;
+ }
DeletableProperty::GcSchedule => {
data.gc_schedule = None;
}
@@ -571,6 +576,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 beed6e7e3..b05fe8b5b 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.
@@ -499,6 +508,9 @@ pub fn update_sync_job(
DeletableProperty::Comment => {
data.comment = None;
}
+ DeletableProperty::Disable => {
+ data.disable = false;
+ }
DeletableProperty::Schedule => {
data.schedule = None;
}
@@ -702,6 +714,9 @@ pub fn update_sync_job(
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;
}
@@ -847,6 +862,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,
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 v4 7/9] fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (5 preceding siblings ...)
2026-10-22 16:43 ` [PATCH proxmox-backup v4 6/9] fix #7904: api: implement "disable" for {sync,verify,tape,GC} jobs Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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.
Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=7904
Signed-off-by: Jonas Theisen <j.theisen@proxmox.com>
---
www/tape/window/TapeBackupJob.js | 43 ++++++++++++++++++++++--
www/window/GCJobEdit.js | 56 +++++++++++++++++++++++++++++---
www/window/SyncJobEdit.js | 39 ++++++++++++++++++++--
www/window/VerifyJobEdit.js | 38 ++++++++++++++++++++--
4 files changed, 165 insertions(+), 11 deletions(-)
diff --git a/www/tape/window/TapeBackupJob.js b/www/tape/window/TapeBackupJob.js
index ad03b6076..23ba4bd80 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,6 +80,15 @@ Ext.define('PBS.TapeManagement.BackupJobEdit', {
delete values.delete;
}
+ values.disable = !values.enable;
+ delete values.enable;
+
+ return values;
+ },
+ onSetValues: function (values) {
+ values.enable = !values.disable;
+ delete values.disable;
+
return values;
},
cbind: {
@@ -112,15 +135,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,
+ },
{
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..c9d7d6bf3 100644
--- a/www/window/GCJobEdit.js
+++ b/www/window/GCJobEdit.js
@@ -19,10 +19,56 @@ 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;
+ }
+
+ values['gc-enable'] = !values['gc-disable'];
+ delete values['gc-disable'];
+ me.callParent([values]);
+ },
+
+ getValues: function (dirtyOnly) {
+ let me = this;
+ let values = me.callParent([dirtyOnly]);
+
+ values['gc-disable'] = !values['gc-enable'];
+ delete values['gc-enable'];
+
+ return values;
+ },
+
+ 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,
+ },
+ {
+ 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..e5075b3ab 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,6 +140,15 @@ Ext.define('PBS.window.SyncJobEdit', {
PBS.Utils.delete_if_default(values, 'rate-out');
PBS.Utils.delete_if_default(values, 'remote');
}
+ values.disable = !values.enable;
+ delete values.enable;
+
+ return values;
+ },
+ onSetValues: function (values) {
+ values.enable = !values.disable;
+ delete values.disable;
+
return values;
},
cbind: {
@@ -203,15 +222,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,
+ },
{
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..21ea064a7 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,6 +97,15 @@ Ext.define('PBS.window.VerifyJobEdit', {
if (!values.id && me.up('pbsVerifyJobEdit').isCreate) {
values.id = 'v-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
}
+ values.disable = !values.enable;
+ delete values.enable;
+
+ return values;
+ },
+ onSetValues: function (values) {
+ values.enable = !values.disable;
+ delete values.disable;
+
return values;
},
column1: [
@@ -124,14 +142,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,
+ },
{
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 v4 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (6 preceding siblings ...)
2026-10-22 16:43 ` [PATCH proxmox-backup v4 7/9] fix #7904: ui: add "Enable" checkbox to {sync,verify,tape,GC} job edit Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
2026-10-22 16:43 ` [PATCH proxmox-backup v4 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-10-22 16:43 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 d6bfd459e..a67fabf53 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -335,6 +335,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 v4 9/9] ui: calendar-event: hide clear button if a schedule is required
2026-10-22 16:43 [PATCH proxmox{,-backup} v4 0/9] fix #7904: Implement Enable checkboxes for all jobs Jonas Theisen
` (7 preceding siblings ...)
2026-10-22 16:43 ` [PATCH proxmox-backup v4 8/9] ui: job overview: show "Disabled" as schedule of disabled jobs Jonas Theisen
@ 2026-10-22 16:43 ` Jonas Theisen
8 siblings, 0 replies; 10+ messages in thread
From: Jonas Theisen @ 2026-10-22 16:43 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