Files
Gitea-Tools/webui/lease_views.py
sysadminandClaude Opus 4.8 09e83fd034 feat: add lease and collision visibility to web UI (Closes #433)
Adds read-only /leases and /api/leases routes for issue claim inventory,
reviewer PR lease comments, duplicate PR/branch warnings, and local issue
lock visibility without acquire/release mutations.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-07 15:29:56 -04:00

130 lines
4.8 KiB
Python

"""HTML views for lease and collision visibility (#433)."""
from __future__ import annotations
import html
import json
from webui.layout import render_page
from webui.lease_loader import LeaseSnapshot
def _escape(text: str) -> str:
return html.escape(text, quote=True)
def _warnings_block(snapshot: LeaseSnapshot) -> str:
warnings = list(snapshot.duplicate_prs) + list(snapshot.duplicate_branches)
if not warnings:
return "<p class='muted'>No duplicate PR/branch collisions detected in current inventory.</p>"
items = "".join(f"<li>{_escape(w.message)}</li>" for w in warnings)
return f"<ul class='collision-warnings'>{items}</ul>"
def _lock_block(snapshot: LeaseSnapshot) -> str:
lock = snapshot.issue_lock
if not lock:
return "<p class='muted'>No active local issue lock file.</p>"
return (
"<pre class='prompt-text'>"
f"{_escape(json.dumps(lock, indent=2, sort_keys=True))}"
"</pre>"
)
def _claims_table(snapshot: LeaseSnapshot) -> str:
entries = snapshot.claim_inventory.get("entries") or []
if not entries:
return "<p class='muted'>No in-progress issue claims in fetched inventory.</p>"
rows = []
for entry in entries:
rows.append(
"<tr>"
f"<td>#{entry.get('issue_number')}</td>"
f"<td><span class='badge badge-{_escape(str(entry.get('status')))}'>"
f"{_escape(str(entry.get('status')))}</span></td>"
f"<td>{_escape(str(entry.get('linked_open_pr') or '—'))}</td>"
f"<td>{entry.get('heartbeat_count', 0)}</td>"
f"<td>{_escape(', '.join(entry.get('matching_branches') or []) or '—')}</td>"
f"<td class='muted'>{_escape('; '.join(entry.get('reasons') or []))}</td>"
"</tr>"
)
return (
"<table class='registry leases'>"
"<thead><tr><th>Issue</th><th>Claim status</th><th>Open PR</th>"
"<th>Heartbeats</th><th>Branches</th><th>Notes</th></tr></thead>"
f"<tbody>{''.join(rows)}</tbody></table>"
)
def _reviewer_leases_table(snapshot: LeaseSnapshot) -> str:
if not snapshot.reviewer_leases:
return (
"<p class='muted'>No active reviewer PR lease comments found "
"(marker <code>&lt;!-- mcp-review-lease:v1 --&gt;</code>; see #407).</p>"
)
rows = []
for lease in snapshot.reviewer_leases:
rows.append(
"<tr>"
f"<td>#{lease.get('pr_number')}</td>"
f"<td>{_escape(str(lease.get('reviewer_identity') or '—'))}</td>"
f"<td><code>{_escape(str(lease.get('profile') or '—'))}</code></td>"
f"<td>{_escape(str(lease.get('phase') or '—'))}</td>"
f"<td><code>{_escape(str(lease.get('expires_at') or '—'))}</code></td>"
"</tr>"
)
return (
"<table class='registry leases'>"
"<thead><tr><th>PR</th><th>Reviewer</th><th>Profile</th>"
"<th>Phase</th><th>Expires</th></tr></thead>"
f"<tbody>{''.join(rows)}</tbody></table>"
)
def _history_links(snapshot: LeaseSnapshot) -> str:
items = "".join(
f"<li><a href=\"https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools/issues/"
f"{item['number']}\">#{item['number']}</a> — {_escape(item['title'])}</li>"
for item in snapshot.collision_history
)
return f"<ul>{items}</ul>"
LEASE_PAGE_STYLES = """
<style>
.collision-warnings li { color: #f0c4c4; margin: 0.35rem 0; }
table.leases td:nth-child(6) { font-size: 0.85rem; }
.badge-active, .badge-awaiting_review { color: #8fd19e; border-color: #3d6b4a; }
.badge-stale, .badge-phantom, .badge-reclaimable { color: #f0a8a8; border-color: #7a3b3b; }
</style>
"""
def render_leases_page(snapshot: LeaseSnapshot) -> str:
error_block = ""
if snapshot.fetch_error:
error_block = (
'<div class="stub"><p><strong>Partial load:</strong> '
f"{_escape(snapshot.fetch_error)}</p></div>"
)
body = (
"<h2>Leases &amp; collisions</h2>"
"<p>Read-only visibility for issue claims, reviewer PR leases, and "
"duplicate-work risks. Does not acquire or release leases.</p>"
f"{error_block}"
f"<p class='meta'>Project: <code>{_escape(snapshot.repo_label)}</code></p>"
"<h3>Collision warnings</h3>"
f"{_warnings_block(snapshot)}"
"<h3>Local issue lock</h3>"
f"{_lock_block(snapshot)}"
"<h3>In-progress issue claims (#268)</h3>"
f"{_claims_table(snapshot)}"
"<h3>Reviewer PR leases (#407)</h3>"
f"{_reviewer_leases_table(snapshot)}"
"<h3>Collision / lease history issues</h3>"
f"{_history_links(snapshot)}"
"<p><a href=\"/api/leases\">JSON API</a></p>"
f"{LEASE_PAGE_STYLES}"
)
return render_page(title="Leases", body_html=body)