Load projects from versioned webui/data/projects.registry.json with profile
mappings, workflow/schema paths, and read-only onboarding checklist UI.
Seeds Gitea-Tools; exposes /projects, /projects/{id}, and /api/projects.
Closes #427
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""HTML views for project registry pages (#427)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import html
|
|
|
|
from webui.layout import render_page
|
|
from webui.project_registry import ProjectRecord, ProjectRegistry
|
|
|
|
|
|
def _escape(text: str) -> str:
|
|
return html.escape(text, quote=True)
|
|
|
|
|
|
def render_projects_list(registry: ProjectRegistry) -> str:
|
|
rows = []
|
|
for project in registry.projects:
|
|
rows.append(
|
|
"<tr>"
|
|
f"<td><a href=\"/projects/{_escape(project.id)}\">{_escape(project.repo_name)}</a></td>"
|
|
f"<td>{_escape(project.gitea_owner)}</td>"
|
|
f"<td>{_escape(project.remote_host)}</td>"
|
|
f"<td>{_escape(project.default_branch)}</td>"
|
|
f"<td><code>{_escape(project.profiles['author'])}</code></td>"
|
|
"</tr>"
|
|
)
|
|
table = (
|
|
"<table class=\"registry\">"
|
|
"<thead><tr>"
|
|
"<th>Repository</th><th>Owner</th><th>Remote</th>"
|
|
"<th>Branch</th><th>Author profile</th>"
|
|
"</tr></thead>"
|
|
f"<tbody>{''.join(rows)}</tbody></table>"
|
|
)
|
|
body = (
|
|
"<h2>Projects</h2>"
|
|
"<p>Configured repositories managed by the MCP Control Plane.</p>"
|
|
f"<p class=\"meta\">Registry: <code>{_escape(str(registry.source_path))}</code> "
|
|
f"(version {registry.version})</p>"
|
|
f"{table}"
|
|
"<p><a href=\"/api/projects\">JSON API</a></p>"
|
|
)
|
|
return render_page(title="Projects", body_html=body)
|
|
|
|
|
|
def render_project_detail(project: ProjectRecord) -> str:
|
|
profile_rows = "".join(
|
|
f"<tr><th>{_escape(role)}</th><td><code>{_escape(name)}</code></td></tr>"
|
|
for role, name in project.profiles.items()
|
|
)
|
|
workflow_rows = "".join(
|
|
f"<tr><th>{_escape(key)}</th><td><code>{_escape(path)}</code></td></tr>"
|
|
for key, path in project.workflow_paths.items()
|
|
)
|
|
schema_rows = "".join(
|
|
f"<tr><th>{_escape(key)}</th><td><code>{_escape(path)}</code></td></tr>"
|
|
for key, path in project.schema_paths.items()
|
|
)
|
|
checklist_items = []
|
|
for index, step in enumerate(project.onboarding_checklist, start=1):
|
|
checklist_items.append(
|
|
"<li>"
|
|
f"<strong>{index}. {_escape(step.title)}</strong>"
|
|
f"<p>{_escape(step.description)}</p>"
|
|
"</li>"
|
|
)
|
|
checklist_html = (
|
|
"<ol class=\"checklist\">" + "".join(checklist_items) + "</ol>"
|
|
if checklist_items
|
|
else "<p>No onboarding steps defined.</p>"
|
|
)
|
|
body = (
|
|
f"<h2>{_escape(project.repo_name)}</h2>"
|
|
"<p><a href=\"/projects\">← All projects</a></p>"
|
|
"<h3>Identity</h3>"
|
|
"<table class=\"detail\">"
|
|
f"<tr><th>Registry id</th><td><code>{_escape(project.id)}</code></td></tr>"
|
|
f"<tr><th>Gitea owner</th><td>{_escape(project.gitea_owner)}</td></tr>"
|
|
f"<tr><th>Remote host</th><td>{_escape(project.remote_host)}</td></tr>"
|
|
f"<tr><th>Default branch</th><td><code>{_escape(project.default_branch)}</code></td></tr>"
|
|
f"<tr><th>Local checkout</th><td><code>{_escape(project.local_checkout_path)}</code></td></tr>"
|
|
"</table>"
|
|
"<h3>Profiles</h3>"
|
|
f"<table class=\"detail\">{profile_rows}</table>"
|
|
"<h3>Workflow paths</h3>"
|
|
f"<table class=\"detail\">{workflow_rows}</table>"
|
|
"<h3>Schema paths</h3>"
|
|
f"<table class=\"detail\">{schema_rows}</table>"
|
|
"<h3>Onboarding checklist</h3>"
|
|
"<p class=\"meta\">Read-only MVP — complete these steps outside the UI.</p>"
|
|
f"{checklist_html}"
|
|
)
|
|
return render_page(title=project.repo_name, body_html=body) |