import logging from fastapi import APIRouter, Response from models.schemas import ( DriveHealthSummary, EnclosureHealth, EnclosureWithDrives, HostDrive, Overview, SlotWithDrive, ) from services import store from services.health import compute_health_status logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/overview", tags=["overview"]) @router.get("", response_model=Overview) async def get_overview(response: Response): """Aggregate view of all enclosures, slots, and drive health. Pure read from the store — the poller is the only thing that touched hardware to produce this data. """ inventory = await store.get_inventory() pool_map = await store.get_zfs_map() enc_results: list[EnclosureWithDrives] = [] total_drives = 0 warnings = 0 errors = 0 all_healthy = True for enc in inventory.get("enclosures", []): slots_out: list[SlotWithDrive] = [] for s in enc.get("slots", []): dev = s.get("device") drive_summary = None if dev: total_drives += 1 sd = await store.get_smart(dev) if sd: status = compute_health_status(sd) if status == "error": errors += 1 all_healthy = False elif status == "warning": warnings += 1 zinfo = pool_map.get(dev, {}) drive_summary = DriveHealthSummary( device=sd.get("device", dev), model=sd.get("model"), serial=sd.get("serial"), wwn=sd.get("wwn"), firmware=sd.get("firmware"), capacity_bytes=sd.get("capacity_bytes"), smart_healthy=sd.get("smart_healthy"), smart_supported=sd.get("smart_supported", True), temperature_c=sd.get("temperature_c"), power_on_hours=sd.get("power_on_hours"), reallocated_sectors=sd.get("reallocated_sectors"), pending_sectors=sd.get("pending_sectors"), uncorrectable_errors=sd.get("uncorrectable_errors"), zfs_pool=zinfo.get("pool"), zfs_vdev=zinfo.get("vdev"), zfs_state=zinfo.get("state"), health_status=status, ) elif s.get("populated"): total_drives += 1 slots_out.append(SlotWithDrive( slot=s["slot"], populated=s["populated"], device=dev, drive=drive_summary, )) ses = await store.get_ses(enc["id"]) enc_health = None if ses: enc_health = EnclosureHealth(**ses) if enc_health.overall_status == "CRITICAL": errors += 1 all_healthy = False elif enc_health.overall_status == "WARNING": warnings += 1 enc_results.append(EnclosureWithDrives( id=enc["id"], sg_device=enc.get("sg_device"), vendor=enc.get("vendor", ""), model=enc.get("model", ""), revision=enc.get("revision", ""), total_slots=enc.get("total_slots", 0), populated_slots=enc.get("populated_slots", 0), slots=slots_out, health=enc_health, )) # Host (non-enclosure) drives — fully precomputed by the poller. host_drives_out: list[HostDrive] = [] for hd in await store.get_host_drives(): total_drives += 1 hs = hd.get("health_status", "healthy") if hs == "error": errors += 1 all_healthy = False elif hs == "warning": warnings += 1 for pd in hd.get("physical_drives", []): total_drives += 1 pd_hs = pd.get("health_status", "healthy") if pd_hs == "error": errors += 1 all_healthy = False elif pd_hs == "warning": warnings += 1 host_drives_out.append(HostDrive(**hd)) # Surface poller freshness so stale data is visible. meta = await store.get_meta() if meta and meta.get("last_sweep_ts"): response.headers["X-Poll-Age"] = str(int(store.now() - meta["last_sweep_ts"])) return Overview( healthy=all_healthy and errors == 0, drive_count=total_drives, warning_count=warnings, error_count=errors, enclosures=enc_results, host_drives=host_drives_out, )