feat: built-in read-only dashboard at /ui + GET /runs
Build & push image / build-and-push (push) Successful in 26s
CI / test (push) Successful in 10m24s

Serves a self-contained vanilla-JS dashboard (embedded via go:embed): a per-model performance table — runs, minutes, findings, confirmed/false-positive/ungraded, points, points-per-minute, points-per-run, by-severity — with drill-down filters (date range, repo, provider, model, lens, grade/severity), free-text search, and a click-to-scope findings detail table.

Scoring stays client-side: the page has an editable points curve and computes points + value-per-minute in the browser, so the store remains point-free. Adds GET /runs (lists all runs, incl. zero-finding ones) so minutes/runs are filterable. The /ui shell is public (carries no data); data endpoints stay token-gated and the JS sends the token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 00:22:39 -04:00
parent 9458528b40
commit 35ebc53561
6 changed files with 460 additions and 12 deletions
+36
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
)
@@ -86,6 +87,41 @@ func TestServerAuth(t *testing.T) {
}
}
// TestViewShellPublic: the dashboard shell (/ and /ui) is reachable without a
// token even when one is set, but the data endpoints stay gated.
func TestViewShellPublic(t *testing.T) {
srv := testServer(t, "secret")
resp := mustGet(t, srv, "", "/ui")
if resp.StatusCode != 200 {
t.Errorf("GET /ui (no token) = %d, want 200", resp.StatusCode)
}
if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/html") {
t.Errorf("GET /ui content-type = %q, want text/html", ct)
}
// data must still require the token
if resp := mustGet(t, srv, "", "/runs"); resp.StatusCode != http.StatusUnauthorized {
t.Errorf("GET /runs (no token) = %d, want 401", resp.StatusCode)
}
}
// TestListRuns: POSTed runs come back via GET /runs with created_at populated.
func TestListRuns(t *testing.T) {
srv := testServer(t, "")
if resp := post(t, srv, "", "/runs", Run{RunID: "r1", Repo: "r", PR: 1, Model: "m", Provider: "p", DurationSecs: 120}); resp.StatusCode != 200 {
t.Fatalf("POST /runs = %d", resp.StatusCode)
}
resp := mustGet(t, srv, "", "/runs")
if resp.StatusCode != 200 {
t.Fatalf("GET /runs = %d", resp.StatusCode)
}
var runs []Run
json.NewDecoder(resp.Body).Decode(&runs)
if len(runs) != 1 || runs[0].RunID != "r1" || runs[0].DurationSecs != 120 || runs[0].CreatedAt == "" {
t.Fatalf("unexpected runs: %+v", runs)
}
}
func mustGet(t *testing.T, srv *httptest.Server, token, path string) *http.Response {
t.Helper()
req, _ := http.NewRequest("GET", srv.URL+path, nil)