From 8b91d3037cf6b3a2c1958a7b9a50f435eb42142e Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Sun, 3 May 2026 12:07:26 +0100 Subject: [PATCH] P2R-02 follow-up: Run-now works on disabled schedules with confirm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface the Run-now button on every schedule when the host is online, not just enabled ones. Disabled rows render the button as a non-primary style + a HX-confirm dialog ("This schedule is paused — running it now won't change that. Fire it once anyway?"); enabled rows keep the zero-friction primary button. Server-side, Run-now no longer short-circuits on !Enabled — it dispatches the source groups inline rather than via dispatchScheduledJob (which always bails on disabled schedules, since cron-tick semantics are different from explicit operator intent). The audit-log entry inside dispatchBackupForGroup still records every fire. --- internal/server/http/ui_schedules.go | 22 ++++++++++++++++++---- web/templates/pages/host_schedules.html | 21 ++++++++++++++++----- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/internal/server/http/ui_schedules.go b/internal/server/http/ui_schedules.go index 1d0c487..8352313 100644 --- a/internal/server/http/ui_schedules.go +++ b/internal/server/http/ui_schedules.go @@ -301,9 +301,8 @@ func (s *Server) handleUIScheduleRun(w stdhttp.ResponseWriter, r *stdhttp.Reques stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError) return } - if !sc.Enabled { - stdhttp.Error(w, "schedule is paused — enable it first or use per-group Run-now from the Sources tab", - stdhttp.StatusConflict) + if len(sc.SourceGroupIDs) == 0 { + stdhttp.Error(w, "this schedule has no source groups attached", stdhttp.StatusConflict) return } if s.deps.Hub == nil { @@ -316,9 +315,24 @@ func (s *Server) handleUIScheduleRun(w stdhttp.ResponseWriter, r *stdhttp.Reques stdhttp.StatusConflict) return } + + // Manual Run-now ignores Enabled. "Disabled" only suppresses + // cron-tick firing; an ad-hoc one-off run is a separate intent + // (and the dispatch is audit-logged inside dispatchBackupForGroup). + // We dispatch inline rather than calling dispatchScheduledJob, + // which short-circuits on !Enabled. ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() - s.dispatchScheduledJob(ctx, host.ID, conn, sid, time.Now().UTC()) + now := time.Now().UTC() + for _, gid := range sc.SourceGroupIDs { + g, gerr := s.deps.Store.GetSourceGroup(ctx, host.ID, gid) + if gerr != nil { + slog.Warn("ui schedule run: load source group", + "host_id", host.ID, "schedule_id", sid, "group_id", gid, "err", gerr) + continue + } + s.dispatchBackupForGroup(ctx, conn, host.ID, sid, g, now) + } if wantsHTML(r) { // HX-Redirect would jump to a single job log, but a multi-group diff --git a/web/templates/pages/host_schedules.html b/web/templates/pages/host_schedules.html index 541c45a..764ae2d 100644 --- a/web/templates/pages/host_schedules.html +++ b/web/templates/pages/host_schedules.html @@ -53,11 +53,22 @@ {{end}}
- {{if and $sc.Enabled (eq $host.Status "online")}} - + {{if eq $host.Status "online"}} + {{if $sc.Enabled}} + + {{else}} + + {{end}} + {{else}} + {{end}}