feat: container monitoring integration + polish (PR-3)

- Integration tests: SSE auth, connection limits, lifecycle hooks, event persistence (6 tests)
- Instance events history API: GET /instances/{id}/events
- Documentation updates: terminal.md, backend.md, frontend.md
- Performance: SSE max 5 connections, health monitor write-on-change

Quality gates: pytest 21 monitoring passed, 172 unit passed (4 pre-existing), vitest 14 passed, tsc clean, eslint clean, ruff clean
This commit is contained in:
2026-05-29 10:23:28 +02:00
parent f13a63dc2f
commit 2682e0268c
6 changed files with 481 additions and 4 deletions
+59
View File
@@ -2284,6 +2284,65 @@ async def check_instance_tunnel_health(
return response
@router.get(
"/{project_id}/repositories/{repo_id}/instances/{instance_id}/events",
summary="Get instance events history",
description="Get lifecycle event history for a tool instance.",
)
async def get_instance_events(
project_id: uuid.UUID,
repo_id: uuid.UUID,
instance_id: uuid.UUID,
limit: int = 50,
user_id: uuid.UUID = Depends(get_current_user_id),
session: AsyncSession = Depends(get_db_session),
) -> list[dict]:
"""Get lifecycle event history for an instance.
Args:
project_id: UUID of the project.
repo_id: UUID of the repository.
instance_id: UUID of the instance.
limit: Maximum number of events to return (default: 50).
user_id: ID of the authenticated user.
session: Database session.
Returns:
List of event dictionaries.
"""
from sqlalchemy import select
from src.models.instance_event import InstanceEvent
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
instance = await session.get(ToolInstance, instance_id)
if instance is None or instance.repository_id != repo_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="instance not found"
)
result = await session.execute(
select(InstanceEvent)
.where(InstanceEvent.instance_id == instance_id)
.order_by(InstanceEvent.created_at.desc())
.limit(limit)
)
rows = result.scalars().all()
return [
{
"id": str(row.id),
"event_type": row.event_type,
"status": row.status,
"message": row.message,
"metadata": row.event_metadata,
"created_at": row.created_at.isoformat() if row.created_at else None,
}
for row in rows
]
@router.get(
"/{project_id}/repositories/{repo_id}/instances/{instance_id}/proxy/{path:path}",
summary="Proxy to instance",