Skip to content

feat: interactive TUI mode (--tui, scroll + filter) - #4

Merged
sunng87 merged 9 commits into
mainfrom
feat/tui
Jul 10, 2026
Merged

sunng87 merged 9 commits into
mainfrom
feat/tui

Conversation

@sunng87

@sunng87 sunng87 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

Adds an interactive, scrollable, filterable TUI as an orthogonal flag
(--tui) over the existing traffic sources — it works with both --mode pcap
and --mode mitm. The chosen source runs in a background thread and feeds a
shared sink; the TUI (ratatui 0.30 + crossterm) drains it on the main thread.

tapgres --tui                      # pcap source, interactive view
tapgres --mode mitm --tui          # TLS proxy source, interactive view

Why orthogonal

Source (pcap/mitm) and view (stdout/tui) are separate concerns, so --tui
is a flag rather than a third --mode value. For mitm+TUI the proxy's tokio
runtime is moved into a background thread so the TUI owns the main thread.

Changes

  • decode: SharedSink (thread-safe, capped at 50k lines) + decode::status;
    out() routes to the sink when installed, otherwise stdout (unchanged
    behaviour). This is the seam that lets the (multi-threaded) sources feed the TUI.
  • capture (new lib module): pcap loop + device helpers moved out of main
    so the TUI can spawn it in a thread; status routed via decode::status
    (stderr when not in TUI → identical to before).
  • proxy: serve() is now pub async; status/per-connection messages go
    through decode::status so they appear in the TUI.
  • tui (new): installs the sink, spawns the source, runs the ratatui loop,
    always restores the terminal on exit.
  • main: --tui flag + thin source × view dispatch.
  • README: documents --tui with controls.

Controls

Key Action
q / Ctrl-C quit
j/k, arrows, PgUp/PgDn scroll
g / G top / bottom
f toggle follow (auto-tail)
/ filter by substring (Enter applies, Esc cancels)
c clear

Lines are coloured by direction (F→B cyan, B→F grey; connection notices
yellow; warnings red).

Verification

  • cargo fmt / cargo clippy --all-targets -- -D warnings / cargo test (4/4) clean
  • nix build ✅ and nix flake check --no-build ✅ (CI gates)
  • PTY smoke test against bundled PostgreSQL 18 (mitm+TUI): renders live
    decoded TLS traffic — title bar (tapgres — mitm mode · N events · following),
    bordered log with coloured lines (Query: SELECT 'tui-hello'…, DataRow: { msg='tui-hello' }, Terminate), footer keybindings — and exits cleanly
    (rc=0, terminal restored) on q.

Notes / scope

  • First-cut feature set: scroll + follow + substring filter + clear. Structured
    filtering (by connection / message type), saved filters, search-within, etc.
    are natural follow-ups.
  • --tui with pcap still needs capture privileges (CAP_NET_RAW/root), same
    as the stdout path.
  • The source thread runs until process exit (no graceful shutdown wired up in
    this cut).

Checklist

  • cargo fmt
  • cargo clippy --all-targets -- -D warnings
  • cargo test
  • nix build / nix flake check --no-build
  • README updated
  • PTY smoke test (render + clean exit)

sunng87 added 2 commits July 9, 2026 23:30
An orthogonal presentation layer over the existing traffic sources: the pcap
capture or the mitm proxy runs in a background thread and feeds decoded lines
into a shared sink; the TUI (ratatui + crossterm) drains them on the main thread
and renders a scrollable, filterable, follow-tail view.

- decode: SharedSink (thread-safe, capped at 50k lines) + decode::status; out()
  routes to the sink when installed, else stdout (unchanged)
- capture: new lib module — pcap loop + device helpers moved out of main so the
  TUI can spawn it; status routed via decode::status (stderr when not in TUI)
- proxy: serve() now pub async; status/connection messages via decode::status
- tui: new module; --tui works with --mode pcap and --mode mitm (mitm spawns its
  own tokio runtime in the background thread)
- main: --tui flag; thin dispatch (source x view)
- controls: q/Ctrl-C, j/k/arrows/PgUp/PgDn, g/G, f follow, / filter, c clear

Verified under a PTY against bundled PostgreSQL 18: mitm+TUI renders live
decoded TLS traffic (title/log/footer, coloured by direction) and quits cleanly.
Replaces the SharedSink mutex with a single crossbeam-channel: decode::out and
decode::status push onto OUTPUT_TX; a consumer chosen at startup owns the
receiver. The decoder is now fully sink-agnostic (one output path, no mode
branch), and the stdout/stderr split is preserved by the consumer (Line→stdout,
Status→stderr) rather than baked into the decoder.

- decode: Output enum (Line/Status), set_output/close_output, deliver(); drop
  SharedSink/SHARED_SINK
- main: run_stdout() spawns a single consumer thread for the stdout modes and
  joins it on source exit so nothing is left unflushed
- tui: app_loop consumes the channel via try_recv instead of draining a sink

crossbeam-channel (runtime-independent) chosen over tokio mpsc so the channel
works from both plain threads (pcap/stdout) and tokio tasks (mitm) without
requiring tokio's `sync` feature.

Verified: stdout mitm routes Line→stdout / Status→stderr with no leakage; TUI
still renders live traffic and quits cleanly (rc=0) under a PTY.
sunng87 added 7 commits July 10, 2026 08:34
- B→F traffic is now green (was dark grey, which read as too dim).
- The packet/message name (Query, DataRow, ReadyForQuery, …) is rendered bold
  by splitting each decoded line into [prefix | kind | rest] spans, styling the
  kind with the bold modifier. Warnings stay red and connection notices yellow.

Verified via a PTY capture: F→B emits \e[38;5;6m (cyan), B→F emits
\e[38;5;2m (green), and the message name is wrapped in \e[1m…\e[22m.
Revert the whole-line / message-name colouring: now only the `[F→B]`/`[B→F]`
symbol is highlighted (F→B cyan, B→F magenta — high contrast against each other,
bold), and the timestamp, message name, and body stay the default colour for
easy reading. Warnings remain red, connection notices yellow.

Verified via PTY: symbol spans emit \e[38;5;6 (cyan) / \e[38;5;5 (magenta);
prefix/suffix spans are unstyled (default fg); no whole-line colour remains.
Re-add bold to the packet/message name (e.g. Query, DataRow, ReadyForQuery),
but in the default colour — only the direction symbol stays coloured. So a
decoded line reads: default timestamp, coloured+bold symbol, bold packet name
(default colour), default body.

Verified via PTY: symbol emits \e[38;5;5 (magenta) / \e[38;5;6 (cyan); the
packet name is wrapped with bold-on/(\e[22m) bold-off at default colour.
The substring filter needs a more careful design pass (structured filtering by
connection / message type, saved filters, search-within, etc.), so drop it for
now rather than ship a half-baked version. Removed: the App filter fields, the
`filtered()` helper, the `/`-mode key handling, the filter input/footer/cursor
rendering, and the README row. `draw` now reads `app.events` directly.

Scrolling, follow-tail, and clear remain. Verified via PTY: renders traffic,
`c` clears, `q` quits cleanly.
Embed tui.png at the top of the Interactive TUI section.
@sunng87
sunng87 merged commit a13da20 into main Jul 10, 2026
2 checks passed
@sunng87
sunng87 deleted the feat/tui branch July 10, 2026 02:14
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