Add incident exports, queue SLA, and OpenWrt diff utilities
This commit is contained in:
@@ -4,6 +4,7 @@ from collections import deque
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
from typing import Any
|
||||
from services import runtime_state
|
||||
|
||||
|
||||
def _get_path(cfg: dict[str, Any]) -> str:
|
||||
|
||||
@@ -460,3 +460,45 @@ async def get_openwrt_status(cfg: dict[str, Any], mode: str = "full") -> str:
|
||||
if mode == "leases":
|
||||
return "\n".join(header + lease_section)
|
||||
return "\n".join(header + wifi_section + lease_section)
|
||||
|
||||
|
||||
async def fetch_openwrt_leases(cfg: dict[str, Any]) -> list[str]:
|
||||
"""
|
||||
Fetch DHCP leases as list of strings "IP hostname (MAC)".
|
||||
"""
|
||||
ow_cfg = cfg.get("openwrt", {})
|
||||
host = ow_cfg.get("host")
|
||||
user = ow_cfg.get("user", "root")
|
||||
port = ow_cfg.get("port", 22)
|
||||
identity_file = ow_cfg.get("identity_file")
|
||||
timeout_sec = ow_cfg.get("timeout_sec", 8)
|
||||
strict = ow_cfg.get("strict_host_key_checking", True)
|
||||
|
||||
if not host:
|
||||
raise RuntimeError("OpenWrt host not configured")
|
||||
|
||||
ssh_cmd = [
|
||||
"ssh",
|
||||
"-o",
|
||||
"BatchMode=yes",
|
||||
"-o",
|
||||
f"ConnectTimeout={timeout_sec}",
|
||||
"-o",
|
||||
"LogLevel=ERROR",
|
||||
]
|
||||
if not strict:
|
||||
ssh_cmd += ["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"]
|
||||
if identity_file:
|
||||
ssh_cmd += ["-i", str(identity_file)]
|
||||
ssh_cmd += ["-p", str(port), f"{user}@{host}"]
|
||||
|
||||
remote = "ubus call luci-rpc getDHCPLeases '{\"family\":4}' 2>/dev/null || cat /tmp/dhcp.leases"
|
||||
rc, out = await run_cmd_full(ssh_cmd + ["sh", "-c", remote], timeout=timeout_sec + 10)
|
||||
if rc == 124:
|
||||
raise RuntimeError("timeout")
|
||||
if rc != 0:
|
||||
raise RuntimeError(out.strip() or f"ssh rc={rc}")
|
||||
leases = _safe_json_load(out)
|
||||
if leases:
|
||||
return _extract_leases(leases)
|
||||
return _parse_leases_fallback(out)
|
||||
|
||||
@@ -187,3 +187,11 @@ def format_history(limit: int = 20) -> str:
|
||||
f"(wait {item['wait_sec']}s, run {item['runtime_sec']}s)"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def get_history_raw() -> list[dict[str, Any]]:
|
||||
return list(_history)
|
||||
|
||||
|
||||
def get_stats() -> dict[str, Any]:
|
||||
return dict(_stats)
|
||||
|
||||
@@ -6,6 +6,14 @@ from typing import Any
|
||||
from services.health import health
|
||||
from services.runner import run_cmd_full
|
||||
from services.incidents import log_incident
|
||||
from services import runtime_state
|
||||
|
||||
|
||||
def _save_history(entry: dict[str, Any]) -> None:
|
||||
hist = runtime_state.get("selftest_history", [])
|
||||
hist = hist[:50] if isinstance(hist, list) else []
|
||||
hist.insert(0, entry)
|
||||
runtime_state.set_state("selftest_history", hist[:20])
|
||||
|
||||
|
||||
async def run_selftest(cfg: dict[str, Any], docker_map: dict[str, str]) -> tuple[str, bool]:
|
||||
@@ -40,7 +48,19 @@ async def run_selftest(cfg: dict[str, Any], docker_map: dict[str, str]) -> tuple
|
||||
lines.append(f"🔴 Restic snapshots error: {out.strip() or rc}")
|
||||
ok = False
|
||||
|
||||
return "\n".join(lines), ok
|
||||
result_text = "\n".join(lines)
|
||||
try:
|
||||
_save_history(
|
||||
{
|
||||
"ts": datetime.now().isoformat(),
|
||||
"ok": ok,
|
||||
"summary": result_text.splitlines()[1] if len(lines) > 1 else "",
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result_text, ok
|
||||
|
||||
|
||||
async def schedule_selftest(cfg: dict[str, Any], bot, admin_ids: list[int], docker_map: dict[str, str]):
|
||||
|
||||
Reference in New Issue
Block a user