Update core scanner, parser, SSH client, and UI

This commit is contained in:
2026-05-05 20:07:24 +00:00
parent 40d4679a59
commit d32ca80a22
6 changed files with 313 additions and 79 deletions
+129 -11
View File
@@ -1,7 +1,15 @@
# parser.py - Parse 'show lldp neighbors' output from FS switches
# parser.py - Parse LLDP output from FS, HP/Aruba ProCurve, and Dell switches
import re
def normalize_mac(mac_str):
"""Normalize any MAC format to XX-XX-XX-XX-XX-XX uppercase (e.g. 649d99-aa5100 → 64-9D-99-AA-51-00)."""
clean = re.sub(r'[:\-\.\s]', '', mac_str).upper()
if len(clean) != 12:
return mac_str.strip()
return '-'.join(clean[i:i+2] for i in range(0, 12, 2))
def shorten_interface(iface):
"""GigabitEthernet 1/9 -> Gi1/9, TenGigabitEthernet 1/1 -> Te1/1 etc."""
replacements = [
@@ -67,6 +75,101 @@ def parse_lldp_neighbors(raw_output, local_chassis_id, local_hostname, local_mgm
return neighbors
def parse_aruba_procurve_local(raw_output):
"""Parse 'show lldp info local-device' from HP/Aruba switch into a dict."""
chassis_id = ''
system_name = ''
system_desc = ''
mgmt_ip = ''
for line in raw_output.splitlines():
m = re.search(r'Chassis Id\s*:\s*(.+)', line, re.IGNORECASE)
if m:
chassis_id = normalize_mac(m.group(1).strip())
m = re.search(r'System Name\s*:\s*(.+)', line, re.IGNORECASE)
if m:
system_name = m.group(1).strip()
m = re.search(r'System Description\s*:\s*(.+)', line, re.IGNORECASE)
if m:
system_desc = m.group(1).strip()
m = re.search(r'Address\s*:\s*([\d\.]+)', line, re.IGNORECASE)
if m and '.' in m.group(1):
mgmt_ip = m.group(1).strip()
return {'chassis_id': chassis_id, 'system_name': system_name,
'system_desc': system_desc, 'mgmt_ip': mgmt_ip}
def parse_aruba_procurve_neighbors(raw_output):
"""
Parse 'show lldp info remote-device' tabular output from HP/Aruba switch.
Columns: LocalPort | ChassisId PortId PortDescr SysName
"""
neighbors = []
in_data = False
for line in raw_output.splitlines():
if re.match(r'\s*-+\s*\+', line):
in_data = True
continue
if not in_data or '|' not in line or not line.strip():
continue
left, right = line.split('|', 1)
local_port = left.strip()
parts = right.split()
if len(parts) < 4:
continue
chassis_id = normalize_mac(parts[0])
port_id = parts[1]
sys_name = parts[3]
neighbors.append({
'local_port': local_port,
'chassis_id': chassis_id,
'port_id': port_id,
'port_desc': '',
'system_name': sys_name,
'system_desc': '',
'mgmt_ip': '',
'capabilities': '',
'remote_port': port_id,
})
return neighbors
def parse_neighbor_description(system_desc):
"""
Extract (model, firmware) from an LLDP system description string.
Handles Dell OS10, HP ProCurve, and Aruba ArubaOS-Switch descriptions.
Returns ('', '') if unrecognized.
"""
if not system_desc:
return '', ''
# Dell OS10: "...System Type: S4112F-ON...OS Version: 10.5.4.7..."
if 'Dell' in system_desc or 'OS10' in system_desc:
model_m = re.search(r'System Type\s*:\s*([\w\-]+)', system_desc)
fw_m = re.search(r'OS Version\s*:\s*([\d\s\.]+)', system_desc)
model = f"Dell {model_m.group(1)}" if model_m else 'Dell'
firmware = re.sub(r'\s+', '', fw_m.group(1)).strip('.') if fw_m else ''
return model, firmware
# HP ProCurve / Aruba ArubaOS-Switch:
# "Aruba JL258A 2930F-8G-PoE+-2SFP+ Switch, revision WC.16.10.0012, ROM ..."
# "HP J9576A 3800-24G-PoE+-2SFP+ Switch, revision K.16.02.0019, ROM ..."
if re.match(r'(?:Aruba|HP)\s', system_desc, re.IGNORECASE):
model_m = re.match(r'((?:Aruba|HP)\s+\S+\s+[\w\-\+]+)', system_desc, re.IGNORECASE)
fw_m = re.search(r'revision\s+(\S+?)(?:[,\s]|$)', system_desc)
model = model_m.group(1) if model_m else system_desc.split(',')[0]
firmware = fw_m.group(1) if fw_m else ''
return model, firmware
return '', ''
def parse_hostname_from_prompt(prompt_line):
"""Extract hostname from CLI prompt like 'ls-vhls-sw01#'"""
m = re.match(r'^([A-Za-z0-9_\-]+)[>#]', prompt_line.strip())
@@ -75,14 +178,29 @@ def parse_hostname_from_prompt(prompt_line):
def parse_mgmt_ip_from_interfaces(raw_output):
"""
Parse 'show ip interface brief' to find management VLAN IP.
Looks for Vlan interfaces with an IP assigned.
Returns first Vlan IP found (typically the management VLAN).
Parse 'show ip interface brief' to find management IP.
Handles 'Vlan100' and 'Vlan 100' (space-separated) formats.
Prefers Vlan interfaces; falls back to Management interfaces.
"""
lines = raw_output.splitlines()
for line in lines:
# Match lines like: Vlan100 192.168.1.10 YES ...
m = re.match(r'\s*(Vlan\S+)\s+([\d\.]+)\s+', line, re.IGNORECASE)
if m and not m.group(2).startswith('0.0.0.0'):
return m.group(2)
return None
IPV4 = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
fallback = None
for line in raw_output.splitlines():
line_stripped = line.strip()
if not line_stripped:
continue
# Match lines belonging to a Vlan or Management interface
iface_m = re.match(r'(Vlan|Management|Mgmt)\s*\S*', line_stripped, re.IGNORECASE)
if not iface_m:
continue
# Extract first valid IPv4 address anywhere on the line
ip_m = IPV4.search(line)
if not ip_m:
continue
ip = ip_m.group(1)
if ip.startswith('0.0.0.0') or ip.startswith('127.'):
continue
if re.match(r'Vlan', iface_m.group(1), re.IGNORECASE):
return ip
if not fallback:
fallback = ip
return fallback