feat(3 U3): YunoHost-style PR comment (🌻 + level badge + summary card images, linked) updated in place per PR; text fallback; bridge tests + dashboard do_HEAD

This commit is contained in:
autonomic-bot
2026-05-31 07:46:00 +00:00
parent 656faa3d8e
commit 9a47aa28e3
3 changed files with 144 additions and 30 deletions

View File

@ -192,23 +192,16 @@ def serve_run_file(run_id, fname):
class Handler(BaseHTTPRequestHandler):
def _send(self, code, body, ctype="text/html; charset=utf-8"):
data = body.encode() if isinstance(body, str) else body
self.send_response(code)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def do_GET(self):
path = self.path.split("?")[0].rstrip("/") or "/"
def _route(self, path):
"""Resolve a request path to (code, body, content_type). Shared by GET and HEAD so they
never diverge. `body` is bytes/str for GET; HEAD sends only the status + headers."""
if path in ("/healthz", "/dashboard/healthz"):
return self._send(200, "ok", "text/plain")
return 200, "ok", "text/plain"
if path.startswith("/badge/") and path.endswith(".svg"):
recipe = path[len("/badge/") : -len(".svg")]
row = next((r for r in recipes_cached() if r["recipe"] == recipe), None)
status = row["status"] if row else "unknown"
return self._send(200, render_badge(recipe, status), "image/svg+xml")
return 200, render_badge(recipe, status), "image/svg+xml"
if path.startswith("/runs/"):
# /runs/<run_id>/<file> — stable URL for a run's results.json / summary.png / screenshot /
# badge (R3/R6). Whitelisted + traversal-guarded by serve_run_file.
@ -216,11 +209,32 @@ class Handler(BaseHTTPRequestHandler):
if len(parts) == 2:
got = serve_run_file(parts[0], parts[1])
if got is not None:
return self._send(200, got[1], got[0])
return self._send(404, "not found", "text/plain")
return 200, got[1], got[0]
return 404, "not found", "text/plain"
if path == "/":
return self._send(200, render_overview(recipes_cached()))
return self._send(404, "not found", "text/plain")
return 200, render_overview(recipes_cached()), "text/html; charset=utf-8"
return 404, "not found", "text/plain"
def _send(self, code, body, ctype="text/html; charset=utf-8", head_only=False):
data = body.encode() if isinstance(body, str) else body
self.send_response(code)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
if not head_only:
self.wfile.write(data)
def do_GET(self):
path = self.path.split("?")[0].rstrip("/") or "/"
code, body, ctype = self._route(path)
self._send(code, body, ctype)
def do_HEAD(self):
# Same routing as GET, headers only (no body) — enables cheap existence checks, e.g. the
# comment-bridge deciding image-vs-text fallback for the PR comment (U3).
path = self.path.split("?")[0].rstrip("/") or "/"
code, body, ctype = self._route(path)
self._send(code, body, ctype, head_only=True)
def log_message(self, *a):
pass