package webui import ( "encoding/json" "errors" "net/http" "os" "path/filepath" "strings" "bee/audit/internal/app" "bee/audit/internal/platform" ) func (h *handler) handleAPIServicesList(w http.ResponseWriter, r *http.Request) { if h.opts.App == nil { writeError(w, http.StatusServiceUnavailable, "app not configured") return } names, err := h.opts.App.ListBeeServices() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } type serviceInfo struct { Name string `json:"name"` State string `json:"state"` Body string `json:"body"` } result := make([]serviceInfo, 0, len(names)) for _, name := range names { state := h.opts.App.ServiceState(name) body, _ := h.opts.App.ServiceStatus(name) result = append(result, serviceInfo{Name: name, State: state, Body: body}) } writeJSON(w, result) } func (h *handler) handleAPIServicesAction(w http.ResponseWriter, r *http.Request) { if h.opts.App == nil { writeError(w, http.StatusServiceUnavailable, "app not configured") return } var req struct { Name string `json:"name"` Action string `json:"action"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } var action platform.ServiceAction switch req.Action { case "start": action = platform.ServiceStart case "stop": action = platform.ServiceStop case "restart": action = platform.ServiceRestart default: writeError(w, http.StatusBadRequest, "action must be start|stop|restart") return } result, err := h.opts.App.ServiceActionResult(req.Name, action) status := "ok" if err != nil { status = "error" } // Always return 200 with output so the frontend can display the actual // systemctl error message instead of a generic "exit status 1". writeJSON(w, map[string]string{"status": status, "output": result.Body}) } // ── Network ─────────────────────────────────────────────────────────────────── func (h *handler) handleAPINetworkStatus(w http.ResponseWriter, r *http.Request) { if h.opts.App == nil { writeError(w, http.StatusServiceUnavailable, "app not configured") return } ifaces, err := h.opts.App.ListInterfaces() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, map[string]any{ "interfaces": ifaces, "default_route": h.opts.App.DefaultRoute(), "pending_change": h.hasPendingNetworkChange(), "rollback_in": h.pendingNetworkRollbackIn(), }) } func (h *handler) handleAPINetworkDHCP(w http.ResponseWriter, r *http.Request) { if h.opts.App == nil { writeError(w, http.StatusServiceUnavailable, "app not configured") return } var req struct { Interface string `json:"interface"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } result, err := h.applyPendingNetworkChange(func() (app.ActionResult, error) { if req.Interface == "" || req.Interface == "all" { return h.opts.App.DHCPAllResult() } return h.opts.App.DHCPOneResult(req.Interface) }) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, map[string]any{ "status": "ok", "output": result.Body, "rollback_in": int(netRollbackTimeout.Seconds()), }) } func (h *handler) handleAPINetworkStatic(w http.ResponseWriter, r *http.Request) { if h.opts.App == nil { writeError(w, http.StatusServiceUnavailable, "app not configured") return } var req struct { Interface string `json:"interface"` Address string `json:"address"` Prefix string `json:"prefix"` Gateway string `json:"gateway"` DNS []string `json:"dns"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } cfg := platform.StaticIPv4Config{ Interface: req.Interface, Address: req.Address, Prefix: req.Prefix, Gateway: req.Gateway, DNS: req.DNS, } result, err := h.applyPendingNetworkChange(func() (app.ActionResult, error) { return h.opts.App.SetStaticIPv4Result(cfg) }) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, map[string]any{ "status": "ok", "output": result.Body, "rollback_in": int(netRollbackTimeout.Seconds()), }) } // ── Export ──────────────────────────────────────────────────────────────────── func (h *handler) handleAPIExportList(w http.ResponseWriter, r *http.Request) { entries, err := listExportFiles(h.opts.ExportDir) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, entries) } func (h *handler) handleAPIExportUSBTargets(w http.ResponseWriter, _ *http.Request) { writeAPIListResponse(w, h.opts.App != nil, func() ([]platform.RemovableTarget, error) { return h.opts.App.ListRemovableTargets() }) } func (h *handler) handleAPIBlackboxStatus(w http.ResponseWriter, _ *http.Request) { state, err := app.ReadBlackboxState(filepath.Join(h.opts.ExportDir, "blackbox-state.json")) if err != nil { if errors.Is(err, os.ErrNotExist) { writeJSON(w, app.BlackboxState{Status: "disabled", Targets: []app.BlackboxTargetStatus{}}) return } writeError(w, http.StatusInternalServerError, err.Error()) return } if state.Targets == nil { state.Targets = []app.BlackboxTargetStatus{} } writeJSON(w, state) } func (h *handler) handleAPIBlackboxEnable(w http.ResponseWriter, r *http.Request) { if h.opts.App == nil { writeError(w, http.StatusServiceUnavailable, "app not configured") return } var target platform.RemovableTarget if err := json.NewDecoder(r.Body).Decode(&target); err != nil || strings.TrimSpace(target.Device) == "" { writeError(w, http.StatusBadRequest, "device is required") return } targets, err := h.opts.App.ListRemovableTargets() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } allowed := false for _, candidate := range targets { if candidate.Device == target.Device { target = candidate allowed = true break } } if !allowed { writeError(w, http.StatusBadRequest, "device not in removable target list") return } marker, err := app.EnableBlackboxTarget(target) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, map[string]any{ "status": "ok", "message": "Black-box marker written.", "enrollment_id": marker.EnrollmentID, }) } func (h *handler) handleAPIBlackboxDisable(w http.ResponseWriter, r *http.Request) { var req struct { Device string `json:"device"` EnrollmentID string `json:"enrollment_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := app.DisableBlackboxTarget(req.Device, req.EnrollmentID); err != nil { if errors.Is(err, os.ErrNotExist) { writeError(w, http.StatusNotFound, "black-box target not found") return } writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, map[string]string{"status": "ok", "message": "Black-box marker removed."}) } // ── GPU presence ──────────────────────────────────────────────────────────────