e22b41d452
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.
40 lines
1.7 KiB
HTML
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}}
|