Add device NAPALM view tabs

This commit is contained in:
jeremystretch 2022-10-07 14:17:18 -04:00
parent 5e1a0733e4
commit 1fc8de85a3
6 changed files with 122 additions and 143 deletions

View File

@ -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

View File

@ -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):

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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,

View File

@ -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):