mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Add device NAPALM view tabs
This commit is contained in:
parent
5e1a0733e4
commit
1fc8de85a3
@ -233,10 +233,6 @@ urlpatterns = [
|
||||
path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
|
||||
path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'),
|
||||
path('devices/<int:pk>/delete/', views.DeviceDeleteView.as_view(), name='device_delete'),
|
||||
path('devices/<int:pk>/inventory/', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
||||
path('devices/<int:pk>/status/', views.DeviceStatusView.as_view(), name='device_status'),
|
||||
path('devices/<int:pk>/lldp-neighbors/', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
|
||||
path('devices/<int:pk>/config/', views.DeviceConfigView.as_view(), name='device_config'),
|
||||
path('devices/<int:pk>/', include(get_model_urls('dcim', 'device'))),
|
||||
|
||||
# Modules
|
||||
|
@ -860,8 +860,7 @@ class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Console Ports'),
|
||||
badge=lambda obj: obj.consoleporttemplates.count(),
|
||||
permission='dcim.view_consoleporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_consoleporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -874,8 +873,7 @@ class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Console Server Ports'),
|
||||
badge=lambda obj: obj.consoleserverporttemplates.count(),
|
||||
permission='dcim.view_consoleserverporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_consoleserverporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -888,8 +886,7 @@ class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Power Ports'),
|
||||
badge=lambda obj: obj.powerporttemplates.count(),
|
||||
permission='dcim.view_powerporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_powerporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -902,8 +899,7 @@ class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Power Outlets'),
|
||||
badge=lambda obj: obj.poweroutlettemplates.count(),
|
||||
permission='dcim.view_poweroutlettemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_poweroutlettemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -916,8 +912,7 @@ class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfacetemplates.count(),
|
||||
permission='dcim.view_interfacetemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_interfacetemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -930,8 +925,7 @@ class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Front Ports'),
|
||||
badge=lambda obj: obj.frontporttemplates.count(),
|
||||
permission='dcim.view_frontporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_frontporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -944,8 +938,7 @@ class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Rear Ports'),
|
||||
badge=lambda obj: obj.rearporttemplates.count(),
|
||||
permission='dcim.view_rearporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_rearporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -958,8 +951,7 @@ class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Module Bays'),
|
||||
badge=lambda obj: obj.modulebaytemplates.count(),
|
||||
permission='dcim.view_modulebaytemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_modulebaytemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -972,8 +964,7 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Device Bays'),
|
||||
badge=lambda obj: obj.devicebaytemplates.count(),
|
||||
permission='dcim.view_devicebaytemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_devicebaytemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -986,8 +977,7 @@ class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Inventory Items'),
|
||||
badge=lambda obj: obj.inventoryitemtemplates.count(),
|
||||
permission='dcim.view_invenotryitemtemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_invenotryitemtemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1084,8 +1074,7 @@ class ModuleTypeConsolePortsView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Console Ports'),
|
||||
badge=lambda obj: obj.consoleporttemplates.count(),
|
||||
permission='dcim.view_consoleporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_consoleporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1098,8 +1087,7 @@ class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Console Server Ports'),
|
||||
badge=lambda obj: obj.consoleserverporttemplates.count(),
|
||||
permission='dcim.view_consoleserverporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_consoleserverporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1112,8 +1100,7 @@ class ModuleTypePowerPortsView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Power Ports'),
|
||||
badge=lambda obj: obj.powerporttemplates.count(),
|
||||
permission='dcim.view_powerporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_powerporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1126,8 +1113,7 @@ class ModuleTypePowerOutletsView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Power Outlets'),
|
||||
badge=lambda obj: obj.poweroutlettemplates.count(),
|
||||
permission='dcim.view_poweroutlettemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_poweroutlettemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1140,8 +1126,7 @@ class ModuleTypeInterfacesView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfacetemplates.count(),
|
||||
permission='dcim.view_interfacetemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_interfacetemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1154,8 +1139,7 @@ class ModuleTypeFrontPortsView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Front Ports'),
|
||||
badge=lambda obj: obj.frontporttemplates.count(),
|
||||
permission='dcim.view_frontporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_frontporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1168,8 +1152,7 @@ class ModuleTypeRearPortsView(ModuleTypeComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Rear Ports'),
|
||||
badge=lambda obj: obj.rearporttemplates.count(),
|
||||
permission='dcim.view_rearporttemplate',
|
||||
always_display=False
|
||||
permission='dcim.view_rearporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@ -1742,8 +1725,7 @@ class DeviceConsolePortsView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Console Ports'),
|
||||
badge=lambda obj: obj.consoleports.count(),
|
||||
permission='dcim.view_consoleport',
|
||||
always_display=False
|
||||
permission='dcim.view_consoleport'
|
||||
)
|
||||
|
||||
|
||||
@ -1756,8 +1738,7 @@ class DeviceConsoleServerPortsView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Console Server Ports'),
|
||||
badge=lambda obj: obj.consoleserverports.count(),
|
||||
permission='dcim.view_consoleserverport',
|
||||
always_display=False
|
||||
permission='dcim.view_consoleserverport'
|
||||
)
|
||||
|
||||
|
||||
@ -1770,8 +1751,7 @@ class DevicePowerPortsView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Power Ports'),
|
||||
badge=lambda obj: obj.powerports.count(),
|
||||
permission='dcim.view_powerport',
|
||||
always_display=False
|
||||
permission='dcim.view_powerport'
|
||||
)
|
||||
|
||||
|
||||
@ -1784,8 +1764,7 @@ class DevicePowerOutletsView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Power Outlets'),
|
||||
badge=lambda obj: obj.poweroutlets.count(),
|
||||
permission='dcim.view_poweroutlet',
|
||||
always_display=False
|
||||
permission='dcim.view_poweroutlet'
|
||||
)
|
||||
|
||||
|
||||
@ -1798,8 +1777,7 @@ class DeviceInterfacesView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfaces.count(),
|
||||
permission='dcim.view_interface',
|
||||
always_display=False
|
||||
permission='dcim.view_interface'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
@ -1818,8 +1796,7 @@ class DeviceFrontPortsView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Front Ports'),
|
||||
badge=lambda obj: obj.frontports.count(),
|
||||
permission='dcim.view_frontport',
|
||||
always_display=False
|
||||
permission='dcim.view_frontport'
|
||||
)
|
||||
|
||||
|
||||
@ -1832,8 +1809,7 @@ class DeviceRearPortsView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Rear Ports'),
|
||||
badge=lambda obj: obj.rearports.count(),
|
||||
permission='dcim.view_rearport',
|
||||
always_display=False
|
||||
permission='dcim.view_rearport'
|
||||
)
|
||||
|
||||
|
||||
@ -1846,8 +1822,7 @@ class DeviceModuleBaysView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Module Bays'),
|
||||
badge=lambda obj: obj.modulebays.count(),
|
||||
permission='dcim.view_modulebay',
|
||||
always_display=False
|
||||
permission='dcim.view_modulebay'
|
||||
)
|
||||
|
||||
|
||||
@ -1860,8 +1835,7 @@ class DeviceDeviceBaysView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Device Bays'),
|
||||
badge=lambda obj: obj.devicebays.count(),
|
||||
permission='dcim.view_devicebay',
|
||||
always_display=False
|
||||
permission='dcim.view_devicebay'
|
||||
)
|
||||
|
||||
|
||||
@ -1874,51 +1848,10 @@ class DeviceInventoryView(DeviceComponentsView):
|
||||
tab = ViewTab(
|
||||
label=_('Inventory Items'),
|
||||
badge=lambda obj: obj.inventoryitems.count(),
|
||||
permission='dcim.view_inventoryitem',
|
||||
always_display=False
|
||||
permission='dcim.view_inventoryitem'
|
||||
)
|
||||
|
||||
|
||||
class DeviceStatusView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/status.html'
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'status',
|
||||
}
|
||||
|
||||
|
||||
class DeviceLLDPNeighborsView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/lldp_neighbors.html'
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||
'_path'
|
||||
).exclude(
|
||||
type__in=NONCONNECTABLE_IFACE_TYPES
|
||||
)
|
||||
|
||||
return {
|
||||
'interfaces': interfaces,
|
||||
'active_tab': 'lldp-neighbors',
|
||||
}
|
||||
|
||||
|
||||
class DeviceConfigView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/config.html'
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'config',
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Device, 'configcontext', path='config-context')
|
||||
class DeviceConfigContextView(ObjectConfigContextView):
|
||||
queryset = Device.objects.annotate_config_context_data()
|
||||
@ -1984,7 +1917,68 @@ class DeviceBulkRenameView(generic.BulkRenameView):
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
# Device NAPALM views
|
||||
#
|
||||
|
||||
class NAPALMViewTab(ViewTab):
|
||||
|
||||
def render(self, instance):
|
||||
# Display NAPALM tabs only for devices which meet certain requirements
|
||||
if not (
|
||||
instance.status == 'active' and
|
||||
instance.primary_ip and
|
||||
instance.platform.napalm_driver
|
||||
):
|
||||
return None
|
||||
return super().render(instance)
|
||||
|
||||
|
||||
@register_model_view(Device, 'status')
|
||||
class DeviceStatusView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/status.html'
|
||||
tab = NAPALMViewTab(
|
||||
label=_('Status'),
|
||||
permission='dcim.napalm_read_device',
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'lldp_neighbors', path='lldp-neighbors')
|
||||
class DeviceLLDPNeighborsView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/lldp_neighbors.html'
|
||||
tab = NAPALMViewTab(
|
||||
label=_('LLDP Neighbors'),
|
||||
permission='dcim.napalm_read_device',
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||
'_path'
|
||||
).exclude(
|
||||
type__in=NONCONNECTABLE_IFACE_TYPES
|
||||
)
|
||||
|
||||
return {
|
||||
'interfaces': interfaces,
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Device, 'config')
|
||||
class DeviceConfigView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/config.html'
|
||||
tab = NAPALMViewTab(
|
||||
label=_('Config'),
|
||||
permission='dcim.napalm_read_device',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Modules
|
||||
#
|
||||
|
||||
class ModuleListView(generic.ObjectListView):
|
||||
|
@ -54,24 +54,3 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% if perms.dcim.napalm_read_device and object.status == 'active' and object.primary_ip and object.platform.napalm_driver %}
|
||||
{# NAPALM-enabled tabs #}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'status' %} active{% endif %}" href="{% url 'dcim:device_status' pk=object.pk %}">
|
||||
Status
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'lldp-neighbors' %} active{% endif %}" href="{% url 'dcim:device_lldp_neighbors' pk=object.pk %}">
|
||||
LLDP Neighbors
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'config' %} active{% endif %}" href="{% url 'dcim:device_config' pk=object.pk %}">
|
||||
Configuration
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -1,8 +1,7 @@
|
||||
{% for tab in tabs %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{{ tab.url }}" class="nav-link{% if tab.is_active %} active{% endif %}">
|
||||
{{ tab.label }}
|
||||
{% if tab.badge_value %}{% badge tab.badge_value %}{% endif %}
|
||||
{{ tab.label }} {% badge tab.badge %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -32,24 +32,16 @@ def model_view_tabs(context, instance):
|
||||
if tab.permission and not user.has_perm(tab.permission):
|
||||
continue
|
||||
|
||||
# Determine the value of the tab's badge (if any)
|
||||
if tab.badge and callable(tab.badge):
|
||||
badge_value = tab.badge(instance)
|
||||
else:
|
||||
badge_value = tab.badge
|
||||
|
||||
if not tab.always_display and not badge_value:
|
||||
continue
|
||||
|
||||
viewname = f"{app_label}:{model_name}_{config['name']}"
|
||||
active_tab = context.get('tab')
|
||||
tabs.append({
|
||||
'name': config['name'],
|
||||
'url': reverse(viewname, args=[instance.pk]),
|
||||
'label': tab.label,
|
||||
'badge_value': badge_value,
|
||||
'is_active': active_tab and active_tab == tab,
|
||||
})
|
||||
if attrs := tab.render(instance):
|
||||
viewname = f"{app_label}:{model_name}_{config['name']}"
|
||||
active_tab = context.get('tab')
|
||||
tabs.append({
|
||||
'name': config['name'],
|
||||
'url': reverse(viewname, args=[instance.pk]),
|
||||
'label': attrs['label'],
|
||||
'badge': attrs['badge'],
|
||||
'is_active': active_tab and active_tab == tab,
|
||||
})
|
||||
|
||||
return {
|
||||
'tabs': tabs,
|
||||
|
@ -134,12 +134,31 @@ class GetReturnURLMixin:
|
||||
|
||||
|
||||
class ViewTab:
|
||||
|
||||
def __init__(self, label, badge=None, permission=None, always_display=True):
|
||||
"""
|
||||
ViewTabs are used for navigation among multiple object-specific views, such as the changelog or journal for
|
||||
a particular object.
|
||||
"""
|
||||
def __init__(self, label, badge=None, permission=None):
|
||||
self.label = label
|
||||
self.badge = badge
|
||||
self.permission = permission
|
||||
self.always_display = always_display
|
||||
|
||||
def render(self, instance):
|
||||
"""Return the attributes needed to render a tab in HTML."""
|
||||
badge_value = self._get_badge_value(instance)
|
||||
if self.badge and not badge_value:
|
||||
return None
|
||||
return {
|
||||
'label': self.label,
|
||||
'badge': badge_value,
|
||||
}
|
||||
|
||||
def _get_badge_value(self, instance):
|
||||
if not self.badge:
|
||||
return None
|
||||
if callable(self.badge):
|
||||
return self.badge(instance)
|
||||
return self.badge
|
||||
|
||||
|
||||
def register_model_view(model, name, path=None, kwargs=None):
|
||||
|
Loading…
Reference in New Issue
Block a user