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.
- unifi_network_maps/__init__.py +1 -1
- unifi_network_maps/adapters/unifi.py +83 -101
- unifi_network_maps/assets/icons/modern/ap.svg +9 -0
- unifi_network_maps/assets/icons/modern/camera.svg +9 -0
- unifi_network_maps/assets/icons/modern/client.svg +9 -0
- unifi_network_maps/assets/icons/modern/game_console.svg +10 -0
- unifi_network_maps/assets/icons/modern/gateway.svg +17 -0
- unifi_network_maps/assets/icons/modern/iot.svg +9 -0
- unifi_network_maps/assets/icons/modern/nas.svg +9 -0
- unifi_network_maps/assets/icons/modern/other.svg +10 -0
- unifi_network_maps/assets/icons/modern/phone.svg +10 -0
- unifi_network_maps/assets/icons/modern/printer.svg +9 -0
- unifi_network_maps/assets/icons/modern/speaker.svg +10 -0
- unifi_network_maps/assets/icons/modern/switch.svg +10 -0
- unifi_network_maps/assets/icons/modern/tv.svg +10 -0
- unifi_network_maps/assets/icons/modern-flat/ap.svg +5 -0
- unifi_network_maps/assets/icons/modern-flat/camera.svg +5 -0
- unifi_network_maps/assets/icons/modern-flat/client.svg +5 -0
- unifi_network_maps/assets/icons/modern-flat/game_console.svg +6 -0
- unifi_network_maps/assets/icons/modern-flat/gateway.svg +13 -0
- unifi_network_maps/assets/icons/modern-flat/iot.svg +5 -0
- unifi_network_maps/assets/icons/modern-flat/nas.svg +5 -0
- unifi_network_maps/assets/icons/modern-flat/other.svg +6 -0
- unifi_network_maps/assets/icons/modern-flat/phone.svg +6 -0
- unifi_network_maps/assets/icons/modern-flat/printer.svg +5 -0
- unifi_network_maps/assets/icons/modern-flat/speaker.svg +6 -0
- unifi_network_maps/assets/icons/modern-flat/switch.svg +6 -0
- unifi_network_maps/assets/icons/modern-flat/tv.svg +6 -0
- unifi_network_maps/assets/themes/dark.yaml +53 -10
- unifi_network_maps/assets/themes/default.yaml +34 -0
- unifi_network_maps/assets/themes/minimal-dark.yaml +98 -0
- unifi_network_maps/assets/themes/minimal.yaml +92 -0
- unifi_network_maps/assets/themes/unifi-dark.yaml +97 -0
- unifi_network_maps/assets/themes/unifi.yaml +92 -0
- unifi_network_maps/cli/args.py +54 -0
- unifi_network_maps/cli/main.py +18 -7
- unifi_network_maps/cli/render.py +79 -27
- unifi_network_maps/cli/runtime.py +29 -15
- unifi_network_maps/io/debug.py +2 -1
- unifi_network_maps/io/export.py +19 -13
- unifi_network_maps/io/mock_data.py +5 -3
- unifi_network_maps/io/paths.py +5 -3
- unifi_network_maps/model/classify.py +199 -0
- unifi_network_maps/model/clients.py +271 -0
- unifi_network_maps/model/connection.py +37 -0
- unifi_network_maps/model/diff.py +544 -0
- unifi_network_maps/model/edges.py +558 -0
- unifi_network_maps/model/helpers.py +64 -0
- unifi_network_maps/model/lldp.py +20 -25
- unifi_network_maps/model/mock.py +110 -23
- unifi_network_maps/model/snapshot.py +294 -0
- unifi_network_maps/model/topology.py +143 -931
- unifi_network_maps/model/topology_coerce.py +339 -0
- unifi_network_maps/model/vlans.py +32 -46
- unifi_network_maps/model/wan.py +132 -0
- unifi_network_maps/render/device_ports_md.py +39 -97
- unifi_network_maps/render/device_summary.py +53 -0
- unifi_network_maps/render/lldp_md.py +29 -219
- unifi_network_maps/render/markdown_tables.py +8 -0
- unifi_network_maps/render/mermaid.py +11 -2
- unifi_network_maps/render/mkdocs.py +2 -1
- unifi_network_maps/render/svg.py +566 -908
- unifi_network_maps/render/svg_icons.py +231 -0
- unifi_network_maps/render/svg_isometric.py +1196 -0
- unifi_network_maps/render/svg_labels.py +184 -0
- unifi_network_maps/render/svg_theme.py +166 -32
- unifi_network_maps/render/theme.py +86 -1
- {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/METADATA +107 -31
- {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/RECORD +73 -31
- unifi_network_maps/assets/icons/isometric/printer.svg +0 -122
- {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/WHEEL +0 -0
- {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/entry_points.txt +0 -0
- {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {unifi_network_maps-1.4.14.dist-info → unifi_network_maps-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Icon loading and color management for SVG rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .svg_theme import SvgTheme
|
|
9
|
+
|
|
10
|
+
# Icon file mappings per icon set
|
|
11
|
+
# Isometric set uses existing icons from root and isometric/ directories
|
|
12
|
+
# New device types fall back to generic icons in isometric set
|
|
13
|
+
_ICON_FILES_ISOMETRIC = {
|
|
14
|
+
"gateway": "router-network.svg",
|
|
15
|
+
"switch": "server-network.svg",
|
|
16
|
+
"ap": "access-point.svg",
|
|
17
|
+
"camera": "laptop.svg",
|
|
18
|
+
"tv": "laptop.svg",
|
|
19
|
+
"phone": "laptop.svg",
|
|
20
|
+
"printer": "laptop.svg",
|
|
21
|
+
"nas": "server.svg",
|
|
22
|
+
"speaker": "laptop.svg",
|
|
23
|
+
"game_console": "laptop.svg",
|
|
24
|
+
"iot": "server.svg",
|
|
25
|
+
"client": "laptop.svg",
|
|
26
|
+
"client_cluster": "laptop.svg",
|
|
27
|
+
"other": "server.svg",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_ISO_ICON_FILES_ISOMETRIC = {
|
|
31
|
+
"gateway": "router.svg",
|
|
32
|
+
"switch": "switch-module.svg",
|
|
33
|
+
"ap": "tower.svg",
|
|
34
|
+
"camera": "laptop.svg",
|
|
35
|
+
"tv": "laptop.svg",
|
|
36
|
+
"phone": "laptop.svg",
|
|
37
|
+
"printer": "laptop.svg",
|
|
38
|
+
"nas": "server.svg",
|
|
39
|
+
"speaker": "laptop.svg",
|
|
40
|
+
"game_console": "laptop.svg",
|
|
41
|
+
"iot": "server.svg",
|
|
42
|
+
"client": "laptop.svg",
|
|
43
|
+
"client_cluster": "laptop.svg",
|
|
44
|
+
"other": "server.svg",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Modern set uses consistent naming in modern/ directory
|
|
48
|
+
_ICON_FILES_MODERN = {
|
|
49
|
+
"gateway": "gateway.svg",
|
|
50
|
+
"switch": "switch.svg",
|
|
51
|
+
"ap": "ap.svg",
|
|
52
|
+
"camera": "camera.svg",
|
|
53
|
+
"tv": "tv.svg",
|
|
54
|
+
"phone": "phone.svg",
|
|
55
|
+
"printer": "printer.svg",
|
|
56
|
+
"nas": "nas.svg",
|
|
57
|
+
"speaker": "speaker.svg",
|
|
58
|
+
"game_console": "game_console.svg",
|
|
59
|
+
"iot": "iot.svg",
|
|
60
|
+
"client": "client.svg",
|
|
61
|
+
"client_cluster": "client.svg",
|
|
62
|
+
"other": "other.svg",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Icon set registry: maps set names to (flat_dir, iso_dir, flat_files, iso_files)
|
|
66
|
+
_ICON_SETS = {
|
|
67
|
+
"isometric": (
|
|
68
|
+
"", # Flat icons in root icons/ directory
|
|
69
|
+
"isometric", # Isometric icons in isometric/ subdirectory
|
|
70
|
+
_ICON_FILES_ISOMETRIC,
|
|
71
|
+
_ISO_ICON_FILES_ISOMETRIC,
|
|
72
|
+
),
|
|
73
|
+
"modern": (
|
|
74
|
+
"modern-flat", # Flat icons for orthogonal SVG
|
|
75
|
+
"modern", # Isometric icons for iso SVG
|
|
76
|
+
_ICON_FILES_MODERN,
|
|
77
|
+
_ICON_FILES_MODERN,
|
|
78
|
+
),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Node type fill/stroke colors for orthogonal rendering
|
|
82
|
+
_TYPE_COLORS = {
|
|
83
|
+
"gateway": ("#ffd199", "#f08a00"),
|
|
84
|
+
"switch": ("#bfe4ff", "#1c6dd0"),
|
|
85
|
+
"ap": ("#c4f2d4", "#1f9a50"),
|
|
86
|
+
"camera": ("#b3e5fc", "#0277bd"),
|
|
87
|
+
"tv": ("#d1c4e9", "#512da8"),
|
|
88
|
+
"phone": ("#c8e6c9", "#388e3c"),
|
|
89
|
+
"printer": ("#cfd8dc", "#546e7a"),
|
|
90
|
+
"nas": ("#ffe0b2", "#e65100"),
|
|
91
|
+
"speaker": ("#b2dfdb", "#00796b"),
|
|
92
|
+
"game_console": ("#e1bee7", "#7b1fa2"),
|
|
93
|
+
"iot": ("#b2ebf2", "#00838f"),
|
|
94
|
+
"client": ("#e4ccff", "#6b2fb4"),
|
|
95
|
+
"client_cluster": ("#d4b8ff", "#5a25a0"),
|
|
96
|
+
"other": ("#e3e3e3", "#7b7b7b"),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Type ordering for layout sorting
|
|
100
|
+
_TYPE_ORDER = [
|
|
101
|
+
"gateway",
|
|
102
|
+
"switch",
|
|
103
|
+
"ap",
|
|
104
|
+
"camera",
|
|
105
|
+
"tv",
|
|
106
|
+
"phone",
|
|
107
|
+
"printer",
|
|
108
|
+
"nas",
|
|
109
|
+
"speaker",
|
|
110
|
+
"game_console",
|
|
111
|
+
"iot",
|
|
112
|
+
"client",
|
|
113
|
+
"client_cluster",
|
|
114
|
+
"other",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _darken_hex(color: str, factor: float = 0.35) -> str:
|
|
119
|
+
"""Darken a hex color by *factor* (0..1). Returns 6-digit hex."""
|
|
120
|
+
c = color.lstrip("#")
|
|
121
|
+
if len(c) != 6:
|
|
122
|
+
return color
|
|
123
|
+
r, g, b = int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16)
|
|
124
|
+
m = 1.0 - factor
|
|
125
|
+
return f"#{int(r * m):02x}{int(g * m):02x}{int(b * m):02x}"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _build_decal_colors(theme: SvgTheme, factor: float = 0.35) -> dict[str, str]:
|
|
129
|
+
"""Derive per-type icon decal colors by darkening each node's gradient end."""
|
|
130
|
+
node_attrs = {
|
|
131
|
+
"gateway": theme.node_gateway,
|
|
132
|
+
"switch": theme.node_switch,
|
|
133
|
+
"ap": theme.node_ap,
|
|
134
|
+
"client": theme.node_client,
|
|
135
|
+
"other": theme.node_other,
|
|
136
|
+
"camera": theme.node_camera,
|
|
137
|
+
"tv": theme.node_tv,
|
|
138
|
+
"phone": theme.node_phone,
|
|
139
|
+
"printer": theme.node_printer,
|
|
140
|
+
"nas": theme.node_nas,
|
|
141
|
+
"speaker": theme.node_speaker,
|
|
142
|
+
"game_console": theme.node_game_console,
|
|
143
|
+
"iot": theme.node_iot,
|
|
144
|
+
"client_cluster": theme.node_client_cluster,
|
|
145
|
+
}
|
|
146
|
+
return {name: _darken_hex(pair[1], factor) for name, pair in node_attrs.items()}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _load_icons(icon_set: str = "isometric", decal_color: str = "#1a1a1a") -> dict[str, str]:
|
|
150
|
+
"""Load flat (non-isometric) icons for the specified icon set.
|
|
151
|
+
|
|
152
|
+
Falls back to isometric icons if the requested icon is not found in the set.
|
|
153
|
+
Modern icons use #DECAL0 as placeholder which gets replaced with decal_color.
|
|
154
|
+
"""
|
|
155
|
+
base = Path(__file__).resolve().parents[1] / "assets" / "icons"
|
|
156
|
+
icons: dict[str, str] = {}
|
|
157
|
+
|
|
158
|
+
set_config = _ICON_SETS.get(icon_set, _ICON_SETS["isometric"])
|
|
159
|
+
subdir, _, file_map, _ = set_config
|
|
160
|
+
|
|
161
|
+
fallback_config = _ICON_SETS["isometric"]
|
|
162
|
+
fallback_subdir, _, fallback_files, _ = fallback_config
|
|
163
|
+
|
|
164
|
+
for node_type in _ICON_FILES_ISOMETRIC.keys():
|
|
165
|
+
filename = file_map.get(node_type)
|
|
166
|
+
if filename:
|
|
167
|
+
path = base / subdir / filename if subdir else base / filename
|
|
168
|
+
if path.exists():
|
|
169
|
+
data = path.read_text(encoding="utf-8")
|
|
170
|
+
data = data.replace("#DECAL0", decal_color)
|
|
171
|
+
encoded = base64.b64encode(data.encode("utf-8")).decode("ascii")
|
|
172
|
+
icons[node_type] = f"data:image/svg+xml;base64,{encoded}"
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
fallback_filename = fallback_files.get(node_type)
|
|
176
|
+
if fallback_filename:
|
|
177
|
+
fallback_path = (
|
|
178
|
+
base / fallback_subdir / fallback_filename
|
|
179
|
+
if fallback_subdir
|
|
180
|
+
else base / fallback_filename
|
|
181
|
+
)
|
|
182
|
+
if fallback_path.exists():
|
|
183
|
+
data = fallback_path.read_bytes()
|
|
184
|
+
encoded = base64.b64encode(data).decode("ascii")
|
|
185
|
+
icons[node_type] = f"data:image/svg+xml;base64,{encoded}"
|
|
186
|
+
|
|
187
|
+
return icons
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _load_isometric_icons(
|
|
191
|
+
icon_set: str = "isometric",
|
|
192
|
+
decal_color: str = "#5A6878",
|
|
193
|
+
decal_colors: dict[str, str] | None = None,
|
|
194
|
+
) -> dict[str, str]:
|
|
195
|
+
"""Load isometric icons for the specified icon set.
|
|
196
|
+
|
|
197
|
+
Falls back to isometric icons if the requested icon is not found in the set.
|
|
198
|
+
Modern icons use #DECAL0 as placeholder which gets replaced with a per-type
|
|
199
|
+
color from *decal_colors* (falling back to *decal_color*).
|
|
200
|
+
"""
|
|
201
|
+
base = Path(__file__).resolve().parents[1] / "assets" / "icons"
|
|
202
|
+
icons: dict[str, str] = {}
|
|
203
|
+
|
|
204
|
+
set_config = _ICON_SETS.get(icon_set, _ICON_SETS["isometric"])
|
|
205
|
+
_, iso_subdir, _, iso_file_map = set_config
|
|
206
|
+
|
|
207
|
+
fallback_config = _ICON_SETS["isometric"]
|
|
208
|
+
_, fallback_iso_subdir, _, fallback_iso_files = fallback_config
|
|
209
|
+
|
|
210
|
+
for node_type in _ISO_ICON_FILES_ISOMETRIC.keys():
|
|
211
|
+
filename = iso_file_map.get(node_type)
|
|
212
|
+
if filename:
|
|
213
|
+
path = base / iso_subdir / filename
|
|
214
|
+
if path.exists():
|
|
215
|
+
content = path.read_text(encoding="utf-8")
|
|
216
|
+
color = decal_colors.get(node_type, decal_color) if decal_colors else decal_color
|
|
217
|
+
content = content.replace("#DECAL0", color)
|
|
218
|
+
data = content.encode("utf-8")
|
|
219
|
+
encoded = base64.b64encode(data).decode("ascii")
|
|
220
|
+
icons[node_type] = f"data:image/svg+xml;base64,{encoded}"
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
fallback_filename = fallback_iso_files.get(node_type)
|
|
224
|
+
if fallback_filename:
|
|
225
|
+
fallback_path = base / fallback_iso_subdir / fallback_filename
|
|
226
|
+
if fallback_path.exists():
|
|
227
|
+
data = fallback_path.read_bytes()
|
|
228
|
+
encoded = base64.b64encode(data).decode("ascii")
|
|
229
|
+
icons[node_type] = f"data:image/svg+xml;base64,{encoded}"
|
|
230
|
+
|
|
231
|
+
return icons
|