Files
statuspage/server/main.go
T
aziis98 c988ddf22f ui: uptime chip, #0004 shadows, tick marks, raw metrics; server: config meta injection
- ui: add uptime% chip (green >=99, red below) after ssh; remove "up" word
- ui: all shadows use #0004 shadow-color; modal shadow-lg derives from it
- ui: status modal draws a datetime tick mark every 20 history entries from the newest
- server: drop max_points/downsampling/bucketing; metrics served raw, plots share a ~24h window
- docs: fix ps --no-headers in top example; document raw metrics API
- server: inject <meta name=title/description> into index.html from config title + per-group host patterns (shared with --check recap)
2026-08-02 02:06:27 +02:00

130 lines
3.5 KiB
Go

package main
import (
"context"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/joho/godotenv"
"github.com/spf13/pflag"
)
func envOr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
func printConfigSummary(path string, cfg *Config) {
log.Printf("config %s ok: %d machines", path, len(cfg.Machines))
for _, line := range machineSummary(cfg) {
log.Printf(" %s", line)
}
}
func buildFrontend() error {
cmd := "npm"
if _, err := exec.LookPath("npm"); err != nil {
cmd = "bun"
}
c := exec.Command(cmd, "run", "build")
c.Stdout = os.Stdout
c.Stderr = os.Stderr
log.Printf("running %s run build", cmd)
return c.Run()
}
func main() {
_ = godotenv.Load()
dev := pflag.Bool("dev", false, "run `npm run build` before serving dist/")
devServer := pflag.Bool("dev-server", false, "frontend served by the vite dev server, do not serve dist/")
mock := pflag.Bool("mock", false, "serve generated mock data instead of probing real machines")
check := pflag.Bool("check", false, "load and expand the config, print a machine summary, then exit")
addr := pflag.String("addr", envOr("ADDR", ":5000"), "listen address")
configPath := pflag.StringP("config", "c", envOr("CONFIG", "config.local.yaml"), "yaml config file")
dbPath := pflag.String("db", envOr("DB", "data.volume/history.db"), "sqlite history database file")
pflag.Parse()
if *check {
cfg, err := LoadConfig(*configPath)
if err != nil {
log.Fatalf("config check failed: %v", err)
}
printConfigSummary(*configPath, cfg)
return
}
if *dev {
if err := buildFrontend(); err != nil {
log.Fatalf("build failed: %v", err)
}
}
cfg, err := LoadConfig(*configPath)
if err != nil {
log.Fatalf("loading config: %v", err)
}
log.Printf("loaded %d machines from %s", len(cfg.Machines), *configPath)
title := cfg.Title
desc := strings.Join(machineSummary(cfg), ", ")
mux := http.NewServeMux()
if *mock {
log.Printf("mock mode: serving generated data (no real probes)")
mm := NewMockMonitor()
mux.HandleFunc("GET /api/status", mm.statusHandler)
mux.HandleFunc("GET /api/history/{machine}", mm.historyHandler)
mux.HandleFunc("GET /api/metrics/aggregate", mm.metricsAggregateHandler)
mux.HandleFunc("GET /api/metrics/{machine}", mm.metricsHandler)
mux.HandleFunc("POST /api/refresh/{machine}", mm.refreshHandler)
if !*devServer {
mux.Handle("GET /", indexHandler("dist", title, desc))
}
log.Printf("listening on %s (mock)", *addr)
if err := http.ListenAndServe(*addr, mux); err != nil {
log.Fatal(err)
}
return
}
if dir := filepath.Dir(*dbPath); dir != "." {
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Fatalf("creating db dir: %v", err)
}
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
history, err := NewHistory(*dbPath)
if err != nil {
log.Fatalf("opening history db: %v", err)
}
defer history.Close()
go history.pruneLoop(ctx)
mon := NewMonitor(cfg, history)
mon.Start(ctx)
mux.HandleFunc("GET /api/status", mon.statusHandler)
mux.HandleFunc("GET /api/history/{machine}", mon.historyHandler)
mux.HandleFunc("GET /api/metrics/aggregate", mon.metricsAggregateHandler)
mux.HandleFunc("GET /api/metrics/{machine}", mon.metricsHandler)
mux.HandleFunc("POST /api/refresh/{machine}", mon.refreshHandler)
if !*devServer {
mux.Handle("GET /", indexHandler("dist", title, desc))
}
log.Printf("listening on %s", *addr)
if err := http.ListenAndServe(*addr, mux); err != nil {
log.Fatal(err)
}
}