Files
selfpost/internal/web/view/kit.go
T
mixandClaude Opus 5.5 a8fd96c752 health-setup-checks: the certificate's name, Postfix's greeting, the proxy
Decided by the owner on 2026-10-13 (in place of preflight), taken up on
2026-10-16; mockups b2dcefc, cc33890, 0feaa55 (his: the greeting on a row
of its own, the name check a tag beside Names).

- The certificate: health.Certificate keeps its names (DNS names, or the
  CN without any) and ForHost holds it to SELFPOST_HOSTNAME as a TLS
  client does - VerifyHostname over the DNS names, the CN only without
  any, as OpenSSL's X509_check_host and so a sending Postfix still take it
  (src/tls/tls_client.c:439, 513). A certificate for another name fails;
  the tag beside Names says matches / not for the hostname.
- The greeting: postconf -x -h smtpd_banner, read once at the panel's
  start like the served certificate's path (main.cf is not rewritten
  after), is what smtpd writes after "220 " (src/smtpd/smtpd.c:5979). A
  first word other than the hostname warns, with what to set. A fact may
  now take the whole row (Fact.Wide, sp-wide) and carry a tag beside its
  label (Fact.LabelTag); both on the kit page.
- The proxy: auth.Module.Seen says the address the panel counts sign-ins
  against, the peer, and whether X-Forwarded-For was read or left unread
  for an untrusted peer; the Configuration card shows "Your address" and,
  in the second case, warns that every visitor shares one sign-in limit
  and names the /32 (or /128) to put in TRUSTED_PROXY_CIDR.

Tests: TestCertificateForHost (CN-only, DNS names, a wildcard, the CN
ignored beside DNS names), TestSeenAddress, TestHealthSaysWhereItSeesTheViewer,
TestHealthGreetingAndCertificateName; e2e reads the stand's right state on
Health (matches, "220 mail.e2e.test ESMTP", a direct client). go vet + go
test (26 packages) and make e2e on the test server for this tree.

Docs: help (word for word with the mockup), guide § Overview and Health,
CHANGELOG [Unreleased] § Added, roadmap row done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:51:42 +03:00

198 lines
10 KiB
Go

package view
// The data of the kit page (GET /server/components): every partial of
// components.html in every state it has, filled the way the mockup components
// page (docs/assets/panel-redesign/panel/src/components.html) is filled, same
// boxes, same order, same words. It is production code, not a test fixture,
// because the handler serves it; the guard tests render the same value
// (fixtures_test.go).
// Kit is the data of the kit page, rendered by templates/kit.html. The fields
// are named after the boxes of the page they fill.
type Kit struct {
Meta
Head Head
Flash Flash
FlashError Flash
Layouts Box
LayoutFacts []Fact
Postmark Box
Postmarks []Postmark
StatusTag Box
StatusRows []KitStatusRow
Sheet Sheet
BoxHead Box
BoxFoot Foot
DangerZone Box
Credential Box
HealthCard Box
HealthCards []HealthCard
DNSRecord Box
Records []Record
Tables Box
TablesFoot Foot
RowAction RowAction
LogPane Box
LogLines []LogLine
Facts Box
FactList []Fact
Timeline Box
Steps []Step
EmptyState Box
Empty EmptyState
ConfirmList Box
Confirm []Text
SideMenu SideMenu
FormRows Box
Route Route
HelpTopic HelpTopic
Shell Box
ShellFacts []Fact
TypeScale Box
TypeScaleFoot Foot
KickerSample string
}
// KitStatusRow is one row of the status_tag table: the words that map to one
// tag, and the tag.
type KitStatusRow struct {
Words string
Tag Tag
}
// KitPage returns the kit page's data. The page is shown to the global role, so
// the shell is theirs; it sits under Server in the address and in the menu.
func KitPage() *Kit {
return &Kit{
Meta: Meta{Title: "Components", User: "admin", IsGlobal: true, Section: "server"},
Head: Head{
Postmark: &Postmark{Top: "SELFPOST", Word: "REF", Bottom: "DESIGN CONTRACT", Level: LevelOK},
Kicker: "Reference · page_head",
Title: "Components",
Lead: Plain("Every building block of the panel, in every state it has. A screen is assembled from these and from plain Bulma — nothing else. The title of each box is the name of its template partial."),
Actions: []Action{{Label: "Help", Icon: "ti-help-circle", Href: "/help"}},
},
Flash: Flash{Text: Rich(Strong("flash"), " — the result of the last action, between page_head and the first box. Success, or…")},
FlashError: Flash{Error: true, Text: Rich("…", Strong("flash, error"), " — a failure that belongs to the page, not to one field. A field's error stays under the field in Bulma's ", Code("help is-danger"), ".")},
Layouts: Box{No: "00", Title: "Page layouts", End: Plain("A page is exactly one of these")},
LayoutFacts: []Fact{
{Label: "list", Value: Rich("page_head, then full-width boxes: the add form first, the table second. ", Link("/outbound/domains", "Example"))},
{Label: "detail", Value: Rich("page_head, then ", Code("columns"), ": ", Code("is-3"), " side menu + ", Code("is-9"), " boxes.")},
{Label: "cards", Value: Rich("page_head, then ", Code("columns sp-cards"), ": two ", Code("is-6"),
" stacks of numbered boxes read in rows, odd on the left and even on the right, each stack closed up. On a phone the boxes come in the order of their numbers. The two stacks under this box are laid out so.")},
{Label: "form", Value: Rich("page_head, then boxes in equal ", Code("columns"), ", one submit row under them.")},
{Label: "signed out", Value: Rich("The split brand / form screen, no navbar. ", Link("/login", "Example"))},
},
Postmark: Box{No: "01", InCards: true, Title: "postmark", End: Plain("Worst status of the page's checks")},
Postmarks: []Postmark{
{Top: "SELFPOST", Word: "OK", Bottom: "6 OF 6 CHECKS", Level: LevelOK},
{Top: "SELFPOST", Word: "WARN", Bottom: "1 OF 6 CHECKS", Level: LevelWarn},
{Top: "DNS", Word: "FAIL", Bottom: "1 OF 3 RECORDS", Level: LevelFail},
},
StatusTag: Box{No: "03", InCards: true, Title: "status_tag", End: Plain("One mapping, one function")},
Sheet: Sheet{ID: "kit-sheet", Tag: Tag{Status: "reject"}, Title: "sheet", Rows: []SheetRow{
{Label: "What it holds", Lines: []string{"What does not fit the row, as name and value"}},
{Label: "How it closes", Lines: []string{"Esc", "a click outside", "the cross"}, Mono: true},
}},
StatusRows: []KitStatusRow{
{"ok, published, running, sent, accept", Tag{Status: "ok"}},
{"warn, deferred, quiet, tempfail", Tag{Status: "warn"}},
{"error, fail, mismatch, bounced, reject, rate-limited", Tag{Status: "fail"}},
{"unknown, off, neutral counts", Tag{Status: "off"}},
{"held: quarantine · role: global", Tag{Status: "quarantine"}},
},
BoxHead: Box{No: "02", InCards: true, Title: "box · box_head",
End: Plain("The end slot: a note, a tag, a link or a filter form"),
Help: &HelpLink{Href: "/help#checks", Title: "Optional: the section's Help topic"}},
BoxFoot: Foot{Text: Plain("box_foot — a legend, paging"), End: Plain("end slot")},
DangerZone: Box{Icon: "ti-alert-triangle", Title: "box · sp-danger-zone", Variant: BoxDanger},
Credential: Box{Icon: "ti-eye", Title: "box · sp-credential", End: Plain("Shown once"), Variant: BoxCredential},
HealthCard: Box{No: "04", Title: "health_card", End: Plain("Overview only · each card links to its detail")},
HealthCards: []HealthCard{
{Name: "Name", Value: "Verdict in words", Sub: "ok · one supporting fact", Icon: "ti-server-cog", Href: "#"},
{Name: "Name", Value: "Expires in 12 days", Sub: "sp-warn", Icon: "ti-certificate", Href: "#", Level: LevelWarn},
{Name: "Name", Value: "1 of 2 answering", Sub: "sp-fail", Icon: "ti-plug-connected-x", Href: "#", Level: LevelFail},
},
DNSRecord: Box{No: "05", Title: "dns_record · copy_field", End: Plain("One block per record")},
Records: []Record{
{Name: "Record", Status: Tag{Status: "published"}, Note: Plain("A one-line note"),
Host: "copy_field — single line", Type: "TXT",
Value: "copy_field — multi-line, for a DKIM key", ValueRows: 2},
{Name: "Record", Status: Tag{Status: "mismatch"}, Note: Plain(`"In DNS now" appears only when it differs`),
InDNS: "what the resolver returned", Problem: Plain("What is wrong and the exact fix.")},
},
Tables: Box{No: "07", Title: "Table conventions", End: Rich("Always inside ", Code("table-container"), " · an address breaks after its @")},
TablesFoot: Foot{Text: Plain("Page 1 of 9"), Link: &Anchor{Href: "#", Label: "Older →"}},
RowAction: RowAction{Label: "Delete", Post: "#", Danger: true},
LogPane: Box{No: "09", Title: "log_pane", End: Plain("sp-d time · sp-w warning · sp-e error")},
LogLines: []LogLine{
{Time: "13:58:07", Text: "an ordinary line"},
{Time: "13:58:09", Text: "a deferred delivery — sp-w", Level: "warn"},
{Time: "13:57:41", Text: "a reject or a bounce — sp-e", Level: "error"},
},
Facts: Box{No: "06", Title: "facts"},
FactList: []Fact{
{Label: "Label", Value: Plain("Value")},
{Label: "Mono value", Value: Plain("4XcB7k2Jm9z1"), Mono: true},
{Label: "sp-big", Value: Plain("5 days"), Big: true, Note: Plain("a supporting line")},
{Label: "Label with a tag", Value: Plain("a verdict on the value"), LabelTag: &Tag{Status: "ok", Label: "matches"}},
{Label: "sp-wide", Value: Plain("a value that takes the whole row, a line of a server's own"), Mono: true, Wide: true},
},
Timeline: Box{No: "08", Title: "timeline"},
Steps: []Step{
{Time: "13:58:07 UTC", Strong: "Done", Text: Plain(" — default")},
{Time: "13:58:09 UTC", Strong: "Deferred", Text: Plain(" — sp-warn"), Level: LevelWarn},
{Time: "14:40:00 UTC", Strong: "Bounced", Text: Plain(" — sp-fail"), Level: LevelFail},
{Time: "not yet", Text: Plain("Expected next — sp-pending"), Level: LevelPending},
},
EmptyState: Box{No: "10", Title: "empty_state"},
Empty: EmptyState{Icon: "ti-stack-2", Text: Plain("One sentence: what is absent and why that is fine.")},
ConfirmList: Box{No: "11", Title: "confirm_list"},
Confirm: []Text{
Plain("each thing that will be lost, one per line;"),
Plain("inside an sp-danger-zone box, above the danger button."),
},
SideMenu: SideMenu{Groups: []MenuGroup{
{Label: "side_menu", Items: []MenuItem{
{Label: "Current", Icon: "ti-world-check", Href: "#", Active: true, Tag: &Tag{Status: "fail", Label: "1"}},
{Label: "Section", Icon: "ti-apps", Href: "#", Tag: &Tag{Status: "off", Label: "2"}},
}},
{Label: "Rarely changed", Items: []MenuItem{
{Label: "Delete", Icon: "ti-trash", Href: "#", Danger: true},
}},
}},
FormRows: Box{No: "12", Title: "Form rows · route · help_topic"},
Route: Route{
{Text: "from@example.org"}, {Arrow: true}, {Text: "to@example.net"}, {Tag: &Tag{Status: "deferred"}},
},
HelpTopic: HelpTopic{Title: "help_topic", Body: []Text{Plain("A heading and a paragraph; topics stack inside one box on the Help page.")}},
Shell: Box{No: "13", Title: "shell", End: Plain("Owned by the layout — a screen never writes it")},
ShellFacts: []Fact{
{Label: "Name", Value: Plain("The wordmark, not the stamp: at nav size the stamp's perforation is under a pixel and its brick field dissolves into the bar. The stamp lives on sign-in, setup and the favicon.")},
{Label: "Edge", Value: Rich("Built like the mark: the brick field, then the stamp's paper margin, and the perforation along the outer edge of that margin — round holes, flat teeth, the ink hairline. One tile, ", Code("perf-edge.svg"), ".")},
{Label: "Sibling strip", Value: Plain("The pages of the current group, under the edge. Five or six entries; past that the group is split.")},
{Label: "Menus", Value: Plain("Bulma hover dropdowns, no JavaScript. The user menu holds Account, Help and Sign out.")},
},
TypeScale: Box{No: "14", Title: "Type scale", End: Plain("Three sizes · nothing to be read is under 13px")},
TypeScaleFoot: Foot{Bare: true, Text: Rich("Bulma's ", Code("is-small"), " and ", Code("is-size-7"), " are not used: a control is never small, and ", Code("sp-small"), " replaces the helper. The postmark's fine print is an imprint, not text.")},
KickerSample: "Upper-case mono label",
}
}