feat: add internal web UI server skeleton (Closes #426)
Starlette read-only MVP with shared layout, /health JSON liveness, and route stubs for projects, prompts, runtime, audit, worktrees, and leases. Includes scripts/run-webui, docs/webui-local-dev.md, and tests. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
+117
@@ -0,0 +1,117 @@
|
||||
"""Starlette application for the internal read-only web UI (#426)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from starlette.applications import Starlette
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, JSONResponse, Response
|
||||
from starlette.routing import Route
|
||||
|
||||
from webui.layout import render_page
|
||||
|
||||
_READ_ONLY_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
|
||||
|
||||
|
||||
def _stub_page(title: str, description: str) -> HTMLResponse:
|
||||
body = (
|
||||
f"<h2>{title}</h2>"
|
||||
f'<div class="stub"><p>{description}</p>'
|
||||
"<p>Implementation tracked in a child issue of #425.</p></div>"
|
||||
)
|
||||
return HTMLResponse(render_page(title=title, body_html=body))
|
||||
|
||||
|
||||
async def home(_request: Request) -> HTMLResponse:
|
||||
body = (
|
||||
"<h2>Operator console</h2>"
|
||||
"<p>Local entry point for MCP Control Plane operational views.</p>"
|
||||
"<ul>"
|
||||
"<li><strong>Projects</strong> — registry and onboarding (#427)</li>"
|
||||
"<li><strong>Prompts</strong> — canonical workflow prompt library (#428)</li>"
|
||||
"<li><strong>Runtime</strong> — MCP health and stale-runtime detection (#430)</li>"
|
||||
"<li><strong>Audit</strong> — final-report paste and validator preview (#431)</li>"
|
||||
"<li><strong>Worktrees</strong> — branch hygiene dashboard (#432)</li>"
|
||||
"<li><strong>Leases</strong> — collision and lease visibility (#433)</li>"
|
||||
"</ul>"
|
||||
)
|
||||
return HTMLResponse(render_page(title="Home", body_html=body))
|
||||
|
||||
|
||||
async def health(_request: Request) -> JSONResponse:
|
||||
return JSONResponse({
|
||||
"status": "ok",
|
||||
"service": "mcp-control-plane-webui",
|
||||
"mode": "read-only-mvp",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
|
||||
async def projects(_request: Request) -> HTMLResponse:
|
||||
return _stub_page(
|
||||
"Projects",
|
||||
"Project registry and onboarding model will list configured repos and profiles.",
|
||||
)
|
||||
|
||||
|
||||
async def prompts(_request: Request) -> HTMLResponse:
|
||||
return _stub_page(
|
||||
"Prompts",
|
||||
"Prompt library will surface canonical workflows from skills/llm-project-workflow/.",
|
||||
)
|
||||
|
||||
|
||||
async def runtime(_request: Request) -> HTMLResponse:
|
||||
return _stub_page(
|
||||
"Runtime",
|
||||
"Runtime health will report MCP profile, preflight, and stale-server signals.",
|
||||
)
|
||||
|
||||
|
||||
async def audit(_request: Request) -> HTMLResponse:
|
||||
return _stub_page(
|
||||
"Audit",
|
||||
"Report audit will accept pasted final reports and run validator previews.",
|
||||
)
|
||||
|
||||
|
||||
async def worktrees(_request: Request) -> HTMLResponse:
|
||||
return _stub_page(
|
||||
"Worktrees",
|
||||
"Worktree hygiene will summarize branches/ session folders and cleanup risk.",
|
||||
)
|
||||
|
||||
|
||||
async def leases(_request: Request) -> HTMLResponse:
|
||||
return _stub_page(
|
||||
"Leases",
|
||||
"Lease visibility will show active issue and reviewer PR leases.",
|
||||
)
|
||||
|
||||
|
||||
async def method_not_allowed(request: Request, _exc: Exception) -> Response:
|
||||
if request.method not in _READ_ONLY_METHODS:
|
||||
return JSONResponse(
|
||||
{"error": "read-only-mvp", "detail": f"{request.method} not permitted"},
|
||||
status_code=405,
|
||||
)
|
||||
return JSONResponse({"error": "not_found"}, status_code=404)
|
||||
|
||||
|
||||
def create_app() -> Starlette:
|
||||
"""Build the read-only MVP Starlette app."""
|
||||
return Starlette(
|
||||
debug=False,
|
||||
routes=[
|
||||
Route("/", home, methods=["GET"]),
|
||||
Route("/health", health, methods=["GET"]),
|
||||
Route("/projects", projects, methods=["GET"]),
|
||||
Route("/prompts", prompts, methods=["GET"]),
|
||||
Route("/runtime", runtime, methods=["GET"]),
|
||||
Route("/audit", audit, methods=["GET"]),
|
||||
Route("/worktrees", worktrees, methods=["GET"]),
|
||||
Route("/leases", leases, methods=["GET"]),
|
||||
],
|
||||
exception_handlers={405: method_not_allowed},
|
||||
)
|
||||
Reference in New Issue
Block a user