Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev
This commit is contained in:
@@ -9,6 +9,7 @@ from contextlib import suppress
|
||||
from fastapi import APIRouter, Depends, HTTPException, WebSocket, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
|
||||
from src.auth.dependencies import get_current_user_id, get_db_session
|
||||
from src.models.terminal_session import TerminalSessionModel
|
||||
@@ -152,13 +153,36 @@ async def _handle_terminal_websocket(
|
||||
target_session_id,
|
||||
)
|
||||
if session is None:
|
||||
logger.warning(
|
||||
"Session %s not found for instance %s",
|
||||
target_session_id,
|
||||
instance_id,
|
||||
# Session not in memory — may have been lost on server restart.
|
||||
# Try to restore from the DB row.
|
||||
db_row = await db_session.get(
|
||||
TerminalSessionModel, uuid.UUID(target_session_id)
|
||||
)
|
||||
await websocket.close(code=4004, reason="Session not found")
|
||||
return
|
||||
if (
|
||||
db_row is not None
|
||||
and db_row.instance_id == instance_uuid
|
||||
and db_row.status != "closed"
|
||||
):
|
||||
logger.info(
|
||||
"Restoring terminal session %s for instance %s from DB",
|
||||
target_session_id,
|
||||
instance_id,
|
||||
)
|
||||
session = await terminal_manager.create_session(
|
||||
instance_uuid,
|
||||
instance.container_id,
|
||||
startup_command=startup_command,
|
||||
name=db_row.name,
|
||||
session_id=target_session_id,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Session %s not found for instance %s",
|
||||
target_session_id,
|
||||
instance_id,
|
||||
)
|
||||
await websocket.close(code=4004, reason="Session not found")
|
||||
return
|
||||
# Determine slot key for reset scoping
|
||||
key = terminal_manager._find_key_by_internal_id(
|
||||
instance_id, session.session_id
|
||||
@@ -207,6 +231,8 @@ async def _handle_terminal_websocket(
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
|
||||
except WebSocketDisconnect:
|
||||
logger.debug("WebSocket disconnected for instance %s", instance_id)
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"Terminal session error for instance %s: %s",
|
||||
@@ -214,7 +240,8 @@ async def _handle_terminal_websocket(
|
||||
str(exc),
|
||||
exc_info=True,
|
||||
)
|
||||
await websocket.close(code=4000, reason=f"Error: {exc}")
|
||||
with suppress(Exception):
|
||||
await websocket.close(code=4000, reason=f"Error: {exc}")
|
||||
finally:
|
||||
# Detach WebSocket, don't kill session
|
||||
with suppress(Exception):
|
||||
@@ -237,6 +264,8 @@ async def _read_loop(session_ref: SessionRef, websocket) -> None:
|
||||
if data:
|
||||
try:
|
||||
await websocket.send_bytes(data)
|
||||
except WebSocketDisconnect:
|
||||
break
|
||||
except Exception:
|
||||
break
|
||||
else:
|
||||
@@ -403,7 +432,9 @@ async def list_terminal_sessions(
|
||||
)
|
||||
db_rows = result.scalars().all()
|
||||
|
||||
# Build response with live has_websockets flag
|
||||
# Build response with live has_websockets flag.
|
||||
# Include DB rows even without in-memory counterparts (e.g. after
|
||||
# server restart) so the frontend can display tabs and reconnect.
|
||||
sessions = []
|
||||
for row in db_rows:
|
||||
live_session = terminal_manager.get_session(str(instance_id), str(row.id))
|
||||
|
||||
@@ -131,6 +131,7 @@ class TerminalManager:
|
||||
container_id: str,
|
||||
startup_command: str | None = None,
|
||||
name: str | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> TerminalSession:
|
||||
"""Create a new terminal session for an instance.
|
||||
|
||||
@@ -159,7 +160,8 @@ class TerminalManager:
|
||||
instance_id_str, self.MAX_SESSIONS_PER_INSTANCE
|
||||
)
|
||||
|
||||
session_id = str(uuid.uuid4())
|
||||
if session_id is None:
|
||||
session_id = str(uuid.uuid4())
|
||||
session = TerminalSession(
|
||||
session_id=session_id,
|
||||
instance_id=instance_id,
|
||||
@@ -172,7 +174,7 @@ class TerminalManager:
|
||||
key = (instance_id_str, session_id)
|
||||
self._sessions[key] = session
|
||||
|
||||
# Fire-and-forget DB insert
|
||||
# Fire-and-forget DB insert (skip if row already exists)
|
||||
asyncio.create_task(
|
||||
self._insert_db_session_row(session_id, instance_id, session.name)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user