feat(ops): run artefacts in API/UI + llm-connect host access design
ACTIVITY-WP-0027: document durable ClusterIP host access for llm-connect; join activity_runs to ops_runs; surface Forgejo artefact links on /ops/ui automation detail and new run detail pages.
This commit is contained in:
parent
9965af2096
commit
7711bf9c70
6 changed files with 604 additions and 55 deletions
|
|
@ -25,6 +25,7 @@ from activity_core.ops_console import (
|
|||
is_side_effect_definition,
|
||||
ops_definition_detail,
|
||||
ops_inventory,
|
||||
ops_run_detail,
|
||||
ops_runs,
|
||||
ops_status,
|
||||
recent_audits,
|
||||
|
|
@ -168,6 +169,17 @@ async def list_runs(
|
|||
return await ops_runs(_db(), definition_id, since=since_dt, limit=limit)
|
||||
|
||||
|
||||
@router.get("/automations/{definition_id}/runs/{run_id}")
|
||||
async def get_run(
|
||||
definition_id: uuid.UUID,
|
||||
run_id: uuid.UUID,
|
||||
) -> dict[str, Any]:
|
||||
detail = await ops_run_detail(_db(), definition_id, run_id)
|
||||
if not detail:
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
return detail
|
||||
|
||||
|
||||
@router.post("/automations/{definition_id}/trigger")
|
||||
async def trigger_automation(
|
||||
definition_id: uuid.UUID,
|
||||
|
|
@ -539,12 +551,31 @@ async def ui_detail(definition_id: uuid.UUID) -> HTMLResponse:
|
|||
confirm = "true" if side else "false"
|
||||
run_rows = []
|
||||
for r in runs.get("runs") or []:
|
||||
rid = str(r.get("run_id") or "")
|
||||
arts = r.get("artifacts") or []
|
||||
if arts:
|
||||
art_html = " ".join(
|
||||
f'<a href="{html.escape(str(a.get("url")))}" target="_blank" rel="noopener">'
|
||||
f'{html.escape(str(a.get("label") or a.get("kind") or "artifact"))}</a>'
|
||||
for a in arts
|
||||
if a.get("url") and str(a.get("url")).startswith("https://")
|
||||
) or '<span class="muted">none</span>'
|
||||
else:
|
||||
ops_states = [o.get("state") for o in (r.get("ops_runs") or [])]
|
||||
if any(s == "open" for s in ops_states):
|
||||
art_html = '<span class="muted">pending</span>'
|
||||
elif any(s in ("failed", "expired") for s in ops_states):
|
||||
art_html = '<span class="muted">failed</span>'
|
||||
else:
|
||||
art_html = '<span class="muted">none</span>'
|
||||
run_rows.append(
|
||||
f"<tr><td><code>{html.escape(str(r.get('run_id')))}</code></td>"
|
||||
f"<td>{html.escape(str(r.get('fired_at')))}</td>"
|
||||
f"<td>{html.escape(str(r.get('scheduled_for')))}</td>"
|
||||
f"<tr><td><a href='/ops/ui/automations/{html.escape(aid)}/runs/{html.escape(rid)}'>"
|
||||
f"<code>{html.escape(rid[:13])}…</code></a></td>"
|
||||
f"<td>{html.escape(str(r.get('fired_at') or ''))}</td>"
|
||||
f"<td>{html.escape(str(r.get('scheduled_for') or ''))}</td>"
|
||||
f"<td>{html.escape(str(r.get('tasks_spawned')))}</td>"
|
||||
f"<td>{html.escape(str(len((r.get('evidence') or {}).get('task_spawns') or [])))}</td></tr>"
|
||||
f"<td>{html.escape(str(len((r.get('evidence') or {}).get('task_spawns') or [])))}</td>"
|
||||
f"<td>{art_html}</td></tr>"
|
||||
)
|
||||
detail_json = html.escape(json.dumps(detail, indent=2, default=str))
|
||||
body = f"""
|
||||
|
|
@ -563,8 +594,56 @@ async def ui_detail(definition_id: uuid.UUID) -> HTMLResponse:
|
|||
<pre>{detail_json}</pre>
|
||||
<h2>Recent runs</h2>
|
||||
<table>
|
||||
<thead><tr><th>run_id</th><th>fired_at</th><th>scheduled_for</th><th>tasks</th><th>spawn evidence</th></tr></thead>
|
||||
<tbody>{''.join(run_rows) or '<tr><td colspan="5">No runs</td></tr>'}</tbody>
|
||||
<thead><tr><th>run_id</th><th>fired_at</th><th>scheduled_for</th><th>tasks</th><th>spawns</th><th>Artifacts</th></tr></thead>
|
||||
<tbody>{''.join(run_rows) or '<tr><td colspan="6">No runs</td></tr>'}</tbody>
|
||||
</table>
|
||||
"""
|
||||
return _page(name, body)
|
||||
|
||||
|
||||
@router.get("/ui/automations/{definition_id}/runs/{run_id}", response_class=HTMLResponse)
|
||||
async def ui_run_detail(definition_id: uuid.UUID, run_id: uuid.UUID) -> HTMLResponse:
|
||||
run = await ops_run_detail(_db(), definition_id, run_id)
|
||||
if not run:
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
aid = str(definition_id)
|
||||
rid = str(run_id)
|
||||
arts = run.get("artifacts") or []
|
||||
art_lis = []
|
||||
for a in arts:
|
||||
url = a.get("url") or ""
|
||||
label = html.escape(str(a.get("label") or a.get("kind") or "artifact"))
|
||||
if isinstance(url, str) and url.startswith("https://"):
|
||||
art_lis.append(
|
||||
f'<li><a href="{html.escape(url)}" target="_blank" rel="noopener">{label}</a>'
|
||||
f' <span class="muted">({html.escape(str(a.get("kind") or ""))})</span></li>'
|
||||
)
|
||||
else:
|
||||
art_lis.append(f"<li>{label} <code>{html.escape(str(url))}</code></li>")
|
||||
ops_blocks = []
|
||||
for op in run.get("ops_runs") or []:
|
||||
ops_blocks.append(
|
||||
"<div class='card'>"
|
||||
f"<p><strong>ops_run</strong> <code>{html.escape(str(op.get('id')))}</code> "
|
||||
f"state=<strong>{html.escape(str(op.get('state')))}</strong></p>"
|
||||
f"<p>{html.escape(str(op.get('title') or ''))}</p>"
|
||||
f"<pre>{html.escape(json.dumps(op.get('result') or {{}}, indent=2, default=str))}</pre>"
|
||||
"</div>"
|
||||
)
|
||||
body = f"""
|
||||
<p class="muted"><a href="/ops/ui/automations/{html.escape(aid)}">← back to automation</a></p>
|
||||
<h1>Run <code>{html.escape(rid)}</code></h1>
|
||||
<div class="card">
|
||||
<p>fired_at: <code>{html.escape(str(run.get('fired_at') or ''))}</code></p>
|
||||
<p>scheduled_for: <code>{html.escape(str(run.get('scheduled_for') or ''))}</code></p>
|
||||
<p>tasks_spawned: <strong>{html.escape(str(run.get('tasks_spawned')))}</strong>
|
||||
version: <code>{html.escape(str(run.get('version_used') or ''))}</code></p>
|
||||
</div>
|
||||
<h2>Artifacts</h2>
|
||||
<ul>{''.join(art_lis) or '<li class="muted">No deliverable links for this run</li>'}</ul>
|
||||
<h2>Ops runs</h2>
|
||||
{''.join(ops_blocks) or '<p class="muted">No linked ops_runs</p>'}
|
||||
<h2>Evidence (compact)</h2>
|
||||
<pre>{html.escape(json.dumps(run.get('evidence') or {{}}, indent=2, default=str))}</pre>
|
||||
"""
|
||||
return _page(f"Run {rid[:8]}", body)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue