diff --git a/server.py b/server.py
index 1319d4f..7bb8cb0 100644
--- a/server.py
+++ b/server.py
@@ -1291,6 +1291,42 @@ async def ui_logout():
return response
+@app.post("/ui/publish-run/{run_id}", response_class=HTMLResponse)
+async def ui_publish_run(run_id: str, request: Request, output_name: str = Form(...)):
+ """Publish a run to L2 from the web UI."""
+ token = request.cookies.get("auth_token")
+ if not token:
+ return HTMLResponse('
Not logged in
')
+
+ # Call L2 to publish the run
+ try:
+ resp = http_requests.post(
+ f"{L2_SERVER}/registry/record-run",
+ json={"run_id": run_id, "output_name": output_name},
+ headers={"Authorization": f"Bearer {token}"},
+ timeout=10
+ )
+ if resp.status_code == 400:
+ error = resp.json().get("detail", "Bad request")
+ return HTMLResponse(f'Error: {error}
')
+ resp.raise_for_status()
+ result = resp.json()
+ return HTMLResponse(f'''
+
+ Published to L2 as {result["asset"]["name"]}
+
+ ''')
+ except http_requests.exceptions.HTTPError as e:
+ error_detail = ""
+ try:
+ error_detail = e.response.json().get("detail", str(e))
+ except Exception:
+ error_detail = str(e)
+ return HTMLResponse(f'Error: {error_detail}
')
+ except Exception as e:
+ return HTMLResponse(f'Error: {e}
')
+
+
@app.get("/ui/runs", response_class=HTMLResponse)
async def ui_runs(request: Request):
"""HTMX partial: list of runs."""
@@ -1557,6 +1593,7 @@ async def ui_detail_page(run_id: str, request: Request):
{run.recipe} - {run.run_id[:8]} | Art DAG L1
+
@@ -1706,6 +1743,27 @@ async def ui_detail_page(run_id: str, request: Request):
html += f'''
Raw JSON
{provenance_json}
+ '''
+
+ # Add publish section for completed runs
+ if run.status == "completed" and run.output_hash:
+ html += f'''
+ Publish to L2
+ Register this transformation output on the L2 ActivityPub server.
+
+
+ '''
+
+ html += '''