package api import ( "encoding/json" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/will/k8s-agent-dashboard/internal/store" ) // JSON helper func respondJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) } func respondError(w http.ResponseWriter, status int, message string) { respondJSON(w, status, map[string]string{"error": message}) } // HealthCheck returns API health status func HealthCheck(w http.ResponseWriter, r *http.Request) { respondJSON(w, http.StatusOK, map[string]string{ "status": "ok", "service": "k8s-agent-dashboard", }) } // GetClusterStatus returns current cluster status func GetClusterStatus(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { status := s.GetClusterStatus() respondJSON(w, http.StatusOK, status) } } // GetPendingActions returns all pending actions func GetPendingActions(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { actions := s.GetPendingActions() respondJSON(w, http.StatusOK, map[string]interface{}{ "count": len(actions), "actions": actions, }) } } // ApproveAction approves a pending action func ApproveAction(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if id == "" { respondError(w, http.StatusBadRequest, "missing action id") return } var body struct { Reason string `json:"reason"` } json.NewDecoder(r.Body).Decode(&body) action, err := s.ApproveAction(id, body.Reason) if err != nil { respondError(w, http.StatusNotFound, err.Error()) return } respondJSON(w, http.StatusOK, map[string]interface{}{ "status": "approved", "action": action, "message": "Action approved and ready for execution", }) } } // RejectAction rejects a pending action func RejectAction(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if id == "" { respondError(w, http.StatusBadRequest, "missing action id") return } var body struct { Reason string `json:"reason"` } json.NewDecoder(r.Body).Decode(&body) if body.Reason == "" { body.Reason = "Rejected by user" } action, err := s.RejectAction(id, body.Reason) if err != nil { respondError(w, http.StatusNotFound, err.Error()) return } respondJSON(w, http.StatusOK, map[string]interface{}{ "status": "rejected", "action": action, "message": "Action rejected", }) } } // GetActionHistory returns action history func GetActionHistory(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { limitStr := r.URL.Query().Get("limit") limit := 50 if limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil { limit = l } } history := s.GetActionHistory(limit) respondJSON(w, http.StatusOK, map[string]interface{}{ "count": len(history), "history": history, }) } } // GetWorkflows returns defined workflows func GetWorkflows(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { workflows := s.GetWorkflows() respondJSON(w, http.StatusOK, map[string]interface{}{ "count": len(workflows), "workflows": workflows, }) } } // RunWorkflow triggers a workflow execution func RunWorkflow(s *store.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") if name == "" { respondError(w, http.StatusBadRequest, "missing workflow name") return } // In Phase 2, we just acknowledge the request // Phase 3 will implement actual execution via Claude Code respondJSON(w, http.StatusAccepted, map[string]interface{}{ "status": "queued", "workflow": name, "message": "Workflow queued for execution. Use Claude Code CLI to run workflows.", }) } }