feat(desktop): make cron jobs the first-class sidebar entity
Redesign the cron surface around jobs (not run sessions), following
power-user patterns (GitHub Actions / Airflow / Dagu): master → detail → output.
Sidebar "Cron jobs" section:
- jobs with a state pip + live next-run countdown
- click toggles an inline run-history peek; a run opens its chat (active run highlighted)
- hover: trigger-now + manage (open the Cron page)
- capped at 50 with a "50+" badge
Cron page: de-nested from a collapse-in-row accordion to master/detail —
job list + the selected job's schedule, actions, and run history.
Backend: GET /api/cron/jobs/{id}/runs lists a job's run sessions.
Share STATE_DOT/jobState across both surfaces; drop dead code/keys.
This commit is contained in:
@@ -5650,6 +5650,53 @@ async def get_cron_job(job_id: str, profile: Optional[str] = None):
|
||||
return job
|
||||
|
||||
|
||||
@app.get("/api/cron/jobs/{job_id}/runs")
|
||||
async def list_cron_job_runs(job_id: str, profile: Optional[str] = None, limit: int = 20):
|
||||
"""Run sessions produced by a cron job, newest first.
|
||||
|
||||
Cron runs are stored as ordinary sessions whose id is
|
||||
``cron_{job_id}_{timestamp}`` (see cron/scheduler.run_job). A job's history
|
||||
is therefore every session whose id carries that prefix; ``source='cron'``
|
||||
narrows it and the id substring binds it to this job. Powers the run-history
|
||||
list under each job in the desktop cron detail. Same row shape as
|
||||
``/api/sessions`` so the frontend can reuse SessionInfo.
|
||||
"""
|
||||
selected = profile or _find_cron_job_profile(job_id)
|
||||
# job_id may be a human name; resolve to the canonical id used in run-session ids.
|
||||
canonical = job_id
|
||||
if selected:
|
||||
job = _call_cron_for_profile(selected, "get_job", job_id)
|
||||
if job and job.get("id"):
|
||||
canonical = str(job["id"])
|
||||
|
||||
try:
|
||||
limit_n = max(1, min(int(limit), 100))
|
||||
except (TypeError, ValueError):
|
||||
limit_n = 20
|
||||
|
||||
db = _open_session_db_for_profile(selected)
|
||||
try:
|
||||
runs = db.list_sessions_rich(
|
||||
source="cron",
|
||||
id_query=f"cron_{canonical}_",
|
||||
limit=limit_n,
|
||||
offset=0,
|
||||
order_by_last_active=True,
|
||||
)
|
||||
now = time.time()
|
||||
for s in runs:
|
||||
s["is_active"] = (
|
||||
s.get("ended_at") is None
|
||||
and (now - s.get("last_active", s.get("started_at", 0))) < 300
|
||||
)
|
||||
s["archived"] = bool(s.get("archived"))
|
||||
if selected:
|
||||
s["profile"] = selected
|
||||
return {"runs": runs, "limit": limit_n}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.post("/api/cron/jobs")
|
||||
async def create_cron_job(body: CronJobCreate, profile: str = "default"):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user