2

Add dashboard activity leaderboards

This commit is contained in:
2026-03-12 15:48:33 +01:00
parent f15839cf51
commit 49bda65b3b
9 changed files with 534 additions and 26 deletions

View File

@@ -99,13 +99,22 @@ func TestStoreRecordsEventsAndState(t *testing.T) {
t.Fatalf("unexpected source offset: found=%v offset=%+v", found, offset)
}
overview, err := db.GetOverview(ctx, 10)
overview, err := db.GetOverview(ctx, occurredAt.Add(-time.Hour), 10)
if err != nil {
t.Fatalf("get overview: %v", err)
}
if overview.TotalEvents != 1 || overview.TotalIPs != 1 {
t.Fatalf("unexpected overview counters: %+v", overview)
}
if len(overview.TopIPsByEvents) != 1 || overview.TopIPsByEvents[0].IP != event.ClientIP {
t.Fatalf("unexpected top ips by events: %+v", overview.TopIPsByEvents)
}
if len(overview.TopSources) != 1 || overview.TopSources[0].SourceName != event.SourceName {
t.Fatalf("unexpected top sources: %+v", overview.TopSources)
}
if len(overview.TopURLs) != 1 || overview.TopURLs[0].URI != event.URI {
t.Fatalf("unexpected top urls: %+v", overview.TopURLs)
}
recentIPs, err := db.ListRecentIPRows(ctx, occurredAt.Add(-time.Hour), 10)
if err != nil {
t.Fatalf("list recent ip rows: %v", err)
@@ -161,3 +170,98 @@ func TestStoreRecordsEventsAndState(t *testing.T) {
t.Fatalf("expected no IPs without investigation, got %#v", missingInvestigationIPs)
}
}
func TestStoreOverviewLeaderboardsUseTrafficFromRawJSON(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "blocker.db")
db, err := Open(dbPath)
if err != nil {
t.Fatalf("open store: %v", err)
}
defer db.Close()
ctx := context.Background()
baseTime := time.Date(2025, 3, 12, 15, 0, 0, 0, time.UTC)
events := []*model.Event{
{
SourceName: "public-web",
ProfileName: "public-web",
OccurredAt: baseTime,
RemoteIP: "198.51.100.10",
ClientIP: "203.0.113.10",
Host: "example.test",
Method: "GET",
URI: "/wp-login.php",
Path: "/wp-login.php",
Status: 404,
UserAgent: "curl/8.0",
Decision: model.DecisionActionBlock,
DecisionReason: "php_path",
DecisionReasons: []string{"php_path"},
RawJSON: `{"status":404,"size":2048}`,
},
{
SourceName: "public-web",
ProfileName: "public-web",
OccurredAt: baseTime.Add(10 * time.Second),
RemoteIP: "198.51.100.11",
ClientIP: "203.0.113.10",
Host: "example.test",
Method: "GET",
URI: "/wp-login.php",
Path: "/wp-login.php",
Status: 404,
UserAgent: "curl/8.0",
Decision: model.DecisionActionBlock,
DecisionReason: "php_path",
DecisionReasons: []string{"php_path"},
RawJSON: `{"status":404,"size":1024}`,
},
{
SourceName: "gitea",
ProfileName: "gitea",
OccurredAt: baseTime.Add(20 * time.Second),
RemoteIP: "198.51.100.12",
ClientIP: "203.0.113.20",
Host: "git.example.test",
Method: "GET",
URI: "/install.php",
Path: "/install.php",
Status: 404,
UserAgent: "curl/8.0",
Decision: model.DecisionActionReview,
DecisionReason: "suspicious_path_prefix:/install.php",
DecisionReasons: []string{"suspicious_path_prefix:/install.php"},
RawJSON: `{"status":404,"size":4096}`,
},
}
for _, event := range events {
if err := db.RecordEvent(ctx, event); err != nil {
t.Fatalf("record event %+v: %v", event, err)
}
}
overview, err := db.GetOverview(ctx, baseTime.Add(-time.Minute), 10)
if err != nil {
t.Fatalf("get overview: %v", err)
}
if len(overview.TopIPsByEvents) < 2 {
t.Fatalf("expected at least 2 top IP rows by events, got %+v", overview.TopIPsByEvents)
}
if overview.TopIPsByEvents[0].IP != "203.0.113.10" || overview.TopIPsByEvents[0].Events != 2 || overview.TopIPsByEvents[0].TrafficBytes != 3072 {
t.Fatalf("unexpected top IP by events row: %+v", overview.TopIPsByEvents[0])
}
if len(overview.TopIPsByTraffic) < 2 {
t.Fatalf("expected at least 2 top IP rows by traffic, got %+v", overview.TopIPsByTraffic)
}
if overview.TopIPsByTraffic[0].IP != "203.0.113.20" || overview.TopIPsByTraffic[0].TrafficBytes != 4096 {
t.Fatalf("unexpected top IP by traffic row: %+v", overview.TopIPsByTraffic[0])
}
if len(overview.TopSources) < 2 || overview.TopSources[0].SourceName != "public-web" || overview.TopSources[0].Events != 2 {
t.Fatalf("unexpected top source rows: %+v", overview.TopSources)
}
if len(overview.TopURLs) == 0 || overview.TopURLs[0].URI != "/wp-login.php" || overview.TopURLs[0].Events != 2 {
t.Fatalf("unexpected top url rows: %+v", overview.TopURLs)
}
}