Add delete button for runs in UI

- Add delete_html section to run detail page with delete button
- Add /ui/runs/{run_id}/discard HTMX endpoint
- Failed runs can always be deleted without restrictions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-08 01:53:26 +00:00
parent 6142c8bbab
commit 13b93ab17c

View File

@@ -437,6 +437,55 @@ async def discard_run(run_id: str, username: str = Depends(get_required_user)):
return {"discarded": True, "run_id": run_id}
@app.delete("/ui/runs/{run_id}/discard", response_class=HTMLResponse)
async def ui_discard_run(run_id: str, request: Request):
"""HTMX handler: discard a run."""
current_user = get_user_from_cookie(request)
if not current_user:
return '<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Login required</div>'
run = load_run(run_id)
if not run:
return '<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Run not found</div>'
# Check ownership
actor_id = f"@{current_user}@{L2_DOMAIN}"
if run.username not in (current_user, actor_id):
return '<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Access denied</div>'
# Failed runs can always be deleted
if run.status != "failed":
# Check if activity exists for this run
activity = cache_manager.get_activity(run_id)
if activity:
can_discard, reason = cache_manager.can_discard_activity(run_id)
if not can_discard:
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: {reason}</div>'
success, msg = cache_manager.discard_activity(run_id)
if not success:
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Failed to discard: {msg}</div>'
else:
# Legacy run - check L2 shared status
items_to_check = list(run.inputs or [])
if run.output_hash:
items_to_check.append(run.output_hash)
for content_hash in items_to_check:
if cache_manager.l2_checker.is_shared(content_hash):
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: item {content_hash[:16]}... is published to L2</div>'
# Remove from Redis
redis_client.delete(f"{RUNS_KEY_PREFIX}{run_id}")
return '''
<div class="bg-green-900/50 border border-green-700 text-green-300 px-4 py-3 rounded-lg mb-4">
Run deleted. <a href="/runs" class="underline">Back to runs</a>
</div>
'''
@app.get("/run/{run_id}")
async def run_detail(run_id: str, request: Request):
"""Run detail. HTML for browsers, JSON for APIs."""
@@ -590,6 +639,22 @@ async def run_detail(run_id: str, request: Request):
</div>
'''
# Delete section
delete_html = f'''
<div class="border-t border-dark-500 pt-6 mt-6">
<h2 class="text-lg font-semibold text-white mb-3">Delete Run</h2>
<p class="text-sm text-gray-400 mb-4">
{"This run failed and can be deleted." if run.status == "failed" else "Delete this run and its associated cache entries."}
</p>
<div id="delete-result"></div>
<button hx-delete="/ui/runs/{run.run_id}/discard" hx-target="#delete-result" hx-swap="innerHTML"
hx-confirm="Are you sure you want to delete this run? This cannot be undone."
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors">
Delete Run
</button>
</div>
'''
output_link = ""
if run.output_hash:
output_link = f'''<div class="bg-dark-600 rounded-lg p-4">
@@ -661,6 +726,7 @@ async def run_detail(run_id: str, request: Request):
</div>
{publish_html}
{delete_html}
</div>
'''