Skip to content

Take the TUI for a spin

The TUI is l2trace’s primary interface. Four modes accessed from a home screen, with an audit-time picker at the top that’s always live: type 5m ago into it and every visible row on every screen re-queries for “what we believed five minutes ago.”

Launch it after running through the first-traceroute tutorial:

Terminal window
make tui

home screen — four mode tiles + as-of header

Four tiles — TRACE, HISTORY, OPS, AUDIT. Keyboard shortcuts:

  • Ctrl+T → TRACE mode
  • Ctrl+H → HISTORY mode
  • Ctrl+O → OPS mode
  • Ctrl+U → AUDIT mode
  • F1 or ? → per-screen help overlay
  • Escape or Q → back to home
  • j / k → vim-style cursor on lists and tables

The bar at the top of every screen is the as-of header. It accepts:

  • now (the default — live mode, dot is green)
  • 5m ago, 2h ago, 1d ago (relative)
  • 2026-05-10T14:42:00Z (ISO-8601)

Press Enter to commit. The status flips to ⏱ 2026-05-10T14:42:00+00:00 in amber, and every screen that watches App.audit_time re-queries.

empty TRACE screen — src/dst MAC inputs + Run button

Fill in source MAC, destination MAC, and VLAN. The MAC inputs validate as you type, and the manufacturer pops up next to each field the moment your MAC matches a known OUI prefix — Apple, Inc. next to a 00:03:93:… laptop, Cisco Systems, Inc next to a 00:1a:a1:… IP phone, etc. (Requires make oui-refresh — see Refresh the OUI registry.)

A malformed MAC turns the field red:

invalid MAC turns the input red

When valid input is in place, the form looks like this:

TRACE form filled with valid MACs and VLAN

Press Ctrl+R or click Run to execute. A successful trace (alice-macbook → bob-macbook across sw-access-1 → sw-core-1 → sw-access-2):

TRACE result table — 3-hop path across the campus fabric

Each row is one hop: the device, the ingress port (where the frame entered), the egress port (where it leaves toward the next hop), the port role (access vs trunk), and the source that vouched for that CAM entry. The termination line below tells you why the trace stopped — reached, floods, dead-end, loop, or max-hops.

Press Ctrl+R or click Run. The hop table populates with one row per device on the path, showing in-port, out-port, port role (access / trunk), and the source that vouched for that hop (gnmi, snmp, etc.).

Termination tells you why the trace stopped: reached (good), floods (dst MAC not in CAM at some intermediate switch — frame would be flooded there), dead-end (no adjacency to follow), loop (visited-set guard fired), or max-hops (limit reached).

Change the as-of header while looking at a result — the trace re-runs at the new audit time without re-typing anything.

HISTORY screen with a populated bitemporal timeline for alice-macbook

Search by MAC (and optionally VLAN). The timeline shows every row from mac_observation for that MAC, collapsed across sources (so a MAC seen simultaneously by gNMI and SNMP at the same port appears as one row with sources: gnmi, snmp).

Each timeline entry shows:

  • port @ device (with vendor label if oui_vendor is populated)
  • sources — which collectors saw it
  • valid: — the wire-time range, plus open / closed state
  • observed: — earliest device-clock observation
  • believed: — earliest time the system recorded the belief
  • entry_ids: — the actual primary keys, useful when joining manually

The gap between observed: and believed: is the latency for that row. Big gap = either a clock-skew event or a queue-dwell — see quarantine for the difference.

OPS dashboard — FDB tree, disagreements table, quarantine log tail

Three panes:

  • Live FDB (left): tree-view of device → port → MACs for the current as-of time. Refresh with R or change the audit-time picker.
  • Disagreements (middle): rows where multiple sources point at different ports for the same (mac, vlan, device). Auto-refreshes every 10 seconds. This is the “what’s broken right now” pane — empty = good.
  • Quarantine (right): live tail of NATS quarantine events. Color-coded by reason: yellow for device-skew, magenta for queue-dwell, red for integrity-error.

The disagreement view answers questions like “is gNMI lying to us about where MAC X is?” without needing to know which CLI to type at which switch. How to interpret the disagreements pane.

AUDIT screen — color-coded one-way / asymmetric adjacency rows

One table. For every open adjacency row from A→B, checks whether B→A exists too:

  • ✗ no reverse — no peer observation back from any source. Real problem: one-way cable, LLDP disabled on peer, or port shutdown.
  • △ source asym — link is bidirectional, but the same source only sees one direction. Telemetry asymmetry, not a cable issue — gNMI on one end hasn’t polled yet while SNMP on the other already has.
  • — both directions confirmed by the same source.

Press Ctrl+R to re-run the audit; changing the as-of header re-fires it automatically. This is the same query as l2trace audit-adjacencies on the CLI — see Audit LLDP adjacencies for the operator playbook, and How peer resolution works for why pending unresolved rows are excluded.

Three themes ship out of the box. Pick one via TUI_THEME in .env:

  • default — terminal palette, green-on-near-black
  • hacker — pure green-on-black, more aggressively monochrome
  • amber — amber-on-black, “old vt100” feel
.env
TUI_THEME=hacker

Same TRACE screen, three themes (note this is the same SVG dimensions — Textual renders each theme deterministically from its CSS layer):

defaulthackeramber

And the OPS dashboard across themes — useful for showing what each one looks like with denser content:

defaulthackeramber