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