ui improvements on error when registering repos

This commit is contained in:
2026-04-26 18:27:19 +02:00
parent 2c0213717c
commit dda00b70ac
4 changed files with 109 additions and 18 deletions

View File

@@ -63,14 +63,20 @@ class GitIngestionService:
def _run_git(self, args: list[str], *, cwd: Path | None) -> None:
if shutil.which("git") is None:
raise RuntimeError("git executable was not found")
result = subprocess.run(
["git", *args],
cwd=cwd,
check=False,
capture_output=True,
text=True,
timeout=120,
)
command = ["git", *args]
try:
result = subprocess.run(
command,
cwd=cwd,
check=False,
capture_output=True,
text=True,
timeout=120,
)
except subprocess.TimeoutExpired as exc:
raise RuntimeError(
f"git {' '.join(args)} timed out after {exc.timeout} seconds"
) from exc
if result.returncode != 0:
message = result.stderr.strip() or result.stdout.strip()
raise RuntimeError(f"git {' '.join(args)} failed: {message}")

View File

@@ -34,6 +34,8 @@ def page(title: str, body: str) -> HTMLResponse:
--accent: #0f766e;
--accent-dark: #115e59;
--warn: #9a3412;
--danger: #b42318;
--danger-bg: #fff4f2;
}}
* {{ box-sizing: border-box; }}
body {{
@@ -64,6 +66,17 @@ def page(title: str, body: str) -> HTMLResponse:
border-radius: 8px;
padding: 16px;
}}
.notice {{
border: 1px solid var(--line);
border-radius: 8px;
padding: 10px 12px;
margin-bottom: 12px;
}}
.notice.error {{
border-color: #f3b8ae;
background: var(--danger-bg);
color: var(--danger);
}}
.stack {{ display: grid; gap: 12px; }}
.muted {{ color: var(--muted); }}
.pill {{
@@ -111,6 +124,9 @@ def page(title: str, body: str) -> HTMLResponse:
.tree li {{ margin: 6px 0; }}
.source {{ color: var(--muted); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; }}
.actions {{ display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }}
[data-pending] {{ display: none; color: var(--muted); }}
form.is-submitting [data-pending] {{ display: inline; }}
form.is-submitting button[type="submit"] {{ opacity: .7; cursor: wait; }}
@media (max-width: 780px) {{
header {{ padding: 12px 16px; }}
main {{ padding: 16px; }}
@@ -132,14 +148,28 @@ def page(title: str, body: str) -> HTMLResponse:
</nav>
</header>
<main>{body}</main>
<script>
document.addEventListener("submit", (event) => {{
const form = event.target;
if (form instanceof HTMLFormElement) {{
form.classList.add("is-submitting");
const button = form.querySelector('button[type="submit"]');
if (button) button.setAttribute("disabled", "disabled");
}}
}});
</script>
</body>
</html>
"""
)
@router.get("/ui")
def repository_index(service: RegistryService = Depends(get_service)) -> HTMLResponse:
def render_repository_index(
service: RegistryService,
*,
error_message: str | None = None,
status_code: int = 200,
) -> HTMLResponse:
repositories = service.list_repositories()
rows = "\n".join(
f"""
@@ -152,15 +182,29 @@ def repository_index(service: RegistryService = Depends(get_service)) -> HTMLRes
"""
for repo in repositories
)
error = (
f"""
<div class="notice error" role="alert">
<strong>Registration failed.</strong>
<p>{escape(error_message)}</p>
</div>
"""
if error_message
else ""
)
body = f"""
<h1>Repositories</h1>
{error}
<div class="grid">
<section class="panel">
<h2>Register Repository</h2>
<form class="stack" method="post" action="/ui/repos">
<label>Git URL or local path <input name="url" required></label>
<label>Branch <input name="branch" value="main"></label>
<button type="submit">Register</button>
<div class="actions">
<button type="submit">Register</button>
<span data-pending>Registering repository...</span>
</div>
</form>
</section>
<section class="panel">
@@ -175,7 +219,14 @@ def repository_index(service: RegistryService = Depends(get_service)) -> HTMLRes
</section>
</div>
"""
return page("Repositories", body)
response = page("Repositories", body)
response.status_code = status_code
return response
@router.get("/ui")
def repository_index(service: RegistryService = Depends(get_service)) -> HTMLResponse:
return render_repository_index(service)
@router.get("/ui/discovery")
@@ -360,11 +411,18 @@ def create_repository_from_form(
url: str = Form(...),
branch: str = Form("main"),
service: RegistryService = Depends(get_service),
) -> RedirectResponse:
repository = service.register_repository(
url=url,
branch=branch or "main",
)
):
try:
repository = service.register_repository(
url=url,
branch=branch or "main",
)
except (RuntimeError, ValueError) as exc:
return render_repository_index(
service,
error_message=str(exc),
status_code=400,
)
return RedirectResponse(f"/ui/repos/{repository.id}", status_code=303)

View File

@@ -1073,6 +1073,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
index_response = client.get("/ui")
assert index_response.status_code == 200
assert "Register Repository" in index_response.text
assert "Registering repository..." in index_response.text
create_response = client.post(
"/ui/repos",
@@ -1201,6 +1202,32 @@ def test_ui_register_analyze_and_approve_loop(tmp_path):
app.dependency_overrides.clear()
def test_ui_registration_failure_returns_feedback(tmp_path):
def override_settings():
return Settings(
database_path=str(tmp_path / "ui-error.sqlite3"),
checkout_root=str(tmp_path / "ui-error-checkouts"),
)
app.dependency_overrides[get_settings] = override_settings
client = TestClient(app)
try:
response = client.post(
"/ui/repos",
data={
"url": str(tmp_path / "missing-repo"),
"branch": "main",
},
)
assert response.status_code == 400
assert "Registration failed." in response.text
assert "git clone" in response.text
assert "Register Repository" in response.text
finally:
app.dependency_overrides.clear()
def test_ui_manual_registry_entry_loop(tmp_path):
source = tmp_path / "manual-repo"
source.mkdir()

View File

@@ -27,7 +27,7 @@ configured trusted automation mode.
```task
id: RREG-WP-0003-T01
status: todo
status: in_progress
priority: high
state_hub_task_id: "ab718ce7-d38f-4080-9385-99be1bf80475"
```