mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
incidents + incident_updates + maintenance_windows + status_pages tables. Monitor transitions auto-open incidents on down and auto-resolve on recovery; an active maintenance window covering the monitor suppresses alerts and auto-incidents, and status pages report the covered monitor as `maintenance`. Public status view: GET /api/status-pages/:slug/public aggregates overall state, monitor uptime, systems, open incidents, and scheduled maintenance; rendered read-only at /status/:slug. GET /api/badge/:kind/:id.svg serves stateless shields; GET /api/metrics exposes Prometheus text over existing tables; POST /api/import/csv?kind=monitors|domains does header-driven bulk import; /api/export v2 folds monitors/domains/systems rows into the backup payload. Frontend: /incidents with timeline + status flow, /status admin for pages + windows, /status/:slug public page; nav and palette wired. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
76 lines
2.8 KiB
Go
76 lines
2.8 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// /api/metrics — Prometheus text exposition over the tables we already have.
|
|
// No registry, no deps: the counts are one-line SQL each.
|
|
|
|
func promEscape(s string) string {
|
|
return strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", `\n`).Replace(s)
|
|
}
|
|
|
|
func (s *Server) metrics(c *gin.Context) {
|
|
var b strings.Builder
|
|
count := func(q string) int {
|
|
var n int
|
|
_ = s.db.QueryRow(q).Scan(&n)
|
|
return n
|
|
}
|
|
gauge := func(name, help string, v int) {
|
|
fmt.Fprintf(&b, "# HELP %s %s\n# TYPE %s gauge\n%s %d\n", name, help, name, name, v)
|
|
}
|
|
|
|
gauge("dash_monitors_total", "Configured monitors", count(`SELECT COUNT(*) FROM monitors`))
|
|
gauge("dash_monitors_up", "Monitors currently up", count(`SELECT COUNT(*) FROM monitors WHERE status='up'`))
|
|
gauge("dash_monitors_down", "Monitors currently down", count(`SELECT COUNT(*) FROM monitors WHERE status='down'`))
|
|
gauge("dash_domains_total", "Tracked domains", count(`SELECT COUNT(*) FROM domains`))
|
|
gauge("dash_domains_expired", "Domains past expiry", count(`SELECT COUNT(*) FROM domains WHERE expiry_date IS NOT NULL AND datetime(expiry_date) < datetime('now')`))
|
|
gauge("dash_domains_expiring_30d", "Domains expiring within 30 days",
|
|
count(`SELECT COUNT(*) FROM domains WHERE expiry_date IS NOT NULL
|
|
AND datetime(expiry_date) >= datetime('now')
|
|
AND datetime(expiry_date) <= datetime('now','+30 days')`))
|
|
gauge("dash_systems_total", "Registered systems", count(`SELECT COUNT(*) FROM systems`))
|
|
gauge("dash_systems_up", "Systems currently reporting", count(`SELECT COUNT(*) FROM systems WHERE status='up'`))
|
|
gauge("dash_systems_down", "Systems gone silent", count(`SELECT COUNT(*) FROM systems WHERE status='down'`))
|
|
gauge("dash_incidents_open", "Open or acknowledged incidents", count(`SELECT COUNT(*) FROM incidents WHERE status IN ('open','ack')`))
|
|
gauge("dash_heartbeats_total", "Recorded monitor heartbeats", count(`SELECT COUNT(*) FROM heartbeats`))
|
|
|
|
// Per-monitor up/down gauge — label cardinality is user-controlled and small.
|
|
b.WriteString("# TYPE dash_monitor_up gauge\n")
|
|
if rows, err := s.db.Query(`SELECT name, status FROM monitors`); err == nil {
|
|
for rows.Next() {
|
|
var name, st string
|
|
if rows.Scan(&name, &st) == nil {
|
|
v := 0
|
|
if st == "up" {
|
|
v = 1
|
|
}
|
|
fmt.Fprintf(&b, "dash_monitor_up{name=%q} %d\n", promEscape(name), v)
|
|
}
|
|
}
|
|
rows.Close()
|
|
}
|
|
b.WriteString("# TYPE dash_system_up gauge\n")
|
|
if rows, err := s.db.Query(`SELECT name, status FROM systems`); err == nil {
|
|
for rows.Next() {
|
|
var name, st string
|
|
if rows.Scan(&name, &st) == nil {
|
|
v := 0
|
|
if st == "up" {
|
|
v = 1
|
|
}
|
|
fmt.Fprintf(&b, "dash_system_up{name=%q} %d\n", promEscape(name), v)
|
|
}
|
|
}
|
|
rows.Close()
|
|
}
|
|
|
|
c.Data(http.StatusOK, "text/plain; version=0.0.4; charset=utf-8", []byte(b.String()))
|
|
}
|