from __future__ import annotations import json import threading import urllib.parse from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from typing import Callable from .config import Config from .store import AlertStore DASHBOARD = r''' RouterOS Suricata TZSP

RouterOS Suricata TZSP

TZSP → TAP → Suricata → EVE JSON → SQLite
Loading…
TZSP datagrams
0
Frames injected into TAP
0
Suricata alerts
0
RouterOS blocks
0

System status

ComponentStatusDetails

Ports

ServiceDirectionProtocolAddressPortStatus

Recent alerts

TimeSeveritySignatureSourceDestinationAction
''' class WebServer: def __init__( self, config: Config, store: AlertStore, health_provider: Callable[[], dict], ) -> None: self.config = config self.store = store self.health_provider = health_provider self.server = ThreadingHTTPServer((config.web_bind, config.web_port), self._handler()) self.thread = threading.Thread(target=self.server.serve_forever, name="web-ui", daemon=True) def _handler(self): store = self.store config = self.config health_provider = self.health_provider class Handler(BaseHTTPRequestHandler): def do_GET(self): parsed = urllib.parse.urlparse(self.path) if parsed.path == "/": self._send(200, DASHBOARD.encode("utf-8"), "text/html; charset=utf-8") return if parsed.path in {"/api/health", "/api/status"}: self._json(health_provider()) return if parsed.path == "/api/summary": self._json(store.summary()) return if parsed.path == "/api/config": self._json(config.public_dict()) return if parsed.path == "/api/alerts": query = urllib.parse.parse_qs(parsed.query) try: limit = int(query.get("limit", ["100"])[0]) except ValueError: limit = 100 self._json({"alerts": store.recent(limit)}) return self._json({"error": "not found"}, status=404) def _json(self, obj, status: int = 200): data = json.dumps(obj, ensure_ascii=False).encode("utf-8") self._send(status, data, "application/json; charset=utf-8") def _send(self, status: int, data: bytes, content_type: str): self.send_response(status) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(data))) self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(data) def log_message(self, fmt, *args): return return Handler def start(self) -> None: self.thread.start() print(f"[web] dashboard on http://{self.config.web_bind}:{self.config.web_port}", flush=True) def stop(self) -> None: self.server.shutdown() self.server.server_close()