Bruno Charest 6d8432169b
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Add enhanced terminal history panel UI with animations, keyboard navigation, advanced filtering, search highlighting, and improved storage metrics display with detailed filesystem tables and ZFS/LVM support
2025-12-21 12:31:08 -05:00

138 lines
4.8 KiB
Python

"""
Routes WebSocket pour les mises à jour en temps réel.
"""
import asyncio
import logging
from typing import Optional
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, HTTPException, status
from app.services import ws_manager
from app.crud.terminal_session import TerminalSessionRepository
from app.services.terminal_service import terminal_service
logger = logging.getLogger(__name__)
router = APIRouter()
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""Endpoint WebSocket pour les mises à jour en temps réel."""
await ws_manager.connect(websocket)
try:
while True:
# Garder la connexion ouverte
data = await websocket.receive_text()
# Traiter les messages entrants si nécessaire
except WebSocketDisconnect:
ws_manager.disconnect(websocket)
@router.websocket("/terminal/ws/{session_id}")
async def terminal_websocket_proxy(
websocket: WebSocket,
session_id: str,
):
"""
WebSocket proxy for ttyd terminal sessions.
This endpoint proxies WebSocket connections to the local ttyd instance,
allowing the browser to connect through the exposed port 8008 instead of
needing direct access to the ttyd port (7680+).
The session_id is used to look up the ttyd port from the database.
Authentication is handled via session token in the query string.
"""
import socket
# Get token from query params
token = websocket.query_params.get("token")
if not token:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Missing token")
return
# We need a DB session to look up the terminal session
# Since we're in a WebSocket endpoint, we need to create one manually
from app.core.database import SessionLocal
async with SessionLocal() as db_session:
session_repo = TerminalSessionRepository(db_session)
session = await session_repo.get(session_id)
if not session:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Session not found")
return
# Verify token
if not terminal_service.verify_token(token, session.token_hash):
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Invalid token")
return
# Accept the WebSocket connection
await websocket.accept()
# Connect to local ttyd via TCP socket
ttyd_port = session.ttyd_port
ttyd_host = "127.0.0.1" # ttyd is always local in the container
try:
# Create a TCP socket to ttyd
reader, writer = await asyncio.open_connection(ttyd_host, ttyd_port)
logger.info(f"Proxying WebSocket for session {session_id[:8]}... to ttyd on port {ttyd_port}")
# Bidirectional proxy: forward messages between WebSocket and ttyd
async def forward_ws_to_ttyd():
"""Forward WebSocket messages to ttyd."""
try:
while True:
data = await websocket.receive_bytes()
writer.write(data)
await writer.drain()
except WebSocketDisconnect:
logger.debug(f"WebSocket disconnected for session {session_id[:8]}...")
except Exception as e:
logger.error(f"Error forwarding WS to ttyd: {e}")
finally:
try:
writer.close()
await writer.wait_closed()
except Exception:
pass
async def forward_ttyd_to_ws():
"""Forward ttyd messages to WebSocket."""
try:
while True:
data = await reader.readexactly(1024)
if not data:
break
await websocket.send_bytes(data)
except asyncio.IncompleteReadError:
# Normal EOF
pass
except Exception as e:
logger.error(f"Error forwarding ttyd to WS: {e}")
finally:
try:
await websocket.close()
except Exception:
pass
# Run both directions concurrently
await asyncio.gather(
forward_ws_to_ttyd(),
forward_ttyd_to_ws(),
return_exceptions=True
)
except ConnectionRefusedError:
logger.error(f"Failed to connect to ttyd on {ttyd_host}:{ttyd_port} for session {session_id[:8]}...")
await websocket.close(code=1011, reason="ttyd connection failed")
except Exception as e:
logger.exception(f"Error in terminal WebSocket proxy: {e}")
try:
await websocket.close(code=1011, reason="Proxy error")
except Exception:
pass