Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 554ce2b93a | |||
| 3f3b977fb0 |
@@ -92,6 +92,16 @@ func DisplayStats(rt *domain.JobRuntime) string {
|
|||||||
rt.RunCount, rt.FailCount, rt.LastDurationMS, rt.AvgDurationMS, rt.MaxDurationMS)
|
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,
|
// DisplayIndex returns the position of jobIndex in the given slice of indexes,
|
||||||
// or 0 if not found.
|
// or 0 if not found.
|
||||||
func DisplayIndex(indexes []int, jobIndex int) int {
|
func DisplayIndex(indexes []int, jobIndex int) int {
|
||||||
|
|||||||
+14
-3
@@ -67,7 +67,6 @@ func (s *Service) RunDue(now time.Time) {
|
|||||||
var startErr error
|
var startErr error
|
||||||
if !s.paused {
|
if !s.paused {
|
||||||
sequential := s.store.Config.ExecutionMode == domain.ExecutionModeSequential
|
sequential := s.store.Config.ExecutionMode == domain.ExecutionModeSequential
|
||||||
queue := s.store.Config.OverlapPolicy == domain.OverlapPolicyQueue
|
|
||||||
running := s.anyRunningLocked()
|
running := s.anyRunningLocked()
|
||||||
for index := range s.jobs {
|
for index := range s.jobs {
|
||||||
job := &s.jobs[index]
|
job := &s.jobs[index]
|
||||||
@@ -77,8 +76,9 @@ func (s *Service) RunDue(now time.Time) {
|
|||||||
}
|
}
|
||||||
if runtime.LastState == "Running" {
|
if runtime.LastState == "Running" {
|
||||||
// The job came due again while its own run is still in flight.
|
// The job came due again while its own run is still in flight.
|
||||||
// Apply the overlap policy and step past this occurrence.
|
// Apply the effective overlap policy and step past this
|
||||||
if queue {
|
// occurrence.
|
||||||
|
if s.effectiveOverlapPolicy(job) == domain.OverlapPolicyQueue {
|
||||||
runtime.Pending = true
|
runtime.Pending = true
|
||||||
}
|
}
|
||||||
s.advanceNextDueLocked(job, runtime, now)
|
s.advanceNextDueLocked(job, runtime, now)
|
||||||
@@ -165,6 +165,17 @@ func (s *Service) executeRun(ctx context.Context, jobCopy domain.Job, trigger st
|
|||||||
s.emit(JobChanged{JobID: jobCopy.ID})
|
s.emit(JobChanged{JobID: jobCopy.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// effectiveOverlapPolicy resolves the overlap policy that actually governs a
|
||||||
|
// job: the job's own value when set, otherwise the global Config default. An
|
||||||
|
// empty Job.OverlapPolicy means "inherit the global default", which is why
|
||||||
|
// normalizeJobs leaves it empty rather than backfilling the configured value.
|
||||||
|
func (s *Service) effectiveOverlapPolicy(job *domain.Job) domain.OverlapPolicy {
|
||||||
|
if policy := domain.OverlapPolicy(strings.TrimSpace(job.OverlapPolicy)); policy != "" {
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
return s.store.Config.OverlapPolicy
|
||||||
|
}
|
||||||
|
|
||||||
// anyRunningLocked reports whether any loaded job is currently in the "Running"
|
// anyRunningLocked reports whether any loaded job is currently in the "Running"
|
||||||
// state. It backs the sequential-mode guards in RunNow and RunDue. The caller
|
// state. It backs the sequential-mode guards in RunNow and RunDue. The caller
|
||||||
// must hold mu.
|
// must hold mu.
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import (
|
|||||||
"fyne.io/fyne/v2/widget"
|
"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
|
// 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
|
// with the populated job only when the user clicks Save and all fields pass
|
||||||
// validation.
|
// validation.
|
||||||
@@ -40,6 +44,15 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) {
|
|||||||
startOnly.SetChecked(current.StartOnly)
|
startOnly.SetChecked(current.StartOnly)
|
||||||
enabled := widget.NewCheck("Enabled", nil)
|
enabled := widget.NewCheck("Enabled", nil)
|
||||||
enabled.SetChecked(current.Enabled)
|
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(
|
form := dialog.NewForm(
|
||||||
title,
|
title,
|
||||||
@@ -52,6 +65,7 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) {
|
|||||||
widget.NewFormItem("Command", commandRow),
|
widget.NewFormItem("Command", commandRow),
|
||||||
widget.NewFormItem("Arguments", argumentsEntry),
|
widget.NewFormItem("Arguments", argumentsEntry),
|
||||||
widget.NewFormItem("", startOnly),
|
widget.NewFormItem("", startOnly),
|
||||||
|
widget.NewFormItem("Overlap policy", overlapSelect),
|
||||||
widget.NewFormItem("", enabled),
|
widget.NewFormItem("", enabled),
|
||||||
},
|
},
|
||||||
func(saved bool) {
|
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.Arguments = strings.TrimSpace(argumentsEntry.Text)
|
||||||
current.StartOnly = startOnly.Checked
|
current.StartOnly = startOnly.Checked
|
||||||
current.Enabled = enabled.Checked
|
current.Enabled = enabled.Checked
|
||||||
|
current.OverlapPolicy = overlapSelect.Selected
|
||||||
|
if current.OverlapPolicy == overlapPolicyInherit {
|
||||||
|
current.OverlapPolicy = ""
|
||||||
|
}
|
||||||
// The dialog only edits durable configuration. Runtime status is
|
// The dialog only edits durable configuration. Runtime status is
|
||||||
// initialized (new jobs) or updated (edits) by the caller against the
|
// initialized (new jobs) or updated (edits) by the caller against the
|
||||||
// runtime map, keyed by job ID.
|
// runtime map, keyed by job ID.
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
nextRunLabel := newJobDetailLabel(selectedRuntime.NextRun)
|
nextRunLabel := newJobDetailLabel(selectedRuntime.NextRun)
|
||||||
stateLabel := newJobDetailLabel(selectedRuntime.LastState)
|
stateLabel := newJobDetailLabel(selectedRuntime.LastState)
|
||||||
statsLabel := newJobDetailLabel(app.DisplayStats(selectedRuntime))
|
statsLabel := newJobDetailLabel(app.DisplayStats(selectedRuntime))
|
||||||
|
overlapPolicyLabel := newJobDetailLabel(app.DisplayOverlapPolicy(jobs[selected], svc.Store().Config.OverlapPolicy))
|
||||||
schedulerState := widget.NewLabel("Scheduler running")
|
schedulerState := widget.NewLabel("Scheduler running")
|
||||||
commandOutput := widget.NewTextGrid()
|
commandOutput := widget.NewTextGrid()
|
||||||
commandOutput.SetText(selectedRuntime.Output)
|
commandOutput.SetText(selectedRuntime.Output)
|
||||||
@@ -112,6 +113,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
nextRunLabel.SetText("")
|
nextRunLabel.SetText("")
|
||||||
stateLabel.SetText("")
|
stateLabel.SetText("")
|
||||||
statsLabel.SetText("")
|
statsLabel.SetText("")
|
||||||
|
overlapPolicyLabel.SetText("")
|
||||||
commandOutput.SetText("")
|
commandOutput.SetText("")
|
||||||
selectedLogs = nil
|
selectedLogs = nil
|
||||||
return
|
return
|
||||||
@@ -125,6 +127,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
commandLabel.SetText(current.Command)
|
commandLabel.SetText(current.Command)
|
||||||
argumentsLabel.SetText(app.DisplayArguments(current.Arguments))
|
argumentsLabel.SetText(app.DisplayArguments(current.Arguments))
|
||||||
runModeLabel.SetText(app.DisplayRunMode(current))
|
runModeLabel.SetText(app.DisplayRunMode(current))
|
||||||
|
overlapPolicyLabel.SetText(app.DisplayOverlapPolicy(current, svc.Store().Config.OverlapPolicy))
|
||||||
lastRunLabel.SetText(rt.LastRun)
|
lastRunLabel.SetText(rt.LastRun)
|
||||||
nextRunLabel.SetText(rt.NextRun)
|
nextRunLabel.SetText(rt.NextRun)
|
||||||
stateLabel.SetText(rt.LastState)
|
stateLabel.SetText(rt.LastState)
|
||||||
@@ -356,6 +359,7 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
detailRow("Command", commandLabel),
|
detailRow("Command", commandLabel),
|
||||||
detailRow("Arguments", argumentsLabel),
|
detailRow("Arguments", argumentsLabel),
|
||||||
detailRow("Run mode", runModeLabel),
|
detailRow("Run mode", runModeLabel),
|
||||||
|
detailRow("Overlap policy", overlapPolicyLabel),
|
||||||
detailRow("Last run", lastRunLabel),
|
detailRow("Last run", lastRunLabel),
|
||||||
detailRow("Next run", nextRunLabel),
|
detailRow("Next run", nextRunLabel),
|
||||||
detailRow("State", stateLabel),
|
detailRow("State", stateLabel),
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
widget.NewLabelWithStyle("Queue", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Queue", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
settingsRow("Execution mode", container.New(minWidthLayout{width: settingsControlWidth}, executionModeSelect)),
|
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.NewSeparator(),
|
||||||
widget.NewLabelWithStyle("Storage", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Storage", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
settingsRow("Config YAML", widget.NewLabel(store.Paths.ConfigPath)),
|
settingsRow("Config YAML", widget.NewLabel(store.Paths.ConfigPath)),
|
||||||
|
|||||||
Reference in New Issue
Block a user