mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 15:51:27 +00:00
feat: ops layer — incidents, maintenance, status pages, badges, metrics (merge plan M4)
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>
This commit is contained in:
1 parent
74c987c18a
commit
c84a4be876
17 files changed
+2212
-11
No files matched your search
@@ -0,0 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// POST /api/import/csv?kind=monitors|domains — header-driven CSV bulk import.
|
||||
// Monitors: name,type,url,hostname,port,interval — name + (url|hostname) needed.
|
||||
// Domains: name — one per row.
|
||||
|
||||
func (s *Server) importCSV(c *gin.Context) {
|
||||
kind := c.DefaultQuery("kind", "monitors")
|
||||
if kind != "monitors" && kind != "domains" {
|
||||
fail(c, http.StatusBadRequest, "kind must be monitors or domains")
|
||||
return
|
||||
}
|
||||
rd := csv.NewReader(c.Request.Body)
|
||||
rd.FieldsPerRecord = -1 // tolerate ragged rows; header drives positions
|
||||
recs, err := rd.ReadAll()
|
||||
if err != nil || len(recs) < 2 {
|
||||
fail(c, http.StatusBadRequest, "csv with a header row and at least one entry required")
|
||||
return
|
||||
}
|
||||
head := map[string]int{}
|
||||
for i, h := range recs[0] {
|
||||
head[strings.ToLower(strings.TrimSpace(h))] = i
|
||||
}
|
||||
cell := func(rec []string, col string) string {
|
||||
if i, ok := head[col]; ok && i < len(rec) {
|
||||
return strings.TrimSpace(rec[i])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var created, skipped int
|
||||
var errs []string
|
||||
for i, rec := range recs[1:] {
|
||||
row := i + 2 // header is row 1
|
||||
name := cell(rec, "name")
|
||||
if name == "" {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if kind == "domains" {
|
||||
if _, err := s.db.Exec(`INSERT INTO domains (id, name) VALUES (?, ?)`,
|
||||
newID("d"), strings.ToLower(name)); err != nil {
|
||||
if strings.Contains(err.Error(), "UNIQUE") {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
errs = append(errs, fmt.Sprintf("row %d: %v", row, err))
|
||||
continue
|
||||
}
|
||||
created++
|
||||
continue
|
||||
}
|
||||
|
||||
// monitors
|
||||
typ := orDefault(cell(rec, "type"), "http")
|
||||
if !validMonitorType(typ) {
|
||||
errs = append(errs, fmt.Sprintf("row %d: bad type %q", row, typ))
|
||||
continue
|
||||
}
|
||||
url, host := cell(rec, "url"), cell(rec, "hostname")
|
||||
if url == "" && host == "" {
|
||||
errs = append(errs, fmt.Sprintf("row %d: url or hostname required", row))
|
||||
continue
|
||||
}
|
||||
port, _ := strconv.Atoi(cell(rec, "port"))
|
||||
interval, _ := strconv.Atoi(cell(rec, "interval"))
|
||||
if interval <= 0 {
|
||||
interval = 60
|
||||
}
|
||||
token := ""
|
||||
if typ == "push" {
|
||||
token = newID("push")
|
||||
}
|
||||
if _, err := s.db.Exec(`INSERT INTO monitors
|
||||
(id, name, type, url, hostname, port, interval_s, push_token, position)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)`,
|
||||
newID("m"), name, typ, url, host, port, interval, token,
|
||||
float64(time.Now().UnixNano())/1e9); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("row %d: %v", row, err))
|
||||
continue
|
||||
}
|
||||
created++
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"created": created, "skipped": skipped, "errors": errs})
|
||||
}
|
||||
Reference in new issue
Block a user