Files
steve 65a0134101 P3 sweep fixes: snap-row CSS, tree expand, --no-ownership drop, target path
Bug fixes from the Playwright sweep against the live smoke server:

1. Snapshot-picker layout. The .snap-row class was used in the wireframe
   but never landed in web/styles/input.css; rows rendered as vertical
   blocks instead of a 6-column grid. Added the token (mirrors host-row
   shape with restore-specific column widths).

2. Tree expansion. hx-target='closest .tree-row + .tree-children' isn't
   a valid HTMX selector — modifiers don't chain. Replaced HTMX-driven
   expansion with a small window.__rmTreeToggle helper that uses plain
   fetch + .tree-pair wrapper structure for trivial sibling lookup.
   Caches loaded state per node.

3. --no-ownership flag dropped. Restic 0.17 introduced --no-ownership;
   0.16 rejects it ('unknown flag') before doing any work. Since the
   agent runs as root in the systemd unit, restored files keep their
   original uid/gid either way and the parent dir is root-owned, so
   the 'cp without sudo' rationale doesn't hold. Drop the flag entirely.

4. Default target dir moved to /var/lib/restic-manager/restore. The
   systemd unit pins ReadWritePaths to /etc/restic-manager +
   /var/lib/restic-manager (with ProtectSystem=strict making the rest
   of /var read-only); writes to /var/restic-restore failed with
   'read-only file system'.

5. Confirm summary HTML escaping. defaultTarget JS literal evaluates
   to a string with literal angle brackets; insertion into innerHTML
   must escape them. Added an inline HTML-escape pass.

tasks.md ticked for the Restore sub-phase with a sweep summary
covering the live end-to-end test.
2026-05-04 15:57:42 +01:00

40 lines
1.7 KiB
HTML

{{define "tree_node"}}
{{$page := .Page}}
{{if $page.Error}}
<div class="px-3 py-2 mono text-[12px] text-bad">error: {{$page.Error}}</div>
{{else}}
<div class="flex items-center gap-2 px-3 py-1.5 text-[12px] text-ink-mute border-b border-line-soft">
<span class="mono text-ink-mid">{{$page.Path}}</span>
{{if not $page.Children}}
<span class="text-ink-fade ml-auto mono text-[11px]">empty directory</span>
{{end}}
</div>
{{range $page.Children}}
<div class="tree-pair">
<div class="grid items-center gap-2 px-3 py-[5px] mono text-[12.5px] border-b border-line-soft"
style="grid-template-columns: 14px 16px auto 1fr auto;">
{{if .IsDir}}
<button type="button"
class="tree-toggle text-ink-mute text-[10px] cursor-pointer"
data-tree-url="/hosts/{{$page.HostID}}/restore/tree?snapshot={{$page.SnapshotID}}&path={{.Path}}"
data-loaded="false"
onclick="window.__rmTreeToggle(this)"></button>
{{else}}
<span class="text-ink-fade text-center">·</span>
{{end}}
<label class="cursor-pointer flex items-center justify-center">
<input type="checkbox" name="paths" value="{{.Path}}"
class="w-[13px] h-[13px] cursor-pointer" />
</label>
<span class="{{if .IsDir}}text-ink{{else}}text-ink-mid{{end}}">{{.Name}}{{if .IsDir}}/{{end}}</span>
<span></span>
<span class="text-[11px] text-ink-fade">{{if not .IsDir}}{{if .Size}}{{bytes .Size}}{{else}}—{{end}}{{end}}</span>
</div>
{{if .IsDir}}
<div class="tree-children hidden pl-5 border-l border-line-soft ml-5"></div>
{{end}}
</div>
{{end}}
{{end}}
{{end}}