package web import ( "html/template" "time" ) var funcMap = template.FuncMap{ "fmtTime": func(t time.Time) string { if t.IsZero() { return "—" } return t.UTC().Format("2006-01-02 15:04:05") }, "uptimeClass": func(pct float64) string { switch { case pct >= 99: return "good" case pct >= 95: return "warn" default: return "bad" } }, } var ( indexTmpl = template.Must(template.New("index").Funcs(funcMap).Parse(indexHTML)) historyTmpl = template.Must(template.New("history").Funcs(funcMap).Parse(historyHTML)) incidentsTmpl = template.Must(template.New("incidents").Funcs(funcMap).Parse(incidentsHTML)) statusTmpl = template.Must(template.New("status").Funcs(funcMap).Parse(statusHTML)) ) const sharedCSS = ` *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Courier New', monospace; background: #0f0f0f; color: #d4d4d4; padding: 2rem; } h1 { color: #e2e2e2; margin-bottom: 0.25rem; font-size: 1.4rem; letter-spacing: 0.05em; } .subtitle { color: #666; font-size: 0.8rem; margin-bottom: 1.5rem; } nav { margin-bottom: 1.5rem; } nav a { color: #6b9edb; text-decoration: none; margin-right: 1.5rem; font-size: 0.85rem; } nav a:hover { color: #90bfff; } table { border-collapse: collapse; width: 100%; font-size: 0.875rem; } th { text-align: left; padding: 0.5rem 1rem; color: #888; font-weight: normal; border-bottom: 1px solid #2a2a2a; } td { padding: 0.5rem 1rem; border-bottom: 1px solid #1e1e1e; } tr:hover td { background: #161616; } .badge { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 0.75rem; font-weight: bold; letter-spacing: 0.05em; } .badge-up { background: #1a3a1a; color: #4caf50; border: 1px solid #2d5a2d; } .badge-down { background: #3a1a1a; color: #f44336; border: 1px solid #5a2d2d; } .good { color: #4caf50; } .warn { color: #ff9800; } .bad { color: #f44336; } .dim { color: #555; font-size: 0.8rem; }` const sharedNav = `` const indexHTML = `
Auto-refreshes every 30s — {{fmtTime .Now}} UTC
` + sharedNav + `| Monitor | Status | Response | 24h | 7d | 30d | Last Check | Info |
|---|---|---|---|---|---|---|---|
| {{.Name}} | {{if .Up}}UP{{else}}DOWN{{end}} | {{if .ResponseMS}}{{.ResponseMS}}ms{{else}}—{{end}} | {{printf "%.2f" .Uptime24h}}% | {{printf "%.2f" .Uptime7d}}% | {{printf "%.2f" .Uptime30d}}% | {{fmtTime .CheckedAt}} | {{if .Error}}{{.Error}}{{else}}—{{end}} |
max: {{.MaxMS}}ms avg: {{printf "%.0f" .AvgMS}}ms checks: {{.Count}}
{{else}}No data yet.
{{end}} {{end}} ` const incidentsHTML = `All outage periods, newest first.
{{if .}}| Monitor | Started | Resolved | Duration | Status |
|---|---|---|---|---|
| {{.MonitorName}} | {{fmtTime .StartedAt}} | {{if .Ongoing}}—{{else}}{{fmtTime .ResolvedAt}}{{end}} | {{.Duration}} | {{if .Ongoing}}ONGOING{{else}}resolved{{end}} |
No incidents recorded.
{{end}} ` const statusHTML = `| Service | Status | Uptime (24h) |
|---|---|---|
| {{.Name}} | {{if .Up}}Operational{{else}}Disrupted{{end}} | {{printf "%.2f" .Uptime24h}}% |