package view import ( "strconv" "time" ) // 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. // // The page is two stacks of boxes numbered by importance (the `cards` layout), // and all but the Configuration box are polled (templates/health_body.html): // the handler builds the same Health for the page and for its fragment, and // AsFragment marks what the fragment swaps in out of band. // Health is the data of the Health page. type Health struct { Meta Head Head Flash *Flash Processes Box ProcessRows []ProcessRow ProcessError bool // SenderCheck is what the strict check of the From: line has cost: for how // long the panel did not answer while Postfix was running, and how many // messages were deferred for it. SenderNote is the line under its table. SenderCheck Box SenderRows []SenderCheckRow SenderNote Text Machine Box MachineRows []MachineRow 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. NotifyMail Box NotifyRows []MailRow NotifyNote Text Configuration Box Reload Action } // SenderCheckRow is one period of the Sender check table. type SenderCheckRow struct { Period string Down string Deferred string } // 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"}}, }, // Numbered by importance; the odd ones are the left stack. Processes: Box{No: "01", Title: "Processes", ID: "processes"}, Sockets: Box{No: "02", Title: "Milter sockets", ID: "sockets"}, SenderCheck: Box{No: "03", Title: "Sender check", ID: "sender-check", Help: topicLink(HelpSenderCheck, "What is checked and what these numbers are for")}, Certificate: Box{No: "04", Title: "TLS certificate", ID: "certificate", Help: topicLink(HelpCertificate, "When it warns and what to do")}, 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")}, Machine: Box{No: "07", Title: "Machine", ID: "machine", Help: topicLink(HelpChecks, "What these readings mean")}, Configuration: Box{No: "08", 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 } // WithSenderCheck fills the Sender check card: its three periods, the first // day counted ("" before any was) and the card's verdict. func (h *Health) WithSenderCheck(rows []SenderCheckRow, since, status string) *Health { h.SenderCheck.End = Verdict(status) h.SenderRows = rows h.SenderNote = Rich("The panel holds each message's ", Code("From:"), " against the application that sends it. While the panel does not answer, Postfix defers mail instead of accepting it unchecked. Time is counted only while Postfix itself was running.") if since != "" { h.SenderNote = append(h.SenderNote, Rich(" Counted since ", Mono(since), ".")...) } return h } // AsFragment makes the page its polled fragment: the left stack replaces the // element that polls, and the three readings of the right stack are marked to // be swapped into place by their ids. func (h *Health) AsFragment() *Health { h.Sockets.OOB, h.Certificate.OOB, h.NotifyMail.OOB = true, true, true return h } // Downtime prints a length of time the way the Sender check table does: whole // seconds while they say something, then minutes, then hours. func Downtime(d time.Duration) string { secs := int64(d / time.Second) switch { case secs < 60: return strconv.FormatInt(secs, 10) + " s" case secs < 3600: return strconv.FormatInt(secs/60, 10) + " min " + strconv.FormatInt(secs%60, 10) + " s" } return strconv.FormatInt(secs/3600, 10) + " h " + strconv.FormatInt(secs%3600/60, 10) + " min" } // 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, "")} }