package webui import ( "errors" "fmt" "net/http" "os" "strings" "reanimator/chart/viewer" chartweb "reanimator/chart/web" ) const defaultTitle = "Bee Hardware Audit" type HandlerOptions struct { Title string AuditPath string } func NewHandler(opts HandlerOptions) http.Handler { title := strings.TrimSpace(opts.Title) if title == "" { title = defaultTitle } auditPath := strings.TrimSpace(opts.AuditPath) mux := http.NewServeMux() mux.Handle("GET /static/", http.StripPrefix("/static/", chartweb.Static())) mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) mux.HandleFunc("GET /audit.json", func(w http.ResponseWriter, r *http.Request) { data, err := loadSnapshot(auditPath) if err != nil { if errors.Is(err, os.ErrNotExist) { http.Error(w, "audit snapshot not found", http.StatusNotFound) return } http.Error(w, fmt.Sprintf("read audit snapshot: %v", err), http.StatusInternalServerError) return } w.Header().Set("Cache-Control", "no-store") w.Header().Set("Content-Type", "application/json; charset=utf-8") _, _ = w.Write(data) }) mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { snapshot, err := loadSnapshot(auditPath) if err != nil && !errors.Is(err, os.ErrNotExist) { http.Error(w, fmt.Sprintf("read audit snapshot: %v", err), http.StatusInternalServerError) return } html, err := viewer.RenderHTML(snapshot, title) if err != nil { http.Error(w, fmt.Sprintf("render snapshot: %v", err), http.StatusInternalServerError) return } w.Header().Set("Cache-Control", "no-store") w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write(html) }) return mux } func ListenAndServe(addr string, opts HandlerOptions) error { return http.ListenAndServe(addr, NewHandler(opts)) } func loadSnapshot(path string) ([]byte, error) { if strings.TrimSpace(path) == "" { return nil, os.ErrNotExist } return os.ReadFile(path) }