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:
JiriUhlir
2026-05-29 11:17:17 +02:00
parent 0dcf9b7b05
commit d145161e4e
9 changed files with 519 additions and 67 deletions
+54 -9
View File
@@ -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):