panel-redesign: the health cards of Overview carry no anchor

Follows the design commit before it: Machine, Processes, TLS certificate, Milter sockets and Reverse DNS lead to /server/health as it opens; so does the link in the Needs attention line, which takes its address from the card.

  go vet ./internal/web/...: ok
  go test -count=1 ./internal/web/...: ok

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-11 14:14:17 +03:00
co-authored by Claude Opus 5.5
parent ddde177776
commit 5af4d6c368
6 changed files with 22 additions and 22 deletions
@@ -201,7 +201,7 @@ func TestOverviewPageShowsVerdictsAndDomains(t *testing.T) {
} }
for _, want := range []string{ for _, want := range []string{
`<h1 class="title is-3">`, `class="sp-postmark`, `<p class="sp-kicker">mail.example.org</p>`, `<h1 class="title is-3">`, `class="sp-postmark`, `<p class="sp-kicker">mail.example.org</p>`,
`href="/server/health#certificate"`, `href="/server/health#sockets"`, `href="/outbound/queue"`, `href="/server/health"`, `href="/server/health"`, `href="/outbound/queue"`,
`<h2>Outbound domains</h2>`, `<a href="/outbound/domains/1"><strong>example.org</strong></a>`, `<h2>Outbound domains</h2>`, `<a href="/outbound/domains/1"><strong>example.org</strong></a>`,
"DKIM", "SPF", "DMARC", "1\u00a0msg", `<th>30 days</th>`, "DKIM", "SPF", "DMARC", "1\u00a0msg", `<th>30 days</th>`,
`href="/server/health"`, `href="/server/health"`,
+5 -5
View File
@@ -326,13 +326,13 @@ func healthCards(c map[string]any) []view.HealthCard {
} }
return []view.HealthCard{ return []view.HealthCard{
card("Machine", "ti-cpu", "/server/health#machine", machine.Status, cpu, ram), card("Machine", "ti-cpu", "/server/health", machine.Status, cpu, ram),
card("Processes", "ti-server-cog", "/server/health#processes", c["ProcessStatus"].(health.Status), procValue, procSub), card("Processes", "ti-server-cog", "/server/health", c["ProcessStatus"].(health.Status), procValue, procSub),
card("TLS certificate", "ti-certificate", "/server/health#certificate", cert.Status, certValue, cert.Subject), card("TLS certificate", "ti-certificate", "/server/health", cert.Status, certValue, cert.Subject),
card("Queue", "ti-stack-2", "/outbound/queue", c["QueueStatus"].(health.Status), queueValue, "outbound"), card("Queue", "ti-stack-2", "/outbound/queue", c["QueueStatus"].(health.Status), queueValue, "outbound"),
card("Milter sockets", "ti-plug-connected", "/server/health#sockets", c["SocketStatus"].(health.Status), card("Milter sockets", "ti-plug-connected", "/server/health", c["SocketStatus"].(health.Status),
fmt.Sprintf("%d of %d answering", answering, len(sockets)), socketSub), fmt.Sprintf("%d of %d answering", answering, len(sockets)), socketSub),
card("Reverse DNS", "ti-arrows-exchange", "/server/health#hostname", ptr.Status, ptrValue, c["Hostname"].(string)), card("Reverse DNS", "ti-arrows-exchange", "/server/health", ptr.Status, ptrValue, c["Hostname"].(string)),
} }
} }
@@ -38,12 +38,12 @@ func checksFixture() map[string]any {
func TestHealthCards(t *testing.T) { func TestHealthCards(t *testing.T) {
cards := healthCards(checksFixture()) cards := healthCards(checksFixture())
want := []struct{ name, level, value, sub, href string }{ want := []struct{ name, level, value, sub, href string }{
{"Machine", "warn", "No reading", "", "/server/health#machine"}, {"Machine", "warn", "No reading", "", "/server/health"},
{"Processes", "fail", "1 of 2 running", "postfix", "/server/health#processes"}, {"Processes", "fail", "1 of 2 running", "postfix", "/server/health"},
{"TLS certificate", "warn", "Expires in 12 days", "mail.example.org", "/server/health#certificate"}, {"TLS certificate", "warn", "Expires in 12 days", "mail.example.org", "/server/health"},
{"Queue", "", "Empty", "outbound", "/outbound/queue"}, {"Queue", "", "Empty", "outbound", "/outbound/queue"},
{"Milter sockets", "warn", "1 of 2 answering", "send-log", "/server/health#sockets"}, {"Milter sockets", "warn", "1 of 2 answering", "send-log", "/server/health"},
{"Reverse DNS", "", "Forward = reverse", "mail.example.org", "/server/health#hostname"}, {"Reverse DNS", "", "Forward = reverse", "mail.example.org", "/server/health"},
} }
if len(cards) != len(want) { if len(cards) != len(want) {
t.Fatalf("%d cards, want %d", len(cards), len(want)) t.Fatalf("%d cards, want %d", len(cards), len(want))
+5 -5
View File
@@ -250,12 +250,12 @@ func TestBoxFootVariants(t *testing.T) {
func TestHealthCards(t *testing.T) { func TestHealthCards(t *testing.T) {
out := squash(partial(t, "health_cards", []HealthCard{ out := squash(partial(t, "health_cards", []HealthCard{
{Name: "Machine", Value: "CPU 12 %", Sub: "RAM 41 %", Icon: "ti-cpu", Href: "/server/health#machine"}, {Name: "Machine", Value: "CPU 12 %", Sub: "RAM 41 %", Icon: "ti-cpu", Href: "/server/health"},
{Name: "TLS certificate", Value: "Expires in 12 days", Sub: "renewal has not run", Icon: "ti-certificate", Href: "/server/health#certificate", Level: LevelWarn}, {Name: "TLS certificate", Value: "Expires in 12 days", Sub: "renewal has not run", Icon: "ti-certificate", Href: "/server/health", Level: LevelWarn},
{Name: "Sockets", Value: "1 of 2", Sub: "x", Icon: "ti-plug-connected-x", Href: "/server/health#sockets", Level: LevelFail}, {Name: "Sockets", Value: "1 of 2", Sub: "x", Icon: "ti-plug-connected-x", Href: "/server/health", Level: LevelFail},
})) }))
mustContain(t, out, `<div class="sp-health"><a href="/server/health#machine"><span class="sp-h-icon"><i class="ti ti-cpu"></i></span><span><span class="sp-h-name">Machine</span><span class="sp-h-value">CPU 12 %</span><span class="sp-h-sub">RAM 41 %</span></span></a>`, mustContain(t, out, `<div class="sp-health"><a href="/server/health"><span class="sp-h-icon"><i class="ti ti-cpu"></i></span><span><span class="sp-h-name">Machine</span><span class="sp-h-value">CPU 12 %</span><span class="sp-h-sub">RAM 41 %</span></span></a>`,
`<a href="/server/health#certificate" class="sp-warn">`, `<a href="/server/health#sockets" class="sp-fail">`) `<a href="/server/health" class="sp-warn">`, `<a href="/server/health" class="sp-fail">`)
} }
func TestDNSRecordShowsInDNSNowOnlyOnAMismatch(t *testing.T) { func TestDNSRecordShowsInDNSNowOnlyOnAMismatch(t *testing.T) {
+5 -5
View File
@@ -61,12 +61,12 @@ func admin() Meta { return Meta{User: "admin", IsGlobal: true} }
// checks, three outbound domains and two inbound. // checks, three outbound domains and two inbound.
func overviewFixture() *Overview { func overviewFixture() *Overview {
cards := []HealthCard{ cards := []HealthCard{
{Name: "Machine", Value: "CPU 12%", Sub: "RAM 41%", Icon: "ti-cpu", Href: "/server/health#machine"}, {Name: "Machine", Value: "CPU 12%", Sub: "RAM 41%", Icon: "ti-cpu", Href: "/server/health"},
{Name: "Processes", Value: "5 of 5 running", Sub: "all programs", Icon: "ti-server-cog", Href: "/server/health#processes"}, {Name: "Processes", Value: "5 of 5 running", Sub: "all programs", Icon: "ti-server-cog", Href: "/server/health"},
{Name: "TLS certificate", Value: "Expires in 12 days", Sub: "mail.example.org", Icon: "ti-certificate", Href: "/server/health#certificate", Level: LevelWarn}, {Name: "TLS certificate", Value: "Expires in 12 days", Sub: "mail.example.org", Icon: "ti-certificate", Href: "/server/health", Level: LevelWarn},
{Name: "Queue", Value: "Empty", Sub: "outbound", Icon: "ti-stack-2", Href: "/outbound/queue"}, {Name: "Queue", Value: "Empty", Sub: "outbound", Icon: "ti-stack-2", Href: "/outbound/queue"},
{Name: "Milter sockets", Value: "2 of 2 answering", Sub: "OpenDKIM, send-log", Icon: "ti-plug-connected", Href: "/server/health#sockets"}, {Name: "Milter sockets", Value: "2 of 2 answering", Sub: "OpenDKIM, send-log", Icon: "ti-plug-connected", Href: "/server/health"},
{Name: "Reverse DNS", Value: "Forward = reverse", Sub: "mail.example.org", Icon: "ti-arrows-exchange", Href: "/server/health#hostname"}, {Name: "Reverse DNS", Value: "Forward = reverse", Sub: "mail.example.org", Icon: "ti-arrows-exchange", Href: "/server/health"},
} }
o := NewOverview(admin(), "mail.example.org", cards, time.Date(2026, 10, 9, 14, 2, 31, 0, time.UTC), 30, true) o := NewOverview(admin(), "mail.example.org", cards, time.Date(2026, 10, 9, 14, 2, 31, 0, time.UTC), 30, true)
dns := func(dkim, spf, dmarc string) []Tag { dns := func(dkim, spf, dmarc string) []Tag {
+1 -1
View File
@@ -61,7 +61,7 @@ func TestOverviewHeadFollowsTheCards(t *testing.T) {
pageHas(t, "Overview", out, pageHas(t, "Overview", out,
`<h1 class="title is-3">Running, with warnings.</h1>`, `<h1 class="title is-3">Running, with warnings.</h1>`,
`class="sp-postmark sp-warn"`, `<b>WARN</b>`, `1 OF 6 CHECKS`, `class="sp-postmark sp-warn"`, `<b>WARN</b>`, `1 OF 6 CHECKS`,
`Needs attention: <a href="/server/health#certificate">TLS certificate</a> (Expires in 12 days).`, `Needs attention: <a href="/server/health">TLS certificate</a> (Expires in 12 days).`,
`Updated 14:02:31 UTC`, `<p class="sp-kicker">mail.example.org</p>`, `Updated 14:02:31 UTC`, `<p class="sp-kicker">mail.example.org</p>`,
`class="sp-warn"`, // the certificate card `class="sp-warn"`, // the certificate card
`href="/server/health"`, `>All details</a>`, `href="/server/health"`, `>All details</a>`,