67ca769686
CI / Test (linux/amd64) (pull_request) Failing after 44s
CI / Lint (pull_request) Failing after 13s
CI / Build (windows/amd64) (pull_request) Successful in 19s
CI / Build (linux/amd64) (pull_request) Successful in 19s
CI / Build (linux/arm64) (pull_request) Successful in 25s
Schedules list: status (enabled/paused) + cron + source-group tags + actions (Run-now when enabled+online, Edit, Delete). Run-now reuses dispatchScheduledJob — same path real cron fires take, so each referenced source group runs as its own backup with its own tag. Falls back to a 409 if the agent is offline. Schedule new/edit form: cron input with five preset chips (quick-pick @hourly / nightly / 6h / weekly / monthly), source-group multi-pick rendered as styled checkbox cards (visual state tracks the underlying box via a tiny inline script), enabled toggle. No paths/excludes/retention/kind on the schedule itself — those live on source groups now. Server-side validation re-renders with the operator's input + ticked groups intact. Every successful mutation calls pushScheduleSetAsync. Adds .schd-row, .preset-chip, .picker styles.
117 lines
5.7 KiB
HTML
117 lines
5.7 KiB
HTML
{{define "title"}}{{.Title}}{{end}}
|
|
|
|
{{define "content"}}
|
|
{{template "host_chrome" .}}
|
|
{{$page := .Page}}
|
|
{{$host := $page.Host}}
|
|
{{$f := $page.Form}}
|
|
<div class="max-w-[1280px] mx-auto px-8 pb-24 pt-6">
|
|
|
|
<h1 class="text-[22px] font-medium tracking-[-0.005em] mt-1">
|
|
{{if $page.IsNew}}New schedule{{else}}Edit schedule{{end}}
|
|
</h1>
|
|
|
|
{{if $page.Error}}
|
|
<div class="mt-5 panel rounded-[6px] px-4 py-3 text-[13px]"
|
|
style="border-color: color-mix(in oklch, var(--bad), transparent 60%); background: color-mix(in oklch, var(--bad), transparent 92%); color: var(--ink);">
|
|
{{$page.Error}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<form method="post" action="{{$page.SaveAction}}" class="panel rounded-[7px] p-7 mt-6">
|
|
<div class="grid grid-cols-12 gap-6">
|
|
|
|
<div class="col-span-7">
|
|
<h3 class="text-[11.5px] font-semibold uppercase tracking-[0.08em] text-ink-mute mb-3.5">When</h3>
|
|
<label class="field-label" for="cron">Cron expression</label>
|
|
<input type="text" id="cron" name="cron" class="field mono" value="{{$f.CronExpr}}" required autofocus />
|
|
<div class="flex flex-wrap gap-1.5 mt-2.5" id="cron-presets">
|
|
{{range list "0 3 * * *" "@hourly" "0 */6 * * *" "0 3 * * 0" "0 3 1 * *"}}
|
|
<span class="preset-chip" data-cron="{{.}}">{{.}}</span>
|
|
{{end}}
|
|
</div>
|
|
<div class="field-help mt-2.5">
|
|
Standard 5-field cron with descriptors. Server validates with the same parser the agent uses to fire — what saves here is what runs.
|
|
</div>
|
|
|
|
<h3 class="text-[11.5px] font-semibold uppercase tracking-[0.08em] text-ink-mute mb-3.5 mt-6 pt-4 border-t border-line-soft">
|
|
What — pick one or more source groups
|
|
</h3>
|
|
{{if eq (len $page.AvailableGroups) 0}}
|
|
<div class="text-[12.5px] text-ink-mute leading-[1.6]">
|
|
This host has no source groups yet — <a href="/hosts/{{$host.ID}}/sources/new" class="text-accent underline">create one first</a>
|
|
so this schedule has something to back up.
|
|
</div>
|
|
{{else}}
|
|
<div class="grid grid-cols-1 gap-1.5" id="group-pickers">
|
|
{{range $page.AvailableGroups}}
|
|
{{$checked := index $page.SelectedGroupIDs .ID}}
|
|
<label class="picker {{if $checked}}checked{{end}}">
|
|
<input type="checkbox" name="source_group_ids" value="{{.ID}}" {{if $checked}}checked{{end}} />
|
|
<span class="check"></span>
|
|
<span class="mono text-ink flex-1">{{.Name}}</span>
|
|
<span class="text-[11.5px] text-ink-fade">
|
|
{{len .Includes}} include{{if ne (len .Includes) 1}}s{{end}} ·
|
|
{{.RetentionPolicy.Summary}}
|
|
</span>
|
|
</label>
|
|
{{end}}
|
|
</div>
|
|
<div class="field-help mt-2.5">
|
|
Each picked group runs as a separate <span class="mono text-ink-mid">restic backup</span> with its own tag — its own snapshot, its own retention. Pick multiple to fire them all on the same cron tick.
|
|
</div>
|
|
{{end}}
|
|
|
|
<h3 class="text-[11.5px] font-semibold uppercase tracking-[0.08em] text-ink-mute mb-3.5 mt-6 pt-4 border-t border-line-soft">Status</h3>
|
|
<label class="flex items-center gap-2.5 text-[13px] cursor-pointer">
|
|
<input type="checkbox" name="enabled" value="1" {{if $f.Enabled}}checked{{end}} class="w-3.5 h-3.5" />
|
|
<span>Enabled</span>
|
|
<span class="text-ink-fade">— uncheck to keep the row but stop firing.</span>
|
|
</label>
|
|
|
|
<div class="mt-6 pt-4 border-t border-line-soft flex gap-2">
|
|
<button type="submit" class="btn btn-primary btn-lg">{{if $page.IsNew}}Create schedule{{else}}Save changes{{end}}</button>
|
|
<a href="/hosts/{{$host.ID}}/schedules" class="btn btn-lg">Cancel</a>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="col-span-5 border-l border-line-soft pl-6">
|
|
<div class="text-[11px] text-ink-fade uppercase tracking-[0.1em] mb-3">No paths, no retention, no kind</div>
|
|
<p class="text-pretty text-[12.5px] text-ink-mid leading-[1.6]">
|
|
That stuff lives on source groups now. A schedule's only job is to be the cron expression and to point at the groups it should fire.
|
|
Change a group's retention, every schedule that points at it inherits the change without further edits.
|
|
</p>
|
|
<p class="text-pretty text-[12.5px] text-ink-mid leading-[1.6] mt-3">
|
|
<strong>Forget / prune / check are not schedule kinds anymore.</strong>
|
|
They run on host-level cadences from the
|
|
<a href="/hosts/{{$host.ID}}/repo" class="text-accent underline">Repo tab</a>.
|
|
</p>
|
|
<div class="panel rounded-[6px] px-4 py-3.5 mt-4" style="background: var(--bg);">
|
|
<div class="text-[11px] uppercase tracking-[0.08em] text-ink-fade">If the agent is offline at fire time</div>
|
|
<p class="text-[12px] text-ink-mid mt-1.5 leading-[1.55]">
|
|
Server retries per the group's retry policy (max attempts + exponential backoff).
|
|
</p>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
// Preset chip → fill cron input. Group picker → toggle the
|
|
// "checked" class so the visual state tracks the underlying box.
|
|
(function () {
|
|
var cronInput = document.getElementById('cron');
|
|
document.querySelectorAll('#cron-presets .preset-chip').forEach(function (chip) {
|
|
chip.addEventListener('click', function () { cronInput.value = chip.dataset.cron; });
|
|
});
|
|
document.querySelectorAll('#group-pickers .picker').forEach(function (label) {
|
|
var box = label.querySelector('input[type="checkbox"]');
|
|
box.addEventListener('change', function () {
|
|
label.classList.toggle('checked', box.checked);
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{{end}}
|