From b616688411d47c12cf03386431463a824a84b903 Mon Sep 17 00:00:00 2001 From: patrick-motsch Date: Wed, 18 Feb 2026 22:00:34 +0100 Subject: [PATCH] feat: add screenshot proxy endpoints for debug viewer (SysAdmin only) Co-authored-by: Cursor --- .../features/teamsbot/routeFeatureTeamsbot.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/modules/features/teamsbot/routeFeatureTeamsbot.py b/modules/features/teamsbot/routeFeatureTeamsbot.py index 52a3ba72..8619505f 100644 --- a/modules/features/teamsbot/routeFeatureTeamsbot.py +++ b/modules/features/teamsbot/routeFeatureTeamsbot.py @@ -975,6 +975,85 @@ async def testAuthSingleVariant( raise HTTPException(status_code=503, detail=f"Browser Bot connection failed: {str(e)}") +# ========================================================================= +# Debug Screenshot Endpoints (SysAdmin only) +# ========================================================================= + +@router.get("/{instanceId}/sessions/{sessionId}/screenshots") +@limiter.limit("30/minute") +async def listSessionScreenshots( + request: Request, + instanceId: str, + sessionId: str, + context: RequestContext = Depends(getRequestContext), +): + """List debug screenshots for a session. Proxied from Browser Bot filesystem.""" + if not context.isSysAdmin: + raise HTTPException(status_code=403, detail="SysAdmin privileges required") + _validateInstanceAccess(instanceId, context) + effectiveConfig = _getInstanceConfig(instanceId) + browserBotUrl = effectiveConfig._getEffectiveBrowserBotUrl() + if not browserBotUrl: + raise HTTPException(status_code=503, detail="Browser Bot URL not configured") + + import aiohttp + browserBotUrl = browserBotUrl.rstrip("/") + try: + timeout = aiohttp.ClientTimeout(total=15) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(f"{browserBotUrl}/api/bot/screenshots/{sessionId}") as resp: + if resp.status == 200: + data = await resp.json() + screenshots = data.get("screenshots", []) + for s in screenshots: + s["url"] = f"/api/teamsbot/{instanceId}/screenshots/{s['name']}" + return {"screenshots": screenshots} + else: + errorText = await resp.text() + raise HTTPException(status_code=resp.status, detail=f"Browser Bot error: {errorText}") + except aiohttp.ClientError as e: + logger.error(f"Screenshot list error: {e}") + raise HTTPException(status_code=503, detail=f"Browser Bot connection failed: {str(e)}") + + +@router.get("/{instanceId}/screenshots/{filename}") +@limiter.limit("60/minute") +async def getScreenshotFile( + request: Request, + instanceId: str, + filename: str, + context: RequestContext = Depends(getRequestContext), +): + """Serve a single debug screenshot image. Proxied from Browser Bot.""" + if not context.isSysAdmin: + raise HTTPException(status_code=403, detail="SysAdmin privileges required") + _validateInstanceAccess(instanceId, context) + + if not filename.endswith(".png") or ".." in filename or "/" in filename or "\\" in filename: + raise HTTPException(status_code=400, detail="Invalid filename") + + effectiveConfig = _getInstanceConfig(instanceId) + browserBotUrl = effectiveConfig._getEffectiveBrowserBotUrl() + if not browserBotUrl: + raise HTTPException(status_code=503, detail="Browser Bot URL not configured") + + import aiohttp + from fastapi.responses import Response as FastAPIResponse + browserBotUrl = browserBotUrl.rstrip("/") + try: + timeout = aiohttp.ClientTimeout(total=30) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(f"{browserBotUrl}/api/bot/screenshots/file/{filename}") as resp: + if resp.status == 200: + imageBytes = await resp.read() + return FastAPIResponse(content=imageBytes, media_type="image/png") + else: + raise HTTPException(status_code=resp.status, detail="Screenshot not found") + except aiohttp.ClientError as e: + logger.error(f"Screenshot file error: {e}") + raise HTTPException(status_code=503, detail=f"Browser Bot connection failed: {str(e)}") + + # ========================================================================= # Browser Bot Communication Endpoints (HTTP Fallback + WebSocket) # =========================================================================