diff --git a/app/main.py b/app/main.py index a5af512..b8ba21e 100644 --- a/app/main.py +++ b/app/main.py @@ -263,6 +263,21 @@ async def api_reboot(request: Request, db: Session = Depends(get_db)): return {"status": "ok", "mac": mac} +@app.get("/api/ap-clients") +async def api_ap_clients(request: Request, mac: str = "", site_key: str = ""): + user = get_current_user(request) + if not user: + raise HTTPException(status_code=401, detail="Not authenticated") + if not mac or not site_key: + raise HTTPException(status_code=400, detail="mac and site_key required") + try: + clients = await omada_client.get_ap_clients(mac, site_key) + return {"clients": clients} + except Exception as exc: + logger.error("Failed to fetch clients for AP %s: %s", mac, exc) + raise HTTPException(status_code=502, detail=str(exc)) + + @app.post("/api/reboot-bulk") async def api_reboot_bulk(request: Request, db: Session = Depends(get_db)): user = get_current_user(request) diff --git a/app/omada.py b/app/omada.py index 05ea806..03df688 100644 --- a/app/omada.py +++ b/app/omada.py @@ -188,6 +188,27 @@ class OmadaClient: return int(device.get("uptimeLong", 0)) raise RuntimeError(f"AP {mac} not found in site {site_key}") + async def get_ap_clients(self, ap_mac: str, site_key: str) -> list[dict]: + """Fetch wireless clients currently connected to a specific AP.""" + await self._ensure_ready() + norm_mac = ap_mac.upper().replace("-", ":") + try: + data = await self._request_with_retry( + "GET", + f"{OMADA_BASE_URL}/{self._omadac_id}/api/v2/sites/{site_key}/clients", + params={"page": 1, "pageSize": 200, "filters.active": "true", "filters.apMac": ap_mac}, + ) + except RuntimeError: + # Older firmware may not support filters.apMac — fall back and filter client-side + data = await self._request_with_retry( + "GET", + f"{OMADA_BASE_URL}/{self._omadac_id}/api/v2/sites/{site_key}/clients", + params={"page": 1, "pageSize": 200, "filters.active": "true"}, + ) + result = data.get("result", {}) + clients = result.get("data", []) if isinstance(result, dict) else (result if isinstance(result, list) else []) + return [c for c in clients if c.get("apMac", "").upper().replace("-", ":") == norm_mac] + async def reboot_ap(self, mac: str, site_key: str, min_uptime: int = 300) -> dict: await self._ensure_ready() uptime = await self._get_ap_uptime(mac, site_key) diff --git a/app/templates/index.html b/app/templates/index.html index e448d42..1f4f068 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -93,7 +93,16 @@ title="{% if not online %}AP is offline{% else %}Uptime too low — minimum 5 minutes required{% endif %}" {% endif %} /> - {{ name }} + + + {{ name }} + + + + + {{ site_name }} {{ ip or '—' }} {{ mac or '—' }} @@ -148,6 +157,25 @@ + + +