Coverage for netbox/navigation/menu.py: 82%
30 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from functools import cache
3from django.utils.translation import gettext_lazy as _
5from netbox.registry import registry
7from . import *
9#
10# Nav menus
11#
13ORGANIZATION_MENU = Menu(
14 label=_('Organization'),
15 icon_class='mdi mdi-domain',
16 groups=(
17 MenuGroup(
18 label=_('Sites'),
19 items=(
20 get_model_item('dcim', 'region', _('Regions')),
21 get_model_item('dcim', 'sitegroup', _('Site Groups')),
22 get_model_item('dcim', 'site', _('Sites')),
23 get_model_item('dcim', 'location', _('Locations')),
24 ),
25 ),
26 MenuGroup(
27 label=_('Tenancy'),
28 items=(
29 get_model_item('tenancy', 'tenant', _('Tenants')),
30 get_model_item('tenancy', 'tenantgroup', _('Tenant Groups')),
31 ),
32 ),
33 MenuGroup(
34 label=_('Contacts'),
35 items=(
36 get_model_item('tenancy', 'contact', _('Contacts')),
37 get_model_item('tenancy', 'contactgroup', _('Contact Groups')),
38 get_model_item('tenancy', 'contactrole', _('Contact Roles')),
39 get_model_item('tenancy', 'contactassignment', _('Contact Assignments'), actions=['bulk_import']),
40 ),
41 ),
42 ),
43)
45RACKS_MENU = Menu(
46 label=_('Racks'),
47 icon_class='mdi mdi-door-sliding',
48 groups=(
49 MenuGroup(
50 label=_('Racks'),
51 items=(
52 get_model_item('dcim', 'rack', _('Racks')),
53 get_model_item('dcim', 'rackreservation', _('Reservations')),
54 MenuItem(
55 link='dcim:rack_elevation_list',
56 link_text=_('Elevations'),
57 permissions=['dcim.view_rack']
58 ),
59 ),
60 ),
61 MenuGroup(
62 label=_('Rack Organization'),
63 items=(
64 get_model_item('dcim', 'rackgroup', _('Rack Groups')),
65 get_model_item('dcim', 'rackrole', _('Rack Roles')),
66 ),
67 ),
68 MenuGroup(
69 label=_('Rack Types'),
70 items=(
71 get_model_item('dcim', 'racktype', _('Rack Types')),
72 ),
73 ),
74 ),
75)
77DEVICES_MENU = Menu(
78 label=_('Devices'),
79 icon_class='mdi mdi-server',
80 groups=(
81 MenuGroup(
82 label=_('Devices'),
83 items=(
84 get_model_item('dcim', 'device', _('Devices')),
85 get_model_item('dcim', 'module', _('Modules')),
86 get_model_item('dcim', 'devicerole', _('Device Roles')),
87 get_model_item('dcim', 'platform', _('Platforms')),
88 get_model_item('dcim', 'virtualchassis', _('Virtual Chassis')),
89 get_model_item('dcim', 'virtualdevicecontext', _('Virtual Device Contexts')),
90 ),
91 ),
92 MenuGroup(
93 label=_('Device Types'),
94 items=(
95 get_model_item('dcim', 'devicetype', _('Device Types')),
96 get_model_item('dcim', 'moduletype', _('Module Types')),
97 get_model_item('dcim', 'moduletypeprofile', _('Module Type Profiles')),
98 get_model_item('dcim', 'modulebaytype', _('Module Bay Types')),
99 get_model_item('dcim', 'manufacturer', _('Manufacturers')),
100 ),
101 ),
102 MenuGroup(
103 label=_('Device Components'),
104 items=(
105 get_model_item('dcim', 'interface', _('Interfaces')),
106 get_model_item('dcim', 'frontport', _('Front Ports')),
107 get_model_item('dcim', 'rearport', _('Rear Ports')),
108 get_model_item('dcim', 'consoleport', _('Console Ports')),
109 get_model_item('dcim', 'consoleserverport', _('Console Server Ports')),
110 get_model_item('dcim', 'powerport', _('Power Ports')),
111 get_model_item('dcim', 'poweroutlet', _('Power Outlets')),
112 get_model_item('dcim', 'coolingintake', _('Cooling Intakes')),
113 get_model_item('dcim', 'coolingoutflow', _('Cooling Outflows')),
114 get_model_item('dcim', 'modulebay', _('Module Bays')),
115 get_model_item('dcim', 'devicebay', _('Device Bays')),
116 get_model_item('dcim', 'inventoryitem', _('Inventory Items')),
117 get_model_item('dcim', 'inventoryitemrole', _('Inventory Item Roles')),
118 ),
119 ),
120 MenuGroup(
121 label=_('Addressing'),
122 items=(
123 get_model_item('dcim', 'macaddress', _('MAC Addresses')),
124 ),
125 ),
126 ),
127)
129CONNECTIONS_MENU = Menu(
130 label=_('Connections'),
131 icon_class='mdi mdi-connection',
132 groups=(
133 MenuGroup(
134 label=_('Connections'),
135 items=(
136 get_model_item('dcim', 'cable', _('Cables')),
137 get_model_item('dcim', 'cablebundle', _('Cable Bundles')),
138 get_model_item('wireless', 'wirelesslink', _('Wireless Links')),
139 MenuItem(
140 link='dcim:interface_connections_list',
141 link_text=_('Interface Connections'),
142 permissions=['dcim.view_interface']
143 ),
144 MenuItem(
145 link='dcim:console_connections_list',
146 link_text=_('Console Connections'),
147 permissions=['dcim.view_consoleport']
148 ),
149 MenuItem(
150 link='dcim:power_connections_list',
151 link_text=_('Power Connections'),
152 permissions=['dcim.view_powerport']
153 ),
154 ),
155 ),
156 ),
157)
159WIRELESS_MENU = Menu(
160 label=_('Wireless'),
161 icon_class='mdi mdi-wifi',
162 groups=(
163 MenuGroup(
164 label=_('Wireless'),
165 items=(
166 get_model_item('wireless', 'wirelesslan', _('Wireless LANs')),
167 get_model_item('wireless', 'wirelesslangroup', _('Wireless LAN Groups')),
168 ),
169 ),
170 ),
171)
173IPAM_MENU = Menu(
174 label=_('IPAM'),
175 icon_class='mdi mdi-counter',
176 groups=(
177 MenuGroup(
178 label=_('IP Addresses'),
179 items=(
180 get_model_item('ipam', 'ipaddress', _('IP Addresses')),
181 get_model_item('ipam', 'iprange', _('IP Ranges')),
182 ),
183 ),
184 MenuGroup(
185 label=_('Prefixes'),
186 items=(
187 get_model_item('ipam', 'prefix', _('Prefixes')),
188 get_model_item('ipam', 'role', _('Prefix & VLAN Roles')),
189 ),
190 ),
191 MenuGroup(
192 label=_('ASNs'),
193 items=(
194 get_model_item('ipam', 'asnrange', _('ASN Ranges')),
195 get_model_item('ipam', 'asn', _('ASNs')),
196 ),
197 ),
198 MenuGroup(
199 label=_('Aggregates'),
200 items=(
201 get_model_item('ipam', 'aggregate', _('Aggregates')),
202 get_model_item('ipam', 'rir', _('RIRs')),
203 ),
204 ),
205 MenuGroup(
206 label=_('VRFs'),
207 items=(
208 get_model_item('ipam', 'vrf', _('VRFs')),
209 get_model_item('ipam', 'routetarget', _('Route Targets')),
210 ),
211 ),
212 MenuGroup(
213 label=_('VLANs'),
214 items=(
215 get_model_item('ipam', 'vlan', _('VLANs')),
216 get_model_item('ipam', 'vlangroup', _('VLAN Groups')),
217 get_model_item('ipam', 'vlantranslationpolicy', _('VLAN Translation Policies')),
218 get_model_item('ipam', 'vlantranslationrule', _('VLAN Translation Rules')),
219 ),
220 ),
221 MenuGroup(
222 label=_('Other'),
223 items=(
224 get_model_item('ipam', 'fhrpgroup', _('FHRP Groups')),
225 get_model_item('ipam', 'servicetemplate', _('Application Service Templates')),
226 get_model_item('ipam', 'service', _('Application Services')),
227 ),
228 ),
229 ),
230)
232VPN_MENU = Menu(
233 label=_('VPN'),
234 icon_class='mdi mdi-graph-outline',
235 groups=(
236 MenuGroup(
237 label=_('Tunnels'),
238 items=(
239 get_model_item('vpn', 'tunnel', _('Tunnels')),
240 get_model_item('vpn', 'tunnelgroup', _('Tunnel Groups')),
241 get_model_item('vpn', 'tunneltermination', _('Tunnel Terminations')),
242 ),
243 ),
244 MenuGroup(
245 label=_('L2VPNs'),
246 items=(
247 get_model_item('vpn', 'l2vpn', _('L2VPNs')),
248 get_model_item('vpn', 'l2vpntermination', _('L2VPN Terminations')),
249 ),
250 ),
251 MenuGroup(
252 label=_('Security'),
253 items=(
254 get_model_item('vpn', 'ikeproposal', _('IKE Proposals')),
255 get_model_item('vpn', 'ikepolicy', _('IKE Policies')),
256 get_model_item('vpn', 'ipsecproposal', _('IPSec Proposals')),
257 get_model_item('vpn', 'ipsecpolicy', _('IPSec Policies')),
258 get_model_item('vpn', 'ipsecprofile', _('IPSec Profiles')),
259 ),
260 ),
261 ),
262)
264VIRTUALIZATION_MENU = Menu(
265 label=_('Virtualization'),
266 icon_class='mdi mdi-monitor',
267 groups=(
268 MenuGroup(
269 label=_('Virtual Machines'),
270 items=(
271 get_model_item('virtualization', 'virtualmachine', _('Virtual Machines')),
272 get_model_item('virtualization', 'vminterface', _('Interfaces')),
273 get_model_item('virtualization', 'virtualdisk', _('Virtual Disks')),
274 get_model_item('virtualization', 'virtualmachinetype', _('Virtual Machine Types')),
275 ),
276 ),
277 MenuGroup(
278 label=_('Clusters'),
279 items=(
280 get_model_item('virtualization', 'cluster', _('Clusters')),
281 get_model_item('virtualization', 'clustertype', _('Cluster Types')),
282 get_model_item('virtualization', 'clustergroup', _('Cluster Groups')),
283 ),
284 ),
285 ),
286)
288CIRCUITS_MENU = Menu(
289 label=_('Circuits'),
290 icon_class='mdi mdi-transit-connection-variant',
291 groups=(
292 MenuGroup(
293 label=_('Circuits'),
294 items=(
295 get_model_item('circuits', 'circuit', _('Circuits')),
296 get_model_item('circuits', 'circuittype', _('Circuit Types')),
297 get_model_item('circuits', 'circuittermination', _('Circuit Terminations')),
298 ),
299 ),
300 MenuGroup(
301 label=_('Virtual Circuits'),
302 items=(
303 get_model_item('circuits', 'virtualcircuit', _('Virtual Circuits')),
304 get_model_item('circuits', 'virtualcircuittype', _('Virtual Circuit Types')),
305 get_model_item('circuits', 'virtualcircuittermination', _('Virtual Circuit Terminations')),
306 ),
307 ),
308 MenuGroup(
309 label=_('Groups'),
310 items=(
311 get_model_item('circuits', 'circuitgroup', _('Circuit Groups')),
312 get_model_item('circuits', 'circuitgroupassignment', _('Group Assignments')),
313 ),
314 ),
315 MenuGroup(
316 label=_('Providers'),
317 items=(
318 get_model_item('circuits', 'provider', _('Providers')),
319 get_model_item('circuits', 'provideraccount', _('Provider Accounts')),
320 get_model_item('circuits', 'providernetwork', _('Provider Networks')),
321 ),
322 ),
323 ),
324)
326POWER_MENU = Menu(
327 label=_('Power'),
328 icon_class='mdi mdi-flash',
329 groups=(
330 MenuGroup(
331 label=_('Power'),
332 items=(
333 get_model_item('dcim', 'powerfeed', _('Power Feeds')),
334 get_model_item('dcim', 'powerpanel', _('Power Panels')),
335 ),
336 ),
337 ),
338)
340COOLING_MENU = Menu(
341 label=_('Cooling'),
342 icon_class='mdi mdi-snowflake',
343 groups=(
344 MenuGroup(
345 label=_('Cooling'),
346 items=(
347 get_model_item('dcim', 'coolingsource', _('Cooling Sources')),
348 get_model_item('dcim', 'coolingfeed', _('Cooling Feeds')),
349 ),
350 ),
351 ),
352)
354PROVISIONING_MENU = Menu(
355 label=_('Provisioning'),
356 icon_class='mdi mdi-file-document-multiple-outline',
357 groups=(
358 MenuGroup(
359 label=_('Configurations'),
360 items=(
361 get_model_item('extras', 'configcontext', _('Config Contexts'), actions=['add']),
362 get_model_item('extras', 'configcontextprofile', _('Config Context Profiles')),
363 get_model_item('extras', 'configtemplate', _('Config Templates'), actions=['add']),
364 ),
365 ),
366 ),
367)
369CUSTOMIZATION_MENU = Menu(
370 label=_('Customization'),
371 icon_class='mdi mdi-toolbox-outline',
372 groups=(
373 MenuGroup(
374 label=_('Customization'),
375 items=(
376 get_model_item('extras', 'customfield', _('Custom Fields')),
377 get_model_item('extras', 'customfieldchoiceset', _('Custom Field Choices')),
378 get_model_item('extras', 'customlink', _('Custom Links')),
379 get_model_item('extras', 'exporttemplate', _('Export Templates')),
380 get_model_item('extras', 'savedfilter', _('Saved Filters')),
381 get_model_item('extras', 'tableconfig', _('Table Configs'), actions=()),
382 get_model_item('extras', 'tag', 'Tags'),
383 get_model_item('extras', 'imageattachment', _('Image Attachments'), actions=()),
384 ),
385 ),
386 MenuGroup(
387 label=_('Scripts'),
388 items=(
389 MenuItem(
390 link='extras:script_list',
391 link_text=_('Scripts'),
392 permissions=['extras.view_script'],
393 buttons=get_model_buttons('extras', "scriptmodule", actions=['add'])
394 ),
395 ),
396 ),
397 ),
398)
400OPERATIONS_MENU = Menu(
401 label=_('Operations'),
402 icon_class='mdi mdi-cogs',
403 groups=(
404 MenuGroup(
405 label=_('Integrations'),
406 items=(
407 get_model_item('core', 'datasource', _('Data Sources')),
408 get_model_item('extras', 'eventrule', _('Event Rules')),
409 get_model_item('extras', 'webhook', _('Webhooks')),
410 ),
411 ),
412 MenuGroup(
413 label=_('Jobs'),
414 items=(
415 MenuItem(
416 link='core:job_list',
417 link_text=_('Jobs'),
418 permissions=['core.view_job'],
419 ),
420 ),
421 ),
422 MenuGroup(
423 label=_('Logging'),
424 items=(
425 get_model_item('extras', 'notificationgroup', _('Notification Groups')),
426 get_model_item('extras', 'journalentry', _('Journal Entries'), actions=['bulk_import']),
427 get_model_item('core', 'objectchange', _('Change Log'), actions=[]),
428 ),
429 ),
430 ),
431)
433ADMIN_MENU = Menu(
434 label=_('Admin'),
435 icon_class='mdi mdi-account-multiple',
436 groups=(
437 MenuGroup(
438 label=_('Authentication'),
439 items=(
440 get_model_item('users', 'user', _('Users')),
441 get_model_item('users', 'group', _('Groups')),
442 get_model_item('users', 'token', _('API Tokens')),
443 get_model_item('users', 'objectpermission', _('Permissions'), actions=['add']),
444 ),
445 ),
446 MenuGroup(
447 label=_('Ownership'),
448 items=(
449 get_model_item('users', 'ownergroup', _('Owner Groups')),
450 get_model_item('users', 'owner', _('Owners')),
451 ),
452 ),
453 MenuGroup(
454 label=_('System'),
455 items=(
456 MenuItem(
457 link='core:system',
458 link_text=_('System'),
459 staff_only=True,
460 ),
461 MenuItem(
462 link='core:plugin_list',
463 link_text=_('Plugins'),
464 staff_only=True,
465 ),
466 MenuItem(
467 link='core:configrevision_list',
468 link_text=_('Configuration History'),
469 staff_only=True,
470 permissions=['core.view_configrevision'],
471 ),
472 MenuItem(
473 link='core:background_queue_list',
474 link_text=_('Background Tasks'),
475 staff_only=True,
476 ),
477 ),
478 ),
479 ),
480)
483@cache
484def get_menus():
485 """
486 Dynamically build and return the list of navigation menus.
487 This ensures plugin menus registered during app initialization are included.
488 The result is cached since menus don't change without a Django restart.
489 """
490 menus = [
491 ORGANIZATION_MENU,
492 RACKS_MENU,
493 DEVICES_MENU,
494 CONNECTIONS_MENU,
495 WIRELESS_MENU,
496 IPAM_MENU,
497 VPN_MENU,
498 VIRTUALIZATION_MENU,
499 CIRCUITS_MENU,
500 POWER_MENU,
501 COOLING_MENU,
502 PROVISIONING_MENU,
503 CUSTOMIZATION_MENU,
504 OPERATIONS_MENU,
505 ]
507 # Add top-level plugin menus
508 for menu in registry['plugins']['menus']: 508 ↛ 509line 508 didn't jump to line 509 because the loop on line 508 never started
509 menus.append(menu)
511 # Add the default "plugins" menu
512 if registry['plugins']['menu_items']: 512 ↛ 514line 512 didn't jump to line 514 because the condition on line 512 was never true
513 # Build the default plugins menu
514 groups = [
515 MenuGroup(label=label, items=items)
516 for label, items in registry['plugins']['menu_items'].items()
517 ]
518 plugins_menu = Menu(
519 label=_("Plugins"),
520 icon_class="mdi mdi-puzzle",
521 groups=groups
522 )
523 menus.append(plugins_menu)
525 # Add the admin menu last
526 menus.append(ADMIN_MENU)
528 return menus