949fa016b1
scan_state now accumulates log_lines per switch result. The status bar is replaced with a dark terminal panel showing a summary header [done/total ✓ok ✗fail | now: hostname] and a scrolling per-switch log with green/red colouring and timestamps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
142 lines
4.7 KiB
Python
142 lines
4.7 KiB
Python
# 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 parser import parse_neighbor_description
|
|
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": [],
|
|
"log_lines": [],
|
|
"last_scan": None,
|
|
"dept_filter": None, # None = all, "ELEC" or "GW" = dept-only
|
|
}
|
|
|
|
|
|
def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
|
|
"""
|
|
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": [],
|
|
"log_lines": [],
|
|
"dept_filter": dept,
|
|
})
|
|
|
|
scan_id = log_scan_start()
|
|
clear_links() # Fresh start for links each scan
|
|
|
|
def on_progress(done, total, ip, result):
|
|
from datetime import datetime
|
|
ts = datetime.now().strftime("%H:%M:%S")
|
|
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)
|
|
vendor = result.get("vendor", "")
|
|
n = len(result.get("neighbors", []))
|
|
scan_state["log_lines"].append({
|
|
"ts": ts, "ok": True,
|
|
"text": f"✓ {result['hostname']} ({ip}) [{vendor}] — {n} neighbor{'s' if n != 1 else ''}",
|
|
})
|
|
|
|
upsert_switch(
|
|
chassis_id=result["chassis_id"],
|
|
hostname=result["hostname"],
|
|
mgmt_ip=result["mgmt_ip"],
|
|
description=result.get("description", ""),
|
|
firmware=result.get("firmware", ""),
|
|
vendor=result.get("vendor", ""),
|
|
)
|
|
|
|
for neighbor in result["neighbors"]:
|
|
if neighbor.get("chassis_id") and neighbor.get("system_name"):
|
|
nbr_model, nbr_firmware = parse_neighbor_description(
|
|
neighbor.get("system_desc", "")
|
|
)
|
|
upsert_switch(
|
|
chassis_id=neighbor["chassis_id"],
|
|
hostname=neighbor["system_name"],
|
|
mgmt_ip=neighbor.get("mgmt_ip", ""),
|
|
description=nbr_model, # empty for FS neighbors; direct scan fills it in
|
|
firmware=nbr_firmware,
|
|
)
|
|
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
|
|
error = result.get("error", "Unknown error")
|
|
scan_state["errors"].append({"ip": ip, "error": error})
|
|
scan_state["log_lines"].append({
|
|
"ts": ts, "ok": False,
|
|
"text": f"✗ {ip} — {error}",
|
|
})
|
|
|
|
scan_all_switches(switches, progress_callback=on_progress, max_workers=workers, login_delay=login_delay)
|
|
|
|
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']}")
|