diff --git a/src/app/format.go b/src/app/format.go index 4a33ef6..103d677 100644 --- a/src/app/format.go +++ b/src/app/format.go @@ -92,6 +92,16 @@ func DisplayStats(rt *domain.JobRuntime) string { rt.RunCount, rt.FailCount, rt.LastDurationMS, rt.AvgDurationMS, rt.MaxDurationMS) } +// DisplayOverlapPolicy formats a job's effective overlap policy for the details +// panel. When the job has its own policy it is shown as-is; when empty (inherit +// global), the global default is shown with "(global default)" appended. +func DisplayOverlapPolicy(job domain.Job, globalPolicy domain.OverlapPolicy) string { + if p := domain.OverlapPolicy(strings.TrimSpace(job.OverlapPolicy)); p != "" { + return string(p) + } + return string(globalPolicy) + " (global default)" +} + // DisplayIndex returns the position of jobIndex in the given slice of indexes, // or 0 if not found. func DisplayIndex(indexes []int, jobIndex int) int { diff --git a/src/ui/job_dialog.go b/src/ui/job_dialog.go index 333bcac..90e6391 100644 --- a/src/ui/job_dialog.go +++ b/src/ui/job_dialog.go @@ -13,6 +13,10 @@ import ( "fyne.io/fyne/v2/widget" ) +// overlapPolicyInherit is the display label used when a job should inherit the +// global overlap policy. It maps to an empty Job.OverlapPolicy on save. +const overlapPolicyInherit = "(Use global default)" + // showJobDialog opens a create/edit form for a single job. onSave is called // with the populated job only when the user clicks Save and all fields pass // validation. @@ -40,6 +44,15 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) { startOnly.SetChecked(current.StartOnly) enabled := widget.NewCheck("Enabled", nil) enabled.SetChecked(current.Enabled) + overlapSelect := widget.NewSelect( + []string{overlapPolicyInherit, string(domain.OverlapPolicySkip), string(domain.OverlapPolicyQueue)}, + nil, + ) + overlapSelected := overlapPolicyInherit + if current.OverlapPolicy != "" { + overlapSelected = current.OverlapPolicy + } + overlapSelect.SetSelected(overlapSelected) form := dialog.NewForm( title, @@ -52,6 +65,7 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) { widget.NewFormItem("Command", commandRow), widget.NewFormItem("Arguments", argumentsEntry), widget.NewFormItem("", startOnly), + widget.NewFormItem("Overlap policy", overlapSelect), widget.NewFormItem("", enabled), }, func(saved bool) { @@ -75,6 +89,10 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) { current.Arguments = strings.TrimSpace(argumentsEntry.Text) current.StartOnly = startOnly.Checked current.Enabled = enabled.Checked + current.OverlapPolicy = overlapSelect.Selected + if current.OverlapPolicy == overlapPolicyInherit { + current.OverlapPolicy = "" + } // The dialog only edits durable configuration. Runtime status is // initialized (new jobs) or updated (edits) by the caller against the // runtime map, keyed by job ID. diff --git a/src/ui/jobs_view.go b/src/ui/jobs_view.go index b741ed4..3562c16 100644 --- a/src/ui/jobs_view.go +++ b/src/ui/jobs_view.go @@ -76,6 +76,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) { nextRunLabel := newJobDetailLabel(selectedRuntime.NextRun) stateLabel := newJobDetailLabel(selectedRuntime.LastState) statsLabel := newJobDetailLabel(app.DisplayStats(selectedRuntime)) + overlapPolicyLabel := newJobDetailLabel(app.DisplayOverlapPolicy(jobs[selected], svc.Store().Config.OverlapPolicy)) schedulerState := widget.NewLabel("Scheduler running") commandOutput := widget.NewTextGrid() commandOutput.SetText(selectedRuntime.Output) @@ -112,6 +113,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) { nextRunLabel.SetText("") stateLabel.SetText("") statsLabel.SetText("") + overlapPolicyLabel.SetText("") commandOutput.SetText("") selectedLogs = nil return @@ -125,6 +127,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) { commandLabel.SetText(current.Command) argumentsLabel.SetText(app.DisplayArguments(current.Arguments)) runModeLabel.SetText(app.DisplayRunMode(current)) + overlapPolicyLabel.SetText(app.DisplayOverlapPolicy(current, svc.Store().Config.OverlapPolicy)) lastRunLabel.SetText(rt.LastRun) nextRunLabel.SetText(rt.NextRun) stateLabel.SetText(rt.LastState) @@ -356,6 +359,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) { detailRow("Command", commandLabel), detailRow("Arguments", argumentsLabel), detailRow("Run mode", runModeLabel), + detailRow("Overlap policy", overlapPolicyLabel), detailRow("Last run", lastRunLabel), detailRow("Next run", nextRunLabel), detailRow("State", stateLabel), diff --git a/src/ui/settings_view.go b/src/ui/settings_view.go index cd1439a..9a50b5a 100644 --- a/src/ui/settings_view.go +++ b/src/ui/settings_view.go @@ -126,7 +126,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject { widget.NewSeparator(), widget.NewLabelWithStyle("Queue", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), settingsRow("Execution mode", container.New(minWidthLayout{width: settingsControlWidth}, executionModeSelect)), - settingsRow("Overlap policy", container.New(minWidthLayout{width: settingsControlWidth}, overlapPolicySelect)), + settingsRow("Default overlap policy", container.New(minWidthLayout{width: settingsControlWidth}, overlapPolicySelect)), widget.NewSeparator(), widget.NewLabelWithStyle("Storage", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), settingsRow("Config YAML", widget.NewLabel(store.Paths.ConfigPath)),