Browse Source

Live device status PoC

Jeremy Stretch 7 years ago
parent
commit
12472a2612

+ 17 - 9
netbox/dcim/api/views.py

@@ -1,4 +1,5 @@
 from __future__ import unicode_literals
+from collections import OrderedDict
 
 from rest_framework.decorators import detail_route
 from rest_framework.mixins import ListModelMixin
@@ -7,7 +8,7 @@ from rest_framework.response import Response
 from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
 
 from django.conf import settings
-from django.http import Http404, HttpResponseForbidden
+from django.http import HttpResponseBadRequest, HttpResponseForbidden
 from django.shortcuts import get_object_or_404
 
 from dcim.models import (
@@ -225,8 +226,8 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     write_serializer_class = serializers.WritableDeviceSerializer
     filter_class = filters.DeviceFilter
 
-    @detail_route(url_path='napalm/(?P<method>get_[a-z_]+)')
-    def napalm(self, request, pk, method):
+    @detail_route(url_path='napalm')
+    def napalm(self, request, pk):
         """
         Execute a NAPALM method on a Device
         """
@@ -253,16 +254,21 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
                 device.platform, device.platform.napalm_driver
             ))
 
-        # Raise a 404 for invalid NAPALM methods
-        if not hasattr(driver, method):
-            raise Http404()
-
         # Verify user permission
         if not request.user.has_perm('dcim.napalm_read'):
             return HttpResponseForbidden()
 
-        # Connect to the device and execute the given method
+        # Validate requested NAPALM methods
+        napalm_methods = request.GET.getlist('method')
+        for method in napalm_methods:
+            if not hasattr(driver, method):
+                return HttpResponseBadRequest("Unknown NAPALM method: {}".format(method))
+            elif not method.startswith('get_'):
+                return HttpResponseBadRequest("Unsupported NAPALM method: {}".format(method))
+
+        # Connect to the device and execute the requested methods
         # TODO: Improve error handling
+        response = OrderedDict([(m, None) for m in napalm_methods])
         ip_address = str(device.primary_ip.address.ip)
         d = driver(
             hostname=ip_address,
@@ -271,10 +277,12 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
         )
         try:
             d.open()
-            response = getattr(d, method)()
+            for method in napalm_methods:
+                response[method] = getattr(d, method)()
         except Exception as e:
             raise ServiceUnavailable("Error connecting to the device: {}".format(e))
 
+        d.close()
         return Response(response)
 
 

+ 1 - 0
netbox/dcim/urls.py

@@ -122,6 +122,7 @@ urlpatterns = [
     url(r'^devices/(?P<pk>\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'),
     url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
     url(r'^devices/(?P<pk>\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'),
+    url(r'^devices/(?P<pk>\d+)/status/$', views.DeviceStatusView.as_view(), name='device_status'),
     url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
     url(r'^devices/(?P<pk>\d+)/add-secret/$', secret_add, name='device_addsecret'),
     url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceCreateView.as_view(), name='service_assign'),

+ 21 - 0
netbox/dcim/views.py

@@ -921,6 +921,27 @@ class DeviceInventoryView(View):
         })
 
 
+class DeviceStatusView(View):
+
+    def get(self, request, pk):
+
+        device = get_object_or_404(Device, pk=pk)
+        method = request.GET.get('method', 'get_facts')
+
+        interfaces = Interface.objects.order_naturally(
+            device.device_type.interface_ordering
+        ).filter(
+            device=device
+        ).select_related(
+            'connected_as_a', 'connected_as_b'
+        )
+
+        return render(request, 'dcim/device_status.html', {
+            'device': device,
+            'interfaces': interfaces,
+        })
+
+
 class DeviceLLDPNeighborsView(View):
 
     def get(self, request, pk):

+ 67 - 0
netbox/templates/dcim/device_status.html

@@ -0,0 +1,67 @@
+{% extends '_base.html' %}
+
+{% block title %}{{ device }} - NAPALM{% endblock %}
+
+{% block content %}
+    {% include 'dcim/inc/device_header.html' with active_tab='status' %}
+    <div class="row">
+        <div class="col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading"><strong>Device Facts</strong></div>
+                <table class="table panel-body">
+                    <tr>
+                        <th>Hostname</th>
+                        <td id="hostname"></td>
+                    </tr>
+                    <tr>
+                        <th>FQDN</th>
+                        <td id="fqdn"></td>
+                    </tr>
+                    <tr>
+                        <th>Vendor</th>
+                        <td id="vendor"></td>
+                    </tr>
+                    <tr>
+                        <th>Model</th>
+                        <td id="model"></td>
+                    </tr>
+                    <tr>
+                        <th>Serial Number</th>
+                        <td id="serial_number"></td>
+                    </tr>
+                    <tr>
+                        <th>OS Version</th>
+                        <td id="os_version"></td>
+                    </tr>
+                    <tr>
+                        <th>Uptime</th>
+                        <td id="uptime"></td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </div>
+{% endblock %}
+
+{% block javascript %}
+<script type="text/javascript">
+$(document).ready(function() {
+    $.ajax({
+        url: "{% url 'dcim-api:device-napalm' pk=device.pk %}?method=get_facts",
+        dataType: 'json',
+        success: function(json) {
+            $('#hostname').html(json['get_facts']['hostname']);
+            $('#fqdn').html(json['get_facts']['fqdn']);
+            $('#vendor').html(json['get_facts']['vendor']);
+            $('#model').html(json['get_facts']['model']);
+            $('#serial_number').html(json['get_facts']['serial_number']);
+            $('#os_version').html(json['get_facts']['os_version']);
+            $('#uptime').html(json['get_facts']['uptime']);
+        },
+        error: function(xhr) {
+            alert(xhr.responseText);
+        }
+    });
+});
+</script>
+{% endblock %}

+ 1 - 0
netbox/templates/dcim/inc/device_header.html

@@ -45,6 +45,7 @@
 <ul class="nav nav-tabs" style="margin-bottom: 20px">
     <li role="presentation"{% if active_tab == 'info' %} class="active"{% endif %}><a href="{% url 'dcim:device' pk=device.pk %}">Info</a></li>
     <li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}><a href="{% url 'dcim:device_inventory' pk=device.pk %}">Inventory</a></li>
+    <li role="presentation"{% if active_tab == 'status' %} class="active"{% endif %}><a href="{% url 'dcim:device_status' pk=device.pk %}">Status</a></li>
     {% if device.status == 1 and device.platform.rpc_client and device.primary_ip %}
         <li role="presentation"{% if active_tab == 'lldp-neighbors' %} class="active"{% endif %}><a href="{% url 'dcim:device_lldp_neighbors' pk=device.pk %}">LLDP Neighbors</a></li>
     {% endif %}