"""System information endpoints — network, subsystems, dashboard binding.""" from __future__ import annotations import asyncio import os import platform import socket import sys from typing import Any from fastapi import APIRouter from Project.Sanad.config import ( BASE_DIR, DASHBOARD_HOST, DASHBOARD_INTERFACE, DASHBOARD_PORT, DDS_NETWORK_INTERFACE, list_network_interfaces, ) from Project.Sanad.core.logger import get_logger log = get_logger("system_route") router = APIRouter() def _safe_status(component, name: str) -> dict[str, Any]: if component is None: return {"available": False} try: if hasattr(component, "status") and callable(component.status): s = component.status() if not isinstance(s, dict): s = {"raw": str(s)} s.setdefault("available", True) return s return {"available": True} except Exception as exc: log.warning("status() failed for %s: %s", name, exc) return {"available": True, "error": str(exc)} @router.get("/info") async def system_info(): """One-shot system snapshot for the dashboard system panel.""" def _do(): # Subsystems try: from Project.Sanad.main import SUBSYSTEMS except Exception: SUBSYSTEMS = {} subsystem_list = [] for name in sorted(SUBSYSTEMS): comp = SUBSYSTEMS[name] entry = { "name": name, "connected": comp is not None, } if comp is not None and hasattr(comp, "status") and callable(comp.status): try: s = comp.status() if isinstance(s, dict): entry["status"] = s except Exception as exc: entry["status_error"] = str(exc) subsystem_list.append(entry) connected_count = sum(1 for s in subsystem_list if s["connected"]) # Audio device current selection (best-effort) audio_info = {} try: from Project.Sanad.voice import audio_devices as ad audio_info = { "pactl_available": ad.pactl_available(), "current": ad.current_selection(), "detected_profile_ids": [ d["profile"]["id"] for d in ad.detect_plugged_profiles() ] if ad.pactl_available() else [], } except Exception as exc: audio_info = {"error": str(exc)} # Network interfaces try: interfaces = list_network_interfaces() except Exception: interfaces = [] # Determine the URL the dashboard is reachable at bound_host = DASHBOARD_HOST if bound_host == "0.0.0.0": # Try to find the wlan0 IP for display purposes up_ifaces = [i for i in interfaces if i["is_up"] and i["ip"] and not i["ip"].startswith("127.")] display_host = up_ifaces[0]["ip"] if up_ifaces else bound_host else: display_host = bound_host return { "host": { "hostname": socket.gethostname(), "platform": platform.platform(), "python": sys.version.split()[0], "executable": sys.executable, "base_dir": str(BASE_DIR), "pid": os.getpid(), }, "dashboard": { "interface": DASHBOARD_INTERFACE, "bound_host": bound_host, "display_host": display_host, "port": DASHBOARD_PORT, "url": f"http://{display_host}:{DASHBOARD_PORT}", }, "dds": { "interface": DDS_NETWORK_INTERFACE, }, "network": { "interfaces": interfaces, }, "subsystems": { "total": len(subsystem_list), "connected": connected_count, "disconnected": len(subsystem_list) - connected_count, "list": subsystem_list, }, "audio": audio_info, } return await asyncio.to_thread(_do)