diff --git a/README.md b/README.md index df43597..1338d30 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ artifact, regenerated from NetBox and committed so history shows what the | file | what it is | | --- | --- | -| `export_topology.py` | Run inside the NetBox Django shell. Walks every field/distribution switch's cable path (through patch panels and splice modules) and dumps the resulting device graph as JSON. Read-only. | -| `topo_full.json` | Latest exported snapshot: devices (id, name, role, site, status) + edges (physical links, one per cable). | +| `export_topology.py` | Run inside the NetBox Django shell. Walks every field/distribution switch's cable path (through patch panels and splice modules), then pulls every real `Cable` among the devices it found so each edge carries the exact port name on both ends. Read-only. | +| `topo_full.json` | Latest exported snapshot: devices (id, name, role, site, status) + edges (`{a, a_port, b, b_port}`, one per cable). | | `generate_drawio.py` | Pure-Python renderer: `topo_full.json` → `Field-Switch-Topology.drawio`. No NetBox access needed. | | `Field-Switch-Topology.drawio` | The diagram. Open at [app.diagrams.net](https://app.diagrams.net) (or the desktop app, or the VS Code draw.io extension) -- free, no license needed. | +| `render_png.js` + `render_host.html` | Renders the `.drawio` through a real draw.io instance (embed.diagrams.net, driven headlessly with Puppeteer) to produce `Field-Switch-Topology.png` -- an actual render, not an approximation. Needs `npm install puppeteer` and network access. | +| `Field-Switch-Topology.png` | Static preview of the diagram, for anyone who wants a quick look without opening draw.io (viewing it in the Gitea file browser, a PR diff, etc.). | Two distribution switches (`fche-sw17a`, `dmo-lob-sw35a`) anchor two fabrics; everything downstream is field switches daisy-chained through @@ -22,12 +24,18 @@ both fabrics for a documented reason (spare/inventory, decommissioning, or planned/not-yet-installed) -- those are called out in their own box on the diagram rather than silently dropped. +Every connector is labeled with the port at each end (e.g. `9 ↔ 1`), and +switches/patch panels look the part: switches get draw.io's built-in network +switch icon, patch panels get a small rack-unit look (header strip + port +ticks) rather than a plain box. + Connectors in the `.drawio` file are glued by shape id (draw.io's native `source`/`target` reference on each edge) -- drag a switch or panel in the -app and its links follow. That's different from this project's earlier -Visio (`.vsdx`) attempt, where glue had to be hand-written as cell formulas -because the format has no simpler mechanism; draw.io does this natively, -which is a big part of why this repo uses it instead. +app and its links follow, port-tick decorations included since they're +parented to their panel's body cell. That's different from this project's +earlier Visio (`.vsdx`) attempt, where glue had to be hand-written as cell +formulas because the format has no simpler mechanism; draw.io does this +natively, which is a big part of why this repo uses it instead. ## Regenerating @@ -38,12 +46,16 @@ 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 python3 topology/generate_drawio.py topology/topo_full.json topology/Field-Switch-Topology.drawio + +# optional -- refreshes the PNG preview to match; first run: npm install puppeteer +node topology/render_png.js topology/Field-Switch-Topology.drawio topology/Field-Switch-Topology.png ``` -Commit both files together. The diff on `topo_full.json` is the real -changelog -- it shows exactly which device or cable changed between two -snapshots; the `.drawio` diff is mostly noise (coordinates) but is kept in -sync so the rendered diagram never drifts from what NetBox actually says. +Commit all three (`topo_full.json`, `.drawio`, `.png`) together. The diff on +`topo_full.json` is the real changelog -- it shows exactly which device or +cable changed between two snapshots; the `.drawio`/`.png` diffs are mostly +noise (coordinates, pixels) but are kept in sync so the rendered diagram +never drifts from what NetBox actually says. ## Gotchas learned the hard way diff --git a/topology/Field-Switch-Topology.drawio b/topology/Field-Switch-Topology.drawio index 2f48ed5..b804f08 100644 --- a/topology/Field-Switch-Topology.drawio +++ b/topology/Field-Switch-Topology.drawio @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/topology/Field-Switch-Topology.png b/topology/Field-Switch-Topology.png new file mode 100644 index 0000000..b489a86 Binary files /dev/null and b/topology/Field-Switch-Topology.png differ diff --git a/topology/export_topology.py b/topology/export_topology.py index 335334b..aaa21d9 100644 --- a/topology/export_topology.py +++ b/topology/export_topology.py @@ -12,8 +12,18 @@ 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. + +Two passes: + 1. Walk every switch's cable path (Interface.trace()) to find every + device -- patch panel, splice module, other switch -- it physically + touches. This is just to build the device set; NetBox's cached path + is what can go stale (see README), which is why pass 2 doesn't rely + on it for the edges themselves. + 2. Pull every real Cable among those devices directly, so each edge + carries the exact port name on both ends (what generate_drawio.py + labels connectors with) instead of just "these two are connected." """ -from dcim.models import Device, Interface +from dcim.models import Device, Interface, Cable import json switch_ids = set( @@ -27,24 +37,38 @@ ifaces = Interface.objects.filter( ).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)))) + +cables = Cable.objects.filter( + terminations__termination_type__model__in=["interface", "frontport", "rearport"], +).distinct() + +edges = {} +for c in cables: + terms = list(c.terminations.all()) + a_side = [t for t in terms if t.cable_end == "A"] + b_side = [t for t in terms if t.cable_end == "B"] + for ta in a_side: + for tb in b_side: + da = getattr(ta.termination, "device", None) + db = getattr(tb.termination, "device", None) + if not da or not db: + continue + if da.id in touched_devices and db.id in touched_devices and da.id != db.id: + key = tuple(sorted((da.id, db.id))) + edges[key] = { + "a": da.id, "a_port": ta.termination.name, + "b": db.id, "b_port": tb.termination.name, + } devs = Device.objects.filter(id__in=touched_devices).select_related( "site", "role", "device_type" @@ -63,6 +87,6 @@ out = { } for d in devs ], - "edges": [[a, b] for a, b in sorted(edges)], + "edges": list(edges.values()), } print(json.dumps(out)) diff --git a/topology/generate_drawio.py b/topology/generate_drawio.py index 2d2af82..ea332e6 100644 --- a/topology/generate_drawio.py +++ b/topology/generate_drawio.py @@ -5,7 +5,8 @@ Render topo_full.json (produced by export_topology.py) as a draw.io Edges reference shapes by id (source=/target=), which is how draw.io's native glue works -- no hand-rolled formulas needed: dragging a shape in -the app keeps every attached line attached. +the app keeps every attached line attached. Patch panel port ticks/accent +strips are parented to the panel's body cell, so they move with it too. Usage: python3 generate_drawio.py [topo_full.json] [Field-Switch-Topology.drawio] @@ -19,13 +20,13 @@ OUT_PATH = sys.argv[2] if len(sys.argv) > 2 else "Field-Switch-Topology.drawio" data = json.load(open(IN_PATH)) devs = {d["id"]: d for d in data["devices"]} -edges = data["edges"] +edges = data["edges"] # [{a, a_port, b, b_port}, ...] # ---------------- components ---------------- adj = {d: [] for d in devs} -for a, b in edges: - adj[a].append(b) - adj[b].append(a) +for e in edges: + adj[e["a"]].append(e["b"]) + adj[e["b"]].append(e["a"]) parent = {d: d for d in devs} def find(x): @@ -37,8 +38,8 @@ def union(a, b): ra, rb = find(a), find(b) if ra != rb: parent[ra] = rb -for a, b in edges: - union(a, b) +for e in edges: + union(e["a"], e["b"]) from collections import defaultdict comps = defaultdict(list) for d in devs: @@ -74,9 +75,9 @@ depth_dmo, yslot_dmo = layout_tree(7, dmo_comp) # ---------------- geometry constants (px) ---------------- SWITCH_ICON = 46 # icon box; label renders below it SWITCH_LABEL_H = 30 -PANEL_W, PANEL_H = 130, 34 -COL_W = 190 -ROW_H = 82 +PANEL_W, PANEL_H = 132, 42 +COL_W = 200 +ROW_H = 86 PAD = 40 LABEL_STRIP = 34 SECTION_GAP = 70 @@ -137,7 +138,7 @@ STYLE = { "spare": dict(fill="#F1F1F2", stroke="#9AA0AA", font="#5B6478", dashed=1, bold=0), "decom": dict(fill="#FBE6E2", stroke="#C04B34", font="#8A3423", dashed=1, bold=0), "planned": dict(fill="#FDF1CF", stroke="#B8862C", font="#7A5C00", dashed=1, bold=0), - "panel": dict(fill="#EEF0F2", stroke="#8C97A6", font="#495566", dashed=0, bold=0), + "panel": dict(fill="#3A4457", stroke="#242C38", font="#FFFFFF", dashed=0, bold=0), } def style_for(dev): @@ -166,9 +167,9 @@ def vertex(cell_id, value, x, y, w, h, style, parent="1"): f'' ) -def edge(cell_id, source, target, style): +def edge(cell_id, source, target, style, value=""): add_cell( - f'' ) @@ -198,10 +199,29 @@ add_cell( f'' ) -# node shapes -- switches get a real network-switch icon (label rendered below it by -# draw.io's own verticalLabelPosition, not baked into the geometry), patch panels get -# a plain labeled box. -node_style = {} +# ---------------- node shapes ---------------- +# Switches: a real network-switch icon, hostname caption below it. +# Patch panels: a small rack-style unit -- dark body, a lighter header strip, +# and a row of port ticks along the bottom -- built from cells parented to +# the panel's own body cell, so the whole unit moves together when dragged. +def panel_shape(did, x, y, w, h, name, st): + body_id = f"n{did}" + vertex(body_id, name, x, y, w, h, + f'rounded=1;arcSize=10;whiteSpace=wrap;html=1;fillColor={st["fill"]};strokeColor={st["stroke"]};' + f'fontColor={st["font"]};fontSize=9;verticalAlign=bottom;spacingBottom=5;') + # header strip (lighter steel), relative to the body + vertex(f"{body_id}-strip", "", 5, 4, w - 10, 5, + 'rounded=1;arcSize=40;fillColor=#8C97A6;strokeColor=none;', parent=body_id) + # port ticks along the top-middle, evenly spaced + n_ticks = 6 + tick_w, tick_h = 10, 7 + usable = w - 16 + gap = usable / n_ticks + for k in range(n_ticks): + tx = 8 + gap * k + (gap - tick_w) / 2 + vertex(f"{body_id}-t{k}", "", tx, 15, tick_w, tick_h, + 'rounded=0;fillColor=#2A313D;strokeColor=#5B6577;strokeWidth=0.75;', parent=body_id) + for did, dev in devs.items(): if did not in pos: continue @@ -209,11 +229,7 @@ for did, dev in devs.items(): st = style_for(dev) dashed = "dashed=1;dashPattern=4 3;" if st["dashed"] else "" if dev["role"] == "patch-panel": - style = ( - f'rounded=0;whiteSpace=wrap;html=1;fillColor={st["fill"]};strokeColor={st["stroke"]};' - f'fontColor={st["font"]};fontSize=9;{dashed}' - ) - vertex(f"n{did}", dev["name"], x, y, w, h, style) + panel_shape(did, x, y, w, h, dev["name"], st) else: style = ( 'shape=mxgraph.networks.switch;html=1;verticalLabelPosition=bottom;verticalAlign=top;' @@ -222,18 +238,23 @@ for did, dev in devs.items(): ) vertex(f"n{did}", dev["name"], x, y, SWITCH_ICON, SWITCH_ICON, style) -# connectors -- glued natively via source/target, orthogonal routing -for idx, (a, b) in enumerate(edges): +# ---------------- connectors, labeled with the port at each end ---------------- +for idx, e in enumerate(edges): + a, b = e["a"], e["b"] if a in pos and b in pos: + label = f'{e["a_port"]} ↔ {e["b_port"]}' edge(f"e{idx}", f"n{a}", f"n{b}", "edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;strokeColor=#9AA6B8;strokeWidth=1.2;" - "jettySize=auto;orthogonalLoop=1;") + "startArrow=none;endArrow=none;" + "jettySize=auto;orthogonalLoop=1;fontSize=7.5;fontColor=#7C8AA6;" + "labelBackgroundColor=#F7F8FBcc;align=center;verticalAlign=middle;", + value=label) # legend legend_items = [ ("#2F5FAD", "#173D6E", "Distribution switch"), ("#F3F5FA", "#7C8AA6", "Field switch"), - ("#EEF0F2", "#8C97A6", "Patch panel / splice module"), + ("#3A4457", "#242C38", "Patch panel / splice module"), ("#F1F1F2", "#9AA0AA", "Spare / decommissioned / planned"), ] lx = MARGIN diff --git a/topology/render_host.html b/topology/render_host.html new file mode 100644 index 0000000..cb92783 --- /dev/null +++ b/topology/render_host.html @@ -0,0 +1,24 @@ + + + + + diff --git a/topology/render_png.js b/topology/render_png.js new file mode 100644 index 0000000..6f0e55b --- /dev/null +++ b/topology/render_png.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node +/** + * Render a .drawio file to PNG via a real draw.io instance (embed.diagrams.net), + * driven headlessly with Puppeteer -- this is an actual render, not an + * approximation, so switch icons, patch panel styling, and port labels all + * come out exactly as they'd look opening the file in the app. + * + * Needs `npm install puppeteer` first (downloads its own Chromium; not the + * system browser). Needs network access to embed.diagrams.net. + * + * Usage: + * node render_png.js Field-Switch-Topology.drawio Field-Switch-Topology.png + */ +const puppeteer = require('puppeteer'); +const fs = require('fs'); +const path = require('path'); + +const xmlPath = process.argv[2]; +const outPath = process.argv[3]; +if (!xmlPath || !outPath) { + console.error('usage: node render_png.js '); + process.exit(1); +} +const xml = fs.readFileSync(xmlPath, 'utf8'); + +(async () => { + const browser = await puppeteer.launch({ + headless: 'new', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + const page = await browser.newPage(); + await page.setViewport({ width: 1700, height: 1300 }); + page.on('pageerror', (err) => console.error('PAGE ERROR:', err)); + + await page.evaluateOnNewDocument((x) => { window.__XML__ = x; }, xml); + await page.goto('file://' + path.join(__dirname, 'render_host.html'), { + waitUntil: 'load', + timeout: 30000, + }); + + let dataUrl = null; + const deadline = Date.now() + 60000; + while (Date.now() < deadline) { + dataUrl = await page.evaluate(() => window.__RESULT__); + const err = await page.evaluate(() => window.__ERR__); + if (err) throw new Error('draw.io export error: ' + err); + if (dataUrl) break; + await new Promise((r) => setTimeout(r, 500)); + } + if (!dataUrl) throw new Error('timed out waiting for draw.io to export (network to embed.diagrams.net?)'); + + const base64 = dataUrl.split(',')[1]; + fs.writeFileSync(outPath, Buffer.from(base64, 'base64')); + console.log('wrote', outPath, fs.statSync(outPath).size, 'bytes'); + await browser.close(); +})().catch((e) => { console.error(e); process.exit(1); }); diff --git a/topology/topo_full.json b/topology/topo_full.json index 885ac67..5a93c54 100644 --- a/topology/topo_full.json +++ b/topology/topo_full.json @@ -1 +1 @@ -{"devices": [{"id": 57, "name": "afl-104", "role": "patch-panel", "role_name": "Patch Panel", "site": "Highway 104 Base Station", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 92, "name": "afl-cr13", "role": "patch-panel", "role_name": "Patch Panel", "site": "County Road 13 Base Station", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 85, "name": "afl-lem", "role": "patch-panel", "role_name": "Patch Panel", "site": "Lemler Substation", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 58, "name": "afl-mag", "role": "patch-panel", "role_name": "Patch Panel", "site": "Magnolia Springs Base Station", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 82, "name": "afl-swf", "role": "patch-panel", "role_name": "Patch Panel", "site": "Southwest Foley Substation", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 42, "name": "bs-104-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Highway 104 Base Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 93, "name": "bs-cr13-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "County Road 13 Base Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 40, "name": "bs-lem-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 38, "name": "bs-lil-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lillian Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 43, "name": "bs-mag-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Magnolia Springs Base Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 41, "name": "bs-nwt-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "North Water Tank", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 39, "name": "bs-swf-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Southwest Foley Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 7, "name": "dmo-lob-sw35a", "role": "dist-switch", "role_name": "Distribution Switch", "site": "DMO-LOB", "status": "active", "device_type": "S4112F-ON"}, {"id": 2, "name": "FCHE-PP-32B", "role": "patch-panel", "role_name": "Patch Panel", "site": "FCHE Cable Headend", "status": "active", "device_type": "LS Series 288-Port LC UPC"}, {"id": 4, "name": "FCHE-PP17D", "role": "patch-panel", "role_name": "Patch Panel", "site": "FCHE Cable Headend", "status": "active", "device_type": "FIS 48-Port LC UPC"}, {"id": 3, "name": "FCHE-PP34A", "role": "patch-panel", "role_name": "Patch Panel", "site": "FCHE Cable Headend", "status": "active", "device_type": "FIS 144-Port LC UPC 12-Slot"}, {"id": 1, "name": "fche-sw17a", "role": "dist-switch", "role_name": "Distribution Switch", "site": "FCHE Cable Headend", "status": "active", "device_type": "ProCurve 5412zl"}, {"id": 63, "name": "fis-nwt", "role": "patch-panel", "role_name": "Patch Panel", "site": "North Water Tank", "status": "active", "device_type": "F1-DW2X"}, {"id": 11, "name": "gs-nff-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "North Foley Field Gas Regulator", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 19, "name": "ls-avls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Aaronville Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 14, "name": "ls-bsls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Bay Street Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 20, "name": "ls-clls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Cornerstone Lodge Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 10, "name": "ls-guls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Gulftel Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 25, "name": "ls-mals-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Marlin Place Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 23, "name": "ls-mmls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Majestic Manor Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 26, "name": "ls-mpls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "McSwain Place Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 9, "name": "ls-pals-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Peachtree Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 21, "name": "ls-rols-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "River Oaks Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 24, "name": "ls-spls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "South Pointe Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 22, "name": "ls-vhls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Villages at Hickory Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 76, "name": "primex-ebert", "role": "patch-panel", "role_name": "Patch Panel", "site": "Ebert Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 79, "name": "primex-holk", "role": "patch-panel", "role_name": "Patch Panel", "site": "Holk Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 80, "name": "primex-lem", "role": "patch-panel", "role_name": "Patch Panel", "site": "Lemler Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 81, "name": "primex-lil", "role": "patch-panel", "role_name": "Patch Panel", "site": "Lillian Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 83, "name": "primex-wpb", "role": "patch-panel", "role_name": "Patch Panel", "site": "West Perdido Beach Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 33, "name": "rec-10000-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Bay St @ Azalea Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 34, "name": "rec-10008-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Cedar @ Michigan Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 17, "name": "rec-10016-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Underwood Rd Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 28, "name": "rec-10018-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Summerdale Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 18, "name": "rec-10136-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "WHEP Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 15, "name": "rec-10160-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Coca Cola Plant Hwy 104 Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 32, "name": "rec-10660-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Old Foley Rd Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 16, "name": "rec-13032-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Peachtree Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 30, "name": "rec-50094-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Randall Ave @ Hwy 98 Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 31, "name": "rec-52882-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Pollard Rd Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 87, "name": "sec-holk-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Holk Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 88, "name": "sec-lem-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 89, "name": "sec-swf-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Southwest Foley Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 45, "name": "spare-163", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 46, "name": "spare-164", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 47, "name": "spare-165", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 48, "name": "spare-166", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 36, "name": "sub-ebert-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Ebert Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 13, "name": "sub-fair-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Fairhope Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 29, "name": "sub-holk-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Holk Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 49, "name": "sub-lem-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 35, "name": "sub-lem-sw01-decommissioned", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "decommissioning", "device_type": "2930F"}, {"id": 8, "name": "sub-lil-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lillian Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 27, "name": "sub-swf-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Southwest Foley Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 37, "name": "sub-wpb-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "West Perdido Beach Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 90, "name": "wtr-cwt-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Cypress Water Tank", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 12, "name": "wtr-ecc-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Elberta Cross Connect", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 44, "name": "wtr-w12-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Well 12", "status": "planned", "device_type": "IES3110-8TFP-R"}, {"id": 51, "name": "xpak-10000", "role": "patch-panel", "role_name": "Patch Panel", "site": "Bay St @ Azalea Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 53, "name": "xpak-10008", "role": "patch-panel", "role_name": "Patch Panel", "site": "Cedar @ Michigan Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 72, "name": "xpak-10016", "role": "patch-panel", "role_name": "Patch Panel", "site": "Underwood Rd Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 71, "name": "xpak-10018", "role": "patch-panel", "role_name": "Patch Panel", "site": "Summerdale Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 74, "name": "xpak-10136", "role": "patch-panel", "role_name": "Patch Panel", "site": "WHEP Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 54, "name": "xpak-10160", "role": "patch-panel", "role_name": "Patch Panel", "site": "Coca Cola Plant Hwy 104 Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 64, "name": "xpak-10660", "role": "patch-panel", "role_name": "Patch Panel", "site": "Old Foley Rd Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 66, "name": "xpak-13032", "role": "patch-panel", "role_name": "Patch Panel", "site": "Peachtree Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 68, "name": "xpak-50094", "role": "patch-panel", "role_name": "Patch Panel", "site": "Randall Ave @ Hwy 98 Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 67, "name": "xpak-52882", "role": "patch-panel", "role_name": "Patch Panel", "site": "Pollard Rd Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 50, "name": "xpak-avls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Aaronville Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 52, "name": "xpak-bsls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Bay Street Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 55, "name": "xpak-clls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Cornerstone Lodge Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 91, "name": "xpak-cwt", "role": "patch-panel", "role_name": "Patch Panel", "site": "Cypress Water Tank", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 56, "name": "xpak-guls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Gulftel Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 60, "name": "xpak-mals", "role": "patch-panel", "role_name": "Patch Panel", "site": "Marlin Place Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 59, "name": "xpak-mmls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Majestic Manor Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 61, "name": "xpak-mpls", "role": "patch-panel", "role_name": "Patch Panel", "site": "McSwain Place Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 62, "name": "xpak-nff", "role": "patch-panel", "role_name": "Patch Panel", "site": "North Foley Field Gas Regulator", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 65, "name": "xpak-pals", "role": "patch-panel", "role_name": "Patch Panel", "site": "Peachtree Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 69, "name": "xpak-rols", "role": "patch-panel", "role_name": "Patch Panel", "site": "River Oaks Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 70, "name": "xpak-spls", "role": "patch-panel", "role_name": "Patch Panel", "site": "South Pointe Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 73, "name": "xpak-vhls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Villages at Hickory Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}], "edges": [[1, 2], [1, 3], [1, 4], [2, 52], [2, 60], [2, 70], [2, 79], [2, 85], [2, 91], [3, 54], [3, 62], [3, 63], [3, 65], [3, 66], [4, 12], [4, 64], [4, 76], [7, 13], [7, 67], [7, 68], [8, 81], [9, 65], [10, 56], [11, 62], [14, 52], [15, 54], [16, 66], [17, 72], [18, 74], [19, 50], [20, 55], [21, 69], [22, 73], [23, 59], [24, 70], [25, 60], [26, 61], [27, 82], [28, 71], [29, 79], [30, 68], [31, 67], [32, 64], [33, 51], [34, 53], [36, 76], [37, 83], [38, 81], [39, 82], [40, 85], [41, 63], [42, 57], [43, 58], [49, 80], [50, 52], [50, 69], [51, 53], [51, 79], [53, 80], [55, 61], [56, 65], [57, 63], [57, 92], [58, 82], [59, 61], [59, 73], [66, 72], [69, 73], [71, 74], [72, 74], [76, 83], [79, 80], [79, 87], [80, 82], [80, 88], [81, 83], [82, 85], [82, 89], [90, 91], [92, 93]]} +{"devices": [{"id": 57, "name": "afl-104", "role": "patch-panel", "role_name": "Patch Panel", "site": "Highway 104 Base Station", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 92, "name": "afl-cr13", "role": "patch-panel", "role_name": "Patch Panel", "site": "County Road 13 Base Station", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 85, "name": "afl-lem", "role": "patch-panel", "role_name": "Patch Panel", "site": "Lemler Substation", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 58, "name": "afl-mag", "role": "patch-panel", "role_name": "Patch Panel", "site": "Magnolia Springs Base Station", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 82, "name": "afl-swf", "role": "patch-panel", "role_name": "Patch Panel", "site": "Southwest Foley Substation", "status": "active", "device_type": "AFL 12-Port Splice Module"}, {"id": 42, "name": "bs-104-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Highway 104 Base Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 93, "name": "bs-cr13-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "County Road 13 Base Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 40, "name": "bs-lem-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 38, "name": "bs-lil-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lillian Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 43, "name": "bs-mag-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Magnolia Springs Base Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 41, "name": "bs-nwt-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "North Water Tank", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 39, "name": "bs-swf-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Southwest Foley Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 7, "name": "dmo-lob-sw35a", "role": "dist-switch", "role_name": "Distribution Switch", "site": "DMO-LOB", "status": "active", "device_type": "S4112F-ON"}, {"id": 2, "name": "FCHE-PP-32B", "role": "patch-panel", "role_name": "Patch Panel", "site": "FCHE Cable Headend", "status": "active", "device_type": "LS Series 288-Port LC UPC"}, {"id": 4, "name": "FCHE-PP17D", "role": "patch-panel", "role_name": "Patch Panel", "site": "FCHE Cable Headend", "status": "active", "device_type": "FIS 48-Port LC UPC"}, {"id": 3, "name": "FCHE-PP34A", "role": "patch-panel", "role_name": "Patch Panel", "site": "FCHE Cable Headend", "status": "active", "device_type": "FIS 144-Port LC UPC 12-Slot"}, {"id": 1, "name": "fche-sw17a", "role": "dist-switch", "role_name": "Distribution Switch", "site": "FCHE Cable Headend", "status": "active", "device_type": "ProCurve 5412zl"}, {"id": 63, "name": "fis-nwt", "role": "patch-panel", "role_name": "Patch Panel", "site": "North Water Tank", "status": "active", "device_type": "F1-DW2X"}, {"id": 11, "name": "gs-nff-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "North Foley Field Gas Regulator", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 19, "name": "ls-avls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Aaronville Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 14, "name": "ls-bsls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Bay Street Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 20, "name": "ls-clls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Cornerstone Lodge Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 10, "name": "ls-guls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Gulftel Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 25, "name": "ls-mals-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Marlin Place Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 23, "name": "ls-mmls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Majestic Manor Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 26, "name": "ls-mpls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "McSwain Place Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 9, "name": "ls-pals-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Peachtree Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 21, "name": "ls-rols-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "River Oaks Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 24, "name": "ls-spls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "South Pointe Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 22, "name": "ls-vhls-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Villages at Hickory Lift Station", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 76, "name": "primex-ebert", "role": "patch-panel", "role_name": "Patch Panel", "site": "Ebert Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 79, "name": "primex-holk", "role": "patch-panel", "role_name": "Patch Panel", "site": "Holk Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 80, "name": "primex-lem", "role": "patch-panel", "role_name": "Patch Panel", "site": "Lemler Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 81, "name": "primex-lil", "role": "patch-panel", "role_name": "Patch Panel", "site": "Lillian Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 83, "name": "primex-wpb", "role": "patch-panel", "role_name": "Patch Panel", "site": "West Perdido Beach Substation", "status": "active", "device_type": "Primex 12-Port LC Panel"}, {"id": 33, "name": "rec-10000-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Bay St @ Azalea Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 34, "name": "rec-10008-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Cedar @ Michigan Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 17, "name": "rec-10016-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Underwood Rd Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 28, "name": "rec-10018-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Summerdale Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 18, "name": "rec-10136-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "WHEP Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 15, "name": "rec-10160-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Coca Cola Plant Hwy 104 Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 32, "name": "rec-10660-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Old Foley Rd Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 16, "name": "rec-13032-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Peachtree Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 30, "name": "rec-50094-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Randall Ave @ Hwy 98 Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 31, "name": "rec-52882-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Pollard Rd Recloser", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 87, "name": "sec-holk-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Holk Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 88, "name": "sec-lem-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 89, "name": "sec-swf-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Southwest Foley Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 45, "name": "spare-163", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 46, "name": "spare-164", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 47, "name": "spare-165", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 48, "name": "spare-166", "role": "field-switch", "role_name": "Field Switch", "site": "FCHE Cable Headend", "status": "inventory", "device_type": "IES3220-8T4F"}, {"id": 36, "name": "sub-ebert-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Ebert Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 13, "name": "sub-fair-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Fairhope Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 29, "name": "sub-holk-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Holk Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 49, "name": "sub-lem-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 35, "name": "sub-lem-sw01-decommissioned", "role": "field-switch", "role_name": "Field Switch", "site": "Lemler Substation", "status": "decommissioning", "device_type": "2930F"}, {"id": 8, "name": "sub-lil-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Lillian Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 27, "name": "sub-swf-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Southwest Foley Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 37, "name": "sub-wpb-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "West Perdido Beach Substation", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 90, "name": "wtr-cwt-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Cypress Water Tank", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 12, "name": "wtr-ecc-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Elberta Cross Connect", "status": "active", "device_type": "IES3110-8TFP-R"}, {"id": 44, "name": "wtr-w12-sw01", "role": "field-switch", "role_name": "Field Switch", "site": "Well 12", "status": "planned", "device_type": "IES3110-8TFP-R"}, {"id": 51, "name": "xpak-10000", "role": "patch-panel", "role_name": "Patch Panel", "site": "Bay St @ Azalea Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 53, "name": "xpak-10008", "role": "patch-panel", "role_name": "Patch Panel", "site": "Cedar @ Michigan Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 72, "name": "xpak-10016", "role": "patch-panel", "role_name": "Patch Panel", "site": "Underwood Rd Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 71, "name": "xpak-10018", "role": "patch-panel", "role_name": "Patch Panel", "site": "Summerdale Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 74, "name": "xpak-10136", "role": "patch-panel", "role_name": "Patch Panel", "site": "WHEP Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 54, "name": "xpak-10160", "role": "patch-panel", "role_name": "Patch Panel", "site": "Coca Cola Plant Hwy 104 Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 64, "name": "xpak-10660", "role": "patch-panel", "role_name": "Patch Panel", "site": "Old Foley Rd Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 66, "name": "xpak-13032", "role": "patch-panel", "role_name": "Patch Panel", "site": "Peachtree Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 68, "name": "xpak-50094", "role": "patch-panel", "role_name": "Patch Panel", "site": "Randall Ave @ Hwy 98 Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 67, "name": "xpak-52882", "role": "patch-panel", "role_name": "Patch Panel", "site": "Pollard Rd Recloser", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 50, "name": "xpak-avls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Aaronville Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 52, "name": "xpak-bsls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Bay Street Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 55, "name": "xpak-clls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Cornerstone Lodge Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 91, "name": "xpak-cwt", "role": "patch-panel", "role_name": "Patch Panel", "site": "Cypress Water Tank", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 56, "name": "xpak-guls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Gulftel Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 60, "name": "xpak-mals", "role": "patch-panel", "role_name": "Patch Panel", "site": "Marlin Place Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 59, "name": "xpak-mmls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Majestic Manor Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 61, "name": "xpak-mpls", "role": "patch-panel", "role_name": "Patch Panel", "site": "McSwain Place Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 62, "name": "xpak-nff", "role": "patch-panel", "role_name": "Patch Panel", "site": "North Foley Field Gas Regulator", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 65, "name": "xpak-pals", "role": "patch-panel", "role_name": "Patch Panel", "site": "Peachtree Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 69, "name": "xpak-rols", "role": "patch-panel", "role_name": "Patch Panel", "site": "River Oaks Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 70, "name": "xpak-spls", "role": "patch-panel", "role_name": "Patch Panel", "site": "South Pointe Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}, {"id": 73, "name": "xpak-vhls", "role": "patch-panel", "role_name": "Patch Panel", "site": "Villages at Hickory Lift Station", "status": "active", "device_type": "xPAK 6-Port SC/APC"}], "edges": [{"a": 7, "a_port": "4", "b": 13, "b_port": "9"}, {"a": 41, "a_port": "Gi1/10", "b": 63, "b_port": "P2:6"}, {"a": 63, "a_port": "P2:8", "b": 3, "b_port": "55"}, {"a": 1, "a_port": "E3", "b": 3, "b_port": "51"}, {"a": 19, "a_port": "10", "b": 50, "b_port": "2"}, {"a": 14, "a_port": "10", "b": 52, "b_port": "2"}, {"a": 20, "a_port": "9", "b": 55, "b_port": "1"}, {"a": 10, "a_port": "9", "b": 56, "b_port": "1"}, {"a": 25, "a_port": "9", "b": 60, "b_port": "1"}, {"a": 23, "a_port": "10", "b": 59, "b_port": "2"}, {"a": 26, "a_port": "10", "b": 61, "b_port": "2"}, {"a": 9, "a_port": "10", "b": 65, "b_port": "2"}, {"a": 21, "a_port": "10", "b": 69, "b_port": "2"}, {"a": 24, "a_port": "9", "b": 70, "b_port": "1"}, {"a": 22, "a_port": "10", "b": 73, "b_port": "2"}, {"a": 52, "a_port": "2", "b": 50, "b_port": "1"}, {"a": 50, "a_port": "2", "b": 69, "b_port": "1"}, {"a": 69, "a_port": "2", "b": 73, "b_port": "1"}, {"a": 73, "a_port": "2", "b": 59, "b_port": "1"}, {"a": 59, "a_port": "2", "b": 61, "b_port": "1"}, {"a": 61, "a_port": "2", "b": 55, "b_port": "1"}, {"a": 65, "a_port": "2", "b": 56, "b_port": "1"}, {"a": 33, "a_port": "10", "b": 51, "b_port": "2"}, {"a": 34, "a_port": "10", "b": 53, "b_port": "2"}, {"a": 17, "a_port": "10", "b": 72, "b_port": "2"}, {"a": 28, "a_port": "9", "b": 71, "b_port": "1"}, {"a": 18, "a_port": "10", "b": 74, "b_port": "2"}, {"a": 15, "a_port": "9", "b": 54, "b_port": "1"}, {"a": 32, "a_port": "9", "b": 64, "b_port": "1"}, {"a": 16, "a_port": "10", "b": 66, "b_port": "2"}, {"a": 30, "a_port": "9", "b": 68, "b_port": "1"}, {"a": 31, "a_port": "9", "b": 67, "b_port": "1"}, {"a": 7, "a_port": "11", "b": 67, "b_port": "1"}, {"a": 7, "a_port": "1", "b": 68, "b_port": "1"}, {"a": 66, "a_port": "2", "b": 72, "b_port": "1"}, {"a": 72, "a_port": "2", "b": 74, "b_port": "1"}, {"a": 74, "a_port": "2", "b": 71, "b_port": "1"}, {"a": 51, "a_port": "2", "b": 53, "b_port": "1"}, {"a": 1, "a_port": "E12", "b": 2, "b_port": "7"}, {"a": 1, "a_port": "E2", "b": 4, "b_port": "10"}, {"a": 4, "a_port": "31", "b": 64, "b_port": "1"}, {"a": 4, "a_port": "27", "b": 12, "b_port": "9"}, {"a": 76, "a_port": "6", "b": 36, "b_port": "10"}, {"a": 4, "a_port": "12", "b": 76, "b_port": "5"}, {"a": 76, "a_port": "8", "b": 83, "b_port": "3"}, {"a": 83, "a_port": "4", "b": 37, "b_port": "10"}, {"a": 83, "a_port": "6", "b": 81, "b_port": "12"}, {"a": 81, "a_port": "10", "b": 8, "b_port": "9"}, {"a": 2, "a_port": "49", "b": 70, "b_port": "1"}, {"a": 2, "a_port": "3", "b": 60, "b_port": "1"}, {"a": 85, "a_port": "2", "b": 40, "b_port": "Gi1/10"}, {"a": 2, "a_port": "15", "b": 85, "b_port": "1"}, {"a": 85, "a_port": "2", "b": 82, "b_port": "3"}, {"a": 82, "a_port": "6", "b": 39, "b_port": "10"}, {"a": 82, "a_port": "6", "b": 58, "b_port": "1"}, {"a": 58, "a_port": "1", "b": 43, "b_port": "Gi1/9"}, {"a": 2, "a_port": "4", "b": 52, "b_port": "1"}, {"a": 3, "a_port": "96", "b": 54, "b_port": "1"}, {"a": 3, "a_port": "1", "b": 66, "b_port": "1"}, {"a": 3, "a_port": "53", "b": 62, "b_port": "1"}, {"a": 62, "a_port": "1", "b": 11, "b_port": "9"}, {"a": 3, "a_port": "51", "b": 65, "b_port": "1"}, {"a": 2, "a_port": "14", "b": 79, "b_port": "5"}, {"a": 79, "a_port": "4", "b": 29, "b_port": "10"}, {"a": 79, "a_port": "4", "b": 51, "b_port": "1"}, {"a": 82, "a_port": "1", "b": 27, "b_port": "9"}, {"a": 79, "a_port": "6", "b": 87, "b_port": "10"}, {"a": 80, "a_port": "4", "b": 88, "b_port": "10"}, {"a": 79, "a_port": "6", "b": 80, "b_port": "3"}, {"a": 80, "a_port": "2", "b": 82, "b_port": "1"}, {"a": 82, "a_port": "2", "b": 89, "b_port": "9"}, {"a": 53, "a_port": "2", "b": 80, "b_port": "1"}, {"a": 80, "a_port": "2", "b": 49, "b_port": "10"}, {"a": 81, "a_port": "12", "b": 38, "b_port": "Gi1/9"}, {"a": 2, "a_port": "7", "b": 91, "b_port": "1"}, {"a": 90, "a_port": "9", "b": 91, "b_port": "1"}, {"a": 42, "a_port": "Gi1/10", "b": 57, "b_port": "2"}, {"a": 57, "a_port": "1", "b": 63, "b_port": "P2:6"}, {"a": 57, "a_port": "2", "b": 92, "b_port": "1"}, {"a": 93, "a_port": "Gi1/9", "b": 92, "b_port": "1"}]}