You've already forked caddy-opnsense-blocker
Refine dashboard leaderboard filters and layout
This commit is contained in:
@@ -225,6 +225,23 @@ func (s *Store) RecordEvent(ctx context.Context, event *model.Event) error {
|
||||
|
||||
const responseBytesExpression = `CASE WHEN json_valid(e.raw_json) THEN CAST(COALESCE(json_extract(e.raw_json, '$.size'), 0) AS INTEGER) ELSE 0 END`
|
||||
|
||||
func overviewFilterQueryParts(options model.OverviewOptions) (joins []string, clauses []string) {
|
||||
if !options.ShowAllowed {
|
||||
joins = append(joins, `LEFT JOIN ip_state s ON s.ip = e.client_ip`)
|
||||
clauses = append(clauses, `COALESCE(s.state, '') <> '`+string(model.IPStateAllowed)+`'`)
|
||||
}
|
||||
if !options.ShowKnownBots {
|
||||
clauses = append(clauses, `NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM ip_investigations i
|
||||
WHERE i.ip = e.client_ip
|
||||
AND json_valid(i.payload_json)
|
||||
AND json_type(i.payload_json, '$.bot') IS NOT NULL
|
||||
)`)
|
||||
}
|
||||
return joins, clauses
|
||||
}
|
||||
|
||||
func (s *Store) AddDecision(ctx context.Context, decision *model.DecisionRecord) error {
|
||||
if decision == nil {
|
||||
return errors.New("nil decision record")
|
||||
@@ -372,7 +389,7 @@ func (s *Store) ClearManualOverride(ctx context.Context, ip string, reason strin
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOverview(ctx context.Context, since time.Time, limit int) (model.Overview, error) {
|
||||
func (s *Store) GetOverview(ctx context.Context, since time.Time, limit int, options model.OverviewOptions) (model.Overview, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
@@ -407,19 +424,19 @@ func (s *Store) GetOverview(ctx context.Context, since time.Time, limit int) (mo
|
||||
if err != nil {
|
||||
return model.Overview{}, err
|
||||
}
|
||||
topIPsByEvents, err := s.listTopIPRows(ctx, since, limit, "events")
|
||||
topIPsByEvents, err := s.listTopIPRows(ctx, since, limit, "events", options)
|
||||
if err != nil {
|
||||
return model.Overview{}, err
|
||||
}
|
||||
topIPsByTraffic, err := s.listTopIPRows(ctx, since, limit, "traffic")
|
||||
topIPsByTraffic, err := s.listTopIPRows(ctx, since, limit, "traffic", options)
|
||||
if err != nil {
|
||||
return model.Overview{}, err
|
||||
}
|
||||
topSources, err := s.listTopSourceRows(ctx, since, limit)
|
||||
topSources, err := s.listTopSourceRows(ctx, since, limit, options)
|
||||
if err != nil {
|
||||
return model.Overview{}, err
|
||||
}
|
||||
topURLs, err := s.listTopURLRows(ctx, since, limit)
|
||||
topURLs, err := s.listTopURLRows(ctx, since, limit, options)
|
||||
if err != nil {
|
||||
return model.Overview{}, err
|
||||
}
|
||||
@@ -432,21 +449,28 @@ func (s *Store) GetOverview(ctx context.Context, since time.Time, limit int) (mo
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
func (s *Store) listTopIPRows(ctx context.Context, since time.Time, limit int, orderBy string) ([]model.TopIPRow, error) {
|
||||
func (s *Store) listTopIPRows(ctx context.Context, since time.Time, limit int, orderBy string, options model.OverviewOptions) ([]model.TopIPRow, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
joins, clauses := overviewFilterQueryParts(options)
|
||||
query := fmt.Sprintf(`
|
||||
SELECT e.client_ip,
|
||||
COUNT(*) AS event_count,
|
||||
COALESCE(SUM(%s), 0) AS traffic_bytes,
|
||||
MAX(e.occurred_at) AS last_seen_at
|
||||
FROM events e`, responseBytesExpression)
|
||||
if len(joins) > 0 {
|
||||
query += ` ` + strings.Join(joins, ` `)
|
||||
}
|
||||
args := make([]any, 0, 2)
|
||||
if !since.IsZero() {
|
||||
query += ` WHERE e.occurred_at >= ?`
|
||||
clauses = append([]string{`e.occurred_at >= ?`}, clauses...)
|
||||
args = append(args, formatTime(since))
|
||||
}
|
||||
if len(clauses) > 0 {
|
||||
query += ` WHERE ` + strings.Join(clauses, ` AND `)
|
||||
}
|
||||
query += ` GROUP BY e.client_ip`
|
||||
switch orderBy {
|
||||
case "traffic":
|
||||
@@ -483,21 +507,28 @@ func (s *Store) listTopIPRows(ctx context.Context, since time.Time, limit int, o
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *Store) listTopSourceRows(ctx context.Context, since time.Time, limit int) ([]model.TopSourceRow, error) {
|
||||
func (s *Store) listTopSourceRows(ctx context.Context, since time.Time, limit int, options model.OverviewOptions) ([]model.TopSourceRow, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
joins, clauses := overviewFilterQueryParts(options)
|
||||
query := fmt.Sprintf(`
|
||||
SELECT e.source_name,
|
||||
COUNT(*) AS event_count,
|
||||
COALESCE(SUM(%s), 0) AS traffic_bytes,
|
||||
MAX(e.occurred_at) AS last_seen_at
|
||||
FROM events e`, responseBytesExpression)
|
||||
if len(joins) > 0 {
|
||||
query += ` ` + strings.Join(joins, ` `)
|
||||
}
|
||||
args := make([]any, 0, 2)
|
||||
if !since.IsZero() {
|
||||
query += ` WHERE e.occurred_at >= ?`
|
||||
clauses = append([]string{`e.occurred_at >= ?`}, clauses...)
|
||||
args = append(args, formatTime(since))
|
||||
}
|
||||
if len(clauses) > 0 {
|
||||
query += ` WHERE ` + strings.Join(clauses, ` AND `)
|
||||
}
|
||||
query += ` GROUP BY e.source_name ORDER BY event_count DESC, traffic_bytes DESC, last_seen_at DESC, e.source_name ASC LIMIT ?`
|
||||
args = append(args, limit)
|
||||
|
||||
@@ -527,10 +558,11 @@ func (s *Store) listTopSourceRows(ctx context.Context, since time.Time, limit in
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *Store) listTopURLRows(ctx context.Context, since time.Time, limit int) ([]model.TopURLRow, error) {
|
||||
func (s *Store) listTopURLRows(ctx context.Context, since time.Time, limit int, options model.OverviewOptions) ([]model.TopURLRow, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
joins, clauses := overviewFilterQueryParts(options)
|
||||
query := fmt.Sprintf(`
|
||||
SELECT e.host,
|
||||
e.uri,
|
||||
@@ -538,11 +570,17 @@ func (s *Store) listTopURLRows(ctx context.Context, since time.Time, limit int)
|
||||
COALESCE(SUM(%s), 0) AS traffic_bytes,
|
||||
MAX(e.occurred_at) AS last_seen_at
|
||||
FROM events e`, responseBytesExpression)
|
||||
if len(joins) > 0 {
|
||||
query += ` ` + strings.Join(joins, ` `)
|
||||
}
|
||||
args := make([]any, 0, 2)
|
||||
if !since.IsZero() {
|
||||
query += ` WHERE e.occurred_at >= ?`
|
||||
clauses = append([]string{`e.occurred_at >= ?`}, clauses...)
|
||||
args = append(args, formatTime(since))
|
||||
}
|
||||
if len(clauses) > 0 {
|
||||
query += ` WHERE ` + strings.Join(clauses, ` AND `)
|
||||
}
|
||||
query += ` GROUP BY e.host, e.uri ORDER BY event_count DESC, traffic_bytes DESC, last_seen_at DESC, e.host ASC, e.uri ASC LIMIT ?`
|
||||
args = append(args, limit)
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestStoreRecordsEventsAndState(t *testing.T) {
|
||||
t.Fatalf("unexpected source offset: found=%v offset=%+v", found, offset)
|
||||
}
|
||||
|
||||
overview, err := db.GetOverview(ctx, occurredAt.Add(-time.Hour), 10)
|
||||
overview, err := db.GetOverview(ctx, occurredAt.Add(-time.Hour), 10, model.OverviewOptions{ShowKnownBots: true, ShowAllowed: true})
|
||||
if err != nil {
|
||||
t.Fatalf("get overview: %v", err)
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func TestStoreOverviewLeaderboardsUseTrafficFromRawJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
overview, err := db.GetOverview(ctx, baseTime.Add(-time.Minute), 10)
|
||||
overview, err := db.GetOverview(ctx, baseTime.Add(-time.Minute), 10, model.OverviewOptions{ShowKnownBots: true, ShowAllowed: true})
|
||||
if err != nil {
|
||||
t.Fatalf("get overview: %v", err)
|
||||
}
|
||||
@@ -264,4 +264,32 @@ func TestStoreOverviewLeaderboardsUseTrafficFromRawJSON(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
if err := db.SaveInvestigation(ctx, model.IPInvestigation{
|
||||
IP: "203.0.113.10",
|
||||
UpdatedAt: baseTime,
|
||||
Bot: &model.BotMatch{Name: "Googlebot", ProviderID: "google_official", Verified: true},
|
||||
}); err != nil {
|
||||
t.Fatalf("save top bot investigation: %v", err)
|
||||
}
|
||||
if _, err := db.SetManualOverride(ctx, "203.0.113.20", model.ManualOverrideForceAllow, model.IPStateAllowed, "manual allow"); err != nil {
|
||||
t.Fatalf("set manual override for filter test: %v", err)
|
||||
}
|
||||
|
||||
filtered, err := db.GetOverview(ctx, baseTime.Add(-time.Minute), 10, model.OverviewOptions{ShowKnownBots: false, ShowAllowed: false})
|
||||
if err != nil {
|
||||
t.Fatalf("get filtered overview: %v", err)
|
||||
}
|
||||
if len(filtered.TopIPsByEvents) != 0 {
|
||||
t.Fatalf("expected filtered top IPs by events to be empty, got %+v", filtered.TopIPsByEvents)
|
||||
}
|
||||
if len(filtered.TopIPsByTraffic) != 0 {
|
||||
t.Fatalf("expected filtered top IPs by traffic to be empty, got %+v", filtered.TopIPsByTraffic)
|
||||
}
|
||||
if len(filtered.TopSources) != 0 {
|
||||
t.Fatalf("expected filtered top sources to be empty, got %+v", filtered.TopSources)
|
||||
}
|
||||
if len(filtered.TopURLs) != 0 {
|
||||
t.Fatalf("expected filtered top urls to be empty, got %+v", filtered.TopURLs)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user