4310a5cc2d
Frontend: - Vite + React 18 + TypeScript + Tailwind v4 + Shadcn/ui - React Flow v12 canvas with all 11 node types and 5 edge types - Dark theme with project design system (cyan, green, orange, purple accents) - Collapsible sidebar, toolbar, detail panel - Zustand store for canvas state - Demo data with 10 nodes and 10 edges Backend: - FastAPI + SQLAlchemy async + SQLite (Python 3.13) - DB models: Node, Edge, CanvasState, PendingDevice, ScanRun - REST API routes: auth, nodes, edges, canvas, scan, status (WebSocket) - JWT auth + bcrypt via config.yml - Pydantic v2 schemas Infra: - GitHub Actions quality + security workflows - .gitignore, .env.example, verify-tooling.sh, security-check.sh
34 lines
882 B
Python
34 lines
882 B
Python
import json
|
|
|
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
|
|
router = APIRouter()
|
|
|
|
# Active WebSocket connections
|
|
_connections: list[WebSocket] = []
|
|
|
|
|
|
@router.websocket("/ws/status")
|
|
async def ws_status(websocket: WebSocket):
|
|
await websocket.accept()
|
|
_connections.append(websocket)
|
|
try:
|
|
while True:
|
|
await websocket.receive_text()
|
|
except WebSocketDisconnect:
|
|
_connections.remove(websocket)
|
|
|
|
|
|
async def broadcast_status(node_id: str, status: str, checked_at: str, response_time_ms: int | None = None):
|
|
payload = json.dumps({
|
|
"node_id": node_id,
|
|
"status": status,
|
|
"checked_at": checked_at,
|
|
"response_time_ms": response_time_ms,
|
|
})
|
|
for conn in list(_connections):
|
|
try:
|
|
await conn.send_text(payload)
|
|
except Exception:
|
|
_connections.remove(conn)
|