diff --git a/services/openwrt.py b/services/openwrt.py index 506da35..cb7734e 100644 --- a/services/openwrt.py +++ b/services/openwrt.py @@ -22,10 +22,13 @@ def _format_load(load: list[Any] | None) -> str: values = [] for raw in load[:3]: try: - values.append(float(raw) / 65536.0) + values.append(float(raw)) except (TypeError, ValueError): values.append(0.0) - return " ".join(f"{val:.2f}" for val in values) + scale = 1.0 + if values and max(values) > 1000: + scale = 1 / 65536.0 + return " ".join(f"{val * scale:.2f}" for val in values) def _format_rate(rate: Any) -> str: @@ -62,17 +65,27 @@ def _extract_wifi_clients(wireless: dict[str, Any]) -> list[str]: if not isinstance(iface, dict): continue ifname = iface.get("ifname") or "wifi" - assoclist = iface.get("assoclist") or {} - if not isinstance(assoclist, dict): - continue - for mac, meta in assoclist.items(): - if not isinstance(meta, dict): - continue - signal = meta.get("signal") - rx = _format_rate((meta.get("rx") or {}).get("rate")) - tx = _format_rate((meta.get("tx") or {}).get("rate")) - sig = f"{signal}dBm" if isinstance(signal, (int, float)) else "?" - clients.append(f"{ifname} {mac} {sig} rx:{rx} tx:{tx}") + assoclist = iface.get("assoclist") + stations = iface.get("stations") + if isinstance(assoclist, dict): + for mac, meta in assoclist.items(): + if not isinstance(meta, dict): + continue + signal = meta.get("signal") + rx = _format_rate((meta.get("rx") or {}).get("rate")) + tx = _format_rate((meta.get("tx") or {}).get("rate")) + sig = f"{signal}dBm" if isinstance(signal, (int, float)) else "?" + clients.append(f"{ifname} {mac} {sig} rx:{rx} tx:{tx}") + elif isinstance(stations, list): + for meta in stations: + if not isinstance(meta, dict): + continue + mac = meta.get("mac") or "?" + signal = meta.get("signal") + rx = _format_rate((meta.get("rx") or {}).get("rate")) + tx = _format_rate((meta.get("tx") or {}).get("rate")) + sig = f"{signal}dBm" if isinstance(signal, (int, float)) else "?" + clients.append(f"{ifname} {mac} {sig} rx:{rx} tx:{tx}") return clients @@ -91,6 +104,36 @@ def _extract_leases(leases: dict[str, Any]) -> list[str]: return out +def _parse_proc_fallback(raw: str) -> tuple[int | None, list[float] | None]: + uptime = None + load = None + for line in raw.splitlines(): + parts = line.split() + if len(parts) >= 2 and uptime is None: + try: + uptime = int(float(parts[0])) + except ValueError: + uptime = None + if len(parts) >= 3 and load is None: + try: + load = [float(parts[0]), float(parts[1]), float(parts[2])] + except ValueError: + load = None + return uptime, load + + +def _parse_leases_fallback(raw: str) -> list[str]: + out = [] + for line in raw.splitlines(): + parts = line.strip().split() + if len(parts) < 4: + continue + _expiry, mac, ipaddr, host = parts[:4] + host = host if host != "*" else "unknown" + out.append(f"{ipaddr} {host} ({mac})") + return out + + async def get_openwrt_status(cfg: dict[str, Any]) -> str: ow_cfg = cfg.get("openwrt", {}) host = ow_cfg.get("host") @@ -109,6 +152,8 @@ async def get_openwrt_status(cfg: dict[str, Any]) -> str: "BatchMode=yes", "-o", f"ConnectTimeout={timeout_sec}", + "-o", + "LogLevel=ERROR", ] if not strict: ssh_cmd += ["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"] @@ -117,10 +162,12 @@ async def get_openwrt_status(cfg: dict[str, Any]) -> str: ssh_cmd += ["-p", str(port), f"{user}@{host}"] remote = ( - "ubus call system info; echo __SEP__;" + "if ubus call system info >/tmp/ow_sys.json 2>/dev/null; then " + "cat /tmp/ow_sys.json; else cat /proc/uptime; echo; cat /proc/loadavg; fi; echo __SEP__;" "ubus call network.interface.wan status; echo __SEP__;" "ubus call network.wireless status; echo __SEP__;" - "ubus call dhcp ipv4leases" + "if ubus call dhcp ipv4leases >/tmp/ow_leases.json 2>/dev/null; then " + "cat /tmp/ow_leases.json; else cat /tmp/dhcp.leases; fi" ) cmd = ssh_cmd + ["sh", "-c", remote] rc, out = await run_cmd(cmd, timeout=timeout_sec + 5) @@ -131,22 +178,40 @@ async def get_openwrt_status(cfg: dict[str, Any]) -> str: if len(parts) < 4: return "⚠️ OpenWrt response incomplete" + sys_info = None + wan_status = None + wireless = None + leases = None try: sys_info = json.loads(parts[0]) + except json.JSONDecodeError: + sys_info = None + try: wan_status = json.loads(parts[1]) + except json.JSONDecodeError: + wan_status = {} + try: wireless = json.loads(parts[2]) + except json.JSONDecodeError: + wireless = {} + try: leases = json.loads(parts[3]) except json.JSONDecodeError: - return "⚠️ OpenWrt response parse error" + leases = None - uptime = _format_uptime(sys_info.get("uptime") if isinstance(sys_info, dict) else None) - load = _format_load(sys_info.get("load") if isinstance(sys_info, dict) else None) + if isinstance(sys_info, dict): + uptime_raw = sys_info.get("uptime") + load_raw = sys_info.get("load") + else: + uptime_raw, load_raw = _parse_proc_fallback(parts[0]) + uptime = _format_uptime(uptime_raw) + load = _format_load(load_raw) wan_ip = _extract_wan_ip(wan_status) or "unknown" wan_up = wan_status.get("up") if isinstance(wan_status, dict) else None wan_state = "up" if wan_up else "down" wifi_clients = _extract_wifi_clients(wireless) - leases_list = _extract_leases(leases) + leases_list = _extract_leases(leases) if leases else _parse_leases_fallback(parts[3]) lines = [ "📡 OpenWrt",