feat: v0.6 Tracks 2–3b — summary dashboard, investigation view, fingerprint diff#7
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PRs 4, 5, 6 were merged into their chained feature branches rather than main due to a
gh pr edit --basefailure. 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 logTrack 3a — Time-window investigation view (I key)
InvestigateModel: user types a duration → async SQLiteQueryTimeWindowquery → scrollable results tablemergeInvestigateResults: overlays live in-memory connections on historical results (in-memory wins on dedup)StoreTimeWindowFuncwired inNewRouter; nil-safe for no-store modeTrack 3b — Fingerprint diff mode (D key)
DiffModel: two-step duration input (B=comparison, A=baseline) → asyncDiffWindows→ classified tablecomputeDiff: new/gone/spiked/dropped classification usingDiffSpikeMultiplier=3,DiffMinDelta=10StoreDiffFunc+DiffWindowsSQL usingfingerprint_index JOIN connectionswith GROUP BYReview 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 activelt/ltecondition tests,ReloadCustomRulestests,CustomRuleStatstestsTest plan
go test ./...passes🤖 Generated with Claude Code