mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Live device status PoC
This commit is contained in:
parent
f6a8d32880
commit
12472a2612
@ -1,4 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.mixins import ListModelMixin
|
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 rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
|
||||||
|
|
||||||
from django.conf import settings
|
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 django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
@ -225,8 +226,8 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
|
|||||||
write_serializer_class = serializers.WritableDeviceSerializer
|
write_serializer_class = serializers.WritableDeviceSerializer
|
||||||
filter_class = filters.DeviceFilter
|
filter_class = filters.DeviceFilter
|
||||||
|
|
||||||
@detail_route(url_path='napalm/(?P<method>get_[a-z_]+)')
|
@detail_route(url_path='napalm')
|
||||||
def napalm(self, request, pk, method):
|
def napalm(self, request, pk):
|
||||||
"""
|
"""
|
||||||
Execute a NAPALM method on a Device
|
Execute a NAPALM method on a Device
|
||||||
"""
|
"""
|
||||||
@ -253,16 +254,21 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
|
|||||||
device.platform, device.platform.napalm_driver
|
device.platform, device.platform.napalm_driver
|
||||||
))
|
))
|
||||||
|
|
||||||
# Raise a 404 for invalid NAPALM methods
|
|
||||||
if not hasattr(driver, method):
|
|
||||||
raise Http404()
|
|
||||||
|
|
||||||
# Verify user permission
|
# Verify user permission
|
||||||
if not request.user.has_perm('dcim.napalm_read'):
|
if not request.user.has_perm('dcim.napalm_read'):
|
||||||
return HttpResponseForbidden()
|
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
|
# TODO: Improve error handling
|
||||||
|
response = OrderedDict([(m, None) for m in napalm_methods])
|
||||||
ip_address = str(device.primary_ip.address.ip)
|
ip_address = str(device.primary_ip.address.ip)
|
||||||
d = driver(
|
d = driver(
|
||||||
hostname=ip_address,
|
hostname=ip_address,
|
||||||
@ -271,10 +277,12 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
d.open()
|
d.open()
|
||||||
response = getattr(d, method)()
|
for method in napalm_methods:
|
||||||
|
response[method] = getattr(d, method)()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ServiceUnavailable("Error connecting to the device: {}".format(e))
|
raise ServiceUnavailable("Error connecting to the device: {}".format(e))
|
||||||
|
|
||||||
|
d.close()
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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+)/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+)/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+)/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+)/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<pk>\d+)/add-secret/$', secret_add, name='device_addsecret'),
|
||||||
url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceCreateView.as_view(), name='service_assign'),
|
url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceCreateView.as_view(), name='service_assign'),
|
||||||
|
@ -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):
|
class DeviceLLDPNeighborsView(View):
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
67
netbox/templates/dcim/device_status.html
Normal file
67
netbox/templates/dcim/device_status.html
Normal file
@ -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 %}
|
@ -45,6 +45,7 @@
|
|||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
<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 == '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 == '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 %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
|
Loading…
Reference in New Issue
Block a user