SIGN IN SIGN UP

feat(tui): interactive filter query ('/') and threshold alerts (#196)

* feat(tui): interactive filter query ('/') and threshold alerts

Add two tightly-coupled TUI capabilities for local and remote modes:

Filter DSL (`/` key)
- New `src/ui/filter_dsl` module with lexer, parser, and evaluator.
- Fields: temp, util, mem_pct, mem_used, mem_total, power, user, host,
  gpu_name, driver, index, uuid, pstate, numa, device_type.
- Numeric ops `>`, `>=`, `<`, `<=`, `==`, `!=`; string ops `==`, `!=`,
  and regex `~=` (size-limited to 128 KiB via RegexBuilder).
- Logical `&`, `|`, and parenthesised sub-expressions.
- Unknown fields raise a parse error; fields absent on the device cause
  the row to fail closed so mixed views stay readable.
- `DeviceRowView` trait implemented on `GpuInfo`, `ProcessInfo`, and
  `CpuInfo` so renderers can call `apply_filter(&state.filter_query,
  row)` uniformly.

Filter UX
- `/` opens a status-bar buffer with live `[matched X of Y]` preview.
- `Enter` commits; `ESC` clears or aborts; `Ctrl-R` recalls the last
  five queries.
- Invalid queries show an inline red `parse error: ... at col N`
  message without crashing or committing.
- Non-matching rows render at 40% opacity via a post-processing
  `dim_ansi` helper so no renderer signature had to change.

Threshold alerts
- New `src/ui/alerts.rs` with per-device per-rule state machines and
  hysteresis (`hysteresis_c`).
- Rules: temperature (`temp_warn_c` / `temp_crit_c`), sustained idle
  utilization (`util_idle_warn_mins`), and power (`power_crit_w`).
- Transitions produce toast notifications (5 s via
  `AppConfig::NOTIFICATION_DURATION_SECS`), 1 Hz border flash on the
  affected GPU tile, and entries in a 50-slot ring buffer.
- `A` toggles the alert history panel.
- Optional fire-and-forget webhook POST (`reqwest`, 2 s timeout, bounded
  `mpsc` queue with drop-oldest-on-full) via new
  `src/network/webhook.rs`.

Config + CLI
- `AlertConfig` in `src/common/config.rs` with defaults matching the
  issue's `[alerts]` sketch.
- CLI overrides `--alert-temp` and `--alert-util-low-mins` on both
  `local` and `view` subcommands; the TOML loader can layer on top
  when the companion config-file issue lands.

Tests
- 62 parser tests + 23 eval tests covering all field types, hysteresis
  boundaries (entering/leaving crit), regex safety, and the webhook
  body shape (inline + `tests/alert_webhook_test.rs`).
- Event-handler tests for the filter input state machine (`/` opens,
  `ESC` aborts, `Enter` commits, `Ctrl-R` recalls, `q` is literal text
  while editing).

Closes #186

* fix(alerts): recover Crit->Ok directly when warn rule disabled

- alerts: when temp_warn_c=0 and temp_crit_c>0, a device in Crit that
  cools below crit_off now transitions straight to Ok instead of passing
  through a spurious Warn level. warn_off would otherwise be negative
  (0 - hysteresis_c), making `temp <= warn_off` almost never true. Add
  regression test `warn_disabled_crit_enabled_recovers_straight_to_ok`.
- filter_dsl/eval: gate mem_total_field on total_memory > 0 for symmetry
  with mem_pct_field. Prevents mem_total==0 from matching zero-total
  devices (fail-closed). Add `mem_total_absent_when_zero_fails_closed`.
- webhook: update module and `enqueue` doc comments to reflect actual
  behavior (try_send drops the newest payload on overflow, not oldest).
  No behavior change; the UI-never-blocks invariant is preserved.

* fix(security): harden webhook and filter DSL attack surface

Addresses pr-security-checker findings on PR #196:

CRITICAL
- webhook: disable HTTP redirects (Policy::none) to prevent SSRF pivot
  through attacker-controlled 3xx responses from operator-configured hosts

HIGH
- webhook: redact userinfo from URL before logging to avoid credential
  leak when operators accidentally configure basic-auth URLs
- webhook: document that config reload must rebuild DataCollector
  (OnceLock binds URL at first init)

MEDIUM
- alerts: garbage-collect states HashMap for devices that disappear from
  the snapshot (decommission / UUID churn) to prevent slow leak
- data_collector: bell write moved to spawn_blocking to avoid stalling
  Tokio executor on slow terminals
- filter_dsl: add DFA size limit (1 MiB) on regex compilation
- event_handler: cap filter_buffer at 512 chars to block UI DoS via
  bracketed-paste of large blobs
- filter_dsl: add 16 KiB lexer input cap as defense-in-depth for
  non-interactive callers
- renderers/dim: preserve background color SGR codes so selected-row
  and alert-flash highlights survive the filter-dim pass

* test(finalize): add missing coverage and update README security notes

- Add truecolor RGB background preservation tests for dim_ansi (covers
  the `48;2;r;g;b` code path documented but not previously tested)
- Add regression test for filter buffer cap at FILTER_BUFFER_MAX (512)
  to guard the DoS-prevention truncation in the editing event handler
- Document webhook SSRF protection (redirects disabled) and filter query
  buffer limit (512 chars / 16 KiB lexer gate) in README Filtering &
  Alerts section
J
Jeongkyu Shin committed
8e3452a8b049b36877444e13e95bb2819e85d16b
Parent: 50dd009
Committed by GitHub <noreply@github.com> on 4/20/2026, 12:52:43 PM