# scanner.py - Orchestrates the full scan pipeline import logging from nocodb_client import get_switch_ips from ssh_client import scan_all_switches from db import ( upsert_switch, upsert_link, clear_links, log_scan_start, log_scan_finish, merge_duplicate_switches ) from exports import run_all_exports logger = logging.getLogger(__name__) # Global scan state (shared with Flask via import) scan_state = { "running": False, "done": 0, "total": 0, "current_ip": None, "current_hostname": None, "ok": 0, "fail": 0, "errors": [], "last_scan": None, "dept_filter": None, # None = all, "ELEC" or "GW" = dept-only } def run_scan(dept: str = None): """ Full scan: fetch switch IPs from NocoDB → SSH all → parse → store → export. dept: None = all active switches, "ELEC" or "GW" = dept-filtered. """ global scan_state if scan_state["running"]: logger.warning("Scan already running, skipping.") return # Fetch switch list from NocoDB (or fallback) switches = get_switch_ips(dept=dept) if not switches: logger.error("NocoDB returned no switches — aborting scan.") scan_state["running"] = False return logger.error("No switches returned from NocoDB, aborting scan.") return scan_state.update({ "running": True, "done": 0, "total": len(switches), "current_ip": None, "current_hostname": None, "ok": 0, "fail": 0, "errors": [], "dept_filter": dept, }) scan_id = log_scan_start() clear_links() # Fresh start for links each scan def on_progress(done, total, ip, result): scan_state["done"] = done scan_state["total"] = total scan_state["current_ip"] = ip if result["success"]: scan_state["ok"] += 1 scan_state["current_hostname"] = result.get("hostname", ip) upsert_switch( chassis_id=result["chassis_id"], hostname=result["hostname"], mgmt_ip=result["mgmt_ip"], description=result.get("description", ""), ) for neighbor in result["neighbors"]: if neighbor.get("chassis_id") and neighbor.get("system_name"): upsert_switch( chassis_id=neighbor["chassis_id"], hostname=neighbor["system_name"], mgmt_ip=neighbor.get("mgmt_ip", ""), description=neighbor.get("system_desc", ""), ) upsert_link( chassis_a=result["chassis_id"], port_a=neighbor["local_port"], chassis_b=neighbor["chassis_id"], port_b=neighbor["remote_port"], ) else: scan_state["fail"] += 1 scan_state["errors"].append({ "ip": ip, "error": result.get("error", "Unknown error") }) scan_all_switches(switches, progress_callback=on_progress) try: merge_duplicate_switches() except Exception as e: logger.error(f"Merge error: {e}") try: run_all_exports() except Exception as e: logger.error(f"Export error: {e}") log_scan_finish(scan_id, scan_state["ok"], scan_state["fail"]) from datetime import datetime scan_state["last_scan"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") scan_state["running"] = False scan_state["current_ip"] = None scan_state["current_hostname"] = None scan_state["dept_filter"] = None logger.info(f"Scan complete. OK: {scan_state['ok']}, Failed: {scan_state['fail']}")