| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- """KVM Frontend - Static files + API proxy + WebSocket VNC proxy"""
- import os
- import re
- from starlette.applications import Starlette
- from starlette.routing import Route, WebSocketRoute, Mount
- from starlette.staticfiles import StaticFiles
- from starlette.responses import FileResponse, RedirectResponse
- from starlette.websockets import WebSocket
- import uvicorn
- HOST = "0.0.0.0"
- PORT = 8006
- DIST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dist")
- API_BASE = "http://127.0.0.1:8004"
- WS_BASE = "ws://127.0.0.1:8004"
- # ── SPA fallback ──────────────────────────────────────────────
- async def serve_spa(request):
- path = request.url.path
- file_path = os.path.join(DIST_DIR, path.lstrip("/"))
- if os.path.isfile(file_path):
- return FileResponse(file_path)
- return FileResponse(os.path.join(DIST_DIR, "index.html"))
- # ── API proxy ─────────────────────────────────────────────────
- async def proxy_api(request):
- from starlette.requests import Request
- body = await request.body()
- headers = {}
- for key in ["content-type", "authorization"]:
- val = request.headers.get(key)
- if val:
- headers[key.replace("_", "-")] = val
- url = f"{API_BASE}{request.url.path}"
- if request.url.query:
- url += f"?{request.url.query}"
- async with __import__("httpx").AsyncClient(timeout=30) as client:
- try:
- resp = await client.request(
- method=request.method,
- url=url,
- content=body,
- headers=headers,
- )
- return Response(
- resp.content,
- status_code=resp.status_code,
- headers={"access-control-allow-origin": "*"},
- )
- except Exception as e:
- from starlette.responses import JSONResponse
- return JSONResponse({"error": str(e)}, status_code=502)
- from starlette.responses import Response
- return Response("", status_code=500)
- # ── WebSocket VNC proxy ────────────────────────────────────────
- async def vnc_websocket(websocket: WebSocket):
- await websocket.accept()
- # Build backend WebSocket URL
- path = websocket.url.path # e.g. /ws/vnc/vm-name?host_id=local
- backend_url = f"{WS_BASE}{path}"
- try:
- async with __import__("websockets").connect(backend_url) as backend:
- async def forward_to_backend():
- async for msg in websocket.iter_text():
- await backend.send(msg)
- async for msg in websocket.iter_bytes():
- await backend.send(msg)
- async def forward_to_frontend():
- async for msg in backend:
- if isinstance(msg, bytes):
- await websocket.send_bytes(msg)
- else:
- await websocket.send_text(msg)
- await __import__("asyncio").gather(
- forward_to_backend(),
- forward_to_frontend(),
- )
- except Exception as e:
- print(f"WebSocket proxy error: {e}")
- finally:
- try:
- await websocket.close()
- except Exception:
- pass
- from starlette.responses import Response
- routes = [
- WebSocketRoute("/ws/{path:path}", vnc_websocket, name="vnc"),
- Route("/api/{path:path}", proxy_api),
- Route("/api", proxy_api),
- Mount("/", StaticFiles(directory=DIST_DIR, html=True), name="static"),
- ]
- app = Starlette(routes=routes)
- if __name__ == "__main__":
- uvicorn.run(
- app,
- host=HOST,
- port=PORT,
- log_level="info",
- )
|