Goal
Add a toggleable side pane to the --tui view that lists every tracked connection (the connected PostgreSQL clients) with per-connection state and traffic counters, so you can see who is connected at a glance and act on a specific client.
Today the TUI shows only aggregate metrics in the header (conns_live, totals, rate sparkline via metrics.summary() in src/state.rs:303). The per-connection data already exists — MetricsSnapshot::connections: Vec<ConnSnapshot> (src/state.rs:53) — but it is never rendered. This issue surfaces it.
Data source (already available)
The registry already retains everything the pane needs:
ConnSnapshot { id, client: SocketAddr, server: SocketAddr, lifecycle, encrypted, msgs_in, msgs_out, bytes_in, bytes_out } (src/state.rs:45)
ConnectionLifecycle::{ Open { since }, Closed { since, ended } } (src/state.rs:27)
The pane should read metrics.snapshot() (src/state.rs:317) rather than summary(). Performance note / design point: unlike summary() (which deliberately avoids touching connection records), snapshot() walks the registry under its lock and allocates a Vec every frame. Decide how to keep this cheap:
- Only call
snapshot() while the pane is open (preferred — closed pane pays nothing), and/or
- Throttle to a few times per second rather than every frame.
The default connection cap is large (DEFAULT_CONNECTION_CAP = 10_000, src/state.rs:15), so the pane must virtualise/scroll rather than render thousands of rows.
Layout
The current layout is purely vertical: title(5) / log(Fill) / footer(3) (src/tui.rs:486). The pane splits the middle row horizontally, keeping the title and footer full-width:
- Position — left vs right. Right keeps the existing left-aligned message flow anchored and matches the "detail rail" convention. Left surfaces clients first. Slight preference for right.
- Width — a bounded
Constraint, e.g. Min(24)..Max(40) or a percentage with sensible small-terminal fallback (collapse/hide below a threshold width).
- Resize — optional, follow-up; a fixed sensible default is fine for v1.
Per-client row content
For each connection, show:
- Client endpoint —
client.ip and client.port (the primary identity; the display filter keys off these, src/filter.rs:74).
- Lifecycle —
Open/Closed with colour (e.g. green live, dim/grey closed) and age from since. Decide whether closed connections appear at all; if so, how they are sorted/dimmed relative to live ones.
- Encryption — a marker for
encrypted (relevant in mitm mode; distinguishes the decrypted sessions from cleartext pcap ones).
- Traffic — compact per-connection
bytes_in/bytes_out (reuse the human() formatter, src/tui.rs:711) and/or message counts.
server addr and per-connection message counts are secondary; show server addr only when width allows or when it differs from the default upstream.
Interaction / scoping the log
The display-filter subsystem (#13) already parses client.ip == ... and client.port == ... (src/filter.rs:8). Make the pane the ergonomic entry point for that:
j/k (or arrows) to move the selection within the pane.
Enter on a client applies a client.ip == <ip> and client.port == <port> display filter, scoping the message log to that one connection. Esc/clear returns to the unfiltered view.
- Optionally, the header's existing
conns_live counter links to the pane (e.g. it becomes the pane's title/cap summary).
Decide whether selecting a client filters immediately on Enter (explicit) or on navigation (live preview). Preference: explicit Enter, consistent with the existing filter UX.
Keybinding
The pane needs a toggle. The keyspace is getting crowded — relevant existing bindings in normal mode: c clears events, f follow, r rich, w wrap, y filter-edit (src/tui.rs:418). Candidates:
Tab / Shift-Tab to focus-cycle into the pane (clean, no letter conflict, scales if more panes are added later), or
C (capital) / s for clients/side.
Preference: Tab to toggle, with the pane collapsing to a thin status column when unfocused (so a key like j/k always maps to log scroll unless the pane is focused). No startup flag needed for v1; default off to preserve current behaviour.
Scope
TUI only. The line-oriented stdout output (pcap/mitm without --tui) is unaffected. No change to the metrics/state layer's public API beyond (optionally) reading snapshot() from the TUI.
Goal
Add a toggleable side pane to the
--tuiview that lists every tracked connection (the connected PostgreSQL clients) with per-connection state and traffic counters, so you can see who is connected at a glance and act on a specific client.Today the TUI shows only aggregate metrics in the header (
conns_live, totals, rate sparkline viametrics.summary()insrc/state.rs:303). The per-connection data already exists —MetricsSnapshot::connections: Vec<ConnSnapshot>(src/state.rs:53) — but it is never rendered. This issue surfaces it.Data source (already available)
The registry already retains everything the pane needs:
ConnSnapshot { id, client: SocketAddr, server: SocketAddr, lifecycle, encrypted, msgs_in, msgs_out, bytes_in, bytes_out }(src/state.rs:45)ConnectionLifecycle::{ Open { since }, Closed { since, ended } }(src/state.rs:27)The pane should read
metrics.snapshot()(src/state.rs:317) rather thansummary(). Performance note / design point: unlikesummary()(which deliberately avoids touching connection records),snapshot()walks the registry under its lock and allocates aVecevery frame. Decide how to keep this cheap:snapshot()while the pane is open (preferred — closed pane pays nothing), and/orThe default connection cap is large (
DEFAULT_CONNECTION_CAP = 10_000,src/state.rs:15), so the pane must virtualise/scroll rather than render thousands of rows.Layout
The current layout is purely vertical: title(5) / log(
Fill) / footer(3) (src/tui.rs:486). The pane splits the middle row horizontally, keeping the title and footer full-width:Constraint, e.g.Min(24)..Max(40)or a percentage with sensible small-terminal fallback (collapse/hide below a threshold width).Per-client row content
For each connection, show:
client.ipandclient.port(the primary identity; the display filter keys off these,src/filter.rs:74).Open/Closedwith colour (e.g. green live, dim/grey closed) and age fromsince. Decide whether closed connections appear at all; if so, how they are sorted/dimmed relative to live ones.encrypted(relevant inmitmmode; distinguishes the decrypted sessions from cleartextpcapones).bytes_in/bytes_out(reuse thehuman()formatter,src/tui.rs:711) and/or message counts.serveraddr and per-connection message counts are secondary; show server addr only when width allows or when it differs from the default upstream.Interaction / scoping the log
The display-filter subsystem (#13) already parses
client.ip == ... and client.port == ...(src/filter.rs:8). Make the pane the ergonomic entry point for that:j/k(or arrows) to move the selection within the pane.Enteron a client applies aclient.ip == <ip> and client.port == <port>display filter, scoping the message log to that one connection.Esc/clear returns to the unfiltered view.conns_livecounter links to the pane (e.g. it becomes the pane's title/cap summary).Decide whether selecting a client filters immediately on
Enter(explicit) or on navigation (live preview). Preference: explicitEnter, consistent with the existing filter UX.Keybinding
The pane needs a toggle. The keyspace is getting crowded — relevant existing bindings in normal mode:
cclears events,ffollow,rrich,wwrap,yfilter-edit (src/tui.rs:418). Candidates:Tab/Shift-Tabto focus-cycle into the pane (clean, no letter conflict, scales if more panes are added later), orC(capital) /sfor clients/side.Preference:
Tabto toggle, with the pane collapsing to a thin status column when unfocused (so a key likej/kalways maps to log scroll unless the pane is focused). No startup flag needed for v1; default off to preserve current behaviour.Scope
TUI only. The line-oriented stdout output (
pcap/mitmwithout--tui) is unaffected. No change to the metrics/state layer's public API beyond (optionally) readingsnapshot()from the TUI.