refactor: harden diagnostics and consolidate runtime code

This commit is contained in:
Mikhail Chusavitin
2026-09-01 13:01:28 +03:00
parent ac4bc0b2b7
commit 0a6ca8ba0f
49 changed files with 1441 additions and 837 deletions
+71 -82
View File
@@ -1,6 +1,7 @@
package webui
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -16,35 +17,15 @@ import (
)
func (h *handler) handleAPIGNVIDIAGPUs(w http.ResponseWriter, _ *http.Request) {
if h.opts.App == nil {
writeError(w, http.StatusServiceUnavailable, "app not configured")
return
}
gpus, err := h.opts.App.ListNvidiaGPUs()
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if gpus == nil {
gpus = []platform.NvidiaGPU{}
}
writeJSON(w, gpus)
writeAPIListResponse(w, h.opts.App != nil, func() ([]platform.NvidiaGPU, error) {
return h.opts.App.ListNvidiaGPUs()
})
}
func (h *handler) handleAPIGNVIDIAGPUStatuses(w http.ResponseWriter, _ *http.Request) {
if h.opts.App == nil {
writeError(w, http.StatusServiceUnavailable, "app not configured")
return
}
gpus, err := apiListNvidiaGPUStatuses(h.opts.App)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if gpus == nil {
gpus = []platform.NvidiaGPUStatus{}
}
writeJSON(w, gpus)
writeAPIListResponse(w, h.opts.App != nil, func() ([]platform.NvidiaGPUStatus, error) {
return apiListNvidiaGPUStatuses(h.opts.App)
})
}
func (h *handler) handleAPIGNVIDIAReset(w http.ResponseWriter, r *http.Request) {
@@ -70,64 +51,33 @@ func (h *handler) handleAPIGNVIDIAReset(w http.ResponseWriter, r *http.Request)
// ── GPU settings (ECC / power limit) ──────────────────────────────────────────
func (h *handler) handleAPIGNVIDIAGPUSettings(w http.ResponseWriter, _ *http.Request) {
if h.opts.App == nil {
writeError(w, http.StatusServiceUnavailable, "app not configured")
return
}
settings, err := h.opts.App.ListNvidiaGPUSettings()
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if settings == nil {
settings = []platform.NvidiaGPUSetting{}
}
writeJSON(w, settings)
writeAPIListResponse(w, h.opts.App != nil, func() ([]platform.NvidiaGPUSetting, error) {
return h.opts.App.ListNvidiaGPUSettings()
})
}
func (h *handler) handleAPIGNVIDIASetECC(w http.ResponseWriter, r *http.Request) {
if h.opts.App == nil {
writeError(w, http.StatusServiceUnavailable, "app not configured")
return
}
var req struct {
Index int `json:"index"`
Enabled bool `json:"enabled"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
result, err := h.opts.App.SetNvidiaGPUECC(req.Index, req.Enabled)
status := "ok"
if err != nil {
status = "error"
}
writeJSON(w, map[string]string{"status": status, "output": result.Body})
h.handleAPIGNVIDIASetBool(w, r, func(index int, enabled bool) (string, error) {
result, err := h.opts.App.SetNvidiaGPUECC(index, enabled)
return result.Body, err
})
}
func (h *handler) handleAPIGNVIDIASetMIG(w http.ResponseWriter, r *http.Request) {
if h.opts.App == nil {
writeError(w, http.StatusServiceUnavailable, "app not configured")
return
}
var req struct {
Index int `json:"index"`
Enabled bool `json:"enabled"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
result, err := h.opts.App.SetNvidiaGPUMIG(req.Index, req.Enabled)
status := "ok"
if err != nil {
status = "error"
}
writeJSON(w, map[string]string{"status": status, "output": result.Body})
h.handleAPIGNVIDIASetBool(w, r, func(index int, enabled bool) (string, error) {
result, err := h.opts.App.SetNvidiaGPUMIG(index, enabled)
return result.Body, err
})
}
func (h *handler) handleAPIGNVIDIASetCCMode(w http.ResponseWriter, r *http.Request) {
h.handleAPIGNVIDIASetBool(w, r, func(index int, enabled bool) (string, error) {
result, err := h.opts.App.SetNvidiaGPUCCMode(index, enabled)
return result.Body, err
})
}
func (h *handler) handleAPIGNVIDIASetBool(w http.ResponseWriter, r *http.Request, apply func(int, bool) (string, error)) {
if h.opts.App == nil {
writeError(w, http.StatusServiceUnavailable, "app not configured")
return
@@ -140,12 +90,12 @@ func (h *handler) handleAPIGNVIDIASetCCMode(w http.ResponseWriter, r *http.Reque
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
result, err := h.opts.App.SetNvidiaGPUCCMode(req.Index, req.Enabled)
output, err := apply(req.Index, req.Enabled)
status := "ok"
if err != nil {
status = "error"
}
writeJSON(w, map[string]string{"status": status, "output": result.Body})
writeJSON(w, map[string]string{"status": status, "output": output})
}
func (h *handler) handleAPIGNVIDIASetPowerLimit(w http.ResponseWriter, r *http.Request) {
@@ -348,6 +298,9 @@ func validTimezoneName(tz string) bool {
// values supplied by the client's browser (used when the appliance has no
// network/NTP access to keep its own clock in sync).
func (h *handler) handleAPISystemTimeSync(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
var req struct {
Timezone string `json:"timezone"`
EpochMS int64 `json:"epoch_ms"`
@@ -368,19 +321,25 @@ func (h *handler) handleAPISystemTimeSync(w http.ResponseWriter, r *http.Request
writeError(w, http.StatusBadRequest, "invalid timezone")
return
}
if b, err := exec.Command("timedatectl", "set-timezone", req.Timezone).CombinedOutput(); err != nil {
writeError(w, http.StatusInternalServerError, "set-timezone failed: "+strings.TrimSpace(string(b)))
if b, err := exec.CommandContext(ctx, "timedatectl", "set-timezone", req.Timezone).CombinedOutput(); err != nil {
writeError(w, http.StatusInternalServerError, "set-timezone failed: "+commandFailureDetail(b, err))
return
}
fmt.Fprintf(&out, "timezone set to %s\n", req.Timezone)
}
// Manual time only sticks if NTP sync is off.
_ = exec.Command("timedatectl", "set-ntp", "false").Run()
if b, err := exec.CommandContext(ctx, "timedatectl", "set-ntp", "false").CombinedOutput(); err != nil {
canNTP, canErr := exec.CommandContext(ctx, "timedatectl", "show", "-p", "CanNTP", "--value").Output()
if canErr != nil || strings.TrimSpace(string(canNTP)) != "no" {
writeError(w, http.StatusInternalServerError, "disable NTP failed: "+commandFailureDetail(b, err))
return
}
}
sec := req.EpochMS / 1000
if b, err := exec.Command("date", "-u", "-s", "@"+strconv.FormatInt(sec, 10)).CombinedOutput(); err != nil {
writeError(w, http.StatusInternalServerError, "set-time failed: "+strings.TrimSpace(string(b)))
if b, err := exec.CommandContext(ctx, "date", "-u", "-s", "@"+strconv.FormatInt(sec, 10)).CombinedOutput(); err != nil {
writeError(w, http.StatusInternalServerError, "set-time failed: "+commandFailureDetail(b, err))
return
}
out.WriteString("system clock synced\n")
@@ -388,6 +347,36 @@ func (h *handler) handleAPISystemTimeSync(w http.ResponseWriter, r *http.Request
writeJSON(w, map[string]string{"status": "ok", "output": out.String()})
}
func commandFailureDetail(output []byte, err error) string {
if detail := strings.TrimSpace(string(output)); detail != "" {
return detail
}
return err.Error()
}
// handleAPISystemTime reports the host's current wall-clock time and configured
// timezone so the dashboard can show them next to the browser's own clock and
// flag a drift.
func (h *handler) handleAPISystemTime(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
tz := ""
if b, err := exec.CommandContext(ctx, "timedatectl", "show", "-p", "Timezone", "--value").Output(); err == nil {
tz = strings.TrimSpace(string(b))
}
now := time.Now()
if tz == "" {
tz, _ = now.Zone()
}
writeJSON(w, map[string]any{
"epoch_ms": now.UnixMilli(),
"local_time": now.Format("2006-01-02 15:04:05"),
"timezone": tz,
})
}
// ── Tools ─────────────────────────────────────────────────────────────────────
var standardTools = []string{