mavweb: add rule trace page at /trace

New template showing the most recent tick's rule evaluation results
with color-coded table, expandable gate detail panels, nav links
to dash and history. Uses the TickTrace IPC method from #15.
This commit is contained in:
kami
2026-07-05 13:17:47 +04:00
parent 2689db1248
commit 85013f7b8b
5 changed files with 173 additions and 3 deletions
+40
View File
@@ -48,6 +48,9 @@ var dashHTML string
//go:embed history.html
var historyHTML string
//go:embed trace.html
var traceHTML string
// dashTmpl — the monitoring read surface, server-rendered from dash.html (no JS,
// no client fetch); meta-refresh keeps it live. html/template escapes the user
// text in facts/nudges. Read-only: browses the append-only store via CoreAPI,
@@ -133,6 +136,9 @@ func main() {
mux.HandleFunc("/history", func(w http.ResponseWriter, r *http.Request) {
handleHistory(w, r, core)
})
mux.HandleFunc("/trace", func(w http.ResponseWriter, r *http.Request) {
handleTrace(w, r, core)
})
mux.HandleFunc("/api/revert", func(w http.ResponseWriter, r *http.Request) {
handleRevert(w, r, core)
})
@@ -372,6 +378,22 @@ input[type=text]{width:22rem}code{background:#f4f4f4;padding:.1rem .3rem}
var historyTmpl = template.Must(template.New("history").Parse(historyHTML))
var traceTmpl = template.Must(template.New("trace").Funcs(template.FuncMap{
"ago": func(t time.Time) string {
if t.IsZero() {
return "never"
}
return time.Since(t).Round(time.Second).String() + " ago"
},
"fmtTime": func(t *time.Time) string {
if t == nil || t.IsZero() {
return "—"
}
return t.Format("15:04:05")
},
"join": strings.Join,
}).Parse(traceHTML))
func handleHistory(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
if core == nil {
http.Error(w, "history disabled (no -core)", http.StatusServiceUnavailable)
@@ -392,6 +414,24 @@ func handleHistory(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
}
}
func handleTrace(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
if core == nil {
http.Error(w, "trace disabled (no -core)", http.StatusServiceUnavailable)
return
}
ctx := r.Context()
trace, err := core.TickTrace(ctx)
if err != nil {
log.Printf("trace: %v", err)
http.Error(w, "core read failed", http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := traceTmpl.Execute(w, trace); err != nil {
log.Printf("trace render: %v", err)
}
}
func handleRevert(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)