"""HTML views for project registry pages (#427, evolved for #635). Every view renders from :func:`webui.project_registry.project_to_dict`, the same DTO the ``/api/v1/projects`` JSON responses use, so the HTML console and the API can never disagree about status or onboarding progress. """ from __future__ import annotations import html from typing import Any from webui.layout import render_page from webui.project_registry import ( ProjectRecord, ProjectRegistry, RegistryError, project_to_dict, ) _STATE_LABELS = { "complete": "Complete", "pending": "Pending", "blocked": "Blocked", "not_applicable": "Not applicable", } def _escape(text: str) -> str: return html.escape(text, quote=True) def _progress_label(summary: dict[str, Any]) -> str: total = summary["total"] if not total: return "no steps" label = f"{summary['complete']}/{total} complete" if summary["blocked"]: label += f", {summary['blocked']} blocked" return label def render_projects_list(registry: ProjectRegistry) -> str: rows = [] for project in registry.projects: dto = project_to_dict(project) rows.append( "
{_escape(dto['status'])}{_escape(dto['profiles']['author'])}| Repository | Owner | Remote | " "Branch | Status | Onboarding | Author profile | " "
|---|
Configured repositories managed by the MCP Control Plane.
" f"" f"{table}" "JSON API " "(unversioned alias)
" ) return render_page(title="Projects", body_html=body) def render_project_detail(project: ProjectRecord) -> str: dto = project_to_dict(project) profile_rows = "".join( f"{_escape(name)}{_escape(path)}{_escape(path)}{_escape(step['description'])}
" "No onboarding steps defined.
" ) summary = dto["onboarding_summary"] summary_html = ( "" ) health = dto["last_seen_health"] health_html = ( "" if health is None else ( "| Status | {_escape(health['status'])} |
|---|---|
| Checked at | {_escape(str(health['checked_at'] or 'unknown'))} |
| Detail | {_escape(str(health['detail'] or ''))} |
| Registry id | {_escape(dto['id'])} |
|---|---|
| Status | {_escape(dto['status'])} |
| Gitea owner | {_escape(dto['gitea_owner'])} |
| Repository | {_escape(dto['repo_full_name'])} |
| Remote name | {_escape(remote_name)} |
| Remote host | {_escape(dto['remote_host'])} |
| Default branch | {_escape(dto['default_branch'])} |
| Local checkout | {_escape(dto['local_checkout_path'])} |
The registry failed validation, so the console refuses to render a " "partial inventory.
" "| Detail | {_escape(error.message)} |
|---|---|
| Field | {_escape(field)} |
| Source | {_escape(source)} |
| Remediation | {_escape(error.remediation)} |