diff --git a/apps/dashboard/backend/app/clients/backrest_client.py b/apps/dashboard/backend/app/clients/backrest_client.py new file mode 100644 index 0000000..1a65e48 --- /dev/null +++ b/apps/dashboard/backend/app/clients/backrest_client.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import logging +from datetime import datetime, timezone + +from app.clients.base import BaseHTTPClient +from app.config import Settings +from app.models.sources import BackrestSnapshot + + +logger = logging.getLogger(__name__) + + +class BackrestClient(BaseHTTPClient): + def __init__(self, settings: Settings) -> None: + super().__init__(settings, "backrest", settings.backrest_base_url) + + async def fetch_status(self) -> BackrestSnapshot: + snapshot = BackrestSnapshot() + if not self.base_url: + logger.info("backrest skipped: base URL missing") + return snapshot + + try: + data = await self._request_json("GET", "/v1/config") + except Exception as exc: + logger.warning("backrest fetch_status /v1/config failed: %s", exc) + return snapshot + + if not isinstance(data, dict): + return snapshot + + repos = data.get("repos") or [] + repo_count = len(repos) if isinstance(repos, list) else 0 + + # Fetch operation log for recent backup status + last_backup_age_hours: float | None = None + error_count = 0 + last_backup_status = "unknown" + + try: + ops_data = await self._request_json( + "POST", "/v1/operations", + json={"last_n": 20}, + ) + ops = ops_data.get("operations") or [] if isinstance(ops_data, dict) else [] + backup_ops = [ + op for op in ops + if isinstance(op, dict) and op.get("op", {}).get("backupOp") is not None + ] + error_ops = [op for op in backup_ops if op.get("status") == "STATUS_ERROR"] + error_count = len(error_ops) + + if backup_ops: + latest = backup_ops[0] + ts = latest.get("unixTimeEndMs") + if ts: + ended = datetime.fromtimestamp(int(ts) / 1000, tz=timezone.utc) + now = datetime.now(timezone.utc) + last_backup_age_hours = round((now - ended).total_seconds() / 3600, 1) + status_str = latest.get("status", "") + last_backup_status = "ok" if status_str == "STATUS_SUCCESS" else "error" if status_str == "STATUS_ERROR" else "unknown" + + except Exception as exc: + logger.warning("backrest fetch_status /v1/operations failed: %s", exc) + + return BackrestSnapshot( + source_status="online", + repo_count=repo_count, + last_backup_age_hours=last_backup_age_hours, + last_backup_status=last_backup_status, + error_count=error_count, + )