Add a real VLAN legend, matching the reference diagram's style
Previously the only VLAN visual was a small 14px swatch inside the detail table -- easy to miss, not what the user meant by 'legend'. Added a dedicated colored block per VLAN (VLAN <id> on a color fill, name in a matching-outline box next to it), stacked left of the existing detail table, same layout idea as the reference diagram this was modeled on. Also fixed a real bug this surfaced: vlan_color() picked a palette color by hash(vid), which let two different VLANs collide on the same color (209 and 217 both landed on green). Colors are now assigned by position among the VLANs actually present, which can't collide as long as there are <= 8 of them (the palette size) -- true here with room to grow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NM3nivbufxEWiKKmatEtEe
This commit is contained in:
@@ -23,9 +23,14 @@ devs = {d["id"]: d for d in data["devices"]}
|
||||
edges = data["edges"] # [{a, a_port, a_vlan, b, b_port, b_vlan}, ...]
|
||||
vlans = data.get("vlans", []) # [{vid, name, description, prefixes, site}, ...]
|
||||
|
||||
VLAN_PALETTE = ["#D6603F", "#3C9D6B", "#E8A33D", "#5B7FD9", "#9A6BC9", "#3FAAB0", "#C2547E", "#7A8C3F"]
|
||||
VLAN_PALETTE = ["#D6603F", "#3C9D6B", "#C2547E", "#5B7FD9", "#E8A33D", "#3FAAB0", "#9A6BC9", "#7A8C3F"]
|
||||
# assigned by position among the VLANs actually present (sorted by vid, same
|
||||
# order they're listed/rendered in), not a hash -- guarantees no two VLANs
|
||||
# share a color as long as there are <= len(VLAN_PALETTE) of them, which a
|
||||
# hash-based assignment can't promise (two vids can hash to the same slot)
|
||||
_vlan_color_by_vid = {v["vid"]: VLAN_PALETTE[i % len(VLAN_PALETTE)] for i, v in enumerate(vlans)}
|
||||
def vlan_color(vid):
|
||||
return VLAN_PALETTE[hash(vid) % len(VLAN_PALETTE)]
|
||||
return _vlan_color_by_vid.get(vid, "#9AA0AA")
|
||||
|
||||
def vlan_tag(pv):
|
||||
"""Short connector-label suffix for a port's VLAN config, e.g. 'V10' or
|
||||
@@ -137,7 +142,17 @@ VLAN_HEADER_H = 30
|
||||
VLAN_TABLE_W = 900
|
||||
vlan_rows = max(1, len(vlans)) # the placeholder counts as one row
|
||||
VLAN_TABLE_H = VLAN_HEADER_H + vlan_rows * VLAN_ROW_H + 16
|
||||
cursor_y = MARGIN + TITLE_H + LEGEND_ROW_H + VLAN_TABLE_H + 20
|
||||
|
||||
# a dedicated colour-coded legend (VLAN id block + name block per row),
|
||||
# stacked to the left of the detail table -- this is the part that reads
|
||||
# as "legend" at a glance; the table next to it carries the detail
|
||||
VLAN_LEGEND_W = 260
|
||||
VLAN_LEGEND_ROW_H = 42
|
||||
VLAN_LEGEND_GAP = 6
|
||||
VLAN_LEGEND_H = vlan_rows * (VLAN_LEGEND_ROW_H + VLAN_LEGEND_GAP) - VLAN_LEGEND_GAP
|
||||
|
||||
VLAN_SECTION_H = max(VLAN_TABLE_H, VLAN_LEGEND_H)
|
||||
cursor_y = MARGIN + TITLE_H + LEGEND_ROW_H + VLAN_SECTION_H + 20
|
||||
|
||||
w_main, h_main = place_section(depth_main, yslot_main, cursor_y)
|
||||
main_top = cursor_y
|
||||
@@ -160,7 +175,7 @@ iso_rows = (len(iso_sorted) + ISO_COLS - 1) // ISO_COLS
|
||||
w_iso = MARGIN * 2 + PAD * 2 + (ISO_COLS - 1) * COL_W + SWITCH_ICON
|
||||
h_iso = PAD * 2 + LABEL_STRIP + (iso_rows - 1) * ROW_H + SWITCH_ICON + SWITCH_LABEL_H
|
||||
|
||||
PAGE_W = max(w_main, w_dmo, w_iso, VLAN_TABLE_W + MARGIN * 2)
|
||||
PAGE_W = max(w_main, w_dmo, w_iso, VLAN_LEGEND_W + 30 + VLAN_TABLE_W + MARGIN * 2)
|
||||
PAGE_H = cursor_y + h_iso + MARGIN
|
||||
|
||||
# ---------------- style palette ----------------
|
||||
@@ -303,12 +318,40 @@ for i, (fill, stroke, label) in enumerate(legend_items):
|
||||
)
|
||||
lx += 260
|
||||
|
||||
# ---------------- VLAN legend / mapping table ----------------
|
||||
# ---------------- VLAN legend (colour blocks) + mapping table ----------------
|
||||
# Structure is always rendered; content depends on whether NetBox actually
|
||||
# has VLANs defined yet (see export_topology.py). Nothing here is invented --
|
||||
# an empty table renders as an explicit "not configured" placeholder, not as
|
||||
# blank space or made-up VLAN numbers.
|
||||
vt_x, vt_y = MARGIN, MARGIN + TITLE_H + LEGEND_ROW_H
|
||||
# an empty legend/table renders as an explicit "not configured" placeholder,
|
||||
# not as blank space or made-up VLAN numbers.
|
||||
vlg_x, vlg_y = MARGIN, MARGIN + TITLE_H + LEGEND_ROW_H
|
||||
vlg_id_w, vlg_name_w = 70, VLAN_LEGEND_W - 70
|
||||
if not vlans:
|
||||
add_cell(
|
||||
f'<mxCell id="vlg-empty" value="No VLANs" style="rounded=1;arcSize=12;whiteSpace=wrap;html=1;'
|
||||
f'fillColor=#F1F1F2;strokeColor=#9AA0AA;dashed=1;dashPattern=4 3;fontColor=#7C8AA6;fontSize=11;" '
|
||||
f'vertex="1" parent="1"><mxGeometry x="{vlg_x}" y="{vlg_y}" width="{VLAN_LEGEND_W}" '
|
||||
f'height="{VLAN_LEGEND_ROW_H}" as="geometry"/></mxCell>'
|
||||
)
|
||||
else:
|
||||
ly2 = vlg_y
|
||||
for v in vlans:
|
||||
color = vlan_color(v["vid"])
|
||||
add_cell(
|
||||
f'<mxCell id="vlg-id-{v["vid"]}" value="VLAN {v["vid"]}" style="rounded=0;whiteSpace=wrap;'
|
||||
f'html=1;fillColor={color};strokeColor=none;fontColor=#FFFFFF;fontStyle=1;fontSize=13;'
|
||||
f'align=center;verticalAlign=middle;lineHeight=1.1;" vertex="1" parent="1">'
|
||||
f'<mxGeometry x="{vlg_x}" y="{ly2}" width="{vlg_id_w}" height="{VLAN_LEGEND_ROW_H}" as="geometry"/></mxCell>'
|
||||
)
|
||||
add_cell(
|
||||
f'<mxCell id="vlg-name-{v["vid"]}" value="{escape(v["name"])}" style="rounded=0;whiteSpace=wrap;'
|
||||
f'html=1;fillColor=#FFFFFF;strokeColor={color};strokeWidth=2;fontColor=#1A2233;fontStyle=1;'
|
||||
f'fontSize=12;align=center;verticalAlign=middle;" vertex="1" parent="1">'
|
||||
f'<mxGeometry x="{vlg_x+vlg_id_w}" y="{ly2}" width="{vlg_name_w}" height="{VLAN_LEGEND_ROW_H}" '
|
||||
f'as="geometry"/></mxCell>'
|
||||
)
|
||||
ly2 += VLAN_LEGEND_ROW_H + VLAN_LEGEND_GAP
|
||||
|
||||
vt_x, vt_y = MARGIN + VLAN_LEGEND_W + 30, MARGIN + TITLE_H + LEGEND_ROW_H
|
||||
vt_cols = [("VLAN", 60), ("Name", 130), ("Description", 300), ("Subnet", 160), ("Site", 170)]
|
||||
add_cell(
|
||||
f'<mxCell id="vt-bg" value="" style="rounded=0;fillColor=#FFFFFF;strokeColor=#DDE1EA;" '
|
||||
|
||||
Reference in New Issue
Block a user