Skip to content

feat(opensearch-web): dest_ip filter, per-tab session persistence, sidebar redesign, and overview table fixes - #31

Merged
liamadale merged 5 commits into
mainfrom
dev
Apr 25, 2026
Merged

liamadale merged 5 commits into
mainfrom
dev

Conversation

@liamadale

Copy link
Copy Markdown
Owner

Summary

This PR bundles a set of improvements to the PISCES OpenSearch web UI developed iteratively on dev:

  • Destination IP filterdest_ip is now a first-class filter alongside src_ip. Added to the search bar, persisted in sessionStorage, threaded through queries.py and base.py (with a SOURCE_FIELDS guard so modules without destination.ip are not broken).

  • Per-tab sessionStorage filter persistence — Filter state (time range, sensor, src/dest IP, direction, public-only, limit) is now stored in sessionStorage rather than localStorage, giving each browser tab its own independent context. Sidebar navigation restores the tab's saved state; direct URLs still work for link sharing.

  • Sidebar navigation redesign — Category group headers and protocol links now use semantically distinct Font Awesome icons to eliminate visual collisions. Collapsed sidebar hides the scrollbar while keeping scroll functional. Overview link uses fa-border-all with a primary-color tint to distinguish it from protocol links.

  • Overview table sticky column z-index fix — Resolved CSS specificity conflict where .thead-groups th[rowspan="2"] (specificity 0,2,1) was overriding thead .col-ip-addr (specificity 0,1,1), causing z-fighting when scrolling horizontally. Fixed by removing the competing z-index declaration.

  • CSS cache-busting — Static CSS URL now includes an mtime-based ?v= query string so browsers pick up changes without a hard refresh.

  • Phantom row in overview table — Several protocol modules set src_ip = "—" when no source IP is present in the OpenSearch document. Because the em-dash is truthy, it was aggregating into its own row. Fixed in run_cross_protocol_query by adding or ip == "—" to the skip guard.

Test plan

  • Overview table scrolls horizontally with IP Address and TOTAL columns properly frozen (no z-fighting)
  • No row appears in the cross-protocol IP activity table
  • dest_ip filter applies correctly in per-protocol drill-down views
  • Filter state persists within a tab across sidebar navigation
  • Opening the same page in a second tab carries independent filter context
  • Collapsed sidebar icons are visually distinct; scrolling works without a visible scrollbar

… search bar

Extend the query backend and web form to support filtering by destination
IP alongside the existing src_ip filter.

- build_base_query gains a dest_ip_filter param that appends a
  destination.ip term clause when set
- run_query guards dest_ip_filter the same way src_ip is guarded:
  only applied to modules that declare destination.ip in SOURCE_FIELDS
- build_search_params_from_request reads the dest_ip form value
- base.html search bar exposes a Dst IP text input
Zeek modules emit "—" (U+2014) as src_ip when no source IP is present
in a record. run_cross_protocol_query was aggregating these into a
phantom "—" row in the cross-protocol overview matrix.

Extend the IP guard to also skip em-dash values alongside empty strings.
…and fix sticky-column rendering

Sidebar navigation:
- Add always-visible Overview link (cross-protocol matrix) to sidebar
- Replace Hub home link with brand logo that routes to Hub when mounted
  under a script name, or to Overview when running standalone
- Polish collapsed sidebar: hide scrollbar, center icons, apply category
  colour accents to group headers, add .sidebar-overview highlight class
- Update category icons (alerts, network, web, remote, auth, messaging)
- Reorder MODULES so suricata_alert appears before weird in the registry

Filter persistence:
- Replace localStorage-based sensor/time_range persistence with
  sessionStorage so each browser tab maintains independent filter state
- Snapshot all filter keys (time_range, sensor, src_ip, dest_ip,
  direction, public_only, limit, min_risk) on form submit; restore on
  link navigation
- Remove inline onchange handler from the time_range select

CSS cache-busting:
- Compute pisces.css version from file mtime at app startup and inject
  as a ?v= query param in the stylesheet link

Sticky-column rendering:
- Raise col-ip-addr z-index to 20 (top-left corner) and add explicit
  top+left sticky declarations to thead .col-ip-addr and thead .col-total
  so both axes stick reliably at the corner
- Remove drop shadows from col-ip-addr and col-total; keep only the
  inset separator line
- Remove z-index from the shared thead rule to avoid clobbering the
  per-column corner values
Introduces djLint as a dev dependency and wires it into both pre-commit
and the GitHub Actions CI workflow, following the same advisory-on-dev /
blocking-on-PR-to-main pattern used by the existing ruff checks.

- djlint-jinja pre-commit hook lints Jinja templates on every commit
- CI: "Lint HTML (djlint check)" runs blocking on PRs to main,
  advisory on pushes to dev (mirrors ruff check behaviour)
- CI: "Format check HTML (djlint format)" runs advisory on all triggers
- [tool.djlint] config added to pyproject.toml: jinja profile,
  100-char line limit, H021/H023/H030/H031 suppressed with rationale
Resolves all djlint warnings to reach 0 errors across 32 HTML files:

- T003: added block names to all bare endblock tags (21 occurrences)
  across dashboard_web, mantis_web, and opensearch_web templates
- H006: added height/width attributes to brand logo img tags in 4
  base templates
- H025: added closing </option> tags to datalist options in
  filter_form.html
- H014: removed extra blank lines in opensearch_web base.html and
  record_detail.html
- T032: removed extra whitespace in Jinja set tags in ticket_detail.html
  and record_detail.html
- H029: lowercased form method="GET" to method="get" in opensearch_web
  base.html
- H020: replaced empty <span></span> with <span>&nbsp;</span> in
  threat_card.html

Also adds J018 to the djlint ignore list in pyproject.toml — cross-app
internal links cannot use url_for() in a multi-app Flask setup.
@liamadale
liamadale merged commit 5b6cc4d into main Apr 25, 2026
5 checks passed
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