Přidáno:

nová stránka /portal/incidents s title CSBot Services Portal - Incidenty
menu Provoz -> Incidenty
aktivní incidenty a historie posledních 100 incidentů
barevné stavy: open červeně, resolved zeleně
karta Aktivní incidenty a seznam Poslední incidenty na přehledu
realtime napojení přes existující operations-live.js a snapshot
sekce Incidenty služby v detailu služby, posledních 20 incidentů
DB helpery pro service_incidents
migrace/indexy pro lokální prostředí, kde tabulka ještě neexistuje
This commit is contained in:
JiriUhlir
2026-06-01 13:38:06 +02:00
parent 994171be1a
commit 6aa5e69ee4
9 changed files with 374 additions and 9 deletions
+93
View File
@@ -0,0 +1,93 @@
from app.db.database import get_connection
from app.db.migrations import run_migrations
def _incident_select() -> str:
return """
SELECT
i.id,
i.service_id,
COALESCE(a.name, i.service_id) AS service_name,
i.status,
i.started_at,
i.ended_at,
CASE
WHEN i.duration_seconds IS NOT NULL THEN i.duration_seconds
WHEN LOWER(COALESCE(i.status, '')) = 'open' THEN CAST(strftime('%s', 'now') - strftime('%s', i.started_at) AS INTEGER)
ELSE NULL
END AS duration_seconds,
i.title,
i.description
FROM service_incidents i
LEFT JOIN apps a ON a.id = i.service_id
"""
def get_open_incidents(limit: int | None = None):
run_migrations()
con = get_connection()
sql = f"""
{_incident_select()}
WHERE LOWER(COALESCE(i.status, '')) = 'open'
ORDER BY i.started_at DESC, i.id DESC
"""
params = ()
if limit is not None:
sql += " LIMIT ?"
params = (limit,)
rows = con.execute(sql, params).fetchall()
con.close()
return [dict(row) for row in rows]
def get_recent_incidents(limit: int = 100):
run_migrations()
con = get_connection()
rows = con.execute(
f"""
{_incident_select()}
ORDER BY i.started_at DESC, i.id DESC
LIMIT ?
""",
(limit,),
).fetchall()
con.close()
return [dict(row) for row in rows]
def get_service_incidents(service_id: str, limit: int = 20):
run_migrations()
con = get_connection()
rows = con.execute(
f"""
{_incident_select()}
WHERE i.service_id = ?
ORDER BY i.started_at DESC, i.id DESC
LIMIT ?
""",
(service_id, limit),
).fetchall()
con.close()
return [dict(row) for row in rows]
def count_open_incidents() -> int:
run_migrations()
con = get_connection()
row = con.execute(
"""
SELECT COUNT(*) AS count
FROM service_incidents
WHERE LOWER(COALESCE(status, '')) = 'open'
"""
).fetchone()
con.close()
return row["count"] if row else 0
+16
View File
@@ -110,6 +110,20 @@ def run_migrations():
)
"""
)
con.execute(
"""
CREATE TABLE IF NOT EXISTS service_incidents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
service_id TEXT NOT NULL,
status TEXT NOT NULL,
started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
ended_at TEXT,
duration_seconds INTEGER,
title TEXT,
description TEXT
)
"""
)
for statement in (
"ALTER TABLE app_templates ADD COLUMN runtime TEXT",
"ALTER TABLE app_templates ADD COLUMN description TEXT",
@@ -123,6 +137,8 @@ def run_migrations():
except Exception:
pass
con.execute("CREATE INDEX IF NOT EXISTS idx_app_variables_app_id ON app_variables(app_id)")
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_status_started ON service_incidents(status, started_at)")
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_service_started ON service_incidents(service_id, started_at)")
con.commit()
con.close()
+9
View File
@@ -1,5 +1,6 @@
from app.db.database import get_connection
from app.db.health import get_health_summary, get_problem_service_health
from app.db.incidents import count_open_incidents, get_open_incidents, get_recent_incidents
from app.db.migrations import run_migrations
from app.db.workers import is_worker_online
@@ -200,6 +201,9 @@ def get_operations_snapshot():
).fetchone()["count"]
health_summary = get_health_summary()
health_problems = get_problem_service_health()
open_incidents_count = count_open_incidents()
open_incidents = get_open_incidents(limit=10)
recent_incidents = get_recent_incidents(limit=10)
jobs_summary = con.execute(
"""
@@ -305,6 +309,11 @@ def get_operations_snapshot():
"unreachable": health_summary["unreachable"],
"problems": health_problems,
},
"incidents": {
"open": open_incidents_count,
"active": open_incidents,
"recent": recent_incidents,
},
"workers": {
"online": workers_online,
"offline": max(0, total_workers - workers_online),