Add unified operations realtime dashboard
- add /portal/ws/operations WebSocket snapshot endpoint
- add shared operations-live.js with reconnect and operations:snapshot events
- update Operations, Jobs, and Workers pages to use realtime snapshots
- keep job detail live logs on dedicated /ws/jobs/{job_id}/logs endpoint
- add Jobs and Workers pagination/filtering
- remove old page auto-refresh behavior from jobs/deployments
- normalize main navigation labels
- move New App CTA from menu to Apps page
- add backup download action for admins
- add Workers pages and operations dashboard data helpers
This commit is contained in:
+54
-9
@@ -248,6 +248,7 @@ def get_next_queued_job():
|
||||
|
||||
def get_jobs(
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
status: str | None = None,
|
||||
job_type: str | None = None,
|
||||
target: str | None = None,
|
||||
@@ -282,32 +283,76 @@ def get_jobs(
|
||||
END,
|
||||
id DESC
|
||||
LIMIT ?
|
||||
OFFSET ?
|
||||
""",
|
||||
(*params, limit),
|
||||
(*params, limit, offset),
|
||||
).fetchall()
|
||||
|
||||
con.close()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def count_jobs(
|
||||
status: str | None = None,
|
||||
job_type: str | None = None,
|
||||
target: str | None = None,
|
||||
):
|
||||
run_migrations()
|
||||
con = get_connection()
|
||||
|
||||
filters = []
|
||||
params: list[Any] = []
|
||||
if status:
|
||||
filters.append("status = ?")
|
||||
params.append(status)
|
||||
if job_type:
|
||||
filters.append("type = ?")
|
||||
params.append(job_type)
|
||||
if target:
|
||||
filters.append("(target_type LIKE ? OR target_id LIKE ?)")
|
||||
target_pattern = f"%{target}%"
|
||||
params.extend([target_pattern, target_pattern])
|
||||
|
||||
where_sql = f"WHERE {' AND '.join(filters)}" if filters else ""
|
||||
row = con.execute(
|
||||
f"""
|
||||
SELECT COUNT(*) AS count
|
||||
FROM jobs
|
||||
{where_sql}
|
||||
""",
|
||||
params,
|
||||
).fetchone()
|
||||
|
||||
con.close()
|
||||
return row["count"] if row else 0
|
||||
|
||||
|
||||
def get_job_stats():
|
||||
run_migrations()
|
||||
con = get_connection()
|
||||
|
||||
rows = con.execute(
|
||||
"""
|
||||
SELECT status, COUNT(*) AS count
|
||||
SELECT
|
||||
SUM(CASE WHEN status = 'queued' THEN 1 ELSE 0 END) AS queued,
|
||||
SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) AS running,
|
||||
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
||||
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS success,
|
||||
SUM(CASE WHEN status = 'failed' AND created_at >= datetime('now', '-24 hours') THEN 1 ELSE 0 END) AS failed_24h,
|
||||
SUM(CASE WHEN status = 'success' AND created_at >= datetime('now', '-24 hours') THEN 1 ELSE 0 END) AS success_24h
|
||||
FROM jobs
|
||||
WHERE status IN ('queued', 'running', 'failed', 'success')
|
||||
GROUP BY status
|
||||
"""
|
||||
).fetchall()
|
||||
).fetchone()
|
||||
|
||||
con.close()
|
||||
stats = {"queued": 0, "running": 0, "failed": 0, "success": 0}
|
||||
for row in rows:
|
||||
stats[row["status"]] = row["count"]
|
||||
return stats
|
||||
return dict(rows) if rows else {
|
||||
"queued": 0,
|
||||
"running": 0,
|
||||
"failed": 0,
|
||||
"success": 0,
|
||||
"failed_24h": 0,
|
||||
"success_24h": 0,
|
||||
}
|
||||
|
||||
|
||||
def get_job(job_id: int):
|
||||
|
||||
Reference in New Issue
Block a user