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:
@@ -3,7 +3,7 @@ import sqlite3
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
from config import DB_PATH
|
||||
from config import DB_PATH, SSH_USERNAME, SSH_PASSWORD
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,8 +30,8 @@ def init_db():
|
||||
last_seen TEXT
|
||||
)
|
||||
""")
|
||||
# Migrate existing DBs that predate firmware/vendor columns
|
||||
for col, default in [('firmware', ''), ('vendor', '')]:
|
||||
# Migrate existing DBs that predate firmware/vendor/serial columns
|
||||
for col, default in [('firmware', ''), ('vendor', ''), ('serial', '')]:
|
||||
try:
|
||||
c.execute(f"ALTER TABLE switches ADD COLUMN {col} TEXT DEFAULT '{default}'")
|
||||
except Exception:
|
||||
@@ -68,6 +68,10 @@ def init_db():
|
||||
|
||||
c.execute("INSERT OR IGNORE INTO settings VALUES ('autoscan_enabled', 'false')")
|
||||
c.execute("INSERT OR IGNORE INTO settings VALUES ('autoscan_interval', '60')")
|
||||
# Seed SSH credentials from config.py on first run only — the settings
|
||||
# table is the source of truth from then on (editable via /api/credentials).
|
||||
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('ssh_username', ?)", (SSH_USERNAME,))
|
||||
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('ssh_password', ?)", (SSH_PASSWORD,))
|
||||
|
||||
c.execute(
|
||||
"CREATE TABLE IF NOT EXISTS node_positions "
|
||||
@@ -78,11 +82,11 @@ def init_db():
|
||||
conn.close()
|
||||
|
||||
|
||||
def upsert_switch(chassis_id, hostname, mgmt_ip, description, firmware='', vendor=''):
|
||||
def upsert_switch(chassis_id, hostname, mgmt_ip, description, firmware='', vendor='', serial=''):
|
||||
conn = get_conn()
|
||||
conn.execute("""
|
||||
INSERT INTO switches (chassis_id, hostname, mgmt_ip, description, firmware, vendor, last_seen)
|
||||
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
||||
INSERT INTO switches (chassis_id, hostname, mgmt_ip, description, firmware, vendor, serial, last_seen)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
||||
ON CONFLICT(chassis_id) DO UPDATE SET
|
||||
hostname = excluded.hostname,
|
||||
mgmt_ip = CASE WHEN excluded.mgmt_ip != '' AND excluded.mgmt_ip LIKE '%.%.%.%'
|
||||
@@ -90,8 +94,9 @@ def upsert_switch(chassis_id, hostname, mgmt_ip, description, firmware='', vendo
|
||||
description = CASE WHEN excluded.description != '' THEN excluded.description ELSE description END,
|
||||
firmware = CASE WHEN excluded.firmware != '' THEN excluded.firmware ELSE firmware END,
|
||||
vendor = CASE WHEN excluded.vendor != '' THEN excluded.vendor ELSE vendor END,
|
||||
serial = CASE WHEN excluded.serial != '' THEN excluded.serial ELSE serial END,
|
||||
last_seen = excluded.last_seen
|
||||
""", (chassis_id, hostname, mgmt_ip, description, firmware, vendor))
|
||||
""", (chassis_id, hostname, mgmt_ip, description, firmware, vendor, serial))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -103,10 +108,23 @@ def _port_num(port):
|
||||
|
||||
|
||||
def upsert_link(chassis_a, port_a, chassis_b, port_b):
|
||||
"""
|
||||
chassis_a/port_a is always the scanned switch's own self-reported port —
|
||||
trustworthy. chassis_b/port_b is that switch's perception of its neighbor's
|
||||
port, which for LLDP is often the neighbor's raw wire-transmitted Port ID
|
||||
(an ifIndex-style number) rather than the neighbor's own friendly port name.
|
||||
When the neighbor later gets scanned directly, its own upsert_link call
|
||||
supplies the trustworthy label for what is now the "chassis_b" side here —
|
||||
so on a match we overwrite that side with the newly-known self-report
|
||||
instead of leaving the untrusted guess in place.
|
||||
"""
|
||||
self_chassis, self_port = chassis_a, port_a
|
||||
|
||||
# Normalize order so A→B and B→A are treated as the same link
|
||||
if chassis_a > chassis_b:
|
||||
chassis_a, chassis_b = chassis_b, chassis_a
|
||||
port_a, port_b = port_b, port_a
|
||||
|
||||
conn = get_conn()
|
||||
# Exact match first
|
||||
existing = conn.execute("""
|
||||
@@ -116,13 +134,28 @@ def upsert_link(chassis_a, port_a, chassis_b, port_b):
|
||||
# Fuzzy match: same chassis pair, same trailing port numbers
|
||||
# handles 'Gi1/9' vs '9' reported by different vendors for the same cable
|
||||
rows = conn.execute("""
|
||||
SELECT port_a, port_b FROM links WHERE chassis_a=? AND chassis_b=?
|
||||
SELECT id, port_a, port_b FROM links WHERE chassis_a=? AND chassis_b=?
|
||||
""", (chassis_a, chassis_b)).fetchall()
|
||||
for r in rows:
|
||||
if _port_num(port_a) == _port_num(r[0]) and _port_num(port_b) == _port_num(r[1]):
|
||||
existing = True
|
||||
if _port_num(port_a) == _port_num(r[1]) and _port_num(port_b) == _port_num(r[2]):
|
||||
existing = r
|
||||
break
|
||||
if not existing:
|
||||
# A single physical port can only carry one live link, so an exact
|
||||
# match on either side's port name (e.g. Aruba's front-panel label
|
||||
# 'E5' vs the ifIndex-style '101' its neighbors report for it) means
|
||||
# this is the same cable even though the trailing digits don't align.
|
||||
if port_a == r[1] or port_b == r[2]:
|
||||
existing = r
|
||||
break
|
||||
|
||||
if existing:
|
||||
# Overwrite whichever stored side corresponds to the self-reporting
|
||||
# switch with its trusted value — it may currently hold the neighbor's
|
||||
# untrusted guess from when this link was first inserted.
|
||||
col = 'port_a' if self_chassis == chassis_a else 'port_b'
|
||||
conn.execute(f"UPDATE links SET {col}=? WHERE id=?", (self_port, existing['id']))
|
||||
conn.commit()
|
||||
else:
|
||||
conn.execute("""
|
||||
INSERT OR IGNORE INTO links (chassis_a, port_a, chassis_b, port_b)
|
||||
VALUES (?, ?, ?, ?)
|
||||
|
||||
Reference in New Issue
Block a user