Add SEC/AMI scans, scan abort, connection test, runtime credentials, serial tracking

Also fixes several LLDP parsing edge cases (blank-field regex bleed,
endpoint-device filtering, HP ProCurve chassis ID format, Aruba port
naming), corrects link dedup to prefer self-reported port names over
neighbor guesses, and repoints NetBox sync at the new instance where
devices are hand-curated rather than auto-created.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bp1HutYUh9fmTFgwB4qBLz
This commit is contained in:
dcstephenson
2026-07-24 10:40:55 -05:00
parent 3a93c6d6ed
commit 5ffadfae17
7 changed files with 583 additions and 118 deletions
+34 -1
View File
@@ -1,5 +1,6 @@
# scanner.py - Orchestrates the full scan pipeline
import logging
import threading
from nocodb_client import get_switches
from ssh_client import scan_all_switches
from db import (
@@ -24,8 +25,19 @@ scan_state = {
"log_lines": [],
"last_scan": None,
"dept_filter": None, # None = all, "ELEC" or "GW" = dept-only
"aborted": False,
}
_abort_event = threading.Event()
def request_abort():
"""Signal the in-progress scan to stop after currently in-flight switches finish."""
if scan_state["running"]:
_abort_event.set()
return True
return False
def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
"""
@@ -38,6 +50,8 @@ def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
logger.warning("Scan already running, skipping.")
return
_abort_event.clear()
# Fetch switch list from NocoDB (or fallback)
switches = get_switches(dept=dept)
@@ -59,6 +73,7 @@ def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
"errors": [],
"log_lines": [],
"dept_filter": dept,
"aborted": False,
})
scan_id = log_scan_start()
@@ -89,6 +104,7 @@ def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
description=result.get("description", ""),
firmware=result.get("firmware", ""),
vendor=result.get("vendor", ""),
serial=result.get("serial", ""),
)
for neighbor in result["neighbors"]:
@@ -119,7 +135,16 @@ def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
"text": f"{ip}{error_short}",
})
scan_all_switches(switches, progress_callback=on_progress, max_workers=workers, login_delay=login_delay)
scan_all_switches(switches, progress_callback=on_progress, max_workers=workers,
login_delay=login_delay, abort_event=_abort_event)
if _abort_event.is_set():
scan_state["aborted"] = True
from datetime import datetime
scan_state["log_lines"].append({
"ts": datetime.now().strftime("%H:%M:%S"), "ok": False,
"text": "⚠ Scan aborted by user",
})
try:
merge_duplicate_switches()
@@ -141,3 +166,11 @@ def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
scan_state["dept_filter"] = None
logger.info(f"Scan complete. OK: {scan_state['ok']}, Failed: {scan_state['fail']}")
def test_single_switch(ip: str, manufacturer: str = "", login_delay: int = 1) -> dict:
"""Test SSH connectivity/LLDP query against a single switch, independent of the main scan.
Does not touch scan_state or the database — just returns the raw connect_and_query result."""
entry = {"ip": ip, "manufacturer": manufacturer}
results = scan_all_switches([entry], max_workers=1, login_delay=login_delay)
return results[0] if results else {"success": False, "ip": ip, "error": "No result"}