Přidal jsem Health Monitoring do portálu:

Přehled má nové karty: Zdravé služby, Nezdravé služby, Nedostupné služby.
Přehled zobrazuje sekci Služby vyžadující pozornost pro unhealthy a unreachable.
Snapshot /portal/ws/operations teď obsahuje health data a stránka je aktualizuje přes existující realtime vrstvu.
Seznam služeb zobrazuje aktuální health status a poslední kontrolu, včetně řazení Podle zdraví.
Detail služby má sekce Zdraví služby a Historie kontrol s posledními 50 záznamy.
Přidal jsem audit event service.health.view.
Browser title teď používá formát CSBot Services Portal - ..., takže detail služby odpovídá požadavku.
Nepoužil jsem ORM, vše je přes SQLite dotazy.
This commit is contained in:
JiriUhlir
2026-05-29 11:57:57 +02:00
parent 56f0b632de
commit ed194093e6
6 changed files with 303 additions and 3 deletions
+102
View File
@@ -0,0 +1,102 @@
from app.db.database import get_connection
HEALTH_STATUSES = ("healthy", "unhealthy", "unreachable")
def get_latest_service_health():
con = get_connection()
try:
rows = con.execute(
"""
SELECT
h.service_id,
COALESCE(a.name, h.service_id) AS service_name,
h.status,
h.http_status,
h.response_time_ms,
h.error_text,
h.checked_at
FROM service_health h
LEFT JOIN apps a ON a.id = h.service_id
INNER JOIN (
SELECT service_id, MAX(id) AS max_id
FROM service_health
GROUP BY service_id
) latest ON latest.max_id = h.id
"""
).fetchall()
except Exception:
rows = []
con.close()
return {row["service_id"]: dict(row) for row in rows}
def get_service_health(service_id: str):
con = get_connection()
try:
row = con.execute(
"""
SELECT
h.service_id,
COALESCE(a.name, h.service_id) AS service_name,
h.status,
h.http_status,
h.response_time_ms,
h.error_text,
h.checked_at
FROM service_health h
LEFT JOIN apps a ON a.id = h.service_id
WHERE h.service_id = ?
ORDER BY h.id DESC
LIMIT 1
""",
(service_id,),
).fetchone()
except Exception:
row = None
con.close()
return dict(row) if row else None
def get_service_health_history(service_id: str, limit: int = 50):
con = get_connection()
try:
rows = con.execute(
"""
SELECT id, service_id, status, http_status, response_time_ms, error_text, checked_at
FROM service_health
WHERE service_id = ?
ORDER BY id DESC
LIMIT ?
""",
(service_id, limit),
).fetchall()
except Exception:
rows = []
con.close()
return [dict(row) for row in rows]
def get_health_summary():
latest = get_latest_service_health()
summary = {"healthy": 0, "unhealthy": 0, "unreachable": 0}
for item in latest.values():
status = item.get("status")
if status in summary:
summary[status] += 1
return summary
def get_problem_service_health():
latest = get_latest_service_health()
return [
item for item in latest.values()
if item.get("status") in {"unhealthy", "unreachable"}
]
+9
View File
@@ -1,4 +1,5 @@
from app.db.database import get_connection
from app.db.health import get_health_summary, get_problem_service_health
from app.db.migrations import run_migrations
from app.db.workers import is_worker_online
@@ -197,6 +198,8 @@ def get_operations_snapshot():
WHERE LOWER(COALESCE(status, '')) IN ('failed', 'error', 'disabled', 'deleted', 'archived')
"""
).fetchone()["count"]
health_summary = get_health_summary()
health_problems = get_problem_service_health()
jobs_summary = con.execute(
"""
@@ -296,6 +299,12 @@ def get_operations_snapshot():
"active": active_applications,
"problematic": problematic_applications,
},
"health": {
"healthy": health_summary["healthy"],
"unhealthy": health_summary["unhealthy"],
"unreachable": health_summary["unreachable"],
"problems": health_problems,
},
"workers": {
"online": workers_online,
"offline": max(0, total_workers - workers_online),