unifi-network-maps 1.4.4__tar.gz → 1.4.6__tar.gz

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 (133) hide show
  1. unifi_network_maps-1.4.6/CHANGELOG.md +165 -0
  2. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/CONTRIBUTING.md +3 -2
  3. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/PKG-INFO +7 -1
  4. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/README.md +5 -0
  5. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/pyproject.toml +3 -1
  6. unifi_network_maps-1.4.6/src/unifi_network_maps/__init__.py +1 -0
  7. unifi_network_maps-1.4.6/src/unifi_network_maps/cli/__init__.py +5 -0
  8. unifi_network_maps-1.4.6/src/unifi_network_maps/cli/args.py +166 -0
  9. unifi_network_maps-1.4.6/src/unifi_network_maps/cli/main.py +134 -0
  10. unifi_network_maps-1.4.6/src/unifi_network_maps/cli/render.py +255 -0
  11. unifi_network_maps-1.4.6/src/unifi_network_maps/cli/runtime.py +157 -0
  12. unifi_network_maps-1.4.6/src/unifi_network_maps/io/mkdocs_assets.py +21 -0
  13. unifi_network_maps-1.4.6/src/unifi_network_maps/io/mock_generate.py +7 -0
  14. unifi_network_maps-1.4.4/src/unifi_network_maps/io/mock_generate.py → unifi_network_maps-1.4.6/src/unifi_network_maps/model/mock.py +57 -49
  15. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/device_ports_md.py +44 -27
  16. unifi_network_maps-1.4.6/src/unifi_network_maps/render/legend.py +30 -0
  17. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/lldp_md.py +81 -60
  18. unifi_network_maps-1.4.6/src/unifi_network_maps/render/markdown_tables.py +21 -0
  19. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/mermaid.py +72 -85
  20. unifi_network_maps-1.4.6/src/unifi_network_maps/render/mkdocs.py +167 -0
  21. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/svg.py +34 -3
  22. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/device_port_block.md.j2 +5 -0
  23. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/legend_compact.html.j2 +14 -0
  24. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/lldp_device_section.md.j2 +15 -0
  25. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/markdown_section.md.j2 +3 -0
  26. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mermaid_legend.mmd.j2 +30 -0
  27. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mkdocs_document.md.j2 +23 -0
  28. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mkdocs_dual_theme_style.html.j2 +8 -0
  29. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mkdocs_html_block.html.j2 +2 -0
  30. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mkdocs_legend.css.j2 +29 -0
  31. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mkdocs_legend.js.j2 +18 -0
  32. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templates/mkdocs_mermaid_block.md.j2 +4 -0
  33. unifi_network_maps-1.4.6/src/unifi_network_maps/render/templating.py +19 -0
  34. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps.egg-info/PKG-INFO +7 -1
  35. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps.egg-info/SOURCES.txt +20 -0
  36. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps.egg-info/requires.txt +1 -0
  37. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_cli.py +95 -77
  38. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_svg.py +13 -0
  39. unifi_network_maps-1.4.4/CHANGELOG.md +0 -83
  40. unifi_network_maps-1.4.4/src/unifi_network_maps/__init__.py +0 -1
  41. unifi_network_maps-1.4.4/src/unifi_network_maps/cli/__init__.py +0 -41
  42. unifi_network_maps-1.4.4/src/unifi_network_maps/cli/main.py +0 -864
  43. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/LICENSE +0 -0
  44. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/LICENSES.md +0 -0
  45. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/MANIFEST.in +0 -0
  46. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/RELEASING.md +0 -0
  47. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/SECURITY.md +0 -0
  48. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/setup.cfg +0 -0
  49. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/__main__.py +0 -0
  50. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/adapters/__init__.py +0 -0
  51. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/adapters/config.py +0 -0
  52. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/adapters/unifi.py +0 -0
  53. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/__init__.py +0 -0
  54. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/__init__.py +0 -0
  55. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/access-point.svg +0 -0
  56. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/ISOPACKS_LICENSE +0 -0
  57. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/block.svg +0 -0
  58. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/cache.svg +0 -0
  59. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/cardterminal.svg +0 -0
  60. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/cloud.svg +0 -0
  61. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/cronjob.svg +0 -0
  62. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/cube.svg +0 -0
  63. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/desktop.svg +0 -0
  64. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/diamond.svg +0 -0
  65. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/dns.svg +0 -0
  66. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/document.svg +0 -0
  67. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/firewall.svg +0 -0
  68. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/function-module.svg +0 -0
  69. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/image.svg +0 -0
  70. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/laptop.svg +0 -0
  71. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/loadbalancer.svg +0 -0
  72. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/lock.svg +0 -0
  73. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/mail.svg +0 -0
  74. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/mailmultiple.svg +0 -0
  75. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/mobiledevice.svg +0 -0
  76. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/office.svg +0 -0
  77. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/package-module.svg +0 -0
  78. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/paymentcard.svg +0 -0
  79. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/plane.svg +0 -0
  80. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/printer.svg +0 -0
  81. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/pyramid.svg +0 -0
  82. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/queue.svg +0 -0
  83. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/router.svg +0 -0
  84. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/server.svg +0 -0
  85. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/speech.svg +0 -0
  86. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/sphere.svg +0 -0
  87. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/storage.svg +0 -0
  88. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/switch-module.svg +0 -0
  89. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/tower.svg +0 -0
  90. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/truck-2.svg +0 -0
  91. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/truck.svg +0 -0
  92. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/user.svg +0 -0
  93. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/isometric/vm.svg +0 -0
  94. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/laptop.svg +0 -0
  95. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/router-network.svg +0 -0
  96. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/server-network.svg +0 -0
  97. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/icons/server.svg +0 -0
  98. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/themes/dark.yaml +0 -0
  99. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/assets/themes/default.yaml +0 -0
  100. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/cli/__main__.py +0 -0
  101. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/io/__init__.py +0 -0
  102. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/io/debug.py +0 -0
  103. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/io/export.py +0 -0
  104. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/io/mock_data.py +0 -0
  105. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/model/__init__.py +0 -0
  106. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/model/labels.py +0 -0
  107. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/model/lldp.py +0 -0
  108. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/model/ports.py +0 -0
  109. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/model/topology.py +0 -0
  110. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/__init__.py +0 -0
  111. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/mermaid_theme.py +0 -0
  112. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/svg_theme.py +0 -0
  113. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps/render/theme.py +0 -0
  114. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps.egg-info/dependency_links.txt +0 -0
  115. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps.egg-info/entry_points.txt +0 -0
  116. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/src/unifi_network_maps.egg-info/top_level.txt +0 -0
  117. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_clients.py +0 -0
  118. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_config.py +0 -0
  119. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_contract_unifi.py +0 -0
  120. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_contract_unifi_live.py +0 -0
  121. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_debug.py +0 -0
  122. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_device_ports_md.py +0 -0
  123. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_export.py +0 -0
  124. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_groups.py +0 -0
  125. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_labels.py +0 -0
  126. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_lldp.py +0 -0
  127. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_lldp_md.py +0 -0
  128. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_mermaid.py +0 -0
  129. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_mock_generate.py +0 -0
  130. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_svg_iso.py +0 -0
  131. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_theme.py +0 -0
  132. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_topology.py +0 -0
  133. {unifi_network_maps-1.4.4 → unifi_network_maps-1.4.6}/tests/test_unifi.py +0 -0
@@ -0,0 +1,165 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.4.6] - 2026-01-15
9
+ ### Added
10
+ - Home Assistant docs pointing to the standalone integration repo.
11
+
12
+ ### Changed
13
+ - Home Assistant integration work moved to `unifi-network-maps-ha`; core repo focuses on renderer + CLI.
14
+
15
+ ### Removed
16
+ - HA POC export module, CLI flag, BDD scenarios, and smoketest outputs (now in the HA repo).
17
+
18
+ ### Fixed
19
+ - BDD theme-file scenario
20
+ - SVG links render correctly for vertically stacked nodes.
21
+ - Publish workflow now checks out the tagged source before building.
22
+
23
+ ## [1.4.5] - 2026-01-11
24
+ ### Added
25
+ - Jinja2 templating for MkDocs output, Mermaid legend blocks, and Markdown sections.
26
+ - MkDocs sidebar assets and legend HTML blocks moved into reusable templates.
27
+ - BDD scenarios for module/console entrypoints plus additional CLI validation errors.
28
+
29
+ ### Changed
30
+ - Refactored CLI orchestration into focused CLI/render/runtime modules.
31
+ - Extracted MkDocs rendering and sidebar asset output into dedicated modules.
32
+ - Moved mock generation into the model layer with a thin IO facade.
33
+ - Centralized legend rendering helpers and shared markdown table utilities.
34
+ - Publish workflow now runs only after successful tagged CI.
35
+ - Added explicit workflow permissions to CI/CodeQL workflows.
36
+
37
+ ### Fixed
38
+ - CLI error handling for invalid theme file paths.
39
+
40
+ ### Security
41
+ - Enabled Jinja2 autoescaping for HTML templates and marked trusted HTML blocks safe.
42
+
43
+ ## [1.4.4] - 2026-01-11
44
+ ### Added
45
+ - Added smoke tests for dual-theme MkDocs sidebar legend output.
46
+
47
+ ### Changed
48
+ - Improved dark theme Mermaid readability (labels + link borders).
49
+
50
+ ### Fixed
51
+ - MkDocs sidebar legend duplication with dual-theme output.
52
+
53
+ ## [1.4.2] - 2026-01-10
54
+ ### Added
55
+ - Static code analysis and stricter type-checking.
56
+ - Contract tests for UniFi API wrapper with fixture-based validation.
57
+ - Optional live UniFi contract tests (gated by `UNIFI_CONTRACT_LIVE=1`).
58
+ - Split CI into dedicated jobs and added a contract-test job.
59
+ - Behave-based BDD tests covering CLI outputs, mkdocs assets, and error handling.
60
+ - Mkdocs timestamp (timezone configurable via `--mkdocs-timestamp-zone`).
61
+ - Optional dual Mermaid blocks for MkDocs Material theme switching (`--mkdocs-dual-theme`).
62
+ - `--no-cache` to bypass UniFi API cache reads/writes.
63
+ - File locking around cache read/write operations to avoid concurrent corruption.
64
+ - Optional UniFi API request timeouts via `UNIFI_REQUEST_TIMEOUT_SECONDS`.
65
+ - Made `--output` writes atomic to avoid partial files on interruption.
66
+
67
+ ### Changed
68
+ - Switched UniFi API cache payloads to JSON for safer local storage.
69
+ - Skips cache usage when the cache directory is group/world-writable.
70
+
71
+ ### Fixed
72
+ - Hardened Mermaid label escaping for newlines and backslashes.
73
+ - Device cache serialization to preserve LLDP data when caching.
74
+
75
+ ## [1.4.1] - 2026-01-06
76
+ ### Fixed
77
+ - Fixed pip install failure.
78
+
79
+ ## [1.4.0] - 2026-01-06
80
+ ### Added
81
+ - MkDocs output with gateway/switch details and per-port tables.
82
+ - Port tables show speed, PoE status, power, and wired clients per port.
83
+ - Compact legend with sidebar injection (`--mkdocs-sidebar-legend`).
84
+ - LLDP markdown includes the same device details and port tables when enabled.
85
+ - `--mock-data` for safe, offline rendering from fixtures.
86
+ - Faker-powered `--generate-mock` for deterministic mock fixtures (dev-only).
87
+ - Mock fixtures + SVG/Mermaid examples, with mock smoketest/CI steps.
88
+
89
+ ### Changed
90
+ - Improved uplink labeling (gateway shows Internet for WAN/unknown).
91
+ - Aggregated ports are combined into single LAG rows.
92
+ - Bumped minimum Python to 3.13 and aligned CI to 3.13.
93
+ - Pinned runtime/dev/build dependencies and added `requirements*.txt` + `constraints.txt`.
94
+
95
+ ## [1.3.1] - 2026-01-04
96
+ ### Added
97
+ - `lldp-md` output with per-device details tables and optional client sections.
98
+ - `--client-scope wired|wireless|all` and dashed wireless client links in Mermaid/SVG.
99
+ - Expanded smoketest outputs for wireless/all client scopes and LLDP markdown.
100
+
101
+ ### Fixed
102
+ - Fixed SVG icon loading paths after package reorg.
103
+
104
+ ### Changed
105
+ - Isometric port label placement on front tiles.
106
+
107
+ ## [1.3.0] - 2026-01-04
108
+ ### Added
109
+ - YAML-based theming with default + dark themes and `--theme-file`.
110
+
111
+ ### Changed
112
+ - Reorganized package into submodules (`adapters/`, `model/`, `render/`, `io/`, `cli/`).
113
+ - CLI help now grouped by category; CLI logic split into focused helpers.
114
+ - Isometric SVG layout constants centralized; extra viewBox padding to avoid clipping.
115
+ - LLDP port index fallback matches `port_table` `ifname`/`name`.
116
+ - Added PoE/edge/device count logging and improved label ordering helpers.
117
+ - Coverage excludes asset packages; docs updated (options/groups + AI disclosure).
118
+
119
+ ## [1.2.4] - 2026-01-03
120
+ ### Added
121
+ - Typed `UplinkInfo`/`PortInfo` and uplink fallback for LLDP gaps.
122
+ - CI publish workflow (trusted publishing) and release docs.
123
+ - Project metadata and packaging updated for OSS readiness.
124
+
125
+ ### Changed
126
+ - Deterministic edge ordering for repeatable output.
127
+
128
+ ## [1.1.0] - 2026-01-03
129
+ ### Added
130
+ - Isometric SVG output with grid-aligned links and isometric icon set.
131
+ - Smoketest target with multiple outputs (ports/clients/legend).
132
+ - UniFi API response caching with TTL.
133
+
134
+ ### Changed
135
+ - Improved port label placement and client labeling in SVG outputs.
136
+ - Refined visuals: link gradients, tile gradients, icon placement tweaks.
137
+
138
+ ### Fixed
139
+ - Mermaid legend/grouped output parsing errors.
140
+
141
+ ## [1.0.0] - 2025-12-30
142
+ ### Added
143
+ - Mermaid legend can render as a separate graph.
144
+ - Straight Mermaid links with node type coloring.
145
+ - Wired client leaf nodes and uplink port labels.
146
+ - CLI loads `.env` automatically.
147
+
148
+ ## [0.2.0] - 2026-01-02
149
+ ### Added
150
+ - Versioning workflow and bump tooling.
151
+ - Introduced SVG renderer and tree layout fixes.
152
+ - Increased test coverage and added coverage tooling.
153
+
154
+ [Unreleased]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.5...HEAD
155
+ [1.4.5]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.4...v1.4.5
156
+ [1.4.4]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.2...v1.4.4
157
+ [1.4.2]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.1...v1.4.2
158
+ [1.4.1]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.0...v1.4.1
159
+ [1.4.0]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.3.1...v1.4.0
160
+ [1.3.1]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.3.0...v1.3.1
161
+ [1.3.0]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.2.4...v1.3.0
162
+ [1.2.4]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.1.0...v1.2.4
163
+ [1.1.0]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.0.0...v1.1.0
164
+ [1.0.0]: https://github.com/merlijntishauser/unifi-network-maps/compare/v0.2.0...v1.0.0
165
+ [0.2.0]: https://github.com/merlijntishauser/unifi-network-maps/releases/tag/v0.2.0
@@ -43,6 +43,7 @@ Notes:
43
43
  - Contract tests use fixtures in `tests/test_contract_unifi.py` and run in CI.
44
44
  - Live contract tests require `UNIFI_CONTRACT_LIVE=1` plus UniFi env vars.
45
45
  - BDD tests live in `features/` and run via `behave` (included in `make ci`).
46
+ - Render helper structure is documented in `src/unifi_network_maps/render/README.md`.
46
47
 
47
48
  ## Release
48
49
 
@@ -82,8 +83,8 @@ See `LICENSES.md` for third-party license info.
82
83
 
83
84
  - Automated UniFi → Mermaid/SVG (incl. isometric) output, with ports/PoE/clients.
84
85
  - Deterministic CLI + cache + mock generator for CI and docs.
85
- - MkDocs/Markdown output plus planned HA‑friendly artifacts.
86
+ - MkDocs/Markdown output plus a separate HA integration repo for live updates.
86
87
  - A clean render pipeline that can power multiple export targets.
87
88
 
88
89
  So we’re not duplicating a packaged workflow; we’re combining data + topology modeling + diagram output + documentation/export in a way I haven’t seen in one place.
89
-
90
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unifi-network-maps
3
- Version: 1.4.4
3
+ Version: 1.4.6
4
4
  Summary: Dynamic UniFi -> network maps in mermaid or svg
5
5
  Author: Merlijn
6
6
  License-Expression: MIT
@@ -23,6 +23,7 @@ License-File: LICENSE
23
23
  Requires-Dist: unifi-controller-api==0.3.2
24
24
  Requires-Dist: python-dotenv==1.2.1
25
25
  Requires-Dist: PyYAML==6.0.3
26
+ Requires-Dist: Jinja2==3.1.6
26
27
  Provides-Extra: dev
27
28
  Requires-Dist: Faker==40.1.0; extra == "dev"
28
29
  Requires-Dist: behave==1.3.3; extra == "dev"
@@ -143,6 +144,11 @@ Legend only:
143
144
  unifi-network-maps --legend-only --stdout
144
145
  ```
145
146
 
147
+ ## Home Assistant integration
148
+
149
+ The live Home Assistant integration (Config Flow + coordinator + custom card) lives in a separate repo:
150
+ - https://github.com/merlijntishauser/unifi-network-maps-ha
151
+
146
152
  ## Examples (mock data)
147
153
 
148
154
  These examples are generated from `examples/mock_data.json` (safe, anonymized fixture).
@@ -108,6 +108,11 @@ Legend only:
108
108
  unifi-network-maps --legend-only --stdout
109
109
  ```
110
110
 
111
+ ## Home Assistant integration
112
+
113
+ The live Home Assistant integration (Config Flow + coordinator + custom card) lives in a separate repo:
114
+ - https://github.com/merlijntishauser/unifi-network-maps-ha
115
+
111
116
  ## Examples (mock data)
112
117
 
113
118
  These examples are generated from `examples/mock_data.json` (safe, anonymized fixture).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "unifi-network-maps"
7
- version = "1.4.4"
7
+ version = "1.4.6"
8
8
  description = "Dynamic UniFi -> network maps in mermaid or svg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -26,6 +26,7 @@ dependencies = [
26
26
  "unifi-controller-api==0.3.2",
27
27
  "python-dotenv==1.2.1",
28
28
  "PyYAML==6.0.3",
29
+ "Jinja2==3.1.6",
29
30
  ]
30
31
 
31
32
  [project.urls]
@@ -96,6 +97,7 @@ package-dir = {"" = "src"}
96
97
  "assets/icons/*.svg",
97
98
  "assets/icons/isometric/*.svg",
98
99
  "assets/icons/isometric/ISOPACKS_LICENSE",
100
+ "render/templates/*.j2",
99
101
  ]
100
102
 
101
103
  [tool.setuptools.packages.find]
@@ -0,0 +1 @@
1
+ __version__ = "1.4.6"
@@ -0,0 +1,5 @@
1
+ """CLI package facade."""
2
+
3
+ from .main import main
4
+
5
+ __all__ = ["main"]
@@ -0,0 +1,166 @@
1
+ """CLI argument definitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+
8
+ def build_parser() -> argparse.ArgumentParser:
9
+ parser = argparse.ArgumentParser(
10
+ description="Generate network maps from UniFi LLDP data, as mermaid or SVG"
11
+ )
12
+ add_source_args(parser.add_argument_group("Source"))
13
+ add_mock_args(parser.add_argument_group("Mock"))
14
+ add_functional_args(parser.add_argument_group("Functional"))
15
+ add_mermaid_args(parser.add_argument_group("Mermaid"))
16
+ add_svg_args(parser.add_argument_group("SVG"))
17
+ add_general_render_args(parser.add_argument_group("Output"))
18
+ add_debug_args(parser.add_argument_group("Debug"))
19
+ return parser
20
+
21
+
22
+ def add_source_args(parser: argparse._ArgumentGroup) -> None:
23
+ parser.add_argument("--site", default=None, help="UniFi site name (overrides UNIFI_SITE)")
24
+ parser.add_argument(
25
+ "--env-file",
26
+ default=None,
27
+ help="Path to .env file (overrides default .env discovery)",
28
+ )
29
+ parser.add_argument(
30
+ "--mock-data",
31
+ default=None,
32
+ help="Path to mock data JSON (skips UniFi API calls)",
33
+ )
34
+
35
+
36
+ def add_mock_args(parser: argparse._ArgumentGroup) -> None:
37
+ parser.add_argument(
38
+ "--generate-mock",
39
+ default=None,
40
+ help="Write mock data JSON to the given path and exit",
41
+ )
42
+ parser.add_argument("--mock-seed", type=int, default=1337, help="Seed for mock generation")
43
+ parser.add_argument(
44
+ "--mock-switches",
45
+ type=int,
46
+ default=1,
47
+ help="Number of switches to generate (default: 1)",
48
+ )
49
+ parser.add_argument(
50
+ "--mock-aps",
51
+ type=int,
52
+ default=2,
53
+ help="Number of access points to generate (default: 2)",
54
+ )
55
+ parser.add_argument(
56
+ "--mock-wired-clients",
57
+ type=int,
58
+ default=2,
59
+ help="Number of wired clients to generate (default: 2)",
60
+ )
61
+ parser.add_argument(
62
+ "--mock-wireless-clients",
63
+ type=int,
64
+ default=2,
65
+ help="Number of wireless clients to generate (default: 2)",
66
+ )
67
+
68
+
69
+ def add_functional_args(parser: argparse._ArgumentGroup) -> None:
70
+ parser.add_argument("--include-ports", action="store_true", help="Include port labels in edges")
71
+ parser.add_argument(
72
+ "--include-clients",
73
+ action="store_true",
74
+ help="Include active clients as leaf nodes",
75
+ )
76
+ parser.add_argument(
77
+ "--client-scope",
78
+ choices=["wired", "wireless", "all"],
79
+ default="wired",
80
+ help="Client types to include (default: wired)",
81
+ )
82
+ parser.add_argument(
83
+ "--only-unifi", action="store_true", help="Only include neighbors that are UniFi devices"
84
+ )
85
+ parser.add_argument(
86
+ "--no-cache",
87
+ action="store_true",
88
+ help="Disable UniFi API cache reads and writes",
89
+ )
90
+
91
+
92
+ def add_mermaid_args(parser: argparse._ArgumentGroup) -> None:
93
+ parser.add_argument("--direction", default="TB", choices=["LR", "TB"], help="Mermaid direction")
94
+ parser.add_argument(
95
+ "--group-by-type",
96
+ action="store_true",
97
+ help="Group nodes by gateway/switch/ap in Mermaid subgraphs",
98
+ )
99
+ parser.add_argument(
100
+ "--legend-scale",
101
+ type=float,
102
+ default=1.0,
103
+ help="Scale legend font/link sizes for Mermaid output (default: 1.0)",
104
+ )
105
+ parser.add_argument(
106
+ "--legend-style",
107
+ default="auto",
108
+ choices=["auto", "compact", "diagram"],
109
+ help="Legend style (auto uses compact for mkdocs, diagram otherwise)",
110
+ )
111
+ parser.add_argument(
112
+ "--legend-only",
113
+ action="store_true",
114
+ help="Render only the legend as a separate Mermaid graph",
115
+ )
116
+
117
+
118
+ def add_svg_args(parser: argparse._ArgumentGroup) -> None:
119
+ parser.add_argument("--svg-width", type=int, default=None, help="SVG width override")
120
+ parser.add_argument("--svg-height", type=int, default=None, help="SVG height override")
121
+ parser.add_argument("--theme-file", default=None, help="Path to theme YAML file")
122
+
123
+
124
+ def add_general_render_args(parser: argparse._ArgumentGroup) -> None:
125
+ parser.add_argument(
126
+ "--format",
127
+ default="mermaid",
128
+ choices=["mermaid", "svg", "svg-iso", "lldp-md", "mkdocs"],
129
+ help="Output format",
130
+ )
131
+ parser.add_argument(
132
+ "--markdown",
133
+ action="store_true",
134
+ help="Wrap output in a Markdown mermaid code fence for notes tools like Obsidian",
135
+ )
136
+ parser.add_argument("--output", default=None, help="Output file path")
137
+ parser.add_argument("--stdout", action="store_true", help="Write output to stdout")
138
+ parser.add_argument(
139
+ "--mkdocs-sidebar-legend",
140
+ action="store_true",
141
+ help="For mkdocs output, write sidebar legend assets next to the output file",
142
+ )
143
+ parser.add_argument(
144
+ "--mkdocs-dual-theme",
145
+ action="store_true",
146
+ help="Render light/dark Mermaid blocks for MkDocs Material theme switching",
147
+ )
148
+ parser.add_argument(
149
+ "--mkdocs-timestamp-zone",
150
+ default="Europe/Amsterdam",
151
+ help="Timezone for mkdocs generated timestamp (use 'off' to disable)",
152
+ )
153
+
154
+
155
+ def add_debug_args(parser: argparse._ArgumentGroup) -> None:
156
+ parser.add_argument(
157
+ "--debug-dump",
158
+ action="store_true",
159
+ help="Dump gateway and sample device data to stderr for debugging",
160
+ )
161
+ parser.add_argument(
162
+ "--debug-sample",
163
+ type=int,
164
+ default=2,
165
+ help="Number of non-gateway devices to include in debug dump (default: 2)",
166
+ )
@@ -0,0 +1,134 @@
1
+ """CLI entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+
8
+ from ..adapters.config import Config
9
+ from ..io.export import write_output
10
+ from ..io.mock_data import load_mock_data
11
+ from ..render.legend import render_legend_only, resolve_legend_style
12
+ from ..render.theme import resolve_themes
13
+ from .args import build_parser
14
+ from .render import render_lldp_format, render_standard_format
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _load_dotenv(env_file: str | None = None) -> None:
20
+ try:
21
+ from dotenv import load_dotenv
22
+ except ImportError:
23
+ logger.info("python-dotenv not installed; skipping .env loading")
24
+ return
25
+ load_dotenv(dotenv_path=env_file) if env_file else load_dotenv()
26
+
27
+
28
+ def _parse_args(argv: list[str] | None) -> argparse.Namespace:
29
+ parser = build_parser()
30
+ return parser.parse_args(argv)
31
+
32
+
33
+ def _load_config(args: argparse.Namespace) -> Config | None:
34
+ try:
35
+ _load_dotenv(args.env_file)
36
+ return Config.from_env(env_file=args.env_file)
37
+ except ValueError as exc:
38
+ logging.error(str(exc))
39
+ return None
40
+
41
+
42
+ def _resolve_site(args: argparse.Namespace, config: Config) -> str:
43
+ return args.site or config.site
44
+
45
+
46
+ def _handle_generate_mock(args: argparse.Namespace) -> int | None:
47
+ if not args.generate_mock:
48
+ return None
49
+ try:
50
+ from ..model.mock import MockOptions, mock_payload_json
51
+ except ImportError as exc:
52
+ logging.error("Faker is required for --generate-mock: %s", exc)
53
+ return 2
54
+ options = MockOptions(
55
+ seed=args.mock_seed,
56
+ switch_count=max(1, args.mock_switches),
57
+ ap_count=max(0, args.mock_aps),
58
+ wired_client_count=max(0, args.mock_wired_clients),
59
+ wireless_client_count=max(0, args.mock_wireless_clients),
60
+ )
61
+ content = mock_payload_json(options)
62
+ write_output(content, output_path=args.generate_mock, stdout=args.stdout)
63
+ return 0
64
+
65
+
66
+ def _load_runtime_context(
67
+ args: argparse.Namespace,
68
+ ) -> tuple[Config | None, str, list[object] | None, list[object] | None]:
69
+ if args.mock_data:
70
+ try:
71
+ mock_devices, mock_clients = load_mock_data(args.mock_data)
72
+ except Exception as exc: # noqa: BLE001
73
+ raise ValueError(f"Failed to load mock data: {exc}") from exc
74
+ return None, "mock", mock_devices, mock_clients
75
+ config = _load_config(args)
76
+ if config is None:
77
+ raise ValueError("Config required to run")
78
+ site = _resolve_site(args, config)
79
+ return config, site, None, None
80
+
81
+
82
+ def main(argv: list[str] | None = None) -> int:
83
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
84
+ args = _parse_args(argv)
85
+ mock_result = _handle_generate_mock(args)
86
+ if mock_result is not None:
87
+ return mock_result
88
+ try:
89
+ config, site, mock_devices, mock_clients = _load_runtime_context(args)
90
+ except ValueError as exc:
91
+ logging.error(str(exc))
92
+ return 2
93
+ try:
94
+ mermaid_theme, svg_theme = resolve_themes(args.theme_file)
95
+ except Exception as exc: # noqa: BLE001
96
+ logging.error("Failed to load theme file: %s", exc)
97
+ return 2
98
+
99
+ if args.legend_only:
100
+ legend_style = resolve_legend_style(
101
+ format_name=args.format,
102
+ legend_style=args.legend_style,
103
+ )
104
+ content = render_legend_only(
105
+ legend_style=legend_style,
106
+ legend_scale=args.legend_scale,
107
+ markdown=args.markdown,
108
+ theme=mermaid_theme,
109
+ )
110
+ write_output(content, output_path=args.output, stdout=args.stdout)
111
+ return 0
112
+
113
+ if args.format == "lldp-md":
114
+ return render_lldp_format(
115
+ args,
116
+ config=config,
117
+ site=site,
118
+ mock_devices=mock_devices,
119
+ mock_clients=mock_clients,
120
+ )
121
+
122
+ return render_standard_format(
123
+ args,
124
+ config=config,
125
+ site=site,
126
+ mock_devices=mock_devices,
127
+ mock_clients=mock_clients,
128
+ mermaid_theme=mermaid_theme,
129
+ svg_theme=svg_theme,
130
+ )
131
+
132
+
133
+ if __name__ == "__main__":
134
+ raise SystemExit(main())