feat: read-only Blackwall TUI dashboard (Phase 1) #253

Merged
vxfemboy merged 5 commits from feat/blackwall-tui-dashboard into main 2026-08-02 22:09:35 +00:00
Owner

Summary

Phase 1 of the Blackwall TUI dashboard: a read-only, Blackwall-themed
(ratatui, red-on-black) terminal dashboard over the existing read control
API and the Prometheus :9100 endpoint. No write/mutating endpoint is
introduced or called anywhere in this change.

New crates:

  • crates/blackwall-client — pure async read-only data layer shared by the
    TUI now and the web GUI later (Phase 2, separate plan): a Prometheus
    text-exposition parser (parse_prometheus/MetricsSnapshot), typed views
    (bgp_peers, throughput, armed, deception_sessions), an ApiClient
    over /v1/mitigations/rtbh, /v1/sessions, /v1/detections (decoding
    straight into blackwall_api::dto types — no redefined DTOs), and a
    MetricsClient that scrapes and parses the metrics endpoint.
  • bin/blackwall-tui — the dashboard binary: a single-source theme.rs
    palette (bg #0a0000, blackwall red #ff1e2d, dim ember #8b0f16, hot
    #ff6b6b), an AppState with per-source (metrics vs. API) staleness
    tracking, five render-tested panels (header/throughput/peerings/rtbh/
    sessions), and an async refresh loop (metrics ~1.5s, entities ~5s;
    q/Esc/Ctrl-C to quit).

One metrics addition (crates/blackwall-flow, bin/blackwalld):
blackwall_flow_sampled_bytes_total / blackwall_flow_sampled_packets_total
— two new counters fed by the existing sFlow sample-decode path, extrapolated
from each sample's sampling rate, so the TUI can derive bps/pps via a delta
over time. New metric families only; nothing existing renamed or removed.

Notable deviations from the original plan (flagged, not silent)

  • BGP state decode is against the live exporter, not the plan's assumed
    6-value FSM.
    bin/blackwalld/src/metrics.rs emits
    blackwall_bgp_session_state as a single unlabeled gauge with values
    {0,1,2} (Idle/Connecting/Established) — Blackwall runs exactly one
    upstream BGP session, not a multi-peer table, and the metrics renderer
    (blackwall_metrics::Metric) has no label support at all. views::BgpState
    decodes against that reality (0/1/2Idle/Connect/Established) and
    bgp_peers falls back to a "upstream" peer name when no peer label is
    present (always, today). The fuller FSM variants (Active/OpenSent/
    OpenConfirm) and labeled multi-peer decoding are kept working and tested
    for forward compatibility, but are unreachable against the current
    exporter. See the doc comment in crates/blackwall-client/src/views.rs.
  • Deserialize/Clone added to RtbhDto/DetectionDto/SessionDto in
    crates/blackwall-api/src/dto.rs (previously Serialize-only). Required
    for blackwall-client to decode API responses straight into the shared DTO
    types per the plan's own constraint ("reuse blackwall_api::dto types —
    do not redefine DTOs"); otherwise that constraint is unsatisfiable. Purely
    additive derives, no behavior change.
  • crates/blackwall-flow's counters live on the existing CollectorMetrics
    struct (not a new FlowMetrics type the plan assumed) — that's the type
    that's actually threaded through the collector and the metrics endpoint
    today.
  • Root Cargo.toml's members already globs crates/*/bin/*, so both new
    crates are picked up automatically; no explicit members entries were
    needed (the eBPF crate's exclude is untouched).
  • The real-I/O modules (blackwall-client/src/{api,http}.rs,
    blackwall-tui/src/main.rs) are excluded from the coverage gate the way
    the repo actually does this — a filename pattern in
    scripts/coverage.sh's EXCLUDE regex (matching
    bin/blackwalld/src/api.rs, flow/src/collector_net.rs, etc.) — rather
    than a #[cfg(not(coverage))] attribute, which isn't a mechanism this repo
    defines anywhere.

Verification

From the worktree root, with a local Postgres up (docker compose up -d,
DATABASE_URL set — required for blackwall-state's DB-backed tests, same
as CI):

  • cargo test --workspace — 903 tests passed, 0 failed
  • cargo clippy --workspace --all-targets -- --deny warnings — clean
  • cargo fmt --all -- --check — clean
  • bash scripts/coverage.sh — 96.24% line coverage (gate: ≥90%)
## Summary Phase 1 of the Blackwall TUI dashboard: a read-only, Blackwall-themed (`ratatui`, red-on-black) terminal dashboard over the existing read control API and the Prometheus `:9100` endpoint. No write/mutating endpoint is introduced or called anywhere in this change. **New crates:** - `crates/blackwall-client` — pure async read-only data layer shared by the TUI now and the web GUI later (Phase 2, separate plan): a Prometheus text-exposition parser (`parse_prometheus`/`MetricsSnapshot`), typed views (`bgp_peers`, `throughput`, `armed`, `deception_sessions`), an `ApiClient` over `/v1/mitigations/rtbh`, `/v1/sessions`, `/v1/detections` (decoding straight into `blackwall_api::dto` types — no redefined DTOs), and a `MetricsClient` that scrapes and parses the metrics endpoint. - `bin/blackwall-tui` — the dashboard binary: a single-source `theme.rs` palette (bg `#0a0000`, blackwall red `#ff1e2d`, dim ember `#8b0f16`, hot `#ff6b6b`), an `AppState` with per-source (metrics vs. API) staleness tracking, five render-tested panels (header/throughput/peerings/rtbh/ sessions), and an async refresh loop (metrics ~1.5s, entities ~5s; `q`/`Esc`/`Ctrl-C` to quit). **One metrics addition** (`crates/blackwall-flow`, `bin/blackwalld`): `blackwall_flow_sampled_bytes_total` / `blackwall_flow_sampled_packets_total` — two new counters fed by the existing sFlow sample-decode path, extrapolated from each sample's sampling rate, so the TUI can derive bps/pps via a delta over time. New metric families only; nothing existing renamed or removed. ## Notable deviations from the original plan (flagged, not silent) - **BGP state decode is against the live exporter, not the plan's assumed 6-value FSM.** `bin/blackwalld/src/metrics.rs` emits `blackwall_bgp_session_state` as a single **unlabeled** gauge with values `{0,1,2}` (`Idle`/`Connecting`/`Established`) — Blackwall runs exactly one upstream BGP session, not a multi-peer table, and the metrics renderer (`blackwall_metrics::Metric`) has no label support at all. `views::BgpState` decodes against that reality (`0/1/2` → `Idle`/`Connect`/`Established`) and `bgp_peers` falls back to a `"upstream"` peer name when no `peer` label is present (always, today). The fuller FSM variants (`Active`/`OpenSent`/ `OpenConfirm`) and labeled multi-peer decoding are kept working and tested for forward compatibility, but are unreachable against the current exporter. See the doc comment in `crates/blackwall-client/src/views.rs`. - **`Deserialize`/`Clone` added to `RtbhDto`/`DetectionDto`/`SessionDto`** in `crates/blackwall-api/src/dto.rs` (previously `Serialize`-only). Required for `blackwall-client` to decode API responses straight into the shared DTO types per the plan's own constraint ("reuse `blackwall_api::dto` types — do not redefine DTOs"); otherwise that constraint is unsatisfiable. Purely additive derives, no behavior change. - `crates/blackwall-flow`'s counters live on the existing `CollectorMetrics` struct (not a new `FlowMetrics` type the plan assumed) — that's the type that's actually threaded through the collector and the metrics endpoint today. - Root `Cargo.toml`'s `members` already globs `crates/*`/`bin/*`, so both new crates are picked up automatically; no explicit `members` entries were needed (the eBPF crate's `exclude` is untouched). - The real-I/O modules (`blackwall-client/src/{api,http}.rs`, `blackwall-tui/src/main.rs`) are excluded from the coverage gate the way the repo actually does this — a filename pattern in `scripts/coverage.sh`'s `EXCLUDE` regex (matching `bin/blackwalld/src/api.rs`, `flow/src/collector_net.rs`, etc.) — rather than a `#[cfg(not(coverage))]` attribute, which isn't a mechanism this repo defines anywhere. ## Verification From the worktree root, with a local Postgres up (`docker compose up -d`, `DATABASE_URL` set — required for `blackwall-state`'s DB-backed tests, same as CI): - `cargo test --workspace` — 903 tests passed, 0 failed - `cargo clippy --workspace --all-targets -- --deny warnings` — clean - `cargo fmt --all -- --check` — clean - `bash scripts/coverage.sh` — 96.24% line coverage (gate: ≥90%)
vxfemboy force-pushed feat/blackwall-tui-dashboard from 8e767b4898
Some checks failed
CI / lab (pull_request) Has been cancelled
CI / check (pull_request) Has been cancelled
to 7dbc8056ae
Some checks failed
CI / check (pull_request) Successful in 50m13s
CI / lab (pull_request) Failing after 9m33s
2026-08-02 18:13:19 +00:00
Compare
Sign in to join this conversation.
No description provided.