diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index f52de7ee4..9ddf76ea2 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -953,6 +953,7 @@ class DeviceTypeConsolePortsView(DeviceTypeComponentsView): label=_('Console Ports'), badge=lambda obj: obj.consoleporttemplates.count(), permission='dcim.view_consoleporttemplate', + weight=550, hide_if_empty=True ) @@ -967,6 +968,7 @@ class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView): label=_('Console Server Ports'), badge=lambda obj: obj.consoleserverporttemplates.count(), permission='dcim.view_consoleserverporttemplate', + weight=560, hide_if_empty=True ) @@ -981,6 +983,7 @@ class DeviceTypePowerPortsView(DeviceTypeComponentsView): label=_('Power Ports'), badge=lambda obj: obj.powerporttemplates.count(), permission='dcim.view_powerporttemplate', + weight=570, hide_if_empty=True ) @@ -995,6 +998,7 @@ class DeviceTypePowerOutletsView(DeviceTypeComponentsView): label=_('Power Outlets'), badge=lambda obj: obj.poweroutlettemplates.count(), permission='dcim.view_poweroutlettemplate', + weight=580, hide_if_empty=True ) @@ -1009,6 +1013,7 @@ class DeviceTypeInterfacesView(DeviceTypeComponentsView): label=_('Interfaces'), badge=lambda obj: obj.interfacetemplates.count(), permission='dcim.view_interfacetemplate', + weight=520, hide_if_empty=True ) @@ -1023,6 +1028,7 @@ class DeviceTypeFrontPortsView(DeviceTypeComponentsView): label=_('Front Ports'), badge=lambda obj: obj.frontporttemplates.count(), permission='dcim.view_frontporttemplate', + weight=530, hide_if_empty=True ) @@ -1037,6 +1043,7 @@ class DeviceTypeRearPortsView(DeviceTypeComponentsView): label=_('Rear Ports'), badge=lambda obj: obj.rearporttemplates.count(), permission='dcim.view_rearporttemplate', + weight=540, hide_if_empty=True ) @@ -1051,6 +1058,7 @@ class DeviceTypeModuleBaysView(DeviceTypeComponentsView): label=_('Module Bays'), badge=lambda obj: obj.modulebaytemplates.count(), permission='dcim.view_modulebaytemplate', + weight=510, hide_if_empty=True ) @@ -1065,6 +1073,7 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView): label=_('Device Bays'), badge=lambda obj: obj.devicebaytemplates.count(), permission='dcim.view_devicebaytemplate', + weight=500, hide_if_empty=True ) @@ -1079,6 +1088,7 @@ class DeviceTypeInventoryItemsView(DeviceTypeComponentsView): label=_('Inventory Items'), badge=lambda obj: obj.inventoryitemtemplates.count(), permission='dcim.view_invenotryitemtemplate', + weight=590, hide_if_empty=True ) @@ -1181,6 +1191,7 @@ class ModuleTypeConsolePortsView(ModuleTypeComponentsView): label=_('Console Ports'), badge=lambda obj: obj.consoleporttemplates.count(), permission='dcim.view_consoleporttemplate', + weight=530, hide_if_empty=True ) @@ -1195,6 +1206,7 @@ class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView): label=_('Console Server Ports'), badge=lambda obj: obj.consoleserverporttemplates.count(), permission='dcim.view_consoleserverporttemplate', + weight=540, hide_if_empty=True ) @@ -1209,6 +1221,7 @@ class ModuleTypePowerPortsView(ModuleTypeComponentsView): label=_('Power Ports'), badge=lambda obj: obj.powerporttemplates.count(), permission='dcim.view_powerporttemplate', + weight=550, hide_if_empty=True ) @@ -1223,6 +1236,7 @@ class ModuleTypePowerOutletsView(ModuleTypeComponentsView): label=_('Power Outlets'), badge=lambda obj: obj.poweroutlettemplates.count(), permission='dcim.view_poweroutlettemplate', + weight=560, hide_if_empty=True ) @@ -1237,6 +1251,7 @@ class ModuleTypeInterfacesView(ModuleTypeComponentsView): label=_('Interfaces'), badge=lambda obj: obj.interfacetemplates.count(), permission='dcim.view_interfacetemplate', + weight=500, hide_if_empty=True ) @@ -1251,6 +1266,7 @@ class ModuleTypeFrontPortsView(ModuleTypeComponentsView): label=_('Front Ports'), badge=lambda obj: obj.frontporttemplates.count(), permission='dcim.view_frontporttemplate', + weight=510, hide_if_empty=True ) @@ -1265,6 +1281,7 @@ class ModuleTypeRearPortsView(ModuleTypeComponentsView): label=_('Rear Ports'), badge=lambda obj: obj.rearporttemplates.count(), permission='dcim.view_rearporttemplate', + weight=520, hide_if_empty=True ) @@ -1873,6 +1890,7 @@ class DeviceConsolePortsView(DeviceComponentsView): label=_('Console Ports'), badge=lambda obj: obj.consoleports.count(), permission='dcim.view_consoleport', + weight=550, hide_if_empty=True ) @@ -1887,6 +1905,7 @@ class DeviceConsoleServerPortsView(DeviceComponentsView): label=_('Console Server Ports'), badge=lambda obj: obj.consoleserverports.count(), permission='dcim.view_consoleserverport', + weight=560, hide_if_empty=True ) @@ -1901,6 +1920,7 @@ class DevicePowerPortsView(DeviceComponentsView): label=_('Power Ports'), badge=lambda obj: obj.powerports.count(), permission='dcim.view_powerport', + weight=570, hide_if_empty=True ) @@ -1915,6 +1935,7 @@ class DevicePowerOutletsView(DeviceComponentsView): label=_('Power Outlets'), badge=lambda obj: obj.poweroutlets.count(), permission='dcim.view_poweroutlet', + weight=580, hide_if_empty=True ) @@ -1929,6 +1950,7 @@ class DeviceInterfacesView(DeviceComponentsView): label=_('Interfaces'), badge=lambda obj: obj.interfaces.count(), permission='dcim.view_interface', + weight=520, hide_if_empty=True ) @@ -1949,6 +1971,7 @@ class DeviceFrontPortsView(DeviceComponentsView): label=_('Front Ports'), badge=lambda obj: obj.frontports.count(), permission='dcim.view_frontport', + weight=530, hide_if_empty=True ) @@ -1963,6 +1986,7 @@ class DeviceRearPortsView(DeviceComponentsView): label=_('Rear Ports'), badge=lambda obj: obj.rearports.count(), permission='dcim.view_rearport', + weight=540, hide_if_empty=True ) @@ -1977,6 +2001,7 @@ class DeviceModuleBaysView(DeviceComponentsView): label=_('Module Bays'), badge=lambda obj: obj.modulebays.count(), permission='dcim.view_modulebay', + weight=510, hide_if_empty=True ) @@ -1991,6 +2016,7 @@ class DeviceDeviceBaysView(DeviceComponentsView): label=_('Device Bays'), badge=lambda obj: obj.devicebays.count(), permission='dcim.view_devicebay', + weight=500, hide_if_empty=True ) @@ -2005,6 +2031,7 @@ class DeviceInventoryView(DeviceComponentsView): label=_('Inventory Items'), badge=lambda obj: obj.inventoryitems.count(), permission='dcim.view_inventoryitem', + weight=590, hide_if_empty=True ) @@ -2015,7 +2042,8 @@ class DeviceConfigContextView(ObjectConfigContextView): base_template = 'dcim/device/base.html' tab = ViewTab( label=_('Config Context'), - permission='extras.view_configcontext' + permission='extras.view_configcontext', + weight=2000 ) @@ -2088,6 +2116,7 @@ class DeviceStatusView(generic.ObjectView): tab = NAPALMViewTab( label=_('Status'), permission='dcim.napalm_read_device', + weight=3000 ) @@ -2099,6 +2128,7 @@ class DeviceLLDPNeighborsView(generic.ObjectView): tab = NAPALMViewTab( label=_('LLDP Neighbors'), permission='dcim.napalm_read_device', + weight=3100 ) def get_extra_context(self, request, instance): @@ -2121,6 +2151,7 @@ class DeviceConfigView(generic.ObjectView): tab = NAPALMViewTab( label=_('Config'), permission='dcim.napalm_read_device', + weight=3200 ) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 1d4504d92..130014f3f 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -314,7 +314,8 @@ class AggregatePrefixesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Prefixes'), badge=lambda x: x.get_child_prefixes().count(), - permission='ipam.view_prefix' + permission='ipam.view_prefix', + weight=500 ) def get_children(self, request, parent): @@ -502,7 +503,8 @@ class PrefixPrefixesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Child Prefixes'), badge=lambda x: x.get_child_prefixes().count(), - permission='ipam.view_prefix' + permission='ipam.view_prefix', + weight=500 ) def get_children(self, request, parent): @@ -536,7 +538,8 @@ class PrefixIPRangesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Child Ranges'), badge=lambda x: x.get_child_ranges().count(), - permission='ipam.view_iprange' + permission='ipam.view_iprange', + weight=600 ) def get_children(self, request, parent): @@ -561,7 +564,8 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): tab = ViewTab( label=_('IP Addresses'), badge=lambda x: x.get_child_ips().count(), - permission='ipam.view_ipaddress' + permission='ipam.view_ipaddress', + weight=700 ) def get_children(self, request, parent): @@ -635,7 +639,8 @@ class IPRangeIPAddressesView(generic.ObjectChildrenView): tab = ViewTab( label=_('IP Addresses'), badge=lambda x: x.get_child_ips().count(), - permission='ipam.view_ipaddress' + permission='ipam.view_ipaddress', + weight=500 ) def get_children(self, request, parent): @@ -1075,7 +1080,8 @@ class VLANInterfacesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Device Interfaces'), badge=lambda x: x.get_interfaces().count(), - permission='dcim.view_interface' + permission='dcim.view_interface', + weight=500 ) def get_children(self, request, parent): @@ -1092,7 +1098,8 @@ class VLANVMInterfacesView(generic.ObjectChildrenView): tab = ViewTab( label=_('VM Interfaces'), badge=lambda x: x.get_vminterfaces().count(), - permission='virtualization.view_vminterface' + permission='virtualization.view_vminterface', + weight=510 ) def get_children(self, request, parent): diff --git a/netbox/netbox/views/generic/feature_views.py b/netbox/netbox/views/generic/feature_views.py index ce5b29eb2..d4d02ee4e 100644 --- a/netbox/netbox/views/generic/feature_views.py +++ b/netbox/netbox/views/generic/feature_views.py @@ -27,7 +27,8 @@ class ObjectChangeLogView(View): base_template = None tab = ViewTab( label=_('Changelog'), - permission='extras.view_objectchange' + permission='extras.view_objectchange', + weight=10000 ) def get(self, request, model, **kwargs): @@ -80,7 +81,8 @@ class ObjectJournalView(View): tab = ViewTab( label=_('Journal'), badge=lambda obj: obj.journal_entries.count(), - permission='extras.view_journalentry' + permission='extras.view_journalentry', + weight=9000 ) def get(self, request, model, **kwargs): diff --git a/netbox/utilities/templatetags/tabs.py b/netbox/utilities/templatetags/tabs.py index 3e5bf2127..d41766794 100644 --- a/netbox/utilities/templatetags/tabs.py +++ b/netbox/utilities/templatetags/tabs.py @@ -47,9 +47,13 @@ def model_view_tabs(context, instance): 'url': url, 'label': attrs['label'], 'badge': attrs['badge'], + 'weight': attrs['weight'], 'is_active': active_tab and active_tab == tab, }) + # Order tabs by weight + tabs = sorted(tabs, key=lambda x: x['weight']) + return { 'tabs': tabs, } diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 400f127fc..43ca9a589 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -142,13 +142,15 @@ class ViewTab: label: Human-friendly text badge: A static value or callable to display alongside the label (optional). If a callable is used, it must accept a single argument representing the object being viewed. + weight: Numeric weight to influence ordering among other tabs (default: 1000) permission: The permission required to display the tab (optional). hide_if_empty: If true, the tab will be displayed only if its badge has a meaningful value. (Tabs without a badge are always displayed.) """ - def __init__(self, label, badge=None, permission=None, hide_if_empty=False): + def __init__(self, label, badge=None, weight=1000, permission=None, hide_if_empty=False): self.label = label self.badge = badge + self.weight = weight self.permission = permission self.hide_if_empty = hide_if_empty @@ -160,6 +162,7 @@ class ViewTab: return { 'label': self.label, 'badge': badge_value, + 'weight': self.weight, } def _get_badge_value(self, instance): diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index c9d450567..b97b966b4 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -180,7 +180,8 @@ class ClusterVirtualMachinesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Virtual Machines'), badge=lambda obj: obj.virtual_machines.count(), - permission='virtualization.view_virtualmachine' + permission='virtualization.view_virtualmachine', + weight=500 ) def get_children(self, request, parent): @@ -197,7 +198,8 @@ class ClusterDevicesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Devices'), badge=lambda obj: obj.devices.count(), - permission='virtualization.view_virtualmachine' + permission='virtualization.view_virtualmachine', + weight=600 ) def get_children(self, request, parent): @@ -370,7 +372,8 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView): tab = ViewTab( label=_('Interfaces'), badge=lambda obj: obj.interfaces.count(), - permission='virtualization.view_vminterface' + permission='virtualization.view_vminterface', + weight=500 ) def get_children(self, request, parent): @@ -386,7 +389,8 @@ class VirtualMachineConfigContextView(ObjectConfigContextView): base_template = 'virtualization/virtualmachine.html' tab = ViewTab( label=_('Config Context'), - permission='extras.view_configcontext' + permission='extras.view_configcontext', + weight=2000 )