zai-proxy/dashboard/main.go
jedarden dee82a76a3 chore: update module paths and add evaluation package
- proxy/go.mod: github.com/ardenone/zai-proxy → git.ardenone.com/jedarden/zai-proxy
- dashboard/go.mod: github.com/ardenone/ardenone-cluster/containers/zai-proxy-dashboard → git.ardenone.com/jedarden/zai-proxy/dashboard
- Update all Go import paths in proxy/ and dashboard/ to match new module paths
- Add proxy/evaluation/ package (was missing from initial commit)
- Add docs/plan/plan.md with architecture, security model, telemetry design, and migration checklist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:03:50 -04:00

150 lines
3.9 KiB
Go

// zai-proxy-dashboard is a real-time web dashboard for zai-proxy metrics.
package main
import (
"context"
"embed"
"io/fs"
"net/http"
"os"
"os/signal"
"syscall"
"git.ardenone.com/jedarden/zai-proxy/dashboard/api"
"git.ardenone.com/jedarden/zai-proxy/dashboard/collector"
"git.ardenone.com/jedarden/zai-proxy/dashboard/logger"
"git.ardenone.com/jedarden/zai-proxy/dashboard/storage"
)
//go:embed frontend/dist/*
var frontendFS embed.FS
func main() {
// Initialize structured logging
logger.Init(logger.DefaultConfig())
log := logger.Component("main")
// Load configuration
collectorConfig := collector.DefaultConfig()
storageConfig := storage.DefaultConfig()
apiConfig := api.DefaultConfig()
log.Info("initializing zai-proxy-dashboard",
"scrape_targets", collectorConfig.Targets,
"scrape_interval", collectorConfig.Interval.String(),
"listen_addr", apiConfig.ListenAddr,
)
// Initialize storage
store, err := storage.NewStorage(storageConfig)
if err != nil {
log.Error("failed to initialize storage", "error", err)
os.Exit(1)
}
defer store.Close()
log.Info("storage initialized", "db_path", storageConfig.DBPath)
// Initialize collector
coll := collector.NewCollector(collectorConfig)
log.Info("collector initialized")
// Initialize SSE hub
hub := api.NewSSEHub(apiConfig)
go hub.Run()
log.Info("sse hub started")
// Set up API router
router := api.NewRouter(hub, store, apiConfig)
// Start collector
ctx, cancel := context.WithCancel(context.Background())
go coll.Start(ctx)
// Connect collector to storage and hub
go func() {
snapLog := logger.Component("snapshot-processor")
for snapshot := range coll.Snapshots() {
// Write to storage
store.Write(snapshot)
// Broadcast to SSE clients
hub.Broadcast(snapshot)
snapLog.Debug("processed snapshot",
"variant", snapshot.Variant,
"timestamp", snapshot.Timestamp,
"req_rate", snapshot.ReqRate,
)
}
}()
// Set up HTTP server
mux := http.NewServeMux()
router.SetupRoutes(mux)
// Serve embedded frontend
frontendSub, err := fs.Sub(frontendFS, "frontend/dist")
if err != nil {
log.Error("failed to create frontend sub FS", "error", err)
os.Exit(1)
}
frontendHandler := http.FileServer(http.FS(frontendSub))
mux.Handle("/assets/", frontendHandler)
// Serve index.html for root path - directly read to avoid redirect loop
// (http.FileServer with modified URL.Path can cause 301 redirects)
indexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
content, err := frontendFS.ReadFile("frontend/dist/index.html")
if err != nil {
http.Error(w, "index.html not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(content)
})
// Serve index.html for all other routes (SPA)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
indexHandler.ServeHTTP(w, r)
return
}
// Try static files first, then fallback to index.html for SPA routing
if _, err := frontendFS.Open("frontend/dist" + r.URL.Path); err == nil {
frontendHandler.ServeHTTP(w, r)
return
}
// SPA fallback - serve index.html for client-side routes
indexHandler.ServeHTTP(w, r)
})
// Apply middleware
handler := api.Chain(mux,
api.RecoveryMiddleware,
api.LoggingMiddleware,
api.CORSMiddleware,
)
server := &http.Server{
Addr: apiConfig.ListenAddr,
Handler: handler,
}
// Handle shutdown gracefully
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigChan
log.Info("shutdown signal received", "signal", sig.String())
cancel()
server.Shutdown(context.Background())
}()
log.Info("server starting", "address", apiConfig.ListenAddr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Error("server error", "error", err)
os.Exit(1)
}
log.Info("server shutdown complete")
}