refactor: harden diagnostics and consolidate runtime code
This commit is contained in:
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user