Live Traffic tab
The streaming, dense audit view. Every AI request that came through the firewall, newest first. Click any row to inspect.
Layout
- Header — title, count-strip of verdicts (allow / log / redact / approve / deny), Live/Paused toggle.
- Metrics strip — 5 KPIs above the table: Decisions / Block rate / p95 latency / Active agents / Top matched rule.
- Decisions-per-minute chart — 60 bars, last 60 minutes, stacked by verdict.
- Filter bar — text search + chip filters (agent / verdict / action / destination / rule).
- Table — Verdict / Time / Agent / Action / Destination / Rule / Latency. 32 px rows, mono data, sans chrome.
- Drawer (right side, click any row to open) — three tabs: Request / Decision / Context.
Color language
Verdicts use the same palette everywhere in the SPA:
| Verdict | Pill | Row left-edge stripe |
|---|---|---|
allow | green | — |
log | slate | — |
redact | amber | amber tint |
require_approval | cyan | cyan tint |
deny | red | red tint |
The single-character verdict "gutter" on the far left (A / L / R / ? / D) lets you scan a long table at a glance.
Live tail
The pulse dot in the header indicates the SSE connection is open. New decisions stream in via /api/events and prepend to the list.
If you scroll down, the list pauses — a small "↑ N new decisions" pill appears at the top so the visible rows don't shove around while you're reading them. Click the pill to flush + scroll back to top.
Keyboard:
L— toggle live tail.Esc— close the drawer.Cmd/Ctrl-K— command palette → "Show denied", "Resume live tail", etc.
Filter chips
The chips are conjunctive — you can stack any combination, and they AND together. Click any agent / destination / rule cell in a row to add it as a chip.
Examples:
- "Show me every
denyfromqa-eval-looptoapi.openai.com" → clickdenyin the verdict count strip, click the agent cell on any matching row, click the destination cell. - "Everything not matched by a rule" → search for
no rule matched.
The "shown / total" count next to the search box tells you what fraction of the loaded events the current filter is showing.
The drawer
Click any row → a 480px-wide drawer slides in on the right with three tabs:
- Request — the full request JSON (agent / action / destination / args) plus all metadata (source IP / hostname / user-agent / identity).
- Decision — the verdict, matched rule, reason, and (for redact) the list of redacted field names.
- Context — the last 8 decisions from the same agent, in mono — useful for "is this misfire isolated or part of a pattern".
The agent / destination / rule values in the drawer are buttons — clicking them adds the value as a filter chip and brings the drawer to that filtered view.
Forensic columns (since 3dc3198)
In addition to the original columns, the Live Traffic table now shows:
- Source IP — the LAN address that originated the request.
- Method —
GET / POST / PUT / DELETE. - URL — full URL (path only after the host, no query strings).
These come from request.metadata populated by the collector's mitm addon.
Performance
The in-memory list caps at 800 events. New SSE messages prepend; old ones fall off the bottom. The page never paginates — it's a tail, not an archive. For deeper history, use Audit Log or the API.
When the live tail looks dead
The connection pill in the top bar tells you the truth:
- green "Collector connected" — SSE open, new events flowing.
- amber "Connecting…" — EventSource is in retry-with-backoff.
- red "Collector offline" — backend unreachable, the page is showing stale data.
Quiet network, not dead: if the pill is green but no new rows arrive, your network simply isn't sending traffic. Drop a python3 dev/generate-traffic.py synthetic burst to confirm the path is alive.
→ See Generate test traffic.