feat: Phase 1 scaffold — frontend canvas + backend skeleton
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
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user