upsert_link now does a secondary fuzzy dedup by trailing port number so 'Gi1/9' and '9' are treated as the same port on the same chassis pair. Prevents duplicate edges when an Aruba and FS switch each report the same cable using different port name formats. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LLDP Network Mapper
SSH-based network topology mapper for FS switches (and IOS-like CLIs). Discovers switch topology via LLDP, stores in SQLite, and visualises with Cytoscape.js.
Quick Start
1. Configure
Copy config.py.example to config.py and fill in your values:
cp config.py.example config.py
nano config.py
NOCODB_URL = "http://your-nocodb-host:8080"
NOCODB_TOKEN = "your-api-token"
SSH_USERNAME = "admin"
SSH_PASSWORD = "your-ssh-password"
SSH_PORT = 22
SSH_TIMEOUT = 30
DEVICE_TYPE = "cisco_ios"
Switch IPs are pulled automatically from NocoDB — no manual list needed. See NocoDB Requirements below.
2. Build and run
docker compose up -d --build
3. Open the UI
Navigate to: http://your-host-ip:5000
Click Scan Now to start discovery.
What it does
- SSHs into each switch in parallel (up to 10 at once)
- Runs
show lldp neighborsandshow ip interface brief - Parses neighbors, hostnames, management IPs, chassis IDs
- Stores everything in SQLite (
data/network.db) - De-duplicates bidirectional links automatically
- Renders an interactive Cytoscape.js topology diagram
- Exports to CSV, Mermaid (.md), and Graphviz PNG
Outputs
All files written to data/exports/:
| File | Purpose |
|---|---|
topology.csv |
Switch links with hostnames and IPs |
topology.md |
Mermaid diagram (paste into any markdown viewer) |
topology.dot |
Graphviz source |
topology.png |
Rendered network diagram |
Auto-scan
Toggle auto-scan on/off from the UI. Set interval (15 min to 6 hours). State persists across container restarts.
NocoDB Requirements
Switch inventory is loaded from NocoDB automatically. The table must have:
| Column | Description |
|---|---|
IP |
Switch management IP — required |
Hostname |
Switch hostname |
Active |
Must be YES to be included in scans |
Dept |
Department code (e.g. ELEC, GW) — used for filtering |
Location |
Physical location label |
Model |
Switch model |
Manufacturer |
Switch manufacturer |
Asset Tag |
Asset tag |
The NocoDB base ID and table ID are hardcoded in nocodb_client.py — update them there if you point this at a different NocoDB instance.
Troubleshooting
No switches loaded: Check NOCODB_URL and NOCODB_TOKEN in config.py. NocoDB must return at least one row with Active=YES and a non-empty IP.
Auth errors: Check SSH_USERNAME / SSH_PASSWORD in config.py.
Timeout errors: Increase SSH_TIMEOUT in config.py (default: 30s).
Wrong device type: If your switch uses a non-IOS CLI, try changing DEVICE_TYPE to "linux" or "generic" in config.py.
No management IP found: The script looks for Vlan interfaces in show ip interface brief. If your switch uses a different command, edit parser.py → parse_mgmt_ip_from_interfaces().
Project Structure
lldp-mapper/
├── app.py # Flask API + scheduler
├── db.py # SQLite operations
├── parser.py # LLDP output parser
├── ssh_client.py # Netmiko SSH + parallel scan
├── scanner.py # Orchestrator
├── exports.py # CSV / Mermaid / Graphviz
├── config.py # Switch IPs + credentials (not committed)
├── config.py.example # Config template
├── index.html # Cytoscape.js frontend
├── data/ # SQLite DB + exports (created at runtime)
├── Dockerfile
├── docker-compose.yml
└── README.md