"""System health endpoints (ROADMAP #85, tranche 1). Handlers déplacés depuis :mod:`backend.main` sans changement de comportement : mêmes chemins (``/api/health``, ``/api/health/detailed``), même ``response_model`` (:class:`backend.schemas.HealthResponse`), même dépendance admin. Seule différence : la version est lue via :func:`backend.version.get_version` au lieu de ``app.version`` (valeur identique, figée au démarrage depuis le fichier ``VERSION``). Note : ``uptime_seconds`` reprend l'expression d'origine (``'_SERVER_START_TIME' in globals()``), qui vaut toujours 0 — le global n'est défini nulle part dans ``backend.main`` (voir ``backend.admin`` qui possède son propre compteur). Ce comportement est préservé tel quel ; le corriger fera l'objet d'une tranche ultérieure avec test dédié. """ from fastapi import APIRouter, Depends from backend.auth.middleware import require_admin from backend.indexer import index from backend.schemas import HealthResponse from backend.version import get_git_commit, get_git_describe, get_version router = APIRouter(tags=["System"]) @router.get("/api/health", response_model=HealthResponse) async def api_health(): """Health check endpoint for Docker and monitoring. Returns: Application status, version, vault count and total file count. """ total_files = sum(len(v["files"]) for v in index.values()) total_tokens = sum(len(v.get("files", [])) * 1000 for v in index.values()) # rough approx import time from backend.indexer import _last_full_index_ts # `_SERVER_START_TIME` n'existe dans aucun module (comportement d'origine # préservé : uptime toujours 0 — voir docstring du module). uptime = int(time.time() - _SERVER_START_TIME) if '_SERVER_START_TIME' in globals() else 0 # noqa: F821 return { "status": "ok", "version": get_version(), "vaults": len(index), "total_files": total_files, "total_tokens": total_tokens, "last_full_index_ts": _last_full_index_ts, "uptime_seconds": uptime, "git_describe": get_git_describe(), "git_commit": get_git_commit(), } @router.get("/api/health/detailed", response_model=HealthResponse) async def api_health_detailed(current_user=Depends(require_admin)): """Detailed health check — admin only. Returns enriched metrics including memory, disk, SSE connections, and backup stats. """ import psutil from backend.admin import _count_active_sessions, _get_disk_stats from backend.indexer import _last_full_index_ts, index total_files = sum(len(v["files"]) for v in index.values()) total_tokens = sum(len(v.get("files", [])) * 1000 for v in index.values()) import time uptime = int(time.time() - _SERVER_START_TIME) if '_SERVER_START_TIME' in globals() else 0 # noqa: F821 — voir ci-dessus # Memory vm = psutil.virtual_memory() mem_used_mb = round(vm.used / (1024 ** 2), 1) mem_total_mb = round(vm.total / (1024 ** 2), 1) mem_pct = round(vm.percent, 1) # CPU cpu_pct = psutil.cpu_percent(interval=None) # Disk disk_used_gb, disk_total_gb = _get_disk_stats() disk_free_gb = round(disk_total_gb - disk_used_gb, 2) disk_pct = round((disk_used_gb / disk_total_gb * 100) if disk_total_gb > 0 else 0, 1) # SSE connections (approximation) active_sessions = _count_active_sessions() # Backups from backend.admin import _scan_backups backup_rows = _scan_backups() total_backups = len(backup_rows) total_backup_size_mb = round(sum(r["size"] for r in backup_rows) / (1024 ** 2), 2) oldest_backup_age_days = 0.0 if backup_rows: now_ts = int(time.time()) oldest_ts = min(r["timestamp"] for r in backup_rows) oldest_backup_age_days = round((now_ts - oldest_ts) / 86400, 2) # Index details index_detail = {} for name, data in index.items(): index_detail[name] = { "file_count": len(data["files"]), "tag_count": len(data.get("tags", [])), "token_count_approx": len(data.get("files", [])) * 1000, } return { "status": "ok", "version": get_version(), "vaults": len(index), "total_files": total_files, "total_tokens": total_tokens, "last_full_index_ts": _last_full_index_ts, "uptime_seconds": uptime, "git_describe": get_git_describe(), "git_commit": get_git_commit(), # Enriched fields "memory": { "used_mb": mem_used_mb, "total_mb": mem_total_mb, "percent": mem_pct, }, "cpu": { "percent": cpu_pct, }, "disk": { "used_gb": disk_used_gb, "total_gb": disk_total_gb, "free_gb": disk_free_gb, "percent": disk_pct, }, "connections": { "active_sse": active_sessions, }, "backups": { "total_count": total_backups, "total_size_mb": total_backup_size_mb, "oldest_age_days": oldest_backup_age_days, }, "index": index_detail, }