netbox-plugin-network-map-yativilli 1.0.1__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.
- netbox_plugin_network_map_yativilli-1.0.1.dist-info/METADATA +380 -0
- netbox_plugin_network_map_yativilli-1.0.1.dist-info/RECORD +76 -0
- netbox_plugin_network_map_yativilli-1.0.1.dist-info/WHEEL +5 -0
- netbox_plugin_network_map_yativilli-1.0.1.dist-info/licenses/LICENSE +674 -0
- netbox_plugin_network_map_yativilli-1.0.1.dist-info/top_level.txt +1 -0
- network_map/__init__.py +28 -0
- network_map/api/__init__.py +0 -0
- network_map/api/urls.py +32 -0
- network_map/api/views.py +458 -0
- network_map/bulk.py +89 -0
- network_map/colors.py +251 -0
- network_map/coverage.py +683 -0
- network_map/defaults.py +63 -0
- network_map/floor_plan.py +667 -0
- network_map/geocoding.py +102 -0
- network_map/locale/de/LC_MESSAGES/django.mo +0 -0
- network_map/locale/fr/LC_MESSAGES/django.mo +0 -0
- network_map/lv03.py +148 -0
- network_map/map_tiles.py +403 -0
- network_map/migrations/0001_initial.py +24 -0
- network_map/migrations/__init__.py +0 -0
- network_map/models.py +103 -0
- network_map/navigation.py +30 -0
- network_map/places.py +48 -0
- network_map/png_render.py +122 -0
- network_map/static/network_map/data_coverage/coverage.css +28 -0
- network_map/static/network_map/shared/export_dialog.css +90 -0
- network_map/static/network_map/shared/export_dialog.js +192 -0
- network_map/static/network_map/shared/svg_export.js +1174 -0
- network_map/static/network_map/subnet_map/lv03_grid.js +47 -0
- network_map/static/network_map/subnet_map/subnet_map.css +417 -0
- network_map/static/network_map/subnet_map/subnet_map.js +1215 -0
- network_map/static/network_map/vendor/leaflet/images/layers-2x.png +0 -0
- network_map/static/network_map/vendor/leaflet/images/layers.png +0 -0
- network_map/static/network_map/vendor/leaflet/images/marker-icon-2x.png +0 -0
- network_map/static/network_map/vendor/leaflet/images/marker-icon.png +0 -0
- network_map/static/network_map/vendor/leaflet/images/marker-shadow.png +0 -0
- network_map/static/network_map/vendor/leaflet/leaflet.css +661 -0
- network_map/static/network_map/vendor/leaflet/leaflet.js +5 -0
- network_map/static/network_map/vlan_connection/vlan_connections.css +254 -0
- network_map/static/network_map/vlan_element_list/vlan_element_list.css +130 -0
- network_map/static/network_map/vlan_topology/vlan_topology.css +154 -0
- network_map/static/network_map/vlan_topology/vlan_topology.js +487 -0
- network_map/svg_render/__init__.py +179 -0
- network_map/svg_render/base.py +99 -0
- network_map/svg_render/logical_tree.py +124 -0
- network_map/svg_render/machine_list.py +160 -0
- network_map/svg_render/subnet_map.py +608 -0
- network_map/svg_render/topology.py +263 -0
- network_map/swisstopo.py +436 -0
- network_map/templates/network_map/data_coverage.html +146 -0
- network_map/templates/network_map/includes/_export_dialog.html +6 -0
- network_map/templates/network_map/includes/_info_item.html +10 -0
- network_map/templates/network_map/subnet_map.html +94 -0
- network_map/templates/network_map/vlan_connection.html +146 -0
- network_map/templates/network_map/vlan_element_list.html +89 -0
- network_map/templates/network_map/vlan_topology.html +49 -0
- network_map/templatetags/__init__.py +0 -0
- network_map/templatetags/network_map_export.py +26 -0
- network_map/templatetags/network_map_static.py +31 -0
- network_map/tests/__init__.py +1 -0
- network_map/tests/_helpers.py +67 -0
- network_map/tests/test_api_floor_plan.py +280 -0
- network_map/tests/test_api_svg.py +556 -0
- network_map/tests/test_bulk.py +220 -0
- network_map/tests/test_colors.py +176 -0
- network_map/tests/test_coverage.py +279 -0
- network_map/tests/test_floor_plan.py +214 -0
- network_map/tests/test_geo.py +791 -0
- network_map/tests/test_png.py +67 -0
- network_map/tests/test_templatetags.py +47 -0
- network_map/tests/test_views.py +228 -0
- network_map/tests/test_vlan.py +43 -0
- network_map/urls.py +18 -0
- network_map/views.py +730 -0
- network_map/web_mercator.py +64 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netbox-plugin-network-map-yativilli
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A network_map plugin for netbox
|
|
5
|
+
Author-email: Yannick Wernle <yannick@wernle.net>
|
|
6
|
+
License-Expression: GPL-3.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/yativilli/netbox_networkmap_plugin
|
|
8
|
+
Project-URL: Repository, https://github.com/yativilli/netbox_networkmap_plugin
|
|
9
|
+
Requires-Python: >=3.12.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Provides-Extra: png
|
|
13
|
+
Requires-Dist: cairosvg>=2.7; extra == "png"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# NetBox Network Map Plugin
|
|
17
|
+
|
|
18
|
+
Machines on a network map inside NetBox: a machine list, a VLAN connection tree,
|
|
19
|
+
a topology map and a geographic subnet map, each also handed out as a picture.
|
|
20
|
+
Some views are Swiss-centric - the map shows a canton and draws on swisstopo
|
|
21
|
+
tiles.
|
|
22
|
+
|
|
23
|
+
## Install from a checkout
|
|
24
|
+
|
|
25
|
+
For a development install against the NetBox checkout this plugin lives next to:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd /opt/netbox/network_map_plugin
|
|
29
|
+
/opt/netbox/venv/bin/python -m pip install --editable . --no-deps
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The pages live under `/plugins/networkmap/` (`vlan-list/`, `vlan-topology/`,
|
|
33
|
+
`vlan-connections/`, `subnet-map/`, `coverage/`) and are linked from the NetBox
|
|
34
|
+
menu. In production, collect static files before restarting.
|
|
35
|
+
|
|
36
|
+
## Build
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd /opt/netbox/network_map_plugin
|
|
40
|
+
python -m pip install build
|
|
41
|
+
python -m build
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The sdist and the wheel land in `dist/`; CI builds the same way and attaches
|
|
45
|
+
both to the GitHub Release of the version in `network_map/__init__.py`, so the
|
|
46
|
+
wheel of any release can be downloaded from there instead of built.
|
|
47
|
+
|
|
48
|
+
## Deploy on a NetBox instance
|
|
49
|
+
|
|
50
|
+
1. Get the wheel, from the GitHub Release or from a local `dist/` after
|
|
51
|
+
[building](#build).
|
|
52
|
+
2. Install it into NetBox's virtualenv - with the `png` extra if the instance
|
|
53
|
+
should answer `?format=png` through cairosvg (ImageMagick on the server
|
|
54
|
+
works instead and needs no extra):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
/opt/netbox/venv/bin/python -m pip install \
|
|
58
|
+
"./netbox_plugin_network_map_yativilli-<version>-py3-none-any.whl[png]"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. Enable the plugin in `netbox/netbox/configuration.py`:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
PLUGINS = ["network_map"]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Options go in the same file under `PLUGINS_CONFIG`, see
|
|
68
|
+
[Configuration](#configuration).
|
|
69
|
+
|
|
70
|
+
4. Apply the plugin's migrations and collect its static files:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py migrate
|
|
74
|
+
/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
5. Restart NetBox (and its RQ workers, if it has them), then open any page
|
|
78
|
+
under `/plugins/networkmap/` from the NetBox menu.
|
|
79
|
+
|
|
80
|
+
## Testing
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cd /opt/netbox/netbox
|
|
84
|
+
/opt/netbox/venv/bin/python manage.py test network_map
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Where the database role may not create databases, copy
|
|
88
|
+
`netbox/configuration.py` to `netbox/configuration_testing.py`, set
|
|
89
|
+
`DEBUG = False`, add `'TEST': {'NAME': 'netbox'}` to the default database entry,
|
|
90
|
+
and run the same command with `NETBOX_CONFIGURATION=netbox.configuration_testing`
|
|
91
|
+
and `--keepdb`.
|
|
92
|
+
|
|
93
|
+
## Picture API
|
|
94
|
+
|
|
95
|
+
The kind of picture stands in the path, its format on the query string -
|
|
96
|
+
`?format=svg` (the default) or `?format=png`.
|
|
97
|
+
|
|
98
|
+
| Kind | URL |
|
|
99
|
+
| ------------ | -------------------------------------- |
|
|
100
|
+
| Machine list | `/api/plugins/networkmap/machine-list` |
|
|
101
|
+
| Logical map | `/api/plugins/networkmap/logical-map` |
|
|
102
|
+
| Subnet map | `/api/plugins/networkmap/subnet-map` |
|
|
103
|
+
| Topology map | `/api/plugins/networkmap/topology` |
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
curl -H "Authorization: Token <token>" \
|
|
107
|
+
"https://netbox.example.com/api/plugins/networkmap/topology?format=png" \
|
|
108
|
+
-o topology.png
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- Needs the `network_map.view_vlanelement` permission.
|
|
112
|
+
- A PNG needs a rasteriser on the server: `cairosvg` (the plugin's `png` extra)
|
|
113
|
+
or ImageMagick. Without either the request answers 501; a picture too big for
|
|
114
|
+
the rasteriser comes out smaller instead of failing.
|
|
115
|
+
- The subnet map puts every map tile it fetches into the document as a data URL,
|
|
116
|
+
which is what makes it big: the canton of Bern as it stands in this NetBox
|
|
117
|
+
measures about 1.8 MB as SVG and 3.8 MB as PNG, against 0.5 MB and 0.4 MB with
|
|
118
|
+
`?background=0`, which leaves the ground out.
|
|
119
|
+
- Building the subnet map geocodes every Site that has no coordinates yet - one
|
|
120
|
+
Nominatim lookup each, whose result is saved back on the Site - so this GET
|
|
121
|
+
writes to the database.
|
|
122
|
+
- `/api/plugins/networkmap/` links these addresses, and the schema lists them
|
|
123
|
+
under the `network-map` tag.
|
|
124
|
+
|
|
125
|
+
### Floor plans
|
|
126
|
+
|
|
127
|
+
A floor plan is asked for by its site, because several buildings stand in one
|
|
128
|
+
city and only the site is named uniquely. `/floor-plans/` lists what exists,
|
|
129
|
+
and each plan is then drawn by its own address:
|
|
130
|
+
|
|
131
|
+
| URL | Answers |
|
|
132
|
+
| -------------------------------------------------- | --------------------------------------------- |
|
|
133
|
+
| `/api/plugins/networkmap/floor-plans/` | JSON `{"count", "city", "site", "picture", "subnet-map", "plans"}`; the two filters are echoed back, `picture` links one drawing of every listed plan, `subnet-map` the whole network map those sites sit on, and each plan carries a `city` read off its address |
|
|
134
|
+
| `/api/plugins/networkmap/floor-plans/all/` | every listed plan in one picture, as SVG or PNG (same `city`/`site` filters) |
|
|
135
|
+
| `/api/plugins/networkmap/floor-plan/<site-slug>/` | the site's logical floor map, as SVG or PNG |
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
curl -H "Authorization: Token <token>" \
|
|
139
|
+
"https://netbox.example.com/api/plugins/networkmap/floor-plans/?city=Bern"
|
|
140
|
+
curl -H "Authorization: Token <token>" \
|
|
141
|
+
"https://netbox.example.com/api/plugins/networkmap/floor-plan/sample-site/" \
|
|
142
|
+
-o floor-plan.svg
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- The plan of a site is its logical floor map, built from the site's locations
|
|
146
|
+
and named by the site's slug alone; a site whose machines the map places has
|
|
147
|
+
a plan, every other address is answered 404.
|
|
148
|
+
- `?city=` reads the place out of the site name, physical address, shipping
|
|
149
|
+
address or description, which is where NetBox keeps it; `?site=` takes the
|
|
150
|
+
site's slug.
|
|
151
|
+
- The answer echoes the `city` and `site` it filtered by (null when not given),
|
|
152
|
+
and every plan carries its own `city`, read off its address whether the place
|
|
153
|
+
stands before the street (`Bern, Musterweg 5`) or after a postal code
|
|
154
|
+
(`Musterweg 5, 3000 Bern`).
|
|
155
|
+
- Besides the list, `picture` carries the `svg` and `png` addresses of a single
|
|
156
|
+
drawing holding every listed plan one under the other, at `/floor-plans/all/`
|
|
157
|
+
with the same filters applied.
|
|
158
|
+
- `subnet-map` carries the `svg` and `png` addresses of the whole network map,
|
|
159
|
+
the geographic picture those sites stand on, at `/subnet-map/`.
|
|
160
|
+
- The plan carries its machines the way the browser export of the map does:
|
|
161
|
+
every dot is numbered in the machine's colour, virtual ones hollow, and under
|
|
162
|
+
the picture the machines are listed with their name, description and address.
|
|
163
|
+
- The map is built from the site's locations, floor by floor, and carries the
|
|
164
|
+
same colour key as the map page; the `network_map.floor_plan` module and
|
|
165
|
+
`subnet_map.js` hold the same measurements, which `FloorPlanParityTests`
|
|
166
|
+
checks.
|
|
167
|
+
|
|
168
|
+
## Data Coverage
|
|
169
|
+
|
|
170
|
+
`/plugins/networkmap/coverage/` is a read-only map-readiness report. It checks
|
|
171
|
+
whether the NetBox data behind the existing maps is complete enough to draw
|
|
172
|
+
useful pictures, without geocoding or saving anything while the page is opened.
|
|
173
|
+
|
|
174
|
+
The first report covers:
|
|
175
|
+
|
|
176
|
+
- sites with machines but missing coordinates or an unknown city,
|
|
177
|
+
- VLANs without prefixes and prefixes without active/reserved IP addresses,
|
|
178
|
+
- IP addresses with missing or blank DNS names,
|
|
179
|
+
- machines without a site or without a room for floor plans,
|
|
180
|
+
- locations whose floor cannot be inferred from the room name,
|
|
181
|
+
- gateway-tag problems for the logical connection view.
|
|
182
|
+
|
|
183
|
+
## Configuration
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
PLUGINS_CONFIG = {
|
|
187
|
+
"network_map": {
|
|
188
|
+
"gateway_search_tag": "GATEWAY-TAG",
|
|
189
|
+
"canton_boundary_code": "BE",
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- `gateway_search_tag`: the device tag the Vlan-Connections view uses to find the
|
|
195
|
+
central gateway object.
|
|
196
|
+
- `canton_boundary_code`: the canton whose border the Subnet-Map draws, as a
|
|
197
|
+
two-letter code (`"BE"`, which is also the default) or a BFS id; `"CH"` or
|
|
198
|
+
`"SW"` draws the national border in its place, and `""` draws none.
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary>Advanced settings</summary>
|
|
202
|
+
|
|
203
|
+
All of these go in the same `PLUGINS_CONFIG` dictionary.
|
|
204
|
+
|
|
205
|
+
Geocoding, for a site that has no coordinates yet: `nominatim_url` (defaults to
|
|
206
|
+
OpenStreetMap's service), `country_codes` (`"ch"`), `request_interval_seconds`
|
|
207
|
+
(`1.0`), `request_timeout_seconds` (`10`).
|
|
208
|
+
|
|
209
|
+
Canton border, tried in that order until one answers - the geometry is fetched
|
|
210
|
+
live and cached, and the map renders without a border when none can be had:
|
|
211
|
+
`canton_boundary_url_template` (swisstopo WFS, `{id}` the BFS id; preferred
|
|
212
|
+
because it carries interior rings, which keeps canton pockets such as Steinhof
|
|
213
|
+
SO, so leave `srsName=EPSG%3A4326` in it),
|
|
214
|
+
`canton_boundary_fallback_url_template` (Nominatim, `{code}` as `CH-BE`, also
|
|
215
|
+
carrying holes), `canton_boundary_last_resort_url_template` (`map.geo.admin.ch`,
|
|
216
|
+
without holes), `canton_boundary_cache_seconds` (30 days) and
|
|
217
|
+
`canton_boundary_label` (legend text, the canton name by default). The geometry
|
|
218
|
+
is also served on its own at `/plugins/networkmap/subnet-map/canton-boundary/`.
|
|
219
|
+
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
### Map background
|
|
223
|
+
|
|
224
|
+
The served subnet map stands by default on the same swisstopo tiles, in the same
|
|
225
|
+
LV03 grid, as the page's own map, which is why the two show the same ground;
|
|
226
|
+
`map_background_source` moves the ground to OpenStreetMap instead. Tiles are
|
|
227
|
+
fetched side by side and cached, so the first caller pays for a canton, and a
|
|
228
|
+
tile that cannot be had simply stays out of the picture.
|
|
229
|
+
|
|
230
|
+
| Setting | Default | Meaning |
|
|
231
|
+
| -------------------------------- | ----------------------- | ----------------------------------------------------- |
|
|
232
|
+
| `map_background` | `True` | Draw tiles behind the served subnet map. |
|
|
233
|
+
| `map_background_source` | `"SWISSTOPO"` | Page and pictures alike: `"SWISSTOPO"` or `"OSM"`. |
|
|
234
|
+
| `map_tile_url_template` | the named source's | Tile address, in the grid of whoever is named. |
|
|
235
|
+
| `map_tile_fallback` | empty | Second source asked for a tile the first lacks. |
|
|
236
|
+
| `map_tile_fallback_url_template` | the second source's | That source's tile address. |
|
|
237
|
+
| `map_tile_zoom` | chosen from the picture | Force one zoom instead of the fitting one. |
|
|
238
|
+
| `map_tile_max` | `64` | Tiles per picture; the zoom comes down to pay for it. |
|
|
239
|
+
| `map_tile_cache_seconds` | 30 days | How long a tile is kept. |
|
|
240
|
+
| `map_tile_budget_seconds` | `20` | What one picture waits for its tiles, all together. |
|
|
241
|
+
| `map_attribution` | what the source asks | Credit under the picture, naming the ground. |
|
|
242
|
+
|
|
243
|
+
`map_background_source` says who the ground comes from, for the page's own map
|
|
244
|
+
and for every picture the server draws. `SWISSTOPO` - the default - numbers its
|
|
245
|
+
tiles in the LV03 grid, so `{z}/{y}/{x}` are zoom, row and column and a mirror
|
|
246
|
+
has to answer in that grid; the page maps on that grid too, through the CRS its
|
|
247
|
+
script builds from `lv03_grid.js`. `OSM` numbers them as the rest of the mapping
|
|
248
|
+
world does, `{z}/{x}/{y}` on the Web Mercator pyramid, and the page falls back to
|
|
249
|
+
Leaflet's own Mercator projection. A served picture is still planned in the
|
|
250
|
+
Swiss grid and each of its patches is handed over to the pyramid step covering
|
|
251
|
+
it, so the same ground shows either way - only coarser where the pyramid's steps
|
|
252
|
+
are wider. A source's own address is a setting, which is how a mirror is asked
|
|
253
|
+
instead; `map_attribution` then names whoever the ground came from, which
|
|
254
|
+
OpenStreetMap asks for and swisstopo does not. A change needs a restart, because
|
|
255
|
+
both the page and its tiles are handed over when the page is built.
|
|
256
|
+
|
|
257
|
+
`map_tile_fallback` names the source asked for a tile the first one cannot give
|
|
258
|
+
- `"OSM"` behind a swisstopo mirror that covers only part of the country, or
|
|
259
|
+
`"SWISSTOPO"` behind OpenStreetMap, whose public server sometimes turns a caller
|
|
260
|
+
away. It is empty by default, because a tile the Swiss server lacks is usually
|
|
261
|
+
ground outside the country, and asking twice for every one of those costs time.
|
|
262
|
+
Naming the source that is already named, or handing out the same address twice,
|
|
263
|
+
asks once.
|
|
264
|
+
|
|
265
|
+
### Room around the border
|
|
266
|
+
|
|
267
|
+
Two distances decide where a picture ends. The cut runs `map_border_cut` pixels
|
|
268
|
+
beyond the border, so a band of the neighbouring ground stays in the picture
|
|
269
|
+
instead of the picture ending on the line; `0` cuts on the line. And the border
|
|
270
|
+
keeps `map_border_room` pixels between itself and the edge of the picture, because
|
|
271
|
+
an area touches its own bounding box at a point - west of Geneva, south of Ticino
|
|
272
|
+
- which is where a tight frame used to cut the line off. The left and the bottom
|
|
273
|
+
edges add their own room to that.
|
|
274
|
+
|
|
275
|
+
| Setting | Default | Meaning |
|
|
276
|
+
| ----------------- | ------- | --------------------------------------------- |
|
|
277
|
+
| `map_border_cut` | `3` | How far beyond the border the picture is cut. |
|
|
278
|
+
| `map_border_room` | `22` | Room between the border and the edge. |
|
|
279
|
+
| `map_room_left` | `20` | Room the left edge adds to that. |
|
|
280
|
+
| `map_room_bottom` | `20` | Room the bottom edge adds to that. |
|
|
281
|
+
|
|
282
|
+
They are pixels of the picture and go in the same `PLUGINS_CONFIG` dictionary.
|
|
283
|
+
Both pictures obey them, the served one and the one the page's export button
|
|
284
|
+
makes, so the two never disagree about what belongs in them. A restart is needed,
|
|
285
|
+
and a page already open has to be reloaded, because it carries the numbers it was
|
|
286
|
+
served.
|
|
287
|
+
|
|
288
|
+
Should the canton come out as a plain rectangle roughly centred on Basel - Bern
|
|
289
|
+
squeezed into its own bounding box - the map is drawing a stale geometry, not the
|
|
290
|
+
canton. The border is fetched once and then kept in NetBox's cache for
|
|
291
|
+
`canton_boundary_cache_seconds`, so the map shows whatever that cache holds, not
|
|
292
|
+
what the services answer now.
|
|
293
|
+
|
|
294
|
+
A restart clears it only while the cache lives in memory (`LocMemCache`), which is
|
|
295
|
+
the usual development setup. Once NetBox caches in Redis - the usual production
|
|
296
|
+
choice - the entry is persisted to `dump.rdb` and reloaded when Redis starts, so
|
|
297
|
+
restarting NetBox, or even `redis-server`, brings the same rectangle back. Drop
|
|
298
|
+
the cache entry itself, and the next call fetches the true border:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
python /opt/netbox/netbox/manage.py shell -c \
|
|
302
|
+
'from django.core.cache import cache; cache.delete("network_map:canton_boundary:2")'
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The key ends in the BFS id of the canton (`2` is Bern, `11` Solothurn, and so on;
|
|
306
|
+
`CH` for the national border). Removing it through the cache backend does the same
|
|
307
|
+
- `redis-cli UNLINK ':1:network_map:canton_boundary:2'` - while `FLUSHDB` on the
|
|
308
|
+
caching database also clears it, but discards every other cached value with it.
|
|
309
|
+
|
|
310
|
+
## Code structure
|
|
311
|
+
|
|
312
|
+
| File | Purpose |
|
|
313
|
+
| -------------------------------------- | ------------------------------------------------------------------ |
|
|
314
|
+
| `models.py` | The `VlanElement` anchor model and the dataclasses the views fill. |
|
|
315
|
+
| `views.py` | The plugin pages: collecting what to show and handing it to the browser. |
|
|
316
|
+
| `urls.py`, `navigation.py` | The page routes and the NetBox menu entries. |
|
|
317
|
+
| `__init__.py`, `defaults.py` | Plugin registration, version, and the settings with their defaults. |
|
|
318
|
+
| `colors.py` | The location colour palette and the shades drawn from it. |
|
|
319
|
+
| `places.py` | Reading the city/place out of site names and addresses. |
|
|
320
|
+
| `coverage.py` | Read-only map-readiness checks for the Data Coverage page. |
|
|
321
|
+
| `lv03.py` | WGS84 to LV03 conversion and the swisstopo tile-grid maths. |
|
|
322
|
+
| `web_mercator.py` | The Web Mercator pyramid OpenStreetMap numbers its tiles in. |
|
|
323
|
+
| `geocoding.py` | Nominatim lookup for Sites that have no coordinates yet. |
|
|
324
|
+
| `swisstopo.py` | Fetching the canton border, with fallbacks, from public services. |
|
|
325
|
+
| `map_tiles.py` | Fetching and caching the tiles a served map stands on. |
|
|
326
|
+
| `svg_render/` | Package entry re-exporting the server-side SVG renderers. |
|
|
327
|
+
| `svg_render/base.py` | Shared SVG primitives: text helpers, wrapping, and shared styles. |
|
|
328
|
+
| `svg_render/machine_list.py` | Machine-list picture rendering. |
|
|
329
|
+
| `svg_render/logical_tree.py` | Logical VLAN connection tree rendering. |
|
|
330
|
+
| `svg_render/topology.py` | Topology map rendering. |
|
|
331
|
+
| `svg_render/subnet_map.py` | Geographic subnet map rendering, border framing, and tiles. |
|
|
332
|
+
| `floor_plan.py` | Drawing a site's logical floor plan. |
|
|
333
|
+
| `png_render.py` | Rasterising a served SVG to PNG with cairosvg or ImageMagick. |
|
|
334
|
+
| `api/views.py` | The picture and floor-plan endpoints and their permission gate. |
|
|
335
|
+
| `api/urls.py` | The API routes, with the path parts constrained by regex. |
|
|
336
|
+
| `templatetags/network_map_static.py` | The `static_url` tag keeping cached scripts honest. |
|
|
337
|
+
| `templatetags/network_map_export.py` | The `export_dialog_labels` tag behind the format dialog. |
|
|
338
|
+
| `templates/network_map/*.html` | The pages' markup, data included as JSON. |
|
|
339
|
+
| `templates/network_map/includes/` | `_info_item.html` rows and the `_export_dialog.html` dialog. |
|
|
340
|
+
| `static/network_map/shared/` | `svg_export.js` and the `export_dialog.js` format dialog. |
|
|
341
|
+
| `static/network_map/data_coverage/` | Styles for the Data Coverage page. |
|
|
342
|
+
| `static/network_map/subnet_map/` | Subnet-map script and styles, plus the generated LV03 grid. |
|
|
343
|
+
| `static/network_map/vlan_connection/` | Styles for the VLAN connection page. |
|
|
344
|
+
| `static/network_map/vlan_element_list/`| Styles for the machine list page. |
|
|
345
|
+
| `static/network_map/vlan_topology/` | Topology-map script and styles. |
|
|
346
|
+
| `static/network_map/vendor/leaflet/` | The vendored Leaflet library the subnet map runs on. |
|
|
347
|
+
| `migrations/` | Django's record of the model's state. |
|
|
348
|
+
| `locale/<lang>/LC_MESSAGES/` | The German and French catalogs, compiled `.mo` included. |
|
|
349
|
+
| `tests/` | The split test suite, run as described under Testing. |
|
|
350
|
+
|
|
351
|
+
## Releases
|
|
352
|
+
|
|
353
|
+
`__version__` in `network_map/__init__.py` is the only place the version is
|
|
354
|
+
written; a new NetBox release needs `min_version`/`max_version` widened there, or
|
|
355
|
+
NetBox disables the plugin.
|
|
356
|
+
|
|
357
|
+
Bumping it and merging to `main` releases the plugin: once lint, types and tests
|
|
358
|
+
pass, CI opens the GitHub Release named `v<version>` - the tag must stay `v`
|
|
359
|
+
followed by the version, or the run cannot match the two. Every later run on
|
|
360
|
+
`main` finds the existing release and does nothing, so a release can also be
|
|
361
|
+
re-attempted by hand from the CI workflow's "Run workflow" button.
|
|
362
|
+
|
|
363
|
+
## Translations
|
|
364
|
+
|
|
365
|
+
Catalogs are `network_map/locale/<lang>/LC_MESSAGES/django.po` (`de`, `fr`); the
|
|
366
|
+
compiled `.mo` is committed because this tree does not compile it. After changing
|
|
367
|
+
strings, fill in the new `msgstr` values and rebuild:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
cd /opt/netbox/network_map_plugin
|
|
371
|
+
DJANGO_SETTINGS_MODULE=netbox.settings PYTHONPATH=/opt/netbox/netbox \
|
|
372
|
+
/opt/netbox/venv/bin/django-admin makemessages -l de -l fr --keep-pot
|
|
373
|
+
for lang in de fr; do
|
|
374
|
+
msgfmt --check -o network_map/locale/$lang/LC_MESSAGES/django.mo \
|
|
375
|
+
network_map/locale/$lang/LC_MESSAGES/django.po
|
|
376
|
+
done
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
`--keep-pot` keeps the master catalog `makemessages` would delete after merging;
|
|
380
|
+
`--all` is unusable here because it pulls in NetBox's own language list.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
netbox_plugin_network_map_yativilli-1.0.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
|
+
network_map/__init__.py,sha256=z1xpQAYbHNPPPxSzQC2LVe7GYcZ5IqW96DNUc8u_PYM,826
|
|
3
|
+
network_map/bulk.py,sha256=ek37Y3H_Jx6Sz5zaMuB-caszXBTIDUps0jUPVo3dMg4,3036
|
|
4
|
+
network_map/colors.py,sha256=U9ZWC2zAn1y0P-kof9PGFVK6_SYqKErM18_L2Cihz2s,7359
|
|
5
|
+
network_map/coverage.py,sha256=rXtoyePWbofX-iXQd__zbu9ZpoDMjHVCZjsPwGiS3Wk,23231
|
|
6
|
+
network_map/defaults.py,sha256=8caZJOzo-yPB6JgJNwM1RNjvJ6av7sRn7iEqVreeYtI,2171
|
|
7
|
+
network_map/floor_plan.py,sha256=WlXl7KDARDEtzX8DZCddQJstWYOErQco5aKM3x9f0H8,24616
|
|
8
|
+
network_map/geocoding.py,sha256=TIbDf-Q2Q427NXj_NhT6DWin1To-f1xxLAfSya8U638,3178
|
|
9
|
+
network_map/lv03.py,sha256=m04KduDgoc6teuLSiwEPaQ--n9FiP8HImhasKLfKUPs,4912
|
|
10
|
+
network_map/map_tiles.py,sha256=voeFWbr4NPsLhR592TtHbWPkepu5QQTtpprnxJLg7Rk,15514
|
|
11
|
+
network_map/models.py,sha256=eO0gzKwJJAyOMBr6vPpaDqzuuBGfg8w5ZAkY2ih1uaQ,2333
|
|
12
|
+
network_map/navigation.py,sha256=t8uwg3esPw92VqzwTG6TNyYdiTtluWmogXTX449vW1w,954
|
|
13
|
+
network_map/places.py,sha256=qbRqABuQrU91kODyb1-ypqcRRtHXgMDyWlMVg8euXQI,1664
|
|
14
|
+
network_map/png_render.py,sha256=9T3jf5TUFy1vAn4PQjmxQl_rOdt7qSTRaWr7XCTUFaU,4212
|
|
15
|
+
network_map/swisstopo.py,sha256=yDCIElrZJlfZhfTDBtbssbbxHOezjaKCbDpkFkleDEg,13345
|
|
16
|
+
network_map/urls.py,sha256=CzH0ohTuzwF4c7OvWswPN6iIhQFWl6O9qe7GOXtve9Y,636
|
|
17
|
+
network_map/views.py,sha256=hS3dvyP7ScuZVkcOqkNpQSJwdr7_hI3qcSnT0oTnXpg,28383
|
|
18
|
+
network_map/web_mercator.py,sha256=cM6EEizP9pprA7rqNcmIJsDKay71rPtUh4nbXhpRccY,2405
|
|
19
|
+
network_map/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
network_map/api/urls.py,sha256=99rinQOWYZrV4o6v_cwoke3Vtrwg-tH_zvpixXQXAXw,1028
|
|
21
|
+
network_map/api/views.py,sha256=9Gn-u3qcWk9rvPrPRbZpxzGFCQ97zq2aTHm8KSf1914,16481
|
|
22
|
+
network_map/locale/de/LC_MESSAGES/django.mo,sha256=pcOqHhMqvQP4n92QCcPFUcX6d7gigjjSHQHJYOAaxnM,13349
|
|
23
|
+
network_map/locale/fr/LC_MESSAGES/django.mo,sha256=WvfjTGzkyt4_gIU3ISLJfl2TUZyMZTcm_t9ogiTaFm4,13551
|
|
24
|
+
network_map/migrations/0001_initial.py,sha256=YoAkeIZWqdv1KW0OUhsBpyrZ4DGogHdv1A-PYgxolLg,536
|
|
25
|
+
network_map/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
network_map/static/network_map/data_coverage/coverage.css,sha256=F5NPrnclj2gduJ7AroHEftEoXhfUnWbr-3S15E9HIzg,579
|
|
27
|
+
network_map/static/network_map/shared/export_dialog.css,sha256=P8fD6RPLlk6lGplxPQaydfRNRhXZikXpZeBHOvQ_juY,1932
|
|
28
|
+
network_map/static/network_map/shared/export_dialog.js,sha256=pGh-yyksBh9EuZum8iR_oLiDKHmkcTVpVGHOVKqjn6I,6991
|
|
29
|
+
network_map/static/network_map/shared/svg_export.js,sha256=hGed-iISrhMDHMIhS97N3xdgzgNsx3Ys2mnHXFwuXns,55968
|
|
30
|
+
network_map/static/network_map/subnet_map/lv03_grid.js,sha256=Zu1om3Y3ICSoJhLCjaBMIeBPqZqV3qVquCmlNeQWSaw,170611
|
|
31
|
+
network_map/static/network_map/subnet_map/subnet_map.css,sha256=aEwfIgMoHzfRamFUtUp_QvHk47kdX8H09dAcrbcOOYE,7836
|
|
32
|
+
network_map/static/network_map/subnet_map/subnet_map.js,sha256=gc1ct2WpdI-C-8H4ueKHJVV8DafNceR74Q0KUEYwUt4,51620
|
|
33
|
+
network_map/static/network_map/vendor/leaflet/leaflet.css,sha256=p4NxAoJBhIIN-hmNHrzRCf9tD_miZyoHS5obTRR9BMY,14806
|
|
34
|
+
network_map/static/network_map/vendor/leaflet/leaflet.js,sha256=3HH4pogLw8ob2fqNxfGvSMcC3FELCngkCgfF_u186TU,147517
|
|
35
|
+
network_map/static/network_map/vendor/leaflet/images/layers-2x.png,sha256=Bm2sqFDY_77wB68AsG6sABVyje4nnFHzy2xxbffELt8,1259
|
|
36
|
+
network_map/static/network_map/vendor/leaflet/images/layers.png,sha256=Hbvp0CjikvNvy6j4s6KNXokydU_CIVuaxp5M3s9RB8Y,696
|
|
37
|
+
network_map/static/network_map/vendor/leaflet/images/marker-icon-2x.png,sha256=ABecTB7oMNOhCEEq4NKU9Vd2z-sIXGASmjmqb8SuJSg,2464
|
|
38
|
+
network_map/static/network_map/vendor/leaflet/images/marker-icon.png,sha256=V0w6XMqF9BFAhbaEFZbWLwDXyJLHsD8oy_owHesdxDc,1466
|
|
39
|
+
network_map/static/network_map/vendor/leaflet/images/marker-shadow.png,sha256=Jk9cZAM58ELdcpBiz8BMF_jqDymIK1OOOEjtjxDttNo,618
|
|
40
|
+
network_map/static/network_map/vlan_connection/vlan_connections.css,sha256=HSfNNLYlY5OJNToUGPLVaytZtLzitthiC6VScUmjLTA,4703
|
|
41
|
+
network_map/static/network_map/vlan_element_list/vlan_element_list.css,sha256=-k8QeTarEffiS7QxoTV-ZMCW9n1HCTiOGYxHuTPdxGk,3058
|
|
42
|
+
network_map/static/network_map/vlan_topology/vlan_topology.css,sha256=tifODfrhXIHUmfKWzT5zLu8S-70rBsm61-eQwNBFTl8,2400
|
|
43
|
+
network_map/static/network_map/vlan_topology/vlan_topology.js,sha256=ezA0f_h7jmVD2bEcw4uaEsk5OD1qzEhsW1K7KKbRSj8,20073
|
|
44
|
+
network_map/svg_render/__init__.py,sha256=1bdCGrbE9ePn3qxIXpSAIxYHn9Na5W_tXPqo5WbkGsg,3039
|
|
45
|
+
network_map/svg_render/base.py,sha256=ZBAi_MhGCabMMX-zbFaslTX11cFV3Fp1BbeGEWqnWPg,3790
|
|
46
|
+
network_map/svg_render/logical_tree.py,sha256=pwJOVbLi-QDutiNrIPqEppbxyjKgzBJNc4OvY6w8_cA,4176
|
|
47
|
+
network_map/svg_render/machine_list.py,sha256=n1yn_T8M7sDh9sno9sLc2j00cVtwP12-6Kr_psA0xEY,5761
|
|
48
|
+
network_map/svg_render/subnet_map.py,sha256=0LTPgcEIvZWqEbcBValOmuMGJgOmkBUnNIxZO9j7730,22387
|
|
49
|
+
network_map/svg_render/topology.py,sha256=2cHNfh2wWG_g45KU2cVVOkEIE0KRZ5QL01jXfwqQQws,9153
|
|
50
|
+
network_map/templates/network_map/data_coverage.html,sha256=y6zAcf6LxO1qtusmRaqElLV56lqbaWd64Pt7TVvhE08,6809
|
|
51
|
+
network_map/templates/network_map/subnet_map.html,sha256=RNMWiiS0sMdwWVInVuZ4IQwDk6hynjBOSsU_XZqS92w,4909
|
|
52
|
+
network_map/templates/network_map/vlan_connection.html,sha256=hxxNgLYarqcA1N_MQq7Fb1fBrISuF43dTbQDWdNnDnk,9454
|
|
53
|
+
network_map/templates/network_map/vlan_element_list.html,sha256=u1A9ZrNI26UwtwWNz6g_25ORznIuoGK4wqdtsCW_wg4,4656
|
|
54
|
+
network_map/templates/network_map/vlan_topology.html,sha256=w-u-9rS0cA1DHty5GJmd-JsDUf-rLIBLCDbOsPxJ2XU,2475
|
|
55
|
+
network_map/templates/network_map/includes/_export_dialog.html,sha256=oZBRvuJlFglo48jMIcODJPuWKzCOrwlo6TeXjOgOVKQ,256
|
|
56
|
+
network_map/templates/network_map/includes/_info_item.html,sha256=zuM-6LkFz-qNp8ejfhxrbgrJ7Ay5fvDRuUYhx4z5lnk,318
|
|
57
|
+
network_map/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
|
+
network_map/templatetags/network_map_export.py,sha256=g80x708r6apKmMLrxIQaKPiNoUxAA0S-xXEcRwvWDSc,795
|
|
59
|
+
network_map/templatetags/network_map_static.py,sha256=8ESnPWlFS0ZNHoYX3ackkQbzSpBPPBpYvt1nblKFGN0,835
|
|
60
|
+
network_map/tests/__init__.py,sha256=2dpJPO_sVQ9hbqwDaTCULLCVZJBhV8cvf91ntNZAi-M,40
|
|
61
|
+
network_map/tests/_helpers.py,sha256=pWiesX3_3IoMqJd0OrOxVTvRNVkugW8heLCrGu84i48,1630
|
|
62
|
+
network_map/tests/test_api_floor_plan.py,sha256=fJ9Drn_FooQOLuqQy0yIx2suRvvvfwnrvlA1lywhaJ8,12314
|
|
63
|
+
network_map/tests/test_api_svg.py,sha256=nplz6hy7UTMh0NGMcCThluZddtyTOCwMgBoOUQ7ILFM,22625
|
|
64
|
+
network_map/tests/test_bulk.py,sha256=Xzs4qcNjlDp_kfU36DLx-uHZKYXPEMNrelhMEWGfHqU,8185
|
|
65
|
+
network_map/tests/test_colors.py,sha256=Q_gx7VEDyYON3pxYUAib9gDFbQSH_4WImjQMjKAHUi0,6517
|
|
66
|
+
network_map/tests/test_coverage.py,sha256=Esl5BkgcK9Wv85QVNoH-iUZMgXSCI_24jaLDHfo1fFY,9286
|
|
67
|
+
network_map/tests/test_floor_plan.py,sha256=YZuKIt5jWYSSiBhpHE0IgJwpzISX7G2BH_zaNXSH2CQ,9700
|
|
68
|
+
network_map/tests/test_geo.py,sha256=RIe7LX_Xx9sIhhOhbVn29C-24wl2BwGYRwCj5iEektU,35384
|
|
69
|
+
network_map/tests/test_png.py,sha256=SlMbWWq4sgfP87qQQ5oZ5DFGdLC3qLnIrk1OkY3SrhQ,2854
|
|
70
|
+
network_map/tests/test_templatetags.py,sha256=uvwkydOps9t2ydjqHIPQTTQtS57RKStnLkj8EfagCTI,1785
|
|
71
|
+
network_map/tests/test_views.py,sha256=0d-6OsIT5q2q7-EWbxV-UB7M7dAXoRIA__5YSDiKfDA,8501
|
|
72
|
+
network_map/tests/test_vlan.py,sha256=Wablm_EPp64zwjPAcYowBLA_cpddZnIAIS1LpzG9Xqo,1659
|
|
73
|
+
netbox_plugin_network_map_yativilli-1.0.1.dist-info/METADATA,sha256=MH_JIUlMYG-RE3oIU0-XzerI7nLAx9rdTe3YdbteWw4,20885
|
|
74
|
+
netbox_plugin_network_map_yativilli-1.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
75
|
+
netbox_plugin_network_map_yativilli-1.0.1.dist-info/top_level.txt,sha256=N6-pVw0lv81uCsCaTJZNVdB2S840KP7IzZBb7rQiNno,12
|
|
76
|
+
netbox_plugin_network_map_yativilli-1.0.1.dist-info/RECORD,,
|