134 lines
4.2 KiB
Python
134 lines
4.2 KiB
Python
"""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)
|