Files
restic-manager/web/templates/pages/schedule_edit.html
T
steve 54528b9b15 P2R-02 follow-up: clickable rows on Sources/Schedules + cron-preset tooltips
Aligns Sources and Schedules tab rows with the dashboard's row-click
UX: whole-row click navigates to the row's edit page (mirroring
.host-row.clickable). Drops the redundant Edit buttons; Run-now and
Delete remain in .row-action cells that sit above the row-link
overlay via z-index.

Schedule edit form's cron preset chips now carry human-readable
title= tooltips ("Every day at 03:00", "Every Sunday at 03:00", etc).

tasks.md gets a binding row-design rule covering all current and
future list-row templates, and the P2R-02 entry is split into the
six slices already agreed with the operator (slices 1–3 marked
done, 4 next).
2026-05-03 12:01:55 +01:00

124 lines
6.2 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">
<span class="preset-chip" data-cron="0 3 * * *"
title="Every day at 03:00">0 3 * * *</span>
<span class="preset-chip" data-cron="@hourly"
title="Every hour, on the hour (00 minutes)">@hourly</span>
<span class="preset-chip" data-cron="0 */6 * * *"
title="Every 6 hours, on the hour (00:00, 06:00, 12:00, 18:00)">0 */6 * * *</span>
<span class="preset-chip" data-cron="0 3 * * 0"
title="Every Sunday at 03:00">0 3 * * 0</span>
<span class="preset-chip" data-cron="0 3 1 * *"
title="The 1st of every month at 03:00">0 3 1 * *</span>
</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}}