"""Minimal sysmonstm gateway - standalone mode without dependencies."""
import asyncio
import json
import logging
import os
from datetime import datetime
from fastapi import FastAPI, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
# Configuration
API_KEY = os.environ.get("API_KEY", "")
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
# Logging setup
logging.basicConfig(
level=getattr(logging, LOG_LEVEL.upper(), logging.INFO),
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("gateway")
app = FastAPI(title="sysmonstm")
# Store connected websockets
connections: list[WebSocket] = []
# Store latest metrics from collectors
machines: dict = {}
HTML = """
sysmonstm
No collectors connected
Start a collector to see metrics
"""
@app.get("/", response_class=HTMLResponse)
async def index():
return HTML
@app.get("/health")
async def health():
return {"status": "ok", "machines": len(machines)}
@app.get("/api/machines")
async def get_machines():
return machines
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket, key: str = Query(default="")):
# API key validation for collectors (browsers don't need key)
# Check if this looks like a collector (will send metrics) or browser (will receive)
# We validate key only when metrics are received, allowing browsers to connect freely
await websocket.accept()
connections.append(websocket)
client = websocket.client.host if websocket.client else "unknown"
log.info(f"WebSocket connected: {client}")
try:
# Send current state to new connection
for machine_id, data in machines.items():
await websocket.send_json(
{"type": "metrics", "machine_id": machine_id, **data}
)
# Main loop
while True:
try:
msg = await asyncio.wait_for(websocket.receive_text(), timeout=30)
data = json.loads(msg)
if data.get("type") == "metrics":
# Validate API key for metric submissions
if API_KEY and key != API_KEY:
log.warning(f"Invalid API key from {client}")
await websocket.close(code=4001, reason="Invalid API key")
return
machine_id = data.get("machine_id", "unknown")
machines[machine_id] = data
log.debug(f"Metrics from {machine_id}: cpu={data.get('cpu')}%")
# Broadcast to all connected clients
for conn in connections:
try:
await conn.send_json(
{"type": "metrics", "machine_id": machine_id, **data}
)
except Exception:
pass
except asyncio.TimeoutError:
# Send ping to keep connection alive
await websocket.send_json({"type": "ping"})
except WebSocketDisconnect:
log.info(f"WebSocket disconnected: {client}")
except Exception as e:
log.error(f"WebSocket error: {e}")
finally:
if websocket in connections:
connections.remove(websocket)
if __name__ == "__main__":
import uvicorn
log.info("Starting sysmonstm gateway")
log.info(f" API key: {'configured' if API_KEY else 'not set (open)'}")
uvicorn.run(app, host="0.0.0.0", port=8080)