Skip to content

Commit 19acd9a

Browse files
committed
add dash refactor
1 parent 264d01b commit 19acd9a

10 files changed

Lines changed: 632 additions & 72 deletions

File tree

-183 KB
Binary file not shown.

cli/beacon/internal/endpoint/dashboard/events.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type EventQuery struct {
3333
Since time.Time
3434
Q string
3535
Harness string
36+
Model string
3637
Action string
3738
Severity string
3839
Category string
@@ -217,6 +218,9 @@ func matchesQuery(record EventRecord, query EventQuery) bool {
217218
if query.Harness != "" && !strings.EqualFold(event.Harness.Name, query.Harness) {
218219
return false
219220
}
221+
if query.Model != "" && !containsFold(event.Model, query.Model) {
222+
return false
223+
}
220224
if query.Action != "" && !strings.EqualFold(event.Event.Action, query.Action) {
221225
return false
222226
}
@@ -414,6 +418,7 @@ func activeFilters(query EventQuery) map[string]string {
414418
}
415419
}
416420
add("harness", query.Harness)
421+
add("model", query.Model)
417422
add("action", query.Action)
418423
add("severity", query.Severity)
419424
add("category", query.Category)

cli/beacon/internal/endpoint/dashboard/events_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func TestBuildSummaryAggregatesSignals(t *testing.T) {
9797
Event: schema.EventInfo{Action: "command.executed", Category: "command"},
9898
Severity: schema.SeverityInfo,
9999
Harness: schema.HarnessInfo{Name: "cursor"},
100+
Model: "gpt-5.5",
100101
Session: &schema.SessionInfo{ID: "s1"},
101102
Command: &schema.CommandInfo{Command: "go test ./..."},
102103
}},
@@ -105,6 +106,7 @@ func TestBuildSummaryAggregatesSignals(t *testing.T) {
105106
Event: schema.EventInfo{Action: "mcp.tool_invoked", Category: "mcp"},
106107
Severity: schema.SeverityHigh,
107108
Harness: schema.HarnessInfo{Name: "cursor"},
109+
Model: "claude-4-sonnet",
108110
Session: &schema.SessionInfo{ID: "s2"},
109111
MCP: &schema.MCPInfo{Server: "github", Tool: "get_issue"},
110112
}, WazuhLevel: WazuhLevel("mcp.tool_invoked")},
@@ -137,6 +139,15 @@ func TestBuildSummaryAggregatesSignals(t *testing.T) {
137139
if summary.CountsByHarness["cursor"] != 4 {
138140
t.Fatalf("cursor harness count = %d, want 4", summary.CountsByHarness["cursor"])
139141
}
142+
if summary.CountsByModel["gpt-5.5"] != 1 || summary.CountsByModel["claude-4-sonnet"] != 1 {
143+
t.Fatalf("model counts = %#v, want gpt-5.5 and claude-4-sonnet", summary.CountsByModel)
144+
}
145+
if len(summary.TopHarnesses) == 0 || summary.TopHarnesses[0].Name != "cursor" {
146+
t.Fatalf("top harnesses = %#v, want cursor first", summary.TopHarnesses)
147+
}
148+
if len(summary.TopModels) != 2 {
149+
t.Fatalf("top models = %#v, want 2 models", summary.TopModels)
150+
}
140151
if summary.NeedsReviewEvents != 3 || summary.DeniedApprovalEvents != 1 || summary.PolicyBlockedEvents != 1 {
141152
t.Fatalf("review counts = needs %d denied %d blocked %d, want 3/1/1", summary.NeedsReviewEvents, summary.DeniedApprovalEvents, summary.PolicyBlockedEvents)
142153
}
@@ -217,6 +228,28 @@ func TestReadEventsSecurityFilters(t *testing.T) {
217228
}
218229
}
219230

231+
func TestReadEventsFiltersByModel(t *testing.T) {
232+
path := filepath.Join(t.TempDir(), "runtime.jsonl")
233+
events := []schema.Event{
234+
testSchemaEvent("2026-05-13T01:00:00Z", "cursor", "prompt.submitted", "prompt", "repo-a"),
235+
testSchemaEvent("2026-05-13T01:01:00Z", "claude_code", "prompt.submitted", "prompt", "repo-b"),
236+
}
237+
events[0].Model = "claude-4-sonnet"
238+
events[1].Model = "gpt-5.5"
239+
writeTestLog(t, path, marshalEvents(t, events...)...)
240+
241+
result, err := ReadEvents(path, EventQuery{Model: "sonnet", Limit: 10})
242+
if err != nil {
243+
t.Fatalf("ReadEvents returned error: %v", err)
244+
}
245+
if result.TotalMatched != 1 || result.Events[0].Event.Model != "claude-4-sonnet" {
246+
t.Fatalf("matched %d model %q, want 1 claude-4-sonnet", result.TotalMatched, result.Events[0].Event.Model)
247+
}
248+
if got := result.Filters["model"]; got != "sonnet" {
249+
t.Fatalf("model filter = %q, want sonnet", got)
250+
}
251+
}
252+
220253
func TestFindEventCanReadOutsideTailWindow(t *testing.T) {
221254
path := filepath.Join(t.TempDir(), "runtime.jsonl")
222255
lines := make([][]byte, 0, maxEventLimit+1)
@@ -289,6 +322,32 @@ func TestHandlerEventsEndpoint(t *testing.T) {
289322
}
290323
}
291324

325+
func TestHandlerEventsEndpointParsesModelFilter(t *testing.T) {
326+
path := filepath.Join(t.TempDir(), "runtime.jsonl")
327+
event := testSchemaEvent("2026-05-13T01:00:00Z", "cursor", "prompt.submitted", "prompt", "repo-a")
328+
event.Model = "gpt-5.5"
329+
writeTestLog(t, path, marshalEvents(t, event)...)
330+
331+
handler, err := Handler(Options{LogPath: path, UserMode: true})
332+
if err != nil {
333+
t.Fatalf("Handler returned error: %v", err)
334+
}
335+
req := httptest.NewRequest(http.MethodGet, "/api/events?model=gpt-5.5", nil)
336+
rec := httptest.NewRecorder()
337+
handler.ServeHTTP(rec, req)
338+
339+
if rec.Code != http.StatusOK {
340+
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
341+
}
342+
var result EventResult
343+
if err := json.Unmarshal(rec.Body.Bytes(), &result); err != nil {
344+
t.Fatalf("decode response: %v", err)
345+
}
346+
if len(result.Events) != 1 || result.Events[0].Event.Model != "gpt-5.5" {
347+
t.Fatalf("events len/model = %d/%q, want 1/gpt-5.5", len(result.Events), result.Events[0].Event.Model)
348+
}
349+
}
350+
292351
func TestValidateLoopbackAddr(t *testing.T) {
293352
if err := ValidateLoopbackAddr("127.0.0.1:8765"); err != nil {
294353
t.Fatalf("loopback address rejected: %v", err)

cli/beacon/internal/endpoint/dashboard/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ func parseQuery(r *http.Request, fallbackLimit int) EventQuery {
180180
Limit: limit,
181181
Q: q.Get("q"),
182182
Harness: q.Get("harness"),
183+
Model: q.Get("model"),
183184
Action: q.Get("action"),
184185
Severity: q.Get("severity"),
185186
Category: q.Get("category"),

cli/beacon/internal/endpoint/dashboard/server_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
"net/http/httptest"
77
"path/filepath"
8+
"strings"
89
"testing"
910
)
1011

@@ -33,3 +34,31 @@ func TestStatusUsesExplicitRuntimeLogPath(t *testing.T) {
3334
t.Fatalf("RuntimeLog.EffectiveLogPath = %q, want %q", status.RuntimeLog.EffectiveLogPath, logPath)
3435
}
3536
}
37+
38+
func TestStaticDashboardPagesServe(t *testing.T) {
39+
handler, err := Handler(Options{UserMode: true, LogPath: filepath.Join(t.TempDir(), "runtime.jsonl")})
40+
if err != nil {
41+
t.Fatalf("Handler returned error: %v", err)
42+
}
43+
44+
cases := []struct {
45+
path string
46+
want string
47+
}{
48+
{path: "/", want: "Beacon Endpoint Log Search"},
49+
{path: "/overview.html", want: "Beacon Endpoint Security Overview"},
50+
}
51+
for _, tc := range cases {
52+
t.Run(tc.path, func(t *testing.T) {
53+
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
54+
rec := httptest.NewRecorder()
55+
handler.ServeHTTP(rec, req)
56+
if rec.Code != http.StatusOK {
57+
t.Fatalf("status code = %d, body = %s", rec.Code, rec.Body.String())
58+
}
59+
if !strings.Contains(rec.Body.String(), tc.want) {
60+
t.Fatalf("body did not contain %q", tc.want)
61+
}
62+
})
63+
}
64+
}

0 commit comments

Comments
 (0)