🚧 Add broker status page

This commit is contained in:
Jonas Bengtsson
2026-09-20 07:55:18 +02:00
parent 4322446fcf
commit 1e491642f1
9 changed files with 461 additions and 6 deletions
+24
View File
@@ -61,6 +61,9 @@ class DynsecClient:
self._lock = threading.Lock()
self._responses: dict[str, queue.Queue[dict[str, Any]]] = {}
self._connected = threading.Event()
# Optional tap for $SYS telemetry (app.metrics registers itself
# at startup). Receives (topic, payload) for every $SYS message.
self._event_sink = None
# -- connection lifecycle ------------------------------------------------
def connect(self) -> None:
@@ -118,12 +121,25 @@ class DynsecClient:
return
self._teardown_locked()
def set_event_sink(self, sink) -> None:
"""Register a callable receiving every $SYS message (topic, payload).
Used by app.metrics for the broker dashboard (spec sections 33,
55). The sink must never raise: telemetry must not be able to
disturb the management channel.
"""
self._event_sink = sink
# -- paho callbacks -------------------------------------------------------
def _on_connect(self, client, userdata, flags, reason_code, properties) -> None:
if reason_code == 0:
# FIXED response topic — all commands reply here; requests are
# matched by the correlationData echo inside the payload.
client.subscribe(RESPONSE_TOPIC, qos=1)
# $SYS telemetry for the dashboard (spec sections 33, 55).
# qos 0: telemetry is periodically republished, losing one
# is harmless.
client.subscribe("$SYS/#", qos=0)
self._connected.set()
else:
logger.error("dynsec client connect failed: %s", reason_code)
@@ -149,6 +165,14 @@ class DynsecClient:
def _on_message(self, client, userdata, msg) -> None:
if msg.topic != RESPONSE_TOPIC:
# Not a dynsec reply: forward $SYS telemetry to the sink, if
# one is registered. Swallow sink errors — telemetry must
# never disturb the management channel.
if msg.topic.startswith("$SYS/") and self._event_sink is not None:
try:
self._event_sink(msg.topic, msg.payload)
except Exception: # noqa: BLE001
logger.warning("$SYS event sink raised; message dropped")
return
try:
payload = json.loads(msg.payload.decode("utf-8"))