ui: dashboard hosts-behind tile + filter

- Add ?updates=behind query filter and the matching dashboardFilter
  field; round-trips through encode/parse.
- Compute UpdatesBehind on the dashboard view-model (online + version
  trailing the server) and surface as an amber hero tile that links
  to the filtered list.
- Test exercise covering the new filter case.
This commit is contained in:
2026-05-06 22:20:54 +01:00
parent 9bcd8bc5fe
commit 39304b08d0
2 changed files with 28 additions and 0 deletions
@@ -11,6 +11,7 @@ import (
"time"
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
"gitea.dcglab.co.uk/steve/restic-manager/internal/version"
)
func makeFilterHosts() []store.Host {
@@ -98,6 +99,23 @@ func TestSortDashboardHostsColumns(t *testing.T) {
}
}
// TestFilterAndSortDashboardUpdatesBehind: ?updates=behind narrows
// to hosts whose agent_version is non-empty AND != server's version.
func TestFilterAndSortDashboardUpdatesBehind(t *testing.T) {
t.Parallel()
hosts := []store.Host{
{ID: "01a", Name: "alpha", AgentVersion: "v0.0.1", Status: "online"},
{ID: "01b", Name: "bravo", AgentVersion: version.Version, Status: "online"},
{ID: "01c", Name: "charlie", AgentVersion: "", Status: "online"}, // never seen
{ID: "01d", Name: "delta", AgentVersion: "v0.0.1", Status: "offline"},
}
got := filterAndSortDashboardHosts(hosts, dashboardFilter{Updates: "behind", Sort: "name", Dir: "asc"})
// alpha + delta both behind; bravo (current) and charlie (empty) excluded.
if len(got) != 2 || got[0].Name != "alpha" || got[1].Name != "delta" {
t.Errorf("updates=behind: got %v", namesOf(got))
}
}
// TestParseDashboardFilterDefaults: empty query gives sort=name asc.
func TestParseDashboardFilterDefaults(t *testing.T) {
t.Parallel()
+10
View File
@@ -66,6 +66,16 @@
</div>
</div>
{{/* ---------- Hosts-behind hero tile (P6-18) ---------- */}}
{{if gt $page.UpdatesBehind 0}}
<div class="pt-4">
<a href="?updates=behind" class="hero-tile hero-tile--amber" style="display:inline-flex;">
<span class="hero-num">{{$page.UpdatesBehind}}</span>
<span class="hero-label">{{if eq $page.UpdatesBehind 1}}host behind{{else}}hosts behind{{end}} · review →</span>
</a>
</div>
{{end}}
{{/* ---------- Pending hosts (announce-and-approve queue) ---------- */}}
{{if gt (len $page.PendingHosts) 0}}
<div class="pt-6">