unifi-network-maps 1.4.14__py3-none-any.whl → 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. unifi_network_maps/__init__.py +1 -1
  2. unifi_network_maps/adapters/unifi.py +83 -101
  3. unifi_network_maps/assets/icons/modern/ap.svg +9 -0
  4. unifi_network_maps/assets/icons/modern/camera.svg +9 -0
  5. unifi_network_maps/assets/icons/modern/client.svg +9 -0
  6. unifi_network_maps/assets/icons/modern/game_console.svg +10 -0
  7. unifi_network_maps/assets/icons/modern/gateway.svg +17 -0
  8. unifi_network_maps/assets/icons/modern/iot.svg +9 -0
  9. unifi_network_maps/assets/icons/modern/nas.svg +9 -0
  10. unifi_network_maps/assets/icons/modern/other.svg +10 -0
  11. unifi_network_maps/assets/icons/modern/phone.svg +10 -0
  12. unifi_network_maps/assets/icons/modern/printer.svg +9 -0
  13. unifi_network_maps/assets/icons/modern/speaker.svg +10 -0
  14. unifi_network_maps/assets/icons/modern/switch.svg +10 -0
  15. unifi_network_maps/assets/icons/modern/tv.svg +10 -0
  16. unifi_network_maps/assets/icons/modern-flat/ap.svg +5 -0
  17. unifi_network_maps/assets/icons/modern-flat/camera.svg +5 -0
  18. unifi_network_maps/assets/icons/modern-flat/client.svg +5 -0
  19. unifi_network_maps/assets/icons/modern-flat/game_console.svg +6 -0
  20. unifi_network_maps/assets/icons/modern-flat/gateway.svg +13 -0
  21. unifi_network_maps/assets/icons/modern-flat/iot.svg +5 -0
  22. unifi_network_maps/assets/icons/modern-flat/nas.svg +5 -0
  23. unifi_network_maps/assets/icons/modern-flat/other.svg +6 -0
  24. unifi_network_maps/assets/icons/modern-flat/phone.svg +6 -0
  25. unifi_network_maps/assets/icons/modern-flat/printer.svg +5 -0
  26. unifi_network_maps/assets/icons/modern-flat/speaker.svg +6 -0
  27. unifi_network_maps/assets/icons/modern-flat/switch.svg +6 -0
  28. unifi_network_maps/assets/icons/modern-flat/tv.svg +6 -0
  29. unifi_network_maps/assets/themes/dark.yaml +53 -10
  30. unifi_network_maps/assets/themes/default.yaml +34 -0
  31. unifi_network_maps/assets/themes/minimal-dark.yaml +98 -0
  32. unifi_network_maps/assets/themes/minimal.yaml +92 -0
  33. unifi_network_maps/assets/themes/unifi-dark.yaml +97 -0
  34. unifi_network_maps/assets/themes/unifi.yaml +92 -0
  35. unifi_network_maps/cli/args.py +54 -0
  36. unifi_network_maps/cli/main.py +18 -7
  37. unifi_network_maps/cli/render.py +79 -27
  38. unifi_network_maps/cli/runtime.py +29 -15
  39. unifi_network_maps/io/debug.py +2 -1
  40. unifi_network_maps/io/export.py +19 -13
  41. unifi_network_maps/io/mock_data.py +5 -3
  42. unifi_network_maps/io/paths.py +5 -3
  43. unifi_network_maps/model/classify.py +199 -0
  44. unifi_network_maps/model/clients.py +271 -0
  45. unifi_network_maps/model/connection.py +37 -0
  46. unifi_network_maps/model/diff.py +544 -0
  47. unifi_network_maps/model/edges.py +558 -0
  48. unifi_network_maps/model/helpers.py +64 -0
  49. unifi_network_maps/model/lldp.py +20 -25
  50. unifi_network_maps/model/mock.py +110 -23
  51. unifi_network_maps/model/snapshot.py +294 -0
  52. unifi_network_maps/model/topology.py +143 -931
  53. unifi_network_maps/model/topology_coerce.py +339 -0
  54. unifi_network_maps/model/vlans.py +32 -46
  55. unifi_network_maps/model/wan.py +132 -0
  56. unifi_network_maps/render/device_ports_md.py +39 -97
  57. unifi_network_maps/render/device_summary.py +53 -0
  58. unifi_network_maps/render/lldp_md.py +29 -219
  59. unifi_network_maps/render/markdown_tables.py +8 -0
  60. unifi_network_maps/render/mermaid.py +11 -2
  61. unifi_network_maps/render/mkdocs.py +2 -1
  62. unifi_network_maps/render/svg.py +566 -908
  63. unifi_network_maps/render/svg_icons.py +231 -0
  64. unifi_network_maps/render/svg_isometric.py +1196 -0
  65. unifi_network_maps/render/svg_labels.py +184 -0
  66. unifi_network_maps/render/svg_theme.py +166 -32
  67. unifi_network_maps/render/theme.py +86 -1
  68. {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/METADATA +107 -31
  69. {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/RECORD +73 -31
  70. unifi_network_maps/assets/icons/isometric/printer.svg +0 -122
  71. {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/WHEEL +0 -0
  72. {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/entry_points.txt +0 -0
  73. {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/licenses/LICENSE +0 -0
  74. {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/top_level.txt +0 -0
@@ -25,6 +25,17 @@ mermaid:
25
25
  standard_link_width: 2
26
26
  standard_link_arrow: "none"
27
27
  svg:
28
+ icon_set: modern
29
+ background: "#1a1a1a"
30
+ wan_background: "#1e3a5f"
31
+ text:
32
+ primary: "#f5f5f5"
33
+ secondary: "#a0a0a0"
34
+ status:
35
+ online: "#66bb6a"
36
+ offline: "#ef5350"
37
+ node_side_left: "#5a5a5a"
38
+ node_side_right: "#4a4a4a"
28
39
  links:
29
40
  standard:
30
41
  from: "#66bb6a"
@@ -34,17 +45,49 @@ svg:
34
45
  to: "#1e88e5"
35
46
  nodes:
36
47
  gateway:
37
- from: "#5c3b00"
38
- to: "#3e2a00"
48
+ from: "#ffb347"
49
+ to: "#e69530"
39
50
  switch:
40
- from: "#0f2b3d"
41
- to: "#0a1f2c"
51
+ from: "#5dade2"
52
+ to: "#3498db"
42
53
  ap:
43
- from: "#0f3b2d"
44
- to: "#0b2a20"
54
+ from: "#58d68d"
55
+ to: "#2ecc71"
45
56
  client:
46
- from: "#2b1f3b"
47
- to: "#20162b"
57
+ from: "#bb8fce"
58
+ to: "#9b59b6"
48
59
  other:
49
- from: "#2a2a2a"
50
- to: "#1f1f1f"
60
+ from: "#7f8c8d"
61
+ to: "#6a7b7c"
62
+ # Extended device types - purple tint for consistency
63
+ camera:
64
+ from: "#bb8fce"
65
+ to: "#9b59b6"
66
+ tv:
67
+ from: "#bb8fce"
68
+ to: "#9b59b6"
69
+ phone:
70
+ from: "#bb8fce"
71
+ to: "#9b59b6"
72
+ printer:
73
+ from: "#bb8fce"
74
+ to: "#9b59b6"
75
+ nas:
76
+ from: "#bb8fce"
77
+ to: "#9b59b6"
78
+ speaker:
79
+ from: "#bb8fce"
80
+ to: "#9b59b6"
81
+ game_console:
82
+ from: "#bb8fce"
83
+ to: "#9b59b6"
84
+ iot:
85
+ from: "#bb8fce"
86
+ to: "#9b59b6"
87
+ # VLAN color overrides (brighter for dark mode)
88
+ vlan_colors:
89
+ 1: "#95a5a6" # Default/management - light gray
90
+ 10: "#ff6b6b" # IoT - bright red
91
+ 20: "#5dade2" # Guest - bright blue
92
+ 30: "#f7dc6f" # Trusted - bright yellow
93
+ 100: "#bb8fce" # Security/cameras - bright purple
@@ -22,6 +22,8 @@ mermaid:
22
22
  standard_link_width: 2
23
23
  standard_link_arrow: "none"
24
24
  svg:
25
+ icon_set: isometric
26
+ grid_color: "#b8bcc2"
25
27
  links:
26
28
  standard:
27
29
  from: "#2ecc71"
@@ -45,3 +47,35 @@ svg:
45
47
  other:
46
48
  from: "#eeeeee"
47
49
  to: "#d7d7d7"
50
+ # Extended device types - use client colors for consistency
51
+ camera:
52
+ from: "#f2e5ff"
53
+ to: "#e0c9ff"
54
+ tv:
55
+ from: "#f2e5ff"
56
+ to: "#e0c9ff"
57
+ phone:
58
+ from: "#f2e5ff"
59
+ to: "#e0c9ff"
60
+ printer:
61
+ from: "#f2e5ff"
62
+ to: "#e0c9ff"
63
+ nas:
64
+ from: "#f2e5ff"
65
+ to: "#e0c9ff"
66
+ speaker:
67
+ from: "#f2e5ff"
68
+ to: "#e0c9ff"
69
+ game_console:
70
+ from: "#f2e5ff"
71
+ to: "#e0c9ff"
72
+ iot:
73
+ from: "#f2e5ff"
74
+ to: "#e0c9ff"
75
+ # VLAN color overrides (auto-generated colors used for unlisted VLANs)
76
+ vlan_colors:
77
+ 1: "#6c757d" # Default/management - gray
78
+ 10: "#e74c3c" # IoT - red
79
+ 20: "#3498db" # Guest - blue
80
+ 30: "#f1c40f" # Trusted - yellow
81
+ 100: "#9b59b6" # Security/cameras - purple
@@ -0,0 +1,98 @@
1
+ mermaid:
2
+ nodes:
3
+ gateway:
4
+ fill: "#374151"
5
+ stroke: "#6b7280"
6
+ switch:
7
+ fill: "#374151"
8
+ stroke: "#6b7280"
9
+ ap:
10
+ fill: "#374151"
11
+ stroke: "#6b7280"
12
+ client:
13
+ fill: "#4b5563"
14
+ stroke: "#6b7280"
15
+ other:
16
+ fill: "#4b5563"
17
+ stroke: "#6b7280"
18
+ node_text: "#f3f4f6"
19
+ edge_label_border: "#6b7280"
20
+ edge_label_border_width: 2
21
+ poe_link: "#9ca3af"
22
+ poe_link_width: 2
23
+ poe_link_arrow: "none"
24
+ standard_link: "#6b7280"
25
+ standard_link_width: 2
26
+ standard_link_arrow: "none"
27
+ svg:
28
+ font_family: "Space Grotesk"
29
+ icon_set: isometric
30
+ icon_decal: "#d1d5db"
31
+ grid_color: "#3f4550"
32
+ node_side_left: "#4b5563"
33
+ node_side_right: "#374151"
34
+ background: "#18181b"
35
+ wan_background: "#1f2937"
36
+ text:
37
+ primary: "#f3f4f6"
38
+ secondary: "#9ca3af"
39
+ status:
40
+ online: "#9ca3af"
41
+ offline: "#6b7280"
42
+ wan_globe:
43
+ from: "#6b7280"
44
+ to: "#4b5563"
45
+ links:
46
+ standard:
47
+ from: "#6b7280"
48
+ to: "#4b5563"
49
+ poe:
50
+ from: "#9ca3af"
51
+ to: "#6b7280"
52
+ nodes:
53
+ gateway:
54
+ from: "#6b7280"
55
+ to: "#4b5563"
56
+ switch:
57
+ from: "#6b7280"
58
+ to: "#4b5563"
59
+ ap:
60
+ from: "#6b7280"
61
+ to: "#4b5563"
62
+ client:
63
+ from: "#9ca3af"
64
+ to: "#6b7280"
65
+ other:
66
+ from: "#9ca3af"
67
+ to: "#6b7280"
68
+ # Extended device types - all grayscale for minimal-dark theme
69
+ camera:
70
+ from: "#9ca3af"
71
+ to: "#6b7280"
72
+ tv:
73
+ from: "#9ca3af"
74
+ to: "#6b7280"
75
+ phone:
76
+ from: "#9ca3af"
77
+ to: "#6b7280"
78
+ printer:
79
+ from: "#9ca3af"
80
+ to: "#6b7280"
81
+ nas:
82
+ from: "#9ca3af"
83
+ to: "#6b7280"
84
+ speaker:
85
+ from: "#9ca3af"
86
+ to: "#6b7280"
87
+ game_console:
88
+ from: "#9ca3af"
89
+ to: "#6b7280"
90
+ iot:
91
+ from: "#9ca3af"
92
+ to: "#6b7280"
93
+ vlan_colors:
94
+ 1: "#6b7280"
95
+ 10: "#6b7280"
96
+ 20: "#6b7280"
97
+ 30: "#6b7280"
98
+ 100: "#6b7280"
@@ -0,0 +1,92 @@
1
+ mermaid:
2
+ nodes:
3
+ gateway:
4
+ fill: "#e5e7eb"
5
+ stroke: "#9ca3af"
6
+ switch:
7
+ fill: "#e5e7eb"
8
+ stroke: "#9ca3af"
9
+ ap:
10
+ fill: "#e5e7eb"
11
+ stroke: "#9ca3af"
12
+ client:
13
+ fill: "#f3f4f6"
14
+ stroke: "#d1d5db"
15
+ other:
16
+ fill: "#f3f4f6"
17
+ stroke: "#d1d5db"
18
+ poe_link: "#6b7280"
19
+ poe_link_width: 2
20
+ poe_link_arrow: "none"
21
+ standard_link: "#9ca3af"
22
+ standard_link_width: 2
23
+ standard_link_arrow: "none"
24
+ svg:
25
+ font_family: "Space Grotesk"
26
+ icon_set: isometric
27
+ icon_decal: "#374151"
28
+ grid_color: "#b8bcc2"
29
+ background: "#fafafa"
30
+ text:
31
+ primary: "#374151"
32
+ secondary: "#9ca3af"
33
+ status:
34
+ online: "#6b7280"
35
+ offline: "#9ca3af"
36
+ wan_globe:
37
+ from: "#d1d5db"
38
+ to: "#9ca3af"
39
+ links:
40
+ standard:
41
+ from: "#9ca3af"
42
+ to: "#6b7280"
43
+ poe:
44
+ from: "#6b7280"
45
+ to: "#4b5563"
46
+ nodes:
47
+ gateway:
48
+ from: "#e5e7eb"
49
+ to: "#d1d5db"
50
+ switch:
51
+ from: "#e5e7eb"
52
+ to: "#d1d5db"
53
+ ap:
54
+ from: "#e5e7eb"
55
+ to: "#d1d5db"
56
+ client:
57
+ from: "#f3f4f6"
58
+ to: "#e5e7eb"
59
+ other:
60
+ from: "#f3f4f6"
61
+ to: "#e5e7eb"
62
+ # Extended device types - all grayscale for minimal theme
63
+ camera:
64
+ from: "#f3f4f6"
65
+ to: "#e5e7eb"
66
+ tv:
67
+ from: "#f3f4f6"
68
+ to: "#e5e7eb"
69
+ phone:
70
+ from: "#f3f4f6"
71
+ to: "#e5e7eb"
72
+ printer:
73
+ from: "#f3f4f6"
74
+ to: "#e5e7eb"
75
+ nas:
76
+ from: "#f3f4f6"
77
+ to: "#e5e7eb"
78
+ speaker:
79
+ from: "#f3f4f6"
80
+ to: "#e5e7eb"
81
+ game_console:
82
+ from: "#f3f4f6"
83
+ to: "#e5e7eb"
84
+ iot:
85
+ from: "#f3f4f6"
86
+ to: "#e5e7eb"
87
+ vlan_colors:
88
+ 1: "#9ca3af"
89
+ 10: "#9ca3af"
90
+ 20: "#9ca3af"
91
+ 30: "#9ca3af"
92
+ 100: "#9ca3af"
@@ -0,0 +1,97 @@
1
+ mermaid:
2
+ nodes:
3
+ gateway:
4
+ fill: "#0a3058"
5
+ stroke: "#4797ff"
6
+ switch:
7
+ fill: "#0f3524"
8
+ stroke: "#37be5f"
9
+ ap:
10
+ fill: "#142952"
11
+ stroke: "#6baeff"
12
+ client:
13
+ fill: "#2a2e33"
14
+ stroke: "#808893"
15
+ other:
16
+ fill: "#32373d"
17
+ stroke: "#50565e"
18
+ node_text: "#f9fafa"
19
+ edge_label_border: "#50565e"
20
+ edge_label_border_width: 2
21
+ poe_link: "#37be5f"
22
+ poe_link_width: 2
23
+ poe_link_arrow: "none"
24
+ standard_link: "#4797ff"
25
+ standard_link_width: 2
26
+ standard_link_arrow: "none"
27
+ svg:
28
+ font_family: "Inter"
29
+ icon_set: modern
30
+ icon_decal: "#e8e8e8"
31
+ node_side_left: "#4a5060"
32
+ node_side_right: "#3a4050"
33
+ background: "#1c1e21"
34
+ wan_background: "#1a2a3d"
35
+ text:
36
+ primary: "#f9fafa"
37
+ secondary: "#808893"
38
+ status:
39
+ online: "#37be5f"
40
+ offline: "#ee6368"
41
+ wan_globe:
42
+ from: "#4797ff"
43
+ to: "#2d7ae0"
44
+ links:
45
+ standard:
46
+ from: "#4797ff"
47
+ to: "#2d7ae0"
48
+ poe:
49
+ from: "#37be5f"
50
+ to: "#2a9e4d"
51
+ nodes:
52
+ gateway:
53
+ from: "#4797ff"
54
+ to: "#2d7ae0"
55
+ switch:
56
+ from: "#4dd88a"
57
+ to: "#37be5f"
58
+ ap:
59
+ from: "#6baeff"
60
+ to: "#4797ff"
61
+ client:
62
+ from: "#6b9fff"
63
+ to: "#4a7fd9"
64
+ other:
65
+ from: "#5a6a7a"
66
+ to: "#4a5a6a"
67
+ # Extended device types - brighter blue tint
68
+ camera:
69
+ from: "#6b9fff"
70
+ to: "#4a7fd9"
71
+ tv:
72
+ from: "#6b9fff"
73
+ to: "#4a7fd9"
74
+ phone:
75
+ from: "#6b9fff"
76
+ to: "#4a7fd9"
77
+ printer:
78
+ from: "#6b9fff"
79
+ to: "#4a7fd9"
80
+ nas:
81
+ from: "#6b9fff"
82
+ to: "#4a7fd9"
83
+ speaker:
84
+ from: "#6b9fff"
85
+ to: "#4a7fd9"
86
+ game_console:
87
+ from: "#6b9fff"
88
+ to: "#4a7fd9"
89
+ iot:
90
+ from: "#6b9fff"
91
+ to: "#4a7fd9"
92
+ vlan_colors:
93
+ 1: "#9ca3ab"
94
+ 10: "#ee6368"
95
+ 20: "#4797ff"
96
+ 30: "#e79613"
97
+ 100: "#a78bfa"
@@ -0,0 +1,92 @@
1
+ mermaid:
2
+ nodes:
3
+ gateway:
4
+ fill: "#e8f1ff"
5
+ stroke: "#006fff"
6
+ switch:
7
+ fill: "#eaf7f0"
8
+ stroke: "#38cc65"
9
+ ap:
10
+ fill: "#edf3ff"
11
+ stroke: "#4797ff"
12
+ client:
13
+ fill: "#f0f2f4"
14
+ stroke: "#808893"
15
+ other:
16
+ fill: "#f4f5f6"
17
+ stroke: "#c2c6cb"
18
+ poe_link: "#38cc65"
19
+ poe_link_width: 2
20
+ poe_link_arrow: "none"
21
+ standard_link: "#006fff"
22
+ standard_link_width: 2
23
+ standard_link_arrow: "none"
24
+ svg:
25
+ font_family: "Inter"
26
+ icon_set: modern
27
+ icon_decal: "#212327"
28
+ grid_color: "#a0bde0"
29
+ background: "#f9fafa"
30
+ text:
31
+ primary: "#212327"
32
+ secondary: "#808893"
33
+ status:
34
+ online: "#38cc65"
35
+ offline: "#f0383b"
36
+ wan_globe:
37
+ from: "#4797ff"
38
+ to: "#006fff"
39
+ links:
40
+ standard:
41
+ from: "#006fff"
42
+ to: "#0560d4"
43
+ poe:
44
+ from: "#38cc65"
45
+ to: "#2aaa52"
46
+ nodes:
47
+ gateway:
48
+ from: "#2d8cff"
49
+ to: "#006fff"
50
+ switch:
51
+ from: "#4dd88a"
52
+ to: "#38cc65"
53
+ ap:
54
+ from: "#6baeff"
55
+ to: "#4797ff"
56
+ client:
57
+ from: "#a8c8ff"
58
+ to: "#8ab4f8"
59
+ other:
60
+ from: "#c8d8f0"
61
+ to: "#b0c4e8"
62
+ # Extended device types - subtle blue tint for consistency
63
+ camera:
64
+ from: "#a8c8ff"
65
+ to: "#8ab4f8"
66
+ tv:
67
+ from: "#a8c8ff"
68
+ to: "#8ab4f8"
69
+ phone:
70
+ from: "#a8c8ff"
71
+ to: "#8ab4f8"
72
+ printer:
73
+ from: "#a8c8ff"
74
+ to: "#8ab4f8"
75
+ nas:
76
+ from: "#a8c8ff"
77
+ to: "#8ab4f8"
78
+ speaker:
79
+ from: "#a8c8ff"
80
+ to: "#8ab4f8"
81
+ game_console:
82
+ from: "#a8c8ff"
83
+ to: "#8ab4f8"
84
+ iot:
85
+ from: "#a8c8ff"
86
+ to: "#8ab4f8"
87
+ vlan_colors:
88
+ 1: "#808893"
89
+ 10: "#f0383b"
90
+ 20: "#006fff"
91
+ 30: "#f5a524"
92
+ 100: "#8b5cf6"
@@ -79,6 +79,11 @@ def add_functional_args(parser: argparse._ArgumentGroup) -> None:
79
79
  default="wired",
80
80
  help="Client types to include (default: wired)",
81
81
  )
82
+ parser.add_argument(
83
+ "--collapse-clients",
84
+ action="store_true",
85
+ help="Group clients by uplink device into cluster nodes with count badges",
86
+ )
82
87
  parser.add_argument(
83
88
  "--only-unifi", action="store_true", help="Only include neighbors that are UniFi devices"
84
89
  )
@@ -119,6 +124,55 @@ def add_svg_args(parser: argparse._ArgumentGroup) -> None:
119
124
  parser.add_argument("--svg-width", type=int, default=None, help="SVG width override")
120
125
  parser.add_argument("--svg-height", type=int, default=None, help="SVG height override")
121
126
  parser.add_argument("--theme-file", default=None, help="Path to theme YAML file")
127
+ parser.add_argument(
128
+ "--theme",
129
+ choices=["unifi", "unifi-dark", "minimal", "minimal-dark", "classic", "classic-dark"],
130
+ default=None,
131
+ help="Built-in theme name (--theme-file takes priority if both specified)",
132
+ )
133
+ parser.add_argument(
134
+ "--svg-layout-mode",
135
+ choices=["physical", "grouped"],
136
+ default="physical",
137
+ help="SVG layout mode: physical (default) or grouped by network segment",
138
+ )
139
+ parser.add_argument(
140
+ "--max-vlan-colors",
141
+ type=int,
142
+ default=None,
143
+ help="Limit VLAN colors shown on edges (default: no limit)",
144
+ )
145
+ parser.add_argument(
146
+ "--include-vlan-legend",
147
+ action="store_true",
148
+ help="Add VLAN color legend to SVG output",
149
+ )
150
+ parser.add_argument(
151
+ "--wan-speed",
152
+ default=None,
153
+ help="WAN1 ISP provisioned speed (e.g., '1 Gbps ↓↑')",
154
+ )
155
+ parser.add_argument(
156
+ "--wan-label",
157
+ default=None,
158
+ help="WAN1 ISP/connection name (e.g., 'KPN Fiber')",
159
+ )
160
+ parser.add_argument(
161
+ "--wan2-speed",
162
+ default=None,
163
+ help="WAN2 ISP provisioned speed (e.g., '500 Mbps ↓↑')",
164
+ )
165
+ parser.add_argument(
166
+ "--wan2-label",
167
+ default=None,
168
+ help="WAN2 ISP/connection name (e.g., 'Backup 4G')",
169
+ )
170
+ parser.add_argument(
171
+ "--icon-set",
172
+ choices=["isometric", "modern"],
173
+ default=None,
174
+ help="Icon set to use (default: determined by theme, or 'isometric' if no theme)",
175
+ )
122
176
 
123
177
 
124
178
  def add_general_render_args(parser: argparse._ArgumentGroup) -> None:
@@ -106,18 +106,18 @@ def _handle_generate_mock(args: argparse.Namespace) -> int | None:
106
106
 
107
107
  def _load_runtime_context(
108
108
  args: argparse.Namespace,
109
- ) -> tuple[Config | None, str, list[object] | None, list[object] | None]:
109
+ ) -> tuple[Config | None, str, list[object] | None, list[object] | None, list[object] | None]:
110
110
  if args.mock_data:
111
111
  try:
112
- mock_devices, mock_clients = load_mock_data(args.mock_data)
112
+ mock_devices, mock_clients, mock_networks = load_mock_data(args.mock_data)
113
113
  except Exception as exc: # noqa: BLE001
114
114
  raise ValueError(f"Failed to load mock data: {exc}") from exc
115
- return None, "mock", mock_devices, mock_clients
115
+ return None, "mock", mock_devices, mock_clients, mock_networks
116
116
  config = _load_config(args)
117
117
  if config is None:
118
118
  raise ValueError("Config required to run")
119
119
  site = _resolve_site(args, config)
120
- return config, site, None, None
120
+ return config, site, None, None, None
121
121
 
122
122
 
123
123
  def _handle_json_format(
@@ -163,7 +163,7 @@ def main(argv: list[str] | None = None) -> int:
163
163
  if mock_result is not None:
164
164
  return mock_result
165
165
  try:
166
- config, site, mock_devices, mock_clients = _load_runtime_context(args)
166
+ config, site, mock_devices, mock_clients, mock_networks = _load_runtime_context(args)
167
167
  except ValueError as exc:
168
168
  logging.error(str(exc))
169
169
  return 2
@@ -171,11 +171,20 @@ def main(argv: list[str] | None = None) -> int:
171
171
  if payload_result is not None:
172
172
  return payload_result
173
173
  try:
174
- mermaid_theme, svg_theme = resolve_themes(args.theme_file)
174
+ mermaid_theme, svg_theme = resolve_themes(
175
+ theme_name=args.theme,
176
+ theme_file=args.theme_file,
177
+ )
175
178
  except Exception as exc: # noqa: BLE001
176
- logging.error("Failed to load theme file: %s", exc)
179
+ logging.error("Failed to load theme: %s", exc)
177
180
  return 2
178
181
 
182
+ # Apply CLI icon-set override if specified
183
+ if args.icon_set:
184
+ from dataclasses import replace
185
+
186
+ svg_theme = replace(svg_theme, icon_set=args.icon_set)
187
+
179
188
  if args.legend_only:
180
189
  legend_style = resolve_legend_style(
181
190
  format_name=args.format,
@@ -198,6 +207,7 @@ def main(argv: list[str] | None = None) -> int:
198
207
  site=site,
199
208
  mock_devices=mock_devices,
200
209
  mock_clients=mock_clients,
210
+ mock_networks=mock_networks,
201
211
  )
202
212
 
203
213
  return render_standard_format(
@@ -206,6 +216,7 @@ def main(argv: list[str] | None = None) -> int:
206
216
  site=site,
207
217
  mock_devices=mock_devices,
208
218
  mock_clients=mock_clients,
219
+ mock_networks=mock_networks,
209
220
  mermaid_theme=mermaid_theme,
210
221
  svg_theme=svg_theme,
211
222
  )