unifi-network-maps 1.4.10__tar.gz → 1.4.12__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.
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/CHANGELOG.md +17 -1
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/PKG-INFO +2 -2
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/README.md +1 -1
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/pyproject.toml +1 -1
- unifi_network_maps-1.4.12/src/unifi_network_maps/__init__.py +1 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/render.py +19 -3
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/runtime.py +7 -1
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/topology.py +110 -15
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/lldp_md.py +123 -15
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/svg.py +8 -2
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/PKG-INFO +2 -2
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_clients.py +65 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_lldp_md.py +55 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_svg.py +27 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_topology.py +37 -0
- unifi_network_maps-1.4.10/src/unifi_network_maps/__init__.py +0 -1
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/CONTRIBUTING.md +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/LICENSE +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/LICENSES.md +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/MANIFEST.in +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/RELEASING.md +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/SECURITY.md +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/setup.cfg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/__main__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/adapters/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/adapters/config.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/adapters/unifi.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/access-point.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/ISOPACKS_LICENSE +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/block.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/cache.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/cardterminal.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/cloud.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/cronjob.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/cube.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/desktop.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/diamond.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/dns.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/document.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/firewall.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/function-module.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/image.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/laptop.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/loadbalancer.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/lock.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/mail.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/mailmultiple.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/mobiledevice.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/office.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/package-module.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/paymentcard.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/plane.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/printer.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/pyramid.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/queue.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/router.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/server.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/speech.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/sphere.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/storage.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/switch-module.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/tower.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/truck-2.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/truck.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/user.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/isometric/vm.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/laptop.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/router-network.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/server-network.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/icons/server.svg +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/themes/dark.yaml +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/assets/themes/default.yaml +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/__main__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/args.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/main.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/io/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/io/debug.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/io/export.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/io/mkdocs_assets.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/io/mock_data.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/io/mock_generate.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/labels.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/lldp.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/mock.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/ports.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/__init__.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/device_ports_md.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/legend.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/markdown_tables.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/mermaid.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/mermaid_theme.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/mkdocs.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/svg_theme.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/device_port_block.md.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/legend_compact.html.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/lldp_device_section.md.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/markdown_section.md.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mermaid_legend.mmd.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mkdocs_document.md.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mkdocs_dual_theme_style.html.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mkdocs_html_block.html.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mkdocs_legend.css.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mkdocs_legend.js.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templates/mkdocs_mermaid_block.md.j2 +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/templating.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/theme.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/SOURCES.txt +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/dependency_links.txt +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/entry_points.txt +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/requires.txt +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/top_level.txt +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_cli.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_config.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_contract_unifi.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_contract_unifi_live.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_debug.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_device_ports_md.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_export.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_groups.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_labels.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_lldp.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_mermaid.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_mock_generate.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_svg_iso.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_theme.py +0 -0
- {unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/tests/test_unifi.py +0 -0
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.12] - 2026-01-21
|
|
9
|
+
### Added
|
|
10
|
+
- Filter UniFi clients with --only-unifi, and not only neighbors
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- inconsistencies in --only-unifi
|
|
14
|
+
|
|
15
|
+
## [1.4.11] - 2026-01-19
|
|
16
|
+
### Added
|
|
17
|
+
- Add data-edge-left/right attributes to SVG paths
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Regression in identifying wireless/wired clients
|
|
21
|
+
|
|
8
22
|
## [1.4.10] - 2026-01-18
|
|
9
23
|
### Added
|
|
10
24
|
- Add speed and channel fields to Edge dataclass
|
|
@@ -169,7 +183,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
169
183
|
- Introduced SVG renderer and tree layout fixes.
|
|
170
184
|
- Increased test coverage and added coverage tooling.
|
|
171
185
|
|
|
172
|
-
[Unreleased]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.
|
|
186
|
+
[Unreleased]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.12...HEAD
|
|
187
|
+
[1.4.12]:https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.11...v1.4.12
|
|
188
|
+
[1.4.11]:https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.10...v1.4.11
|
|
173
189
|
[1.4.10]:https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.9...v1.4.10
|
|
174
190
|
[1.4.9]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.8...v1.4.9
|
|
175
191
|
[1.4.8]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.7...v1.4.8
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unifi-network-maps
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.12
|
|
4
4
|
Summary: Dynamic UniFi -> network maps in mermaid or svg
|
|
5
5
|
Author: Merlijn
|
|
6
6
|
License-Expression: MIT
|
|
@@ -223,7 +223,7 @@ Functional:
|
|
|
223
223
|
- `--include-ports`: show port labels (Mermaid shows both ends; SVG shows compact labels).
|
|
224
224
|
- `--include-clients`: add active wired clients as leaf nodes.
|
|
225
225
|
- `--client-scope wired|wireless|all`: which client types to include (default wired).
|
|
226
|
-
- `--only-unifi`: only include neighbors that are UniFi devices.
|
|
226
|
+
- `--only-unifi`: only include neighbors that are UniFi devices; when clients are included, filters to UniFi-managed clients (by explicit UniFi flags or vendor/OUI).
|
|
227
227
|
- `--no-cache`: disable UniFi API cache reads and writes.
|
|
228
228
|
|
|
229
229
|
Mermaid:
|
|
@@ -186,7 +186,7 @@ Functional:
|
|
|
186
186
|
- `--include-ports`: show port labels (Mermaid shows both ends; SVG shows compact labels).
|
|
187
187
|
- `--include-clients`: add active wired clients as leaf nodes.
|
|
188
188
|
- `--client-scope wired|wireless|all`: which client types to include (default wired).
|
|
189
|
-
- `--only-unifi`: only include neighbors that are UniFi devices.
|
|
189
|
+
- `--only-unifi`: only include neighbors that are UniFi devices; when clients are included, filters to UniFi-managed clients (by explicit UniFi flags or vendor/OUI).
|
|
190
190
|
- `--no-cache`: disable UniFi API cache reads and writes.
|
|
191
191
|
|
|
192
192
|
Mermaid:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.12"
|
{unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/render.py
RENAMED
|
@@ -62,7 +62,12 @@ def render_mermaid_output(
|
|
|
62
62
|
direction=args.direction,
|
|
63
63
|
groups=groups,
|
|
64
64
|
group_order=group_order,
|
|
65
|
-
node_types=build_node_type_map(
|
|
65
|
+
node_types=build_node_type_map(
|
|
66
|
+
devices,
|
|
67
|
+
clients,
|
|
68
|
+
client_mode=args.client_scope,
|
|
69
|
+
only_unifi=args.only_unifi,
|
|
70
|
+
),
|
|
66
71
|
theme=mermaid_theme,
|
|
67
72
|
)
|
|
68
73
|
if args.markdown:
|
|
@@ -97,13 +102,23 @@ def render_svg_output(
|
|
|
97
102
|
|
|
98
103
|
return render_svg_isometric(
|
|
99
104
|
edges,
|
|
100
|
-
node_types=build_node_type_map(
|
|
105
|
+
node_types=build_node_type_map(
|
|
106
|
+
devices,
|
|
107
|
+
clients,
|
|
108
|
+
client_mode=args.client_scope,
|
|
109
|
+
only_unifi=args.only_unifi,
|
|
110
|
+
),
|
|
101
111
|
options=options,
|
|
102
112
|
theme=svg_theme,
|
|
103
113
|
)
|
|
104
114
|
return render_svg(
|
|
105
115
|
edges,
|
|
106
|
-
node_types=build_node_type_map(
|
|
116
|
+
node_types=build_node_type_map(
|
|
117
|
+
devices,
|
|
118
|
+
clients,
|
|
119
|
+
client_mode=args.client_scope,
|
|
120
|
+
only_unifi=args.only_unifi,
|
|
121
|
+
),
|
|
107
122
|
options=options,
|
|
108
123
|
theme=svg_theme,
|
|
109
124
|
)
|
|
@@ -190,6 +205,7 @@ def render_lldp_format(
|
|
|
190
205
|
include_ports=args.include_ports,
|
|
191
206
|
show_clients=args.include_clients,
|
|
192
207
|
client_mode=args.client_scope,
|
|
208
|
+
only_unifi=args.only_unifi,
|
|
193
209
|
)
|
|
194
210
|
write_output(content, output_path=args.output, stdout=args.stdout)
|
|
195
211
|
return 0
|
{unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/cli/runtime.py
RENAMED
|
@@ -95,6 +95,7 @@ def build_edges_with_clients(
|
|
|
95
95
|
device_index,
|
|
96
96
|
include_ports=args.include_ports,
|
|
97
97
|
client_mode=args.client_scope,
|
|
98
|
+
only_unifi=args.only_unifi,
|
|
98
99
|
)
|
|
99
100
|
return edges, clients
|
|
100
101
|
|
|
@@ -153,5 +154,10 @@ def resolve_mkdocs_client_ports(
|
|
|
153
154
|
clients = list(fetch_clients(config, site=site))
|
|
154
155
|
else:
|
|
155
156
|
clients = mock_clients
|
|
156
|
-
client_ports = build_client_port_map(
|
|
157
|
+
client_ports = build_client_port_map(
|
|
158
|
+
devices,
|
|
159
|
+
clients,
|
|
160
|
+
client_mode=args.client_scope,
|
|
161
|
+
only_unifi=args.only_unifi,
|
|
162
|
+
)
|
|
157
163
|
return client_ports, None
|
{unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/model/topology.py
RENAMED
|
@@ -460,7 +460,13 @@ def _client_field(client: object, name: str) -> object | None:
|
|
|
460
460
|
|
|
461
461
|
|
|
462
462
|
def _client_display_name(client: object) -> str | None:
|
|
463
|
-
|
|
463
|
+
raw_name = _client_field(client, "name")
|
|
464
|
+
if isinstance(raw_name, str) and raw_name.strip():
|
|
465
|
+
return raw_name.strip()
|
|
466
|
+
preferred = _client_ucore_display_name(client)
|
|
467
|
+
if preferred:
|
|
468
|
+
return preferred
|
|
469
|
+
for key in ("hostname", "mac"):
|
|
464
470
|
value = _client_field(client, key)
|
|
465
471
|
if isinstance(value, str) and value.strip():
|
|
466
472
|
return value.strip()
|
|
@@ -482,20 +488,31 @@ def _client_uplink_mac(client: object) -> str | None:
|
|
|
482
488
|
|
|
483
489
|
|
|
484
490
|
def _client_uplink_port(client: object) -> int | None:
|
|
485
|
-
for
|
|
486
|
-
|
|
487
|
-
if
|
|
488
|
-
return
|
|
489
|
-
|
|
490
|
-
|
|
491
|
+
for value in _client_port_values(client):
|
|
492
|
+
parsed = _parse_port_value(value)
|
|
493
|
+
if parsed is not None:
|
|
494
|
+
return parsed
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _client_port_values(client: object) -> Iterable[object | None]:
|
|
499
|
+
for key in ("uplink_remote_port", "sw_port", "ap_port", "port_idx"):
|
|
500
|
+
yield _client_field(client, key)
|
|
491
501
|
for key in ("uplink", "last_uplink"):
|
|
492
502
|
nested = _client_field(client, key)
|
|
493
503
|
if isinstance(nested, dict):
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
504
|
+
for nested_key in ("uplink_remote_port", "port_idx"):
|
|
505
|
+
yield nested.get(nested_key)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _parse_port_value(value: object | None) -> int | None:
|
|
509
|
+
if isinstance(value, int):
|
|
510
|
+
return value
|
|
511
|
+
if isinstance(value, str):
|
|
512
|
+
stripped = value.strip()
|
|
513
|
+
if stripped.isdigit():
|
|
514
|
+
return int(stripped)
|
|
515
|
+
return extract_port_number(stripped)
|
|
499
516
|
return None
|
|
500
517
|
|
|
501
518
|
|
|
@@ -503,6 +520,73 @@ def _client_is_wired(client: object) -> bool:
|
|
|
503
520
|
return bool(_client_field(client, "is_wired"))
|
|
504
521
|
|
|
505
522
|
|
|
523
|
+
def _client_unifi_flag(client: object) -> bool | None:
|
|
524
|
+
for key in ("is_unifi", "is_unifi_device", "is_ubnt", "is_uap", "is_managed"):
|
|
525
|
+
value = _client_field(client, key)
|
|
526
|
+
if isinstance(value, bool):
|
|
527
|
+
return value
|
|
528
|
+
if isinstance(value, int):
|
|
529
|
+
return value != 0
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _client_vendor(client: object) -> str | None:
|
|
534
|
+
for key in ("oui", "vendor", "vendor_name", "manufacturer", "manufacturer_name"):
|
|
535
|
+
value = _client_field(client, key)
|
|
536
|
+
if isinstance(value, str) and value.strip():
|
|
537
|
+
return value.strip()
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _client_ucore_info(client: object) -> dict[str, object] | None:
|
|
542
|
+
info = _client_field(client, "unifi_device_info_from_ucore")
|
|
543
|
+
if isinstance(info, dict):
|
|
544
|
+
return info
|
|
545
|
+
return None
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _client_ucore_display_name(client: object) -> str | None:
|
|
549
|
+
ucore = _client_ucore_info(client)
|
|
550
|
+
if not ucore:
|
|
551
|
+
return None
|
|
552
|
+
for key in ("name", "computed_model", "product_model", "product_shortname"):
|
|
553
|
+
value = ucore.get(key)
|
|
554
|
+
if isinstance(value, str) and value.strip():
|
|
555
|
+
return value.strip()
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _client_hostname_source(client: object) -> str | None:
|
|
560
|
+
value = _client_field(client, "hostname_source")
|
|
561
|
+
if isinstance(value, str) and value.strip():
|
|
562
|
+
return value.strip()
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _client_is_unifi(client: object) -> bool:
|
|
567
|
+
flag = _client_unifi_flag(client)
|
|
568
|
+
if flag is not None:
|
|
569
|
+
return flag
|
|
570
|
+
ucore = _client_ucore_info(client)
|
|
571
|
+
if ucore:
|
|
572
|
+
managed = ucore.get("managed")
|
|
573
|
+
if isinstance(managed, bool) and managed:
|
|
574
|
+
return True
|
|
575
|
+
if isinstance(ucore.get("product_line"), str) and ucore.get("product_line"):
|
|
576
|
+
return True
|
|
577
|
+
if isinstance(ucore.get("product_shortname"), str) and ucore.get("product_shortname"):
|
|
578
|
+
return True
|
|
579
|
+
for key in ("name", "computed_model", "product_model"):
|
|
580
|
+
value = ucore.get(key)
|
|
581
|
+
if isinstance(value, str) and value.strip():
|
|
582
|
+
return True
|
|
583
|
+
vendor = _client_vendor(client)
|
|
584
|
+
if not vendor:
|
|
585
|
+
return False
|
|
586
|
+
normalized = vendor.lower()
|
|
587
|
+
return "ubiquiti" in normalized or "unifi" in normalized
|
|
588
|
+
|
|
589
|
+
|
|
506
590
|
def _client_channel(client: object) -> int | None:
|
|
507
591
|
for key in ("channel", "radio_channel", "wifi_channel"):
|
|
508
592
|
value = _client_field(client, key)
|
|
@@ -522,17 +606,26 @@ def _client_matches_mode(client: object, mode: str) -> bool:
|
|
|
522
606
|
return wired
|
|
523
607
|
|
|
524
608
|
|
|
609
|
+
def _client_matches_filters(client: object, *, client_mode: str, only_unifi: bool) -> bool:
|
|
610
|
+
if not _client_matches_mode(client, client_mode):
|
|
611
|
+
return False
|
|
612
|
+
if only_unifi and not _client_is_unifi(client):
|
|
613
|
+
return False
|
|
614
|
+
return True
|
|
615
|
+
|
|
616
|
+
|
|
525
617
|
def build_client_edges(
|
|
526
618
|
clients: Iterable[object],
|
|
527
619
|
device_index: dict[str, str],
|
|
528
620
|
*,
|
|
529
621
|
include_ports: bool = False,
|
|
530
622
|
client_mode: str = "wired",
|
|
623
|
+
only_unifi: bool = False,
|
|
531
624
|
) -> list[Edge]:
|
|
532
625
|
edges: list[Edge] = []
|
|
533
626
|
seen: set[tuple[str, str]] = set()
|
|
534
627
|
for client in clients:
|
|
535
|
-
if not
|
|
628
|
+
if not _client_matches_filters(client, client_mode=client_mode, only_unifi=only_unifi):
|
|
536
629
|
continue
|
|
537
630
|
name = _client_display_name(client)
|
|
538
631
|
uplink_mac = _client_uplink_mac(client)
|
|
@@ -569,13 +662,14 @@ def build_node_type_map(
|
|
|
569
662
|
clients: Iterable[object] | None = None,
|
|
570
663
|
*,
|
|
571
664
|
client_mode: str = "wired",
|
|
665
|
+
only_unifi: bool = False,
|
|
572
666
|
) -> dict[str, str]:
|
|
573
667
|
node_types: dict[str, str] = {}
|
|
574
668
|
for device in devices:
|
|
575
669
|
node_types[device.name] = classify_device_type(device)
|
|
576
670
|
if clients:
|
|
577
671
|
for client in clients:
|
|
578
|
-
if not
|
|
672
|
+
if not _client_matches_filters(client, client_mode=client_mode, only_unifi=only_unifi):
|
|
579
673
|
continue
|
|
580
674
|
name = _client_display_name(client)
|
|
581
675
|
if name:
|
|
@@ -672,11 +766,12 @@ def build_client_port_map(
|
|
|
672
766
|
clients: Iterable[object],
|
|
673
767
|
*,
|
|
674
768
|
client_mode: str,
|
|
769
|
+
only_unifi: bool = False,
|
|
675
770
|
) -> ClientPortMap:
|
|
676
771
|
device_index = build_device_index(devices)
|
|
677
772
|
port_map: ClientPortMap = {}
|
|
678
773
|
for client in clients:
|
|
679
|
-
if not
|
|
774
|
+
if not _client_matches_filters(client, client_mode=client_mode, only_unifi=only_unifi):
|
|
680
775
|
continue
|
|
681
776
|
name = _client_display_name(client)
|
|
682
777
|
uplink_mac = _client_uplink_mac(client)
|
{unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/lldp_md.py
RENAMED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from collections.abc import Iterable
|
|
6
6
|
|
|
7
7
|
from ..model.lldp import LLDPEntry, local_port_label
|
|
8
|
+
from ..model.ports import extract_port_number
|
|
8
9
|
from ..model.topology import Device, build_client_port_map, build_device_index, build_port_map
|
|
9
10
|
from .device_ports_md import render_device_port_details
|
|
10
11
|
from .markdown_tables import markdown_table_lines
|
|
@@ -22,7 +23,13 @@ def _client_field(client: object, name: str) -> object | None:
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def _client_display_name(client: object) -> str | None:
|
|
25
|
-
|
|
26
|
+
raw_name = _client_field(client, "name")
|
|
27
|
+
if isinstance(raw_name, str) and raw_name.strip():
|
|
28
|
+
return raw_name.strip()
|
|
29
|
+
preferred = _client_ucore_display_name(client)
|
|
30
|
+
if preferred:
|
|
31
|
+
return preferred
|
|
32
|
+
for key in ("hostname", "mac"):
|
|
26
33
|
value = _client_field(client, key)
|
|
27
34
|
if isinstance(value, str) and value.strip():
|
|
28
35
|
return value.strip()
|
|
@@ -44,20 +51,31 @@ def _client_uplink_mac(client: object) -> str | None:
|
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
def _client_uplink_port(client: object) -> int | None:
|
|
47
|
-
for
|
|
48
|
-
|
|
49
|
-
if
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
for value in _client_port_values(client):
|
|
55
|
+
parsed = _parse_port_value(value)
|
|
56
|
+
if parsed is not None:
|
|
57
|
+
return parsed
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _client_port_values(client: object) -> Iterable[object | None]:
|
|
62
|
+
for key in ("uplink_remote_port", "sw_port", "ap_port", "port_idx"):
|
|
63
|
+
yield _client_field(client, key)
|
|
53
64
|
for key in ("uplink", "last_uplink"):
|
|
54
65
|
nested = _client_field(client, key)
|
|
55
66
|
if isinstance(nested, dict):
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
for nested_key in ("uplink_remote_port", "port_idx"):
|
|
68
|
+
yield nested.get(nested_key)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _parse_port_value(value: object | None) -> int | None:
|
|
72
|
+
if isinstance(value, int):
|
|
73
|
+
return value
|
|
74
|
+
if isinstance(value, str):
|
|
75
|
+
stripped = value.strip()
|
|
76
|
+
if stripped.isdigit():
|
|
77
|
+
return int(stripped)
|
|
78
|
+
return extract_port_number(stripped)
|
|
61
79
|
return None
|
|
62
80
|
|
|
63
81
|
|
|
@@ -65,6 +83,73 @@ def _client_is_wired(client: object) -> bool:
|
|
|
65
83
|
return bool(_client_field(client, "is_wired"))
|
|
66
84
|
|
|
67
85
|
|
|
86
|
+
def _client_unifi_flag(client: object) -> bool | None:
|
|
87
|
+
for key in ("is_unifi", "is_unifi_device", "is_ubnt", "is_uap", "is_managed"):
|
|
88
|
+
value = _client_field(client, key)
|
|
89
|
+
if isinstance(value, bool):
|
|
90
|
+
return value
|
|
91
|
+
if isinstance(value, int):
|
|
92
|
+
return value != 0
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _client_vendor(client: object) -> str | None:
|
|
97
|
+
for key in ("oui", "vendor", "vendor_name", "manufacturer", "manufacturer_name"):
|
|
98
|
+
value = _client_field(client, key)
|
|
99
|
+
if isinstance(value, str) and value.strip():
|
|
100
|
+
return value.strip()
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _client_ucore_info(client: object) -> dict[str, object] | None:
|
|
105
|
+
info = _client_field(client, "unifi_device_info_from_ucore")
|
|
106
|
+
if isinstance(info, dict):
|
|
107
|
+
return info
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _client_ucore_display_name(client: object) -> str | None:
|
|
112
|
+
ucore = _client_ucore_info(client)
|
|
113
|
+
if not ucore:
|
|
114
|
+
return None
|
|
115
|
+
for key in ("name", "computed_model", "product_model", "product_shortname"):
|
|
116
|
+
value = ucore.get(key)
|
|
117
|
+
if isinstance(value, str) and value.strip():
|
|
118
|
+
return value.strip()
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _client_hostname_source(client: object) -> str | None:
|
|
123
|
+
value = _client_field(client, "hostname_source")
|
|
124
|
+
if isinstance(value, str) and value.strip():
|
|
125
|
+
return value.strip()
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _client_is_unifi(client: object) -> bool:
|
|
130
|
+
flag = _client_unifi_flag(client)
|
|
131
|
+
if flag is not None:
|
|
132
|
+
return flag
|
|
133
|
+
ucore = _client_ucore_info(client)
|
|
134
|
+
if ucore:
|
|
135
|
+
managed = ucore.get("managed")
|
|
136
|
+
if isinstance(managed, bool) and managed:
|
|
137
|
+
return True
|
|
138
|
+
if isinstance(ucore.get("product_line"), str) and ucore.get("product_line"):
|
|
139
|
+
return True
|
|
140
|
+
if isinstance(ucore.get("product_shortname"), str) and ucore.get("product_shortname"):
|
|
141
|
+
return True
|
|
142
|
+
for key in ("name", "computed_model", "product_model"):
|
|
143
|
+
value = ucore.get(key)
|
|
144
|
+
if isinstance(value, str) and value.strip():
|
|
145
|
+
return True
|
|
146
|
+
vendor = _client_vendor(client)
|
|
147
|
+
if not vendor:
|
|
148
|
+
return False
|
|
149
|
+
normalized = vendor.lower()
|
|
150
|
+
return "ubiquiti" in normalized or "unifi" in normalized
|
|
151
|
+
|
|
152
|
+
|
|
68
153
|
def _client_matches_mode(client: object, mode: str) -> bool:
|
|
69
154
|
wired = _client_is_wired(client)
|
|
70
155
|
if mode == "all":
|
|
@@ -74,6 +159,14 @@ def _client_matches_mode(client: object, mode: str) -> bool:
|
|
|
74
159
|
return wired
|
|
75
160
|
|
|
76
161
|
|
|
162
|
+
def _client_matches_filters(client: object, *, client_mode: str, only_unifi: bool) -> bool:
|
|
163
|
+
if not _client_matches_mode(client, client_mode):
|
|
164
|
+
return False
|
|
165
|
+
if only_unifi and not _client_is_unifi(client):
|
|
166
|
+
return False
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
|
|
77
170
|
def _lldp_sort_key(entry: LLDPEntry) -> tuple[int, str, str]:
|
|
78
171
|
port_label = local_port_label(entry) or ""
|
|
79
172
|
port_number = "".join(ch for ch in port_label if ch.isdigit())
|
|
@@ -187,10 +280,11 @@ def _client_rows(
|
|
|
187
280
|
*,
|
|
188
281
|
include_ports: bool,
|
|
189
282
|
client_mode: str,
|
|
283
|
+
only_unifi: bool,
|
|
190
284
|
) -> dict[str, list[tuple[str, str | None]]]:
|
|
191
285
|
rows_by_device: dict[str, list[tuple[str, str | None]]] = {}
|
|
192
286
|
for client in clients:
|
|
193
|
-
if not
|
|
287
|
+
if not _client_matches_filters(client, client_mode=client_mode, only_unifi=only_unifi):
|
|
194
288
|
continue
|
|
195
289
|
name = _client_display_name(client)
|
|
196
290
|
uplink_mac = _client_uplink_mac(client)
|
|
@@ -215,6 +309,7 @@ def _prepare_lldp_maps(
|
|
|
215
309
|
include_ports: bool,
|
|
216
310
|
show_clients: bool,
|
|
217
311
|
client_mode: str,
|
|
312
|
+
only_unifi: bool,
|
|
218
313
|
) -> tuple[
|
|
219
314
|
dict[tuple[str, str], str],
|
|
220
315
|
dict[str, list[tuple[int, str]]] | None,
|
|
@@ -222,7 +317,13 @@ def _prepare_lldp_maps(
|
|
|
222
317
|
]:
|
|
223
318
|
device_index = build_device_index(devices)
|
|
224
319
|
client_rows = (
|
|
225
|
-
_client_rows(
|
|
320
|
+
_client_rows(
|
|
321
|
+
clients,
|
|
322
|
+
device_index,
|
|
323
|
+
include_ports=include_ports,
|
|
324
|
+
client_mode=client_mode,
|
|
325
|
+
only_unifi=only_unifi,
|
|
326
|
+
)
|
|
226
327
|
if clients
|
|
227
328
|
else {}
|
|
228
329
|
)
|
|
@@ -231,7 +332,12 @@ def _prepare_lldp_maps(
|
|
|
231
332
|
if include_ports:
|
|
232
333
|
port_map = build_port_map(devices, only_unifi=False)
|
|
233
334
|
if clients and show_clients:
|
|
234
|
-
client_port_map = build_client_port_map(
|
|
335
|
+
client_port_map = build_client_port_map(
|
|
336
|
+
devices,
|
|
337
|
+
clients,
|
|
338
|
+
client_mode=client_mode,
|
|
339
|
+
only_unifi=only_unifi,
|
|
340
|
+
)
|
|
235
341
|
return port_map, client_port_map, client_rows
|
|
236
342
|
|
|
237
343
|
|
|
@@ -306,6 +412,7 @@ def render_lldp_md(
|
|
|
306
412
|
include_ports: bool = False,
|
|
307
413
|
show_clients: bool = False,
|
|
308
414
|
client_mode: str = "wired",
|
|
415
|
+
only_unifi: bool = False,
|
|
309
416
|
) -> str:
|
|
310
417
|
device_index = build_device_index(devices)
|
|
311
418
|
port_map, client_port_map, client_rows = _prepare_lldp_maps(
|
|
@@ -314,6 +421,7 @@ def render_lldp_md(
|
|
|
314
421
|
include_ports=include_ports,
|
|
315
422
|
show_clients=show_clients,
|
|
316
423
|
client_mode=client_mode,
|
|
424
|
+
only_unifi=only_unifi,
|
|
317
425
|
)
|
|
318
426
|
sections: list[str] = []
|
|
319
427
|
for device in sorted(devices, key=lambda item: item.name.lower()):
|
{unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps/render/svg.py
RENAMED
|
@@ -547,8 +547,11 @@ def _render_svg_edges(
|
|
|
547
547
|
f"L {dst_cx} {mid_y} L {dst_cx} {dst_top}"
|
|
548
548
|
)
|
|
549
549
|
dash = ' stroke-dasharray="6 4"' if edge.wireless else ""
|
|
550
|
+
left_attr = _escape_attr(edge.left, quote=True)
|
|
551
|
+
right_attr = _escape_attr(edge.right, quote=True)
|
|
550
552
|
lines.append(
|
|
551
|
-
f'<path d="{path}" stroke="{color}" stroke-width="{width_px}" fill="none"{dash}
|
|
553
|
+
f'<path d="{path}" stroke="{color}" stroke-width="{width_px}" fill="none"{dash} '
|
|
554
|
+
f'data-edge-left="{left_attr}" data-edge-right="{right_attr}"/>'
|
|
552
555
|
)
|
|
553
556
|
if edge.poe:
|
|
554
557
|
icon_x = dst_cx
|
|
@@ -834,9 +837,12 @@ def _render_iso_edges(
|
|
|
834
837
|
dst_cy,
|
|
835
838
|
)
|
|
836
839
|
dash = ' stroke-dasharray="8 6"' if edge.wireless else ""
|
|
840
|
+
left_attr = _escape_attr(edge.left, quote=True)
|
|
841
|
+
right_attr = _escape_attr(edge.right, quote=True)
|
|
837
842
|
lines.append(
|
|
838
843
|
f'<path d="{" ".join(path_cmds)}" stroke="{color}" stroke-width="{width_px}" '
|
|
839
|
-
f'fill="none" stroke-linecap="round" stroke-linejoin="round"{dash}
|
|
844
|
+
f'fill="none" stroke-linecap="round" stroke-linejoin="round"{dash} '
|
|
845
|
+
f'data-edge-left="{left_attr}" data-edge-right="{right_attr}"/>'
|
|
840
846
|
)
|
|
841
847
|
if edge.poe:
|
|
842
848
|
icon_x = dst_cx
|
{unifi_network_maps-1.4.10 → unifi_network_maps-1.4.12}/src/unifi_network_maps.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unifi-network-maps
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.12
|
|
4
4
|
Summary: Dynamic UniFi -> network maps in mermaid or svg
|
|
5
5
|
Author: Merlijn
|
|
6
6
|
License-Expression: MIT
|
|
@@ -223,7 +223,7 @@ Functional:
|
|
|
223
223
|
- `--include-ports`: show port labels (Mermaid shows both ends; SVG shows compact labels).
|
|
224
224
|
- `--include-clients`: add active wired clients as leaf nodes.
|
|
225
225
|
- `--client-scope wired|wireless|all`: which client types to include (default wired).
|
|
226
|
-
- `--only-unifi`: only include neighbors that are UniFi devices.
|
|
226
|
+
- `--only-unifi`: only include neighbors that are UniFi devices; when clients are included, filters to UniFi-managed clients (by explicit UniFi flags or vendor/OUI).
|
|
227
227
|
- `--no-cache`: disable UniFi API cache reads and writes.
|
|
228
228
|
|
|
229
229
|
Mermaid:
|
|
@@ -57,6 +57,58 @@ def test_build_client_edges_includes_uplink_port_label():
|
|
|
57
57
|
assert edges[0].label == "Switch A: Port 3 <-> Laptop"
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def test_build_client_edges_only_unifi_filters_non_unifi():
|
|
61
|
+
device_index = {"aa:bb:cc:dd:ee:ff": "Switch A"}
|
|
62
|
+
clients = [
|
|
63
|
+
{"name": "Desk PC", "is_wired": True, "sw_mac": "aa:bb:cc:dd:ee:ff", "is_unifi": False},
|
|
64
|
+
{"name": "Protect Cam", "is_wired": True, "sw_mac": "aa:bb:cc:dd:ee:ff", "is_unifi": True},
|
|
65
|
+
]
|
|
66
|
+
edges = build_client_edges(clients, device_index, only_unifi=True)
|
|
67
|
+
assert [edge.right for edge in edges] == ["Protect Cam"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_build_client_edges_only_unifi_vendor_fallback():
|
|
71
|
+
device_index = {"aa:bb:cc:dd:ee:ff": "Switch A"}
|
|
72
|
+
clients = [
|
|
73
|
+
{
|
|
74
|
+
"name": "UniFi Sensor",
|
|
75
|
+
"is_wired": True,
|
|
76
|
+
"sw_mac": "aa:bb:cc:dd:ee:ff",
|
|
77
|
+
"oui": "Ubiquiti Inc.",
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
edges = build_client_edges(clients, device_index, only_unifi=True)
|
|
81
|
+
assert edges[0].right == "UniFi Sensor"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_build_client_edges_only_unifi_ucore_managed():
|
|
85
|
+
device_index = {"aa:bb:cc:dd:ee:ff": "Switch A"}
|
|
86
|
+
clients = [
|
|
87
|
+
{
|
|
88
|
+
"name": "Doorbell Lite",
|
|
89
|
+
"is_wired": True,
|
|
90
|
+
"sw_mac": "aa:bb:cc:dd:ee:ff",
|
|
91
|
+
"unifi_device_info_from_ucore": {"managed": True},
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
edges = build_client_edges(clients, device_index, only_unifi=True)
|
|
95
|
+
assert edges[0].right == "Doorbell Lite"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_build_client_edges_prefers_ucore_name_over_hostname():
|
|
99
|
+
device_index = {"aa:bb:cc:dd:ee:ff": "Switch A"}
|
|
100
|
+
clients = [
|
|
101
|
+
{
|
|
102
|
+
"hostname": "espressif",
|
|
103
|
+
"is_wired": True,
|
|
104
|
+
"sw_mac": "aa:bb:cc:dd:ee:ff",
|
|
105
|
+
"unifi_device_info_from_ucore": {"name": "Smart PoE Chime"},
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
edges = build_client_edges(clients, device_index, only_unifi=True)
|
|
109
|
+
assert edges[0].right == "Smart PoE Chime"
|
|
110
|
+
|
|
111
|
+
|
|
60
112
|
def test_build_node_type_map_skips_wireless_clients():
|
|
61
113
|
devices = [
|
|
62
114
|
Device(name="Gateway", model_name="", model="", mac="aa", ip="", type="udm", lldp_info=[])
|
|
@@ -64,3 +116,16 @@ def test_build_node_type_map_skips_wireless_clients():
|
|
|
64
116
|
clients = [{"name": "Phone", "is_wired": False}]
|
|
65
117
|
node_types = build_node_type_map(devices, clients)
|
|
66
118
|
assert "Phone" not in node_types
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_build_node_type_map_only_unifi_filters_clients():
|
|
122
|
+
devices = [
|
|
123
|
+
Device(name="Gateway", model_name="", model="", mac="aa", ip="", type="udm", lldp_info=[])
|
|
124
|
+
]
|
|
125
|
+
clients = [
|
|
126
|
+
{"name": "Desk PC", "is_wired": True, "is_unifi": False},
|
|
127
|
+
{"name": "Protect Cam", "is_wired": True, "is_unifi": True},
|
|
128
|
+
]
|
|
129
|
+
node_types = build_node_type_map(devices, clients, only_unifi=True)
|
|
130
|
+
assert "Protect Cam" in node_types
|
|
131
|
+
assert "Desk PC" not in node_types
|