Files
selfpost/internal/web/view/health.go
T
mixandClaude Opus 5.5 b905f3e560
test / test (push) Canceled after 0s
panel-notifications: Account, Server > Settings, Server > Health, help; e2e
The pages of the mockups accepted in 87fa0a4, built from the kit's partials
(written by a Sonnet agent into the working tree, reviewed here):

- Account: "Send me notifications" in the Profile form, saved with the
  profile; an unticked box is off.
- Server > Settings: the instance switch (box 03), and box 04 Notification
  mail with the DKIM and SPF records of the server's own name, drawn with the
  record partial of a domain's DNS page and checked in DNS. Sending rate
  limits are box 02 in the left column. A record that is not in place is
  tagged as a warning, not a failure: notifications are sent without it.
- Server > Health: card 06 Notification mail with the state of each record
  and a link to Settings, beside Configuration (07); part of the polled
  fragment; Re-check DNS forces it too. Overview does not run this check.
- help.go: the Notifications topic and the clauses in Settings and Your
  account, word for word with help.html.

If the server's key cannot be read the pages still render: the DKIM record
is left out and SPF is shown unchecked.

e2e: the administrator gets a mailbox behind the sink and the server's DKIM
record from Settings is published; the level-2 refusal of the next step must
then arrive as a notification from postmaster@mail.e2e.test, signed as that
name with the key Settings shows, and must not be a row of the Outbound log.
The rotation step now waits for Postfix to be running: it follows a step that
restarts the container, and logrotate's `postfix reload` failed in between.

On the test server, for this tree: go vet clean, go test ./... 23 packages
ok, make e2e ok (147 s); the notification reached the sink 28 s after the
refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:21:48 +03:00

155 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package view
// The data of Server › Health (templates/health.html, mockup health.html):
// every check behind the cards of Overview, as tables, and the home of the two
// server actions, Re-check DNS and Reload configuration.
//
// Two rows of the page are polled (templates/health_body.html): the handler
// builds the same Health for the page and for its fragment, and the fragment
// carries Refresh so that its second row is swapped in out of band.
// Health is the data of the Health page.
type Health struct {
Meta
Head Head
Flash *Flash
// Refresh is set on the polled fragment only. The fragment answers with two
// sibling rows; the first replaces the element that polls, and the second
// is marked to be swapped into place by its id.
Refresh bool
Machine Box
MachineRows []MachineRow
Processes Box
ProcessRows []ProcessRow
ProcessError bool
Certificate Box
CertFacts []Fact
CertDetail string
CertProblem bool
Sockets Box
SocketRows []SocketRow
Hostname Box
HostFacts []Fact
// NotifyMail is the card of the DNS records notifications are signed and
// authorised with (their values are under Server › Settings); NotifyNote
// is the line under its table. It is polled with the rest, so Refresh swaps
// it into place on its own.
NotifyMail Box
NotifyRows []MailRow
NotifyNote Text
Configuration Box
Reload Action
}
// MachineRow is one line of the Machine table. A row that is a level (CPU,
// memory) carries a Gauge; one that is a rate (network) carries Usage text; a
// reading that could not be taken carries neither and shows a dash. Detail is
// the supporting text, one line each.
type MachineRow struct {
Resource string
Gauge *Gauge
Usage string
Detail []string
}
// Gauge is a reading shown as a bar and its figure: Percent fills the bar, Text
// is the figure printed beside it, and Level (ok, warn or fail) colours it.
type Gauge struct {
Percent int
Text string
Level string
}
// Bar is the Bulma class that colours the gauge's progress bar.
func (g Gauge) Bar() string {
switch g.Level {
case LevelFail:
return "is-danger"
case LevelWarn:
return "is-warning"
}
return "is-success"
}
// ProcessRow is one supervised program: its name, its state as a tag and what
// supervisord said about it.
type ProcessRow struct {
Name string
State Tag
Detail string
}
// SocketRow is one milter socket. Note qualifies the name ("inbound spam
// filter"); Detail is shown only when the socket is not fine, since "Listening"
// says nothing the tag does not.
type SocketRow struct {
Name string
Note string
Path string
State Tag
Detail string
}
// MailRow is one record of the Notification mail table: its name, the host it
// is published at and its state as a tag.
type MailRow struct {
Name string
Host string
State Tag
}
// NewMailRow is the row of one record of the server's own name, from what the
// DNS check found. A record that is not in place is a warning, as on Settings.
func NewMailRow(name, host string, c DNSCheck) MailRow {
return MailRow{Name: name, Host: host, State: mailTag(c)}
}
// NewHealth builds the page around its content. The handler fills the tables;
// the boxes, the head and the reload action are the page's own.
func NewHealth(m Meta, flash string) *Health {
m.Title, m.Section, m.Page = "Health", "server", "health"
h := &Health{
Meta: m,
Head: Head{
Kicker: "Server",
Title: "Health",
Lead: Plain("Everything behind the cards on Overview. Refreshes automatically."),
Actions: []Action{{Label: "Re-check DNS", Icon: "ti-refresh", Post: "/server/health/recheck"}},
},
Machine: Box{No: "01", Title: "Machine", ID: "machine", Help: topicLink(HelpChecks, "What these readings mean")},
Processes: Box{No: "02", Title: "Processes", ID: "processes"},
Certificate: Box{No: "03", Title: "TLS certificate", ID: "certificate", Help: topicLink(HelpCertificate, "When it warns and what to do")},
Sockets: Box{No: "04", Title: "Milter sockets", ID: "sockets"},
Hostname: Box{No: "05", Title: "Hostname and reverse DNS", ID: "hostname", Help: topicLink(HelpRDNS, "Forward and reverse DNS")},
NotifyMail: Box{No: "06", Title: "Notification mail", ID: "notification-mail",
Help: topicLink(HelpNotifications, "What the panel sends and what makes it arrive")},
Configuration: Box{No: "07", Title: "Configuration", ID: "configuration",
End: Plain("Safe to run at any time")},
Reload: Action{Label: "Reload configuration", Icon: "ti-reload", Post: "/server/health/reload"},
}
if flash != "" {
h.Flash = &Flash{Text: Plain(flash)}
}
return h
}
// WithNotificationMail fills the Notification mail card: from is the address
// notifications come from, rows the state of each record, and status the
// card's verdict for its head.
func (h *Health) WithNotificationMail(from string, rows []MailRow, status string) *Health {
h.NotifyMail.End = Verdict(status)
h.NotifyRows = rows
h.NotifyNote = Rich("Sent from ", Mono(from), ". What to publish is under ",
Link("/server/settings#notification-mail", "Settings"), ".")
return h
}
// Verdict puts a check's status in the end slot of its box, as the mockup does.
func Verdict(status string) Text { return Text{TagOf(status, "")} }