Full pipeline flow through web interface with live updates

API:
  POST /api/tasks/{id}/run — sets task to in_progress immediately,
    launches subprocess with error handling and logging.
  GET /api/tasks/{id}/running — checks pipelines table for active run.
  Fixed --db flag position in subprocess command.

TaskDetail (live pipeline):
  - Run button starts pipeline, auto-starts 3s polling
  - Pipeline cards update in real-time as agent_logs appear
  - Pulsing blue dot on header while in_progress
  - Spinner on run button during execution
  - Auto-stops polling when status changes from in_progress
  - Cleanup on component unmount (no leaked timers)

ProjectView (run from list):
  - [>] button on each pending task row
  - Confirm dialog before starting
  - Pulsing blue dot for in_progress tasks
  - Click task row → /task/:id with live view

Dashboard (live statuses):
  - Pulsing blue dot next to active task count
  - Auto-poll every 5s when any project has active tasks
  - Stops polling when no active tasks

5 new API tests (running endpoint, run sets status, not found).
141 tests total, all passing. Frontend builds clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johnfrum1234 2026-03-15 15:29:05 +02:00
parent ab693d3c4d
commit db1729730f
5 changed files with 145 additions and 33 deletions

View file

@ -257,6 +257,24 @@ def reject_task(task_id: str, body: TaskReject):
return {"status": "pending", "reason": body.reason}
@app.get("/api/tasks/{task_id}/running")
def is_task_running(task_id: str):
"""Check if task has an active (running) pipeline."""
conn = get_conn()
t = models.get_task(conn, task_id)
if not t:
conn.close()
raise HTTPException(404, f"Task '{task_id}' not found")
row = conn.execute(
"SELECT id, status FROM pipelines WHERE task_id = ? ORDER BY created_at DESC LIMIT 1",
(task_id,),
).fetchone()
conn.close()
if row and row["status"] == "running":
return {"running": True, "pipeline_id": row["id"]}
return {"running": False}
@app.post("/api/tasks/{task_id}/run")
def run_task(task_id: str):
"""Launch pipeline for a task in background. Returns 202."""
@ -265,15 +283,22 @@ def run_task(task_id: str):
if not t:
conn.close()
raise HTTPException(404, f"Task '{task_id}' not found")
# Set task to in_progress immediately so UI updates
models.update_task(conn, task_id, status="in_progress")
conn.close()
# Launch kin run in background subprocess
kin_root = Path(__file__).parent.parent
subprocess.Popen(
[sys.executable, "-m", "cli.main", "run", task_id, "--db",
str(DB_PATH)],
cwd=str(kin_root),
stdout=subprocess.DEVNULL,
)
try:
proc = subprocess.Popen(
[sys.executable, "-m", "cli.main", "--db", str(DB_PATH),
"run", task_id],
cwd=str(kin_root),
stdout=subprocess.DEVNULL,
)
import logging
logging.getLogger("kin").info(f"Pipeline started for {task_id}, pid={proc.pid}")
except Exception as e:
raise HTTPException(500, f"Failed to start pipeline: {e}")
return JSONResponse({"status": "started", "task_id": task_id}, status_code=202)