1
0
Files
kvm-manager/frontend/serve.py
T

116 خطوط
3.8 KiB
Python

"""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",
)