97 lines
2.9 KiB
Python
97 lines
2.9 KiB
Python
"""Statuts du NAS via le moniteur maison `nas_monitor` (`GET /api/status`).
|
|
|
|
Expose : occupation des disques de données (`/mnt/*`), santé des conteneurs Docker,
|
|
et l'état du port forwardé entre gluetun (VPN) et qBittorrent.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
import httpx
|
|
|
|
from config import config
|
|
|
|
|
|
@dataclass
|
|
class NasDisk:
|
|
label: str
|
|
percent: float
|
|
free_gb: float
|
|
|
|
@property
|
|
def free_human(self) -> str:
|
|
if self.free_gb >= 1024:
|
|
return f"{self.free_gb / 1024:.1f}".replace(".", ",") + " To"
|
|
return f"{self.free_gb:.0f} Go"
|
|
|
|
|
|
@dataclass
|
|
class NasStatus:
|
|
ok: bool
|
|
error: str | None = None
|
|
disks: list[NasDisk] = field(default_factory=list)
|
|
docker_running: int = 0
|
|
docker_total: int = 0
|
|
docker_unhealthy: int = 0
|
|
vpn_port: int | None = None
|
|
vpn_ok: bool = False
|
|
|
|
|
|
def _disk_label(mount: str) -> str:
|
|
"""`/mnt/disk1` -> `Disque 1`, sinon le dernier segment capitalisé."""
|
|
base = mount.rstrip("/").split("/")[-1]
|
|
if base.startswith("disk"):
|
|
return "Disque " + base[len("disk"):]
|
|
return base.capitalize() or mount
|
|
|
|
|
|
def _as_int(value: object) -> int | None:
|
|
try:
|
|
return int(value) # type: ignore[arg-type]
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
async def fetch_status() -> NasStatus:
|
|
if not config.nas_url:
|
|
return NasStatus(ok=False, error="NAS désactivé")
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=15) as client:
|
|
resp = await client.get(config.nas_url)
|
|
if resp.status_code != 200:
|
|
return NasStatus(ok=False, error=f"HTTP {resp.status_code}")
|
|
data = resp.json()
|
|
except httpx.HTTPError:
|
|
return NasStatus(ok=False, error="injoignable")
|
|
|
|
metrics = (data.get("host") or {}).get("metrics") or {}
|
|
disks: list[NasDisk] = []
|
|
for disk in metrics.get("disks") or []:
|
|
mount = str(disk.get("mount", ""))
|
|
if not mount.startswith("/mnt"): # on ne garde que les disques de données
|
|
continue
|
|
disks.append(NasDisk(
|
|
label=_disk_label(mount),
|
|
percent=float(disk.get("percent", 0) or 0),
|
|
free_gb=float(disk.get("free_gb", 0) or 0),
|
|
))
|
|
|
|
summary = (data.get("docker") or {}).get("summary") or {}
|
|
|
|
# Port VPN : OK quand le port forwardé par gluetun == port d'écoute qBittorrent.
|
|
qb = data.get("qbittorrent") or {}
|
|
listen = _as_int((qb.get("prefs") or {}).get("listen_port"))
|
|
fwd = _as_int(qb.get("forwarded_port_file"))
|
|
vpn_ok = listen is not None and fwd is not None and listen == fwd
|
|
|
|
return NasStatus(
|
|
ok=True,
|
|
disks=disks,
|
|
docker_running=int(summary.get("running", 0) or 0),
|
|
docker_total=int(summary.get("total", 0) or 0),
|
|
docker_unhealthy=int(summary.get("unhealthy", 0) or 0),
|
|
vpn_port=fwd if fwd is not None else listen,
|
|
vpn_ok=vpn_ok,
|
|
)
|