From a662be9c123c541560d82806cb6696425f5d9bc8 Mon Sep 17 00:00:00 2001 From: Naeel Date: Thu, 16 Apr 2026 15:54:35 +0300 Subject: [PATCH] v9.13: restore stream_instances endpoint (accidentally removed in v9.12) --- k8s/deployment.yaml | 2 +- main.py | 53 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 332ccc9..6e52db6 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: cloud-dashboard - image: naeel/cloud-dashboard:v9.12 + image: naeel/cloud-dashboard:v9.13 imagePullPolicy: Always ports: - containerPort: 8080 diff --git a/main.py b/main.py index eb9e53a..ce5cb1b 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,10 @@ from fastapi import FastAPI, Header, HTTPException, Request +from fastapi.responses import StreamingResponse from fastapi.staticfiles import StaticFiles import httpx from typing import Annotated import asyncio +import json import time app = FastAPI() @@ -256,6 +258,57 @@ async def get_graph( } +@app.get("/api/instances/stream") +async def stream_instances( + request: Request, + x_deck_token: Annotated[str | None, Header()] = None, + x_deck_env: Annotated[str | None, Header()] = None, +): + if not x_deck_token: + raise HTTPException(status_code=401, detail="Token required") + _, deck_api = resolve_deck_api(x_deck_env) + + all_instances = await fetch_all_instances(x_deck_token, deck_api) + uid_to_inst = { + i.get("instanceUid"): i + for i in all_instances + if i.get("instanceUid") + } + + async def generate(): + sem = asyncio.Semaphore(20) + queue: asyncio.Queue = asyncio.Queue() + total = len(uid_to_inst) + + async def fetch_one(uid: str, client: httpx.AsyncClient): + async with sem: + try: + r = await client.get( + f"{deck_api}/index.cfm/instances/{uid}", + headers=auth_headers(x_deck_token), + ) + detail = None if r.status_code >= 400 else r.json().get("instance") + except Exception: + detail = None + await queue.put((uid, detail)) + + async with httpx.AsyncClient(timeout=25) as shared_client: + tasks = [ + asyncio.create_task(fetch_one(uid, shared_client)) + for uid in uid_to_inst + ] + for _ in range(total): + uid, detail = await queue.get() + row = dict(uid_to_inst.get(uid, {})) + if detail: + row["_detail"] = detail + yield json.dumps(row, ensure_ascii=False) + "\n" + + yield json.dumps({"_done": True, "total": total}, ensure_ascii=False) + "\n" + + return StreamingResponse(generate(), media_type="application/x-ndjson") + + @app.get("/api/instances/{uid}") async def get_instance( request: Request,