feat(dashboard): log navigation — range presets, date jumps, and search for both security logs - #838
Merged
Merged
Conversation
…ch for both security logs (#823) Both Security panels gain one shared control row: the chart's range presets (24 Hr / 1 Wk / 1 Mo / All) for following a live log, two native date inputs for jumping to a specific day or span (the 'to' date covers its whole day via a half-open next-midnight bound), and a debounced search box matching any field. Filters compose. Filtering is SERVER-side (?from&to&q on /api/access and /api/audit) so a match deeper than the glance tail is still found: one filter helper serves both surfaces by normalizing each entry's ts (epoch seconds on access rows, the canonical UTC ISO string on audit rows) onto one epoch axis; an undatable row matches no time window but still matches a pure text search. Malformed bounds read as absent — a bad query must never 500 a log view; q is trimmed and length-capped and only ever compared, never stored or echoed unsanitized. The access read deepens to a bounded 1000-row tail only while filtering, and its failed-login counters always describe the whole tail. A filtered view lifts the 20-row glance cap and says 'No entries match this filter' honestly. Verified live in the harness: preset refetch narrows to the window and lifts the cap, search composes with the active range (including the honest empty intersection), the audit card filters independently. Closes #823 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Non-finite from/to bounds (inf/nan parse via float()) now read as absent — nan compares False with everything and would silently warp the window; the malformed-bounds contract is now true, and tested. - A per-surface sequence guard stops a slow stale response from overwriting a fresher filter's view. - Pending search debounce timers are cleared on unmount. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 2, 2026
Merged
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.
Closes #823 — the Security view's two log tables were navigable only by scrolling (and the config-change card's lone grouping dropdown).
What
Both panels gain one shared control row: the chart's range presets (24 Hr / 1 Wk / 1 Mo / All) for following a live log, two native date inputs for jumping to a specific day or span (the "to" date covers its whole day via a half-open next-midnight bound), and a debounced search box matching any field. Filters compose; the existing hour/day/month grouping still applies on top.
Filtering is server-side (
?from&to&qon/api/accessand/api/audit) so a match deeper than the glance tail is found: onefilter_log_entrieshelper serves both surfaces by normalizing each entry's ts (epoch on access rows, canonical UTC ISO on audit rows) onto one epoch axis — an undatable row matches no time window but still matches a pure text search. Malformed bounds read as absent (a bad query never 500s a log view);qis trimmed, length-capped, and only ever compared — the existing sanitation path is untouched. The access read deepens to a bounded 1000-row tail only while filtering; the failed-login counters always describe the whole tail. Filtered views lift the 20-row glance cap and report an honest "No entries match this filter".Verified live (harness)
Preset click narrows to the window and lifts the cap; search composes with the active range — including the honest empty intersection when the searched entry sits outside it; the audit card filters independently; controls render in both cards with the active preset marked.
Coverage
test_audit_service.py: the filter helper — half-open window across both ts shapes on one axis, undatable-row semantics, case-insensitive any-field search incl. numeric values, composition, non-string/non-number ts.test_server.py: both routes' param handling — window + search + whole-tail counters + malformed-bounds degradation.securityview.test.mjs:buildLogQuery(presets, date-override, next-midnight bound, URL encoding), controls rendering with active-preset marking, cap-lift under filter vs glance cap, honest empty messages.make lintclean; docs updated indocs/dashboard.md.🤖 Generated with Claude Code