test / test (push) Canceled after 0s
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>
155 lines
5.2 KiB
Go
155 lines
5.2 KiB
Go
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, "")} }
|