Initial commit: Metro Warden TUI network operations center

This commit is contained in:
2026-03-22 21:33:40 -04:00
commit 98a17d9b7e
45 changed files with 4215 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
"""
Signals Tab — live scrolling monitor of all bus events.
Subscribes to every topic ("**") and appends new events to a scrollable
RichLog widget, styled to resemble an oscilloscope or signal analyser.
"""
from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any
from textual.app import ComposeResult
from textual.binding import Binding
from textual.widget import Widget
from textual.widgets import Label, RichLog, Static
def _preview(data: Any, max_len: int = 80) -> str:
"""Produce a compact single-line preview of event data."""
if data is None:
return "null"
try:
raw = json.dumps(data, default=str)
except Exception:
raw = str(data)
if len(raw) > max_len:
raw = raw[:max_len] + ""
return raw
class SignalsTab(Widget):
"""
Live event signal monitor.
Every event published on the bus appears here with:
- Timestamp (HH:MM:SS.mmm)
- Topic (colour-coded by namespace)
- Data preview (truncated JSON)
"""
DEFAULT_CSS = """
SignalsTab {
layout: vertical;
height: 1fr;
padding: 1 2;
}
SignalsTab .tab-title {
color: $primary;
text-style: bold;
height: 1;
margin-bottom: 1;
}
SignalsTab RichLog {
height: 1fr;
border: round $primary;
background: $surface;
scrollbar-gutter: stable;
}
SignalsTab .hint {
height: 1;
color: $text-muted;
margin-top: 1;
}
"""
BINDINGS = [
Binding("c", "clear_log", "Clear"),
Binding("p", "toggle_pause", "Pause"),
]
# Colour map per topic namespace
TOPIC_COLOURS = {
"network": "cyan",
"dns": "blue",
"firewall": "red",
"system": "green",
"state": "yellow",
"registry": "magenta",
}
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self._paused = False
self._event_count = 0
self._sub_id: str | None = None
def compose(self) -> ComposeResult:
yield Static("// SIGNALS — Live Event Monitor", classes="tab-title")
yield RichLog(id="signal-log", highlight=True, markup=True, max_lines=500)
yield Static(
"Press [bold]c[/bold] to clear | [bold]p[/bold] to pause",
classes="hint",
)
def on_mount(self) -> None:
app = self.app
if hasattr(app, "bus"):
self._sub_id = app.bus.subscribe("**", self._on_any_event)
def on_unmount(self) -> None:
app = self.app
if hasattr(app, "bus") and self._sub_id:
app.bus.unsubscribe(self._sub_id)
def _on_any_event(self, topic: str, data: Any) -> None:
if self._paused:
return
self.call_from_thread(self._append_event, topic, data)
def _append_event(self, topic: str, data: Any) -> None:
self._event_count += 1
log_widget = self.query_one("#signal-log", RichLog)
now = datetime.now(timezone.utc)
ts = now.strftime("%H:%M:%S") + f".{now.microsecond // 1000:03d}"
namespace = topic.split(".")[0]
colour = self.TOPIC_COLOURS.get(namespace, "white")
preview = _preview(data)
# Format: [dim]HH:MM:SS.mmm[/] [colour]topic[/] data
line = (
f"[dim]{ts}[/dim] "
f"[{colour} bold]{topic:<35}[/{colour} bold] "
f"[dim]{preview}[/dim]"
)
log_widget.write(line)
def action_clear_log(self) -> None:
log_widget = self.query_one("#signal-log", RichLog)
log_widget.clear()
self._event_count = 0
def action_toggle_pause(self) -> None:
self._paused = not self._paused
log_widget = self.query_one("#signal-log", RichLog)
status = "[yellow]PAUSED[/yellow]" if self._paused else "[green]LIVE[/green]"
log_widget.write(f" ── {status} ──")