261 lines
7.7 KiB
Go
261 lines
7.7 KiB
Go
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"`
|
|
}
|
|
_ = json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
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) {
|
|
if h.opts.App == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "app not configured")
|
|
return
|
|
}
|
|
targets, err := h.opts.App.ListRemovableTargets()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if targets == nil {
|
|
targets = []platform.RemovableTarget{}
|
|
}
|
|
writeJSON(w, targets)
|
|
}
|
|
|
|
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 ──────────────────────────────────────────────────────────────
|