From 03f3dca243ac1ae1313564894a40514033f48543 Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 22 Jun 2026 16:43:51 +0000 Subject: [PATCH] fix: empty project dropdown in pending-deployment classify modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The classify modal's _loadProjects() fetched /api/projects/list and called .json() on it, but that endpoint returns HTML project cards (used by the projects overview via htmx). Parsing HTML as JSON threw, the catch swallowed it, and the Project dropdown came up empty — so deployments couldn't be assigned to a project. - Add GET /api/projects/list-json returning assignable projects (id, name, status) as JSON, excluding deleted/archived/completed to match the default /list view. - Point the modal's _loadProjects() at the JSON endpoint. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/routers/projects.py | 20 ++++++++++++++++++++ templates/admin/pending_deployments.html | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/backend/routers/projects.py b/backend/routers/projects.py index 498c68c..cab2a34 100644 --- a/backend/routers/projects.py +++ b/backend/routers/projects.py @@ -404,6 +404,26 @@ def _build_combined_location_data( # Project List & Overview # ============================================================================ +@router.get("/list-json") +async def get_projects_list_json(db: Session = Depends(get_db)): + """JSON list of assignable projects (id, name, status) for pickers such as + the pending-deployment classify modal. Excludes deleted/archived/completed, + matching the default /list view. (The /list endpoint returns HTML cards, so + JSON consumers must use this one.)""" + projects = ( + db.query(Project) + .filter(Project.status.notin_(["deleted", "archived", "completed"])) + .order_by(Project.name) + .all() + ) + return JSONResponse({ + "projects": [ + {"id": p.id, "name": p.name, "status": p.status} + for p in projects + ] + }) + + @router.get("/list", response_class=HTMLResponse) async def get_projects_list( request: Request, diff --git a/templates/admin/pending_deployments.html b/templates/admin/pending_deployments.html index 2157f52..1d22ffd 100644 --- a/templates/admin/pending_deployments.html +++ b/templates/admin/pending_deployments.html @@ -295,9 +295,10 @@ function closeClassifyModal() { async function _loadProjects() { try { - const r = await fetch('/api/projects/list'); + // Must be the JSON endpoint — /api/projects/list returns HTML cards. + const r = await fetch('/api/projects/list-json'); const data = r.ok ? await r.json() : { projects: [] }; - // Endpoint shape varies; tolerate either { projects: [...] } or a flat array. + // Tolerate either { projects: [...] } or a flat array. _pdState.projectsCache = Array.isArray(data) ? data : (data.projects || []); } catch (e) { _pdState.projectsCache = [];