unifi-network-maps 1.4.4__py3-none-any.whl → 1.4.6__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/cli/__init__.py +2 -38
- unifi_network_maps/cli/args.py +166 -0
- unifi_network_maps/cli/main.py +23 -753
- unifi_network_maps/cli/render.py +255 -0
- unifi_network_maps/cli/runtime.py +157 -0
- unifi_network_maps/io/mkdocs_assets.py +21 -0
- unifi_network_maps/io/mock_generate.py +2 -294
- unifi_network_maps/model/mock.py +307 -0
- unifi_network_maps/render/device_ports_md.py +44 -27
- unifi_network_maps/render/legend.py +30 -0
- unifi_network_maps/render/lldp_md.py +81 -60
- unifi_network_maps/render/markdown_tables.py +21 -0
- unifi_network_maps/render/mermaid.py +72 -85
- unifi_network_maps/render/mkdocs.py +167 -0
- unifi_network_maps/render/svg.py +34 -3
- unifi_network_maps/render/templates/device_port_block.md.j2 +5 -0
- unifi_network_maps/render/templates/legend_compact.html.j2 +14 -0
- unifi_network_maps/render/templates/lldp_device_section.md.j2 +15 -0
- unifi_network_maps/render/templates/markdown_section.md.j2 +3 -0
- unifi_network_maps/render/templates/mermaid_legend.mmd.j2 +30 -0
- unifi_network_maps/render/templates/mkdocs_document.md.j2 +23 -0
- unifi_network_maps/render/templates/mkdocs_dual_theme_style.html.j2 +8 -0
- unifi_network_maps/render/templates/mkdocs_html_block.html.j2 +2 -0
- unifi_network_maps/render/templates/mkdocs_legend.css.j2 +29 -0
- unifi_network_maps/render/templates/mkdocs_legend.js.j2 +18 -0
- unifi_network_maps/render/templates/mkdocs_mermaid_block.md.j2 +4 -0
- unifi_network_maps/render/templating.py +19 -0
- {unifi_network_maps-1.4.4.dist-info → unifi_network_maps-1.4.6.dist-info}/METADATA +7 -1
- {unifi_network_maps-1.4.4.dist-info → unifi_network_maps-1.4.6.dist-info}/RECORD +34 -14
- {unifi_network_maps-1.4.4.dist-info → unifi_network_maps-1.4.6.dist-info}/WHEEL +0 -0
- {unifi_network_maps-1.4.4.dist-info → unifi_network_maps-1.4.6.dist-info}/entry_points.txt +0 -0
- {unifi_network_maps-1.4.4.dist-info → unifi_network_maps-1.4.6.dist-info}/licenses/LICENSE +0 -0
- {unifi_network_maps-1.4.4.dist-info → unifi_network_maps-1.4.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Legend rendering helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .mermaid import render_legend, render_legend_compact
|
|
6
|
+
from .mermaid_theme import MermaidTheme
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_legend_style(*, format_name: str, legend_style: str) -> str:
|
|
10
|
+
if legend_style == "auto":
|
|
11
|
+
return "compact" if format_name == "mkdocs" else "diagram"
|
|
12
|
+
return legend_style
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def render_legend_only(
|
|
16
|
+
*,
|
|
17
|
+
legend_style: str,
|
|
18
|
+
legend_scale: float,
|
|
19
|
+
markdown: bool,
|
|
20
|
+
theme: MermaidTheme,
|
|
21
|
+
) -> str:
|
|
22
|
+
if legend_style == "compact":
|
|
23
|
+
content = "# Legend\n\n" + render_legend_compact(theme=theme)
|
|
24
|
+
else:
|
|
25
|
+
content = render_legend(theme=theme, legend_scale=legend_scale)
|
|
26
|
+
if markdown:
|
|
27
|
+
content = f"""```mermaid
|
|
28
|
+
{content}```
|
|
29
|
+
"""
|
|
30
|
+
return content
|
|
@@ -7,6 +7,8 @@ from collections.abc import Iterable
|
|
|
7
7
|
from ..model.lldp import LLDPEntry, local_port_label
|
|
8
8
|
from ..model.topology import Device, build_client_port_map, build_device_index, build_port_map
|
|
9
9
|
from .device_ports_md import render_device_port_details
|
|
10
|
+
from .markdown_tables import markdown_table_lines
|
|
11
|
+
from .templating import render_template
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def _normalize_mac(value: str) -> str:
|
|
@@ -78,10 +80,6 @@ def _lldp_sort_key(entry: LLDPEntry) -> tuple[int, str, str]:
|
|
|
78
80
|
return (int(port_number or 0), port_label, entry.port_id)
|
|
79
81
|
|
|
80
82
|
|
|
81
|
-
def _device_header_lines(device: Device) -> list[str]:
|
|
82
|
-
return [f"## {device.name}"]
|
|
83
|
-
|
|
84
|
-
|
|
85
83
|
def _port_summary(device: Device) -> str:
|
|
86
84
|
ports = [port for port in device.port_table if port.port_idx is not None]
|
|
87
85
|
if not ports:
|
|
@@ -140,23 +138,20 @@ def _details_table_lines(
|
|
|
140
138
|
) -> list[str]:
|
|
141
139
|
wired_count, client_sample = _client_summary(device, client_rows)
|
|
142
140
|
client_label = f"Clients ({client_mode})"
|
|
143
|
-
|
|
144
|
-
"
|
|
145
|
-
"",
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
f"| Ports | {_escape_cell(_port_summary(device))} |",
|
|
155
|
-
f"| PoE | {_escape_cell(_poe_summary(device))} |",
|
|
156
|
-
f"| {client_label} | {_escape_cell(wired_count)} |",
|
|
157
|
-
f"| Client examples | {_escape_cell(client_sample)} |",
|
|
158
|
-
"",
|
|
141
|
+
rows = [
|
|
142
|
+
["Model", _escape_cell(device.model_name or device.type or "-")],
|
|
143
|
+
["Type", _escape_cell(device.type or "-")],
|
|
144
|
+
["IP", _escape_cell(device.ip or "-")],
|
|
145
|
+
["MAC", _escape_cell(device.mac or "-")],
|
|
146
|
+
["Firmware", _escape_cell(device.version or "-")],
|
|
147
|
+
["Uplink", _escape_cell(_uplink_summary(device))],
|
|
148
|
+
["Ports", _escape_cell(_port_summary(device))],
|
|
149
|
+
["PoE", _escape_cell(_poe_summary(device))],
|
|
150
|
+
[client_label, _escape_cell(wired_count)],
|
|
151
|
+
["Client examples", _escape_cell(client_sample)],
|
|
159
152
|
]
|
|
153
|
+
lines = ["### Details", ""]
|
|
154
|
+
lines.extend(markdown_table_lines(["Field", "Value"], rows))
|
|
160
155
|
return lines
|
|
161
156
|
|
|
162
157
|
|
|
@@ -241,7 +236,6 @@ def _prepare_lldp_maps(
|
|
|
241
236
|
|
|
242
237
|
|
|
243
238
|
def _render_device_lldp_section(
|
|
244
|
-
lines: list[str],
|
|
245
239
|
device: Device,
|
|
246
240
|
*,
|
|
247
241
|
device_index: dict[str, str],
|
|
@@ -251,40 +245,58 @@ def _render_device_lldp_section(
|
|
|
251
245
|
include_ports: bool,
|
|
252
246
|
show_clients: bool,
|
|
253
247
|
client_mode: str,
|
|
254
|
-
) ->
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
lines.extend(_details_table_lines(device, client_rows, client_mode))
|
|
248
|
+
) -> str:
|
|
249
|
+
details = "\n".join(_details_table_lines(device, client_rows, client_mode)).rstrip()
|
|
250
|
+
ports_section = ""
|
|
258
251
|
if include_ports:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
252
|
+
ports_section = "\n".join(
|
|
253
|
+
[
|
|
254
|
+
"### Ports",
|
|
255
|
+
"",
|
|
256
|
+
render_device_port_details(device, port_map, client_ports=client_port_map).strip(),
|
|
257
|
+
]
|
|
258
|
+
).rstrip()
|
|
264
259
|
if device.lldp_info:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
260
|
+
lldp_section = "\n".join(
|
|
261
|
+
markdown_table_lines(
|
|
262
|
+
["Local Port", "Neighbor", "Neighbor Port", "Chassis ID", "Port Description"],
|
|
263
|
+
_lldp_rows(device.lldp_info, device_index),
|
|
264
|
+
escape=_escape_cell,
|
|
265
|
+
)
|
|
266
|
+
).rstrip()
|
|
271
267
|
else:
|
|
272
|
-
|
|
273
|
-
|
|
268
|
+
lldp_section = "_No LLDP neighbors._"
|
|
269
|
+
clients_section = ""
|
|
274
270
|
rows = client_rows.get(device.name)
|
|
275
271
|
if rows and show_clients:
|
|
276
|
-
lines.append("")
|
|
277
|
-
lines.append("### Clients")
|
|
278
272
|
if include_ports:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
273
|
+
clients_section = "\n".join(
|
|
274
|
+
[
|
|
275
|
+
"### Clients",
|
|
276
|
+
"",
|
|
277
|
+
"\n".join(
|
|
278
|
+
markdown_table_lines(
|
|
279
|
+
["Client", "Port"],
|
|
280
|
+
[
|
|
281
|
+
[_escape_cell(client_name), _escape_cell(port_label or "-")]
|
|
282
|
+
for client_name, port_label in rows
|
|
283
|
+
],
|
|
284
|
+
)
|
|
285
|
+
),
|
|
286
|
+
]
|
|
287
|
+
).rstrip()
|
|
284
288
|
else:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
289
|
+
clients_section = "\n".join(
|
|
290
|
+
["### Clients", *[f"- {_escape_cell(name)}" for name, _ in rows]]
|
|
291
|
+
).rstrip()
|
|
292
|
+
return render_template(
|
|
293
|
+
"lldp_device_section.md.j2",
|
|
294
|
+
device_name=device.name,
|
|
295
|
+
details=details,
|
|
296
|
+
ports_section=ports_section,
|
|
297
|
+
lldp_section=lldp_section,
|
|
298
|
+
clients_section=clients_section,
|
|
299
|
+
).rstrip()
|
|
288
300
|
|
|
289
301
|
|
|
290
302
|
def render_lldp_md(
|
|
@@ -303,17 +315,26 @@ def render_lldp_md(
|
|
|
303
315
|
show_clients=show_clients,
|
|
304
316
|
client_mode=client_mode,
|
|
305
317
|
)
|
|
306
|
-
|
|
318
|
+
sections: list[str] = []
|
|
307
319
|
for device in sorted(devices, key=lambda item: item.name.lower()):
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
320
|
+
sections.append(
|
|
321
|
+
_render_device_lldp_section(
|
|
322
|
+
device,
|
|
323
|
+
device_index=device_index,
|
|
324
|
+
port_map=port_map,
|
|
325
|
+
client_port_map=client_port_map,
|
|
326
|
+
client_rows=client_rows,
|
|
327
|
+
include_ports=include_ports,
|
|
328
|
+
show_clients=show_clients,
|
|
329
|
+
client_mode=client_mode,
|
|
330
|
+
)
|
|
318
331
|
)
|
|
319
|
-
|
|
332
|
+
body = "\n\n".join(section for section in sections if section).rstrip()
|
|
333
|
+
return (
|
|
334
|
+
render_template(
|
|
335
|
+
"markdown_section.md.j2",
|
|
336
|
+
title="LLDP Neighbors",
|
|
337
|
+
body=body,
|
|
338
|
+
).rstrip()
|
|
339
|
+
+ "\n"
|
|
340
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Markdown table helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def markdown_table_lines(
|
|
9
|
+
headers: list[str],
|
|
10
|
+
rows: Iterable[Iterable[str]],
|
|
11
|
+
*,
|
|
12
|
+
escape: Callable[[str], str] | None = None,
|
|
13
|
+
) -> list[str]:
|
|
14
|
+
esc = escape or (lambda value: value)
|
|
15
|
+
lines = [
|
|
16
|
+
"| " + " | ".join(headers) + " |",
|
|
17
|
+
"| " + " | ".join(["---"] * len(headers)) + " |",
|
|
18
|
+
]
|
|
19
|
+
for row in rows:
|
|
20
|
+
lines.append("| " + " | ".join(esc(cell) for cell in row) + " |")
|
|
21
|
+
return lines
|
|
@@ -7,6 +7,7 @@ from collections.abc import Iterable
|
|
|
7
7
|
|
|
8
8
|
from ..model.topology import Edge
|
|
9
9
|
from .mermaid_theme import DEFAULT_THEME, MermaidTheme, class_defs
|
|
10
|
+
from .templating import render_template
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def _escape(label: str) -> str:
|
|
@@ -193,94 +194,80 @@ def render_legend(theme: MermaidTheme = DEFAULT_THEME, *, legend_scale: float =
|
|
|
193
194
|
node_spacing = max(10, round(50 * scale))
|
|
194
195
|
rank_spacing = max(10, round(50 * scale))
|
|
195
196
|
node_padding = max(4, round(12 * scale))
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
" legend_poe_a ---|⚡| legend_poe_b;",
|
|
213
|
-
" legend_no_poe_a --- legend_no_poe_b;",
|
|
214
|
-
" linkStyle 0 arrowhead:none;",
|
|
215
|
-
" linkStyle 1 arrowhead:none;",
|
|
216
|
-
" end",
|
|
217
|
-
" class legend_gateway node_gateway;",
|
|
218
|
-
" class legend_switch node_switch;",
|
|
219
|
-
" class legend_ap node_ap;",
|
|
220
|
-
" class legend_client node_client;",
|
|
221
|
-
" class legend_other node_other;",
|
|
222
|
-
" class legend_poe_a node_legend;",
|
|
223
|
-
" class legend_poe_b node_legend;",
|
|
224
|
-
" class legend_no_poe_a node_legend;",
|
|
225
|
-
" class legend_no_poe_b node_legend;",
|
|
226
|
-
]
|
|
227
|
-
lines.extend(class_defs(theme))
|
|
228
|
-
lines.append(f" classDef node_legend font-size:{legend_font_size}px;")
|
|
229
|
-
lines.append(
|
|
230
|
-
" linkStyle 0 "
|
|
231
|
-
f"stroke:{theme.poe_link},stroke-width:{poe_link_width}px,"
|
|
232
|
-
f"arrowhead:{theme.poe_link_arrow};"
|
|
233
|
-
)
|
|
234
|
-
lines.append(
|
|
235
|
-
" linkStyle 1 "
|
|
236
|
-
f"stroke:{theme.standard_link},stroke-width:{standard_link_width}px,"
|
|
237
|
-
f"arrowhead:{theme.standard_link_arrow};"
|
|
197
|
+
return (
|
|
198
|
+
render_template(
|
|
199
|
+
"mermaid_legend.mmd.j2",
|
|
200
|
+
node_spacing=node_spacing,
|
|
201
|
+
rank_spacing=rank_spacing,
|
|
202
|
+
legend_font_size=legend_font_size,
|
|
203
|
+
node_padding=node_padding,
|
|
204
|
+
class_defs="\n".join(class_defs(theme)),
|
|
205
|
+
poe_link=theme.poe_link,
|
|
206
|
+
poe_link_width=poe_link_width,
|
|
207
|
+
poe_link_arrow=theme.poe_link_arrow,
|
|
208
|
+
standard_link=theme.standard_link,
|
|
209
|
+
standard_link_width=standard_link_width,
|
|
210
|
+
standard_link_arrow=theme.standard_link_arrow,
|
|
211
|
+
).rstrip()
|
|
212
|
+
+ "\n"
|
|
238
213
|
)
|
|
239
|
-
return "\n".join(lines) + "\n"
|
|
240
214
|
|
|
241
215
|
|
|
242
216
|
def render_legend_compact(theme: MermaidTheme = DEFAULT_THEME) -> str:
|
|
243
|
-
def swatch(fill: str, stroke: str, label: str) -> str:
|
|
244
|
-
return (
|
|
245
|
-
f'<span style="display:inline-block;width:12px;height:12px;'
|
|
246
|
-
f"background:{fill};border:1px solid {stroke};border-radius:2px;"
|
|
247
|
-
f'margin-right:6px;"></span>{label}'
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
def line_sample(
|
|
251
|
-
color: str,
|
|
252
|
-
width: int,
|
|
253
|
-
*,
|
|
254
|
-
dashed: bool = False,
|
|
255
|
-
label: str = "",
|
|
256
|
-
bolt: bool = False,
|
|
257
|
-
) -> str:
|
|
258
|
-
dash = ' stroke-dasharray="5 4"' if dashed else ""
|
|
259
|
-
bolt_suffix = " ⚡" if bolt else ""
|
|
260
|
-
return (
|
|
261
|
-
f'<span style="display:inline-flex;align-items:center;gap:6px;">'
|
|
262
|
-
f'<svg width="42" height="10" viewBox="0 0 42 10" '
|
|
263
|
-
f'xmlns="http://www.w3.org/2000/svg" aria-hidden="true">'
|
|
264
|
-
f'<line x1="2" y1="5" x2="40" y2="5" stroke="{color}" '
|
|
265
|
-
f'stroke-width="{max(1, width)}"{dash} />'
|
|
266
|
-
f"</svg>{label}{bolt_suffix}</span>"
|
|
267
|
-
)
|
|
268
|
-
|
|
269
217
|
rows = [
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
218
|
+
{
|
|
219
|
+
"kind": "swatch",
|
|
220
|
+
"fill": theme.node_gateway[0],
|
|
221
|
+
"stroke": theme.node_gateway[1],
|
|
222
|
+
"label": "Gateway",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"kind": "swatch",
|
|
226
|
+
"fill": theme.node_switch[0],
|
|
227
|
+
"stroke": theme.node_switch[1],
|
|
228
|
+
"label": "Switch",
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"kind": "swatch",
|
|
232
|
+
"fill": theme.node_ap[0],
|
|
233
|
+
"stroke": theme.node_ap[1],
|
|
234
|
+
"label": "AP",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"kind": "swatch",
|
|
238
|
+
"fill": theme.node_client[0],
|
|
239
|
+
"stroke": theme.node_client[1],
|
|
240
|
+
"label": "Client",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"kind": "swatch",
|
|
244
|
+
"fill": theme.node_other[0],
|
|
245
|
+
"stroke": theme.node_other[1],
|
|
246
|
+
"label": "Other",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"kind": "line",
|
|
250
|
+
"color": theme.poe_link,
|
|
251
|
+
"width": max(1, theme.poe_link_width),
|
|
252
|
+
"dashed": False,
|
|
253
|
+
"label": "PoE",
|
|
254
|
+
"bolt": True,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"kind": "line",
|
|
258
|
+
"color": theme.standard_link,
|
|
259
|
+
"width": max(1, theme.standard_link_width),
|
|
260
|
+
"dashed": False,
|
|
261
|
+
"label": "Link",
|
|
262
|
+
"bolt": False,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"kind": "line",
|
|
266
|
+
"color": theme.standard_link,
|
|
267
|
+
"width": max(1, theme.standard_link_width),
|
|
268
|
+
"dashed": True,
|
|
269
|
+
"label": "Wireless",
|
|
270
|
+
"bolt": False,
|
|
271
|
+
},
|
|
278
272
|
]
|
|
279
|
-
|
|
280
|
-
'<table class="unifi-legend-table">',
|
|
281
|
-
"<tbody>",
|
|
282
|
-
]
|
|
283
|
-
lines.extend(f" <tr><td>{style}</td></tr>" for style in rows)
|
|
284
|
-
lines.append("</tbody>")
|
|
285
|
-
lines.append("</table>")
|
|
286
|
-
return "\n".join(lines) + "\n"
|
|
273
|
+
return render_template("legend_compact.html.j2", rows=rows)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""MkDocs-specific rendering helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from zoneinfo import ZoneInfo
|
|
9
|
+
|
|
10
|
+
from ..model.topology import ClientPortMap, Device, PortMap, build_node_type_map
|
|
11
|
+
from .device_ports_md import render_device_port_overview
|
|
12
|
+
from .mermaid import render_legend, render_legend_compact, render_mermaid
|
|
13
|
+
from .mermaid_theme import MermaidTheme
|
|
14
|
+
from .templating import render_template
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class MkdocsRenderOptions:
|
|
21
|
+
direction: str
|
|
22
|
+
legend_style: str
|
|
23
|
+
legend_scale: float
|
|
24
|
+
timestamp_zone: str
|
|
25
|
+
client_scope: str
|
|
26
|
+
dual_theme: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def render_mkdocs(
|
|
30
|
+
edges: list,
|
|
31
|
+
devices: list[Device],
|
|
32
|
+
*,
|
|
33
|
+
mermaid_theme: MermaidTheme,
|
|
34
|
+
port_map: PortMap,
|
|
35
|
+
client_ports: ClientPortMap | None,
|
|
36
|
+
options: MkdocsRenderOptions,
|
|
37
|
+
dark_mermaid_theme: MermaidTheme | None = None,
|
|
38
|
+
) -> str:
|
|
39
|
+
clients = None
|
|
40
|
+
node_types = build_node_type_map(devices, clients, client_mode=options.client_scope)
|
|
41
|
+
content = render_mermaid(
|
|
42
|
+
edges,
|
|
43
|
+
direction=options.direction,
|
|
44
|
+
node_types=node_types,
|
|
45
|
+
theme=mermaid_theme,
|
|
46
|
+
)
|
|
47
|
+
dual_theme = options.dual_theme and dark_mermaid_theme is not None
|
|
48
|
+
legend_title = "Legend" if options.legend_style != "compact" else ""
|
|
49
|
+
if dual_theme and dark_mermaid_theme is not None:
|
|
50
|
+
dark_content = render_mermaid(
|
|
51
|
+
edges,
|
|
52
|
+
direction=options.direction,
|
|
53
|
+
node_types=node_types,
|
|
54
|
+
theme=dark_mermaid_theme,
|
|
55
|
+
)
|
|
56
|
+
map_block = _mkdocs_dual_mermaid_block(content, dark_content, base_class="unifi-mermaid")
|
|
57
|
+
legend_block = _mkdocs_dual_legend_block(
|
|
58
|
+
options.legend_style,
|
|
59
|
+
mermaid_theme=mermaid_theme,
|
|
60
|
+
dark_mermaid_theme=dark_mermaid_theme,
|
|
61
|
+
legend_scale=options.legend_scale,
|
|
62
|
+
)
|
|
63
|
+
dual_style = _mkdocs_dual_theme_style()
|
|
64
|
+
else:
|
|
65
|
+
map_block = _mkdocs_mermaid_block(content, class_name="unifi-mermaid")
|
|
66
|
+
legend_block = _mkdocs_single_legend_block(
|
|
67
|
+
options.legend_style,
|
|
68
|
+
mermaid_theme=mermaid_theme,
|
|
69
|
+
legend_scale=options.legend_scale,
|
|
70
|
+
)
|
|
71
|
+
dual_style = ""
|
|
72
|
+
return render_template(
|
|
73
|
+
"mkdocs_document.md.j2",
|
|
74
|
+
title="UniFi network",
|
|
75
|
+
timestamp_line=_timestamp_line(options.timestamp_zone),
|
|
76
|
+
dual_style=dual_style,
|
|
77
|
+
map_block=map_block,
|
|
78
|
+
legend_title=legend_title,
|
|
79
|
+
legend_block=legend_block,
|
|
80
|
+
device_overview=render_device_port_overview(
|
|
81
|
+
devices, port_map, client_ports=client_ports
|
|
82
|
+
).rstrip()
|
|
83
|
+
+ "\n",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _timestamp_line(timestamp_zone: str) -> str:
|
|
88
|
+
if timestamp_zone.strip().lower() in {"off", "none", "false"}:
|
|
89
|
+
return ""
|
|
90
|
+
try:
|
|
91
|
+
zone = ZoneInfo(timestamp_zone)
|
|
92
|
+
except Exception as exc: # noqa: BLE001
|
|
93
|
+
logger.warning("Invalid mkdocs timestamp zone '%s': %s", timestamp_zone, exc)
|
|
94
|
+
return ""
|
|
95
|
+
generated_at = datetime.now(zone).strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
96
|
+
return f"Generated: {generated_at}"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _mkdocs_mermaid_block(content: str, *, class_name: str) -> str:
|
|
100
|
+
return render_template(
|
|
101
|
+
"mkdocs_mermaid_block.md.j2",
|
|
102
|
+
class_name=class_name,
|
|
103
|
+
content=content,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _mkdocs_dual_mermaid_block(
|
|
108
|
+
light_content: str,
|
|
109
|
+
dark_content: str,
|
|
110
|
+
*,
|
|
111
|
+
base_class: str,
|
|
112
|
+
) -> str:
|
|
113
|
+
light = _mkdocs_mermaid_block(light_content, class_name=f"{base_class} {base_class}--light")
|
|
114
|
+
dark = _mkdocs_mermaid_block(dark_content, class_name=f"{base_class} {base_class}--dark")
|
|
115
|
+
return f"{light}\n{dark}"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _mkdocs_single_legend_block(
|
|
119
|
+
legend_style: str,
|
|
120
|
+
*,
|
|
121
|
+
mermaid_theme: MermaidTheme,
|
|
122
|
+
legend_scale: float,
|
|
123
|
+
) -> str:
|
|
124
|
+
if legend_style == "compact":
|
|
125
|
+
return render_template(
|
|
126
|
+
"mkdocs_html_block.html.j2",
|
|
127
|
+
class_name="unifi-legend",
|
|
128
|
+
data_unifi_legend=True,
|
|
129
|
+
content=render_legend_compact(theme=mermaid_theme),
|
|
130
|
+
)
|
|
131
|
+
return "```mermaid\n" + render_legend(theme=mermaid_theme, legend_scale=legend_scale) + "```"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _mkdocs_dual_legend_block(
|
|
135
|
+
legend_style: str,
|
|
136
|
+
*,
|
|
137
|
+
mermaid_theme: MermaidTheme,
|
|
138
|
+
dark_mermaid_theme: MermaidTheme,
|
|
139
|
+
legend_scale: float,
|
|
140
|
+
) -> str:
|
|
141
|
+
if legend_style == "compact":
|
|
142
|
+
light = render_template(
|
|
143
|
+
"mkdocs_html_block.html.j2",
|
|
144
|
+
class_name="unifi-legend unifi-legend--light",
|
|
145
|
+
data_unifi_legend=True,
|
|
146
|
+
content=render_legend_compact(theme=mermaid_theme),
|
|
147
|
+
)
|
|
148
|
+
dark = render_template(
|
|
149
|
+
"mkdocs_html_block.html.j2",
|
|
150
|
+
class_name="unifi-legend unifi-legend--dark",
|
|
151
|
+
data_unifi_legend=True,
|
|
152
|
+
content=render_legend_compact(theme=dark_mermaid_theme),
|
|
153
|
+
)
|
|
154
|
+
return f"{light}\n{dark}"
|
|
155
|
+
light = _mkdocs_mermaid_block(
|
|
156
|
+
render_legend(theme=mermaid_theme, legend_scale=legend_scale),
|
|
157
|
+
class_name="unifi-legend unifi-legend--light",
|
|
158
|
+
)
|
|
159
|
+
dark = _mkdocs_mermaid_block(
|
|
160
|
+
render_legend(theme=dark_mermaid_theme, legend_scale=legend_scale),
|
|
161
|
+
class_name="unifi-legend unifi-legend--dark",
|
|
162
|
+
)
|
|
163
|
+
return f"{light}\n{dark}"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _mkdocs_dual_theme_style() -> str:
|
|
167
|
+
return render_template("mkdocs_dual_theme_style.html.j2") + "\n"
|