Skip to content

feat: v0.6 Tracks 2–3b — summary dashboard, investigation view, fingerprint diff#7

Merged
Crank-Git merged 4 commits into
mainfrom
feat/v0.6-fingerprint-diff
Apr 11, 2026
Merged

feat: v0.6 Tracks 2–3b — summary dashboard, investigation view, fingerprint diff#7
Crank-Git merged 4 commits into
mainfrom
feat/v0.6-fingerprint-diff

Conversation

@Crank-Git
Copy link
Copy Markdown
Owner

Summary

PRs 4, 5, 6 were merged into their chained feature branches rather than main due to a gh pr edit --base failure. This PR delivers the missing three tracks directly.

Track 2 — Summary dashboard (S key)

  • SummaryModel: top-5 source IPs, top-5 JA4 fingerprints, alert breakdown by severity, custom rule fire counts, recent alert log
  • Refreshes on 1s tick; custom rules section is nil-safe for daemon-attach mode

Track 3a — Time-window investigation view (I key)

  • InvestigateModel: user types a duration → async SQLite QueryTimeWindow query → scrollable results table
  • mergeInvestigateResults: overlays live in-memory connections on historical results (in-memory wins on dedup)
  • StoreTimeWindowFunc wired in NewRouter; nil-safe for no-store mode

Track 3b — Fingerprint diff mode (D key)

  • DiffModel: two-step duration input (B=comparison, A=baseline) → async DiffWindows → classified table
  • computeDiff: new/gone/spiked/dropped classification using DiffSpikeMultiplier=3, DiffMinDelta=10
  • StoreDiffFunc + DiffWindows SQL using fingerprint_index JOIN connections with GROUP BY

Review fixes (from /review on PRs 3–6)

  • inTextInput() guard: S/I/D global shortcuts no longer intercepted while filter bar, diff input, or investigate input is active
  • Added lt/lte condition tests, ReloadCustomRules tests, CustomRuleStats tests
  • State machine diagram updated

Test plan

  • go test ./... passes
  • Press S → summary dashboard opens; Esc returns to table
  • Press I → investigation prompt; enter duration → results; Esc closes
  • Press D → diff prompt; enter two durations → classified diff; Esc closes
  • Open filter (f), type "S" → character appears in filter, mode does NOT change
  • Type duration in diff input including 's' character → stays in diff

🤖 Generated with Claude Code

Crank-Git and others added 4 commits April 11, 2026 15:40
Adds a full-screen network overview dashboard accessible from any TUI
mode via the S key (toggle). Esc or S returns to the connection table.

Summary view shows:
- Connection count, fingerprint count, allowlist suppression count
- Alert severity breakdown (critical/high/medium/low)
- Top-5 source IPs by connection count (1s cached aggregation)
- Top-5 JA4 fingerprints by frequency (1s cached aggregation)
- Custom rule fire counts via Evaluator.CustomRuleStats() (atomic reads)
- Last 10 alerts in reverse-chron order

Implementation:
- internal/tui/summary.go: SummaryModel with 1s-cached top-N computation;
  buildTopSrcIPs/buildTopJA4s/topEntries helpers
- internal/tui/router.go: stateSummary state, S global key handler (before
  mode delegation), WithRuleStatsFn builder, updated ASCII state diagram
- internal/tui/keys.go: Summary key binding (S)
- internal/anomaly/evaluator.go: RuleStat type + CustomRuleStats() method
- internal/anomaly/threshold.go: fix fireCount data race — plain int64 →
  atomic.Int64 (concurrent dashboard reads vs shard increments)
- cmd/ja4monitor/main.go: wire WithRuleStatsFn(evaluator.CustomRuleStats)

Tests: 18 new tests covering aggregation helpers, onTick cache behaviour,
View smoke tests (empty/alerts/rules/nil-fn), and S-key state transitions.
Full suite passes under -race.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- storage: QueryTimeWindow(start, end, timeout) — overlapping-lifetime
  query against the read-only SQLite handle; same timeout/context pattern
  as Query; 4 tests covering match, empty window, empty DB, default timeout
- tui: InvestigateModel — input state collects a relative duration (1h/30m/
  24h/2h30m); async queryCmd fires in background; results state shows
  scrollable table merged from SQLite + in-memory connMap (in-memory wins)
- tui: global I key handler in router — enters/toggles investigate mode,
  resets model to fresh input state on each entry; StoreTimeWindowFunc wired
  from store.QueryTimeWindow in NewRouter; nil-safe for daemon attach mode
- 30 new tests (investigate_test.go + sqlite_test.go); all pass under -race

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Compare fingerprint landscapes between two adjacent time windows to
answer "what changed on my network?"

- storage: DiffWindows(aStart, aEnd, bStart, bEnd, timeout) — counts
  fingerprints per window via fingerprint_index JOIN connections; uses
  read-only handle; 4 tests (new/gone, multi-conn counts, empty DB,
  default timeout); DiffSpikeMultiplier=3, DiffMinDelta=10 as exported
  constants so callers can reference the thresholds
- tui: DiffModel with 3-step state machine (inputB → inputA → results);
  D key global handler resets model on each entry; computeDiff classifies
  new/gone/spiked/dropped entries, skips unchanged, sorts by category
  then fp_type then fp_value; color-coded output (green/red/yellow/cyan)
- tui: 4 new diff styles added to styles.go
- 38 new tests (diff_test.go + sqlite_test.go); all pass under -race

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndition and hot-reload tests

- router.go: add inTextInput() guard — suppresses S/I/D global shortcuts
  when filterMode, exportPrompt, diff duration input, or investigate window
  input is active; typing "1h30s" no longer exits those modes accidentally
- router.go: update state-machine ASCII diagram (add AlertList→Detail edge,
  note global shortcuts are suppressed in text-input states)
- threshold_test.go: add TestThresholdRule_CountLT / CountLTE covering the
  two previously-untested conditionMet branches
- evaluator_test.go: add TestReloadCustomRules_{LoadsAndEvaluates,
  InvalidConfigKeepsOldRules,ClearRules} and TestCustomRuleStats_{NilWhenNoRules,
  ReturnsFireCounts} covering ReloadCustomRules and CustomRuleStats
- diff_test.go / investigate_test.go: fix "toggles back to table" tests to
  put the model in results sub-state (D/I are intentionally suppressed in
  input sub-states per the new guard)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Crank-Git Crank-Git merged commit 6cb5805 into main Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant