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:
@ -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
|
||||
|
||||
Reference in New Issue
Block a user