diff --git a/circuitforge_core/resources/coordinator/app.py b/circuitforge_core/resources/coordinator/app.py index c25d061..6c9961b 100644 --- a/circuitforge_core/resources/coordinator/app.py +++ b/circuitforge_core/resources/coordinator/app.py @@ -1,10 +1,14 @@ from __future__ import annotations +from pathlib import Path from typing import Any from fastapi import FastAPI, HTTPException +from fastapi.responses import HTMLResponse from pydantic import BaseModel +_DASHBOARD_HTML = (Path(__file__).parent / "dashboard.html").read_text() + from circuitforge_core.resources.coordinator.agent_supervisor import AgentSupervisor from circuitforge_core.resources.coordinator.eviction_engine import EvictionEngine from circuitforge_core.resources.coordinator.lease_manager import LeaseManager @@ -29,6 +33,10 @@ def create_coordinator_app( app = FastAPI(title="cf-orch-coordinator") + @app.get("/", response_class=HTMLResponse, include_in_schema=False) + def dashboard() -> HTMLResponse: + return HTMLResponse(content=_DASHBOARD_HTML) + @app.get("/api/health") def health() -> dict[str, Any]: return {"status": "ok"} diff --git a/circuitforge_core/resources/coordinator/dashboard.html b/circuitforge_core/resources/coordinator/dashboard.html new file mode 100644 index 0000000..79fc9cb --- /dev/null +++ b/circuitforge_core/resources/coordinator/dashboard.html @@ -0,0 +1,340 @@ + + + + + +cf-orch · dashboard + + + + +
+ + coordinator +
auto-refresh 5s
+
+ +
+ +
Services
+
+ +
GPU Nodes
+
+ +
Active Leases
+ + + + + + + +
ServiceNode / GPUVRAMPriorityTTL / Expires
+ + + + + + diff --git a/tests/test_resources/test_coordinator_app.py b/tests/test_resources/test_coordinator_app.py index 1fb8ce3..e8f6bbe 100644 --- a/tests/test_resources/test_coordinator_app.py +++ b/tests/test_resources/test_coordinator_app.py @@ -100,3 +100,15 @@ def test_get_leases_returns_active_leases(coordinator_client): resp = client.get("/api/leases") assert resp.status_code == 200 assert len(resp.json()["leases"]) == 1 + + +def test_dashboard_serves_html(coordinator_client): + """GET / returns the dashboard HTML page.""" + client, _ = coordinator_client + resp = client.get("/") + assert resp.status_code == 200 + assert "text/html" in resp.headers["content-type"] + # Verify key structural markers are present (without asserting exact markup) + assert "cf-orch" in resp.text + assert "/api/nodes" in resp.text + assert "/api/leases" in resp.text