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…
Frames injected into TAP
0
System status
Ports
| Service | Direction | Protocol | Address | Port | Status |
|---|
Recent alerts
| Time | Severity | Signature | Source | Destination | Action |
|---|
'''
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()