Add NetBox-derived field switch topology (draw.io)
- export_topology.py: dumps switch + patch-panel cable graph from NetBox (Django shell, read-only) - generate_drawio.py: renders topo_full.json as a draw.io diagram with natively-glued connectors (source/target by shape id, no hand-rolled glue formulas) - topo_full.json + Field-Switch-Topology.drawio: current snapshot Replaces the earlier hand-built .vsdx approach: plain-text format diffs meaningfully in git, no Visio license needed to view/edit, and edges glue natively instead of needing point-to-point formula hacks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NM3nivbufxEWiKKmatEtEe
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Run INSIDE the NetBox Django shell to export the field-switch topology
|
||||
(switches + every patch panel / splice module they physically pass through)
|
||||
as JSON, for generate_drawio.py to render.
|
||||
|
||||
Usage (from the NetBox host, e.g. /opt/netbox):
|
||||
|
||||
docker exec netbox-netbox-1 python3 /opt/netbox/netbox/manage.py shell \
|
||||
-c "$(cat topology/export_topology.py)" 2>/dev/null > topology/topo_full.json
|
||||
|
||||
The script only reads data -- it never modifies NetBox. If a switch's
|
||||
connected_endpoints comes back empty when it clearly shouldn't, that is a
|
||||
NetBox cable-path bug, not a bug in this script -- see the "gotchas" section
|
||||
of the repo README before assuming the physical link doesn't exist.
|
||||
"""
|
||||
from dcim.models import Device, Interface
|
||||
import json
|
||||
|
||||
switch_ids = set(
|
||||
Device.objects.filter(
|
||||
role__slug__in=["core-switch", "dist-switch", "field-switch"]
|
||||
).values_list("id", flat=True)
|
||||
)
|
||||
|
||||
ifaces = Interface.objects.filter(
|
||||
device_id__in=switch_ids, cable__isnull=False
|
||||
).select_related("device")
|
||||
|
||||
touched_devices = set(switch_ids)
|
||||
edges = set() # (device_id_a, device_id_b), deduplicated, any two adjacent devices on a physical chain
|
||||
|
||||
for i in ifaces:
|
||||
path = i._path
|
||||
if not path or not path.is_active:
|
||||
continue
|
||||
chain = []
|
||||
for hop in i.trace():
|
||||
for item in hop:
|
||||
for t in item:
|
||||
dev = getattr(t, "device", None)
|
||||
if dev:
|
||||
if not chain or chain[-1] != dev.id:
|
||||
chain.append(dev.id)
|
||||
touched_devices.add(dev.id)
|
||||
for a, b in zip(chain, chain[1:]):
|
||||
if a != b:
|
||||
edges.add(tuple(sorted((a, b))))
|
||||
|
||||
devs = Device.objects.filter(id__in=touched_devices).select_related(
|
||||
"site", "role", "device_type"
|
||||
)
|
||||
|
||||
out = {
|
||||
"devices": [
|
||||
{
|
||||
"id": d.id,
|
||||
"name": d.name,
|
||||
"role": d.role.slug,
|
||||
"role_name": d.role.name,
|
||||
"site": d.site.name if d.site else "",
|
||||
"status": str(d.status),
|
||||
"device_type": d.device_type.model,
|
||||
}
|
||||
for d in devs
|
||||
],
|
||||
"edges": [[a, b] for a, b in sorted(edges)],
|
||||
}
|
||||
print(json.dumps(out))
|
||||
Reference in New Issue
Block a user