Add comprehensive Claude Code monitoring and realtime streaming to the K8s dashboard. Includes API endpoints for health, stats, summary, inventory, and live event streaming. Frontend provides overview, usage, inventory, debug, and live feed views.
117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"flag"
|
|
"io/fs"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/cors"
|
|
"github.com/will/k8s-agent-dashboard/internal/api"
|
|
"github.com/will/k8s-agent-dashboard/internal/claude"
|
|
"github.com/will/k8s-agent-dashboard/internal/store"
|
|
)
|
|
|
|
//go:embed all:web
|
|
var webFS embed.FS
|
|
|
|
func defaultClaudeDir() string {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "/home/will/.claude" // fallback; best-effort
|
|
}
|
|
return filepath.Join(home, ".claude")
|
|
}
|
|
|
|
func main() {
|
|
port := flag.String("port", "8080", "Server port")
|
|
dataDir := flag.String("data", "/data", "Data directory for state")
|
|
claudeDir := flag.String("claude", defaultClaudeDir(), "Claude Code directory")
|
|
flag.Parse()
|
|
|
|
// Initialize store
|
|
s, err := store.New(*dataDir)
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize store: %v", err)
|
|
}
|
|
|
|
// Initialize Claude loader
|
|
claudeLoader := claude.NewLoader(*claudeDir)
|
|
|
|
// Initialize event hub
|
|
hub := claude.NewEventHub(1000)
|
|
|
|
// Create router
|
|
r := chi.NewRouter()
|
|
|
|
// Middleware
|
|
r.Use(middleware.Logger)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.Compress(5))
|
|
r.Use(cors.Handler(cors.Options{
|
|
AllowedOrigins: []string{"*"},
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowedHeaders: []string{"Accept", "Content-Type"},
|
|
ExposedHeaders: []string{"Link"},
|
|
AllowCredentials: false,
|
|
MaxAge: 300,
|
|
}))
|
|
|
|
// API routes
|
|
r.Route("/api", func(r chi.Router) {
|
|
r.Get("/health", api.HealthCheck)
|
|
|
|
r.Route("/claude", func(r chi.Router) {
|
|
r.Get("/health", api.GetClaudeHealth(claudeLoader))
|
|
r.Get("/stats", api.GetClaudeStats(claudeLoader))
|
|
r.Get("/summary", api.GetClaudeSummary(claudeLoader))
|
|
r.Get("/inventory", api.GetClaudeInventory(claudeLoader))
|
|
r.Get("/debug/files", api.GetClaudeDebugFiles(claudeLoader))
|
|
r.Get("/live/backlog", api.GetClaudeLiveBacklog(claudeLoader))
|
|
r.Get("/stream", api.GetClaudeStream(hub))
|
|
})
|
|
r.Get("/status", api.GetClusterStatus(s))
|
|
r.Get("/pending", api.GetPendingActions(s))
|
|
r.Post("/pending/{id}/approve", api.ApproveAction(s))
|
|
r.Post("/pending/{id}/reject", api.RejectAction(s))
|
|
r.Get("/history", api.GetActionHistory(s))
|
|
r.Get("/workflows", api.GetWorkflows(s))
|
|
r.Post("/workflows/{name}/run", api.RunWorkflow(s))
|
|
r.Post("/workflows/{name}/complete", api.CompleteWorkflow(s))
|
|
|
|
// Webhook endpoints
|
|
r.Post("/webhooks/alertmanager", api.AlertmanagerWebhook(s))
|
|
})
|
|
|
|
// Static files
|
|
webContent, err := fs.Sub(webFS, "web")
|
|
if err != nil {
|
|
log.Fatalf("Failed to get web content: %v", err)
|
|
}
|
|
|
|
fileServer := http.FileServer(http.FS(webContent))
|
|
r.Handle("/*", fileServer)
|
|
|
|
// Start server
|
|
addr := ":" + *port
|
|
if envPort := os.Getenv("PORT"); envPort != "" {
|
|
addr = ":" + envPort
|
|
}
|
|
|
|
log.Printf("Starting server on %s", addr)
|
|
log.Printf("Data directory: %s", *dataDir)
|
|
log.Printf("Claude directory: %s", *claudeDir)
|
|
|
|
stop := make(chan struct{})
|
|
go claude.TailHistoryFile(stop, hub, filepath.Join(*claudeDir, "history.jsonl"))
|
|
|
|
if err := http.ListenAndServe(addr, r); err != nil {
|
|
log.Fatalf("Server failed: %v", err)
|
|
}
|
|
}
|