#878: Show assigned IP addresses in device interfaces list

This commit is contained in:
Jeremy Stretch 2017-04-12 22:02:23 -04:00
parent b42dab3eef
commit d5c3f9e780
6 changed files with 84 additions and 74 deletions

View File

@ -1713,7 +1713,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
self.fields['interface'].required = True self.fields['interface'].required = True
# If this device has only one interface, select it by default. # If this device has only one interface, select it by default.
if len(interfaces) == 1: if 'interface' not in self.initial and len(interfaces) == 1:
self.fields['interface'].initial = interfaces[0] self.fields['interface'].initial = interfaces[0]
# If this device does not have any IP addresses assigned, default to setting the first IP as its primary. # If this device does not have any IP addresses assigned, default to setting the first IP as its primary.

View File

@ -13,7 +13,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils.http import urlencode from django.utils.http import urlencode
from django.views.generic import View from django.views.generic import View
from ipam.models import Prefix, IPAddress, Service, VLAN from ipam.models import Prefix, Service, VLAN
from circuits.models import Circuit from circuits.models import Circuit
from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
@ -700,19 +700,15 @@ def device(request, pk):
interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\ interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
.filter(device=device, mgmt_only=False)\ .filter(device=device, mgmt_only=False)\
.select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device', .select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
'circuit_termination__circuit') 'circuit_termination__circuit').prefetch_related('ip_addresses')
mgmt_interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\ mgmt_interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
.filter(device=device, mgmt_only=True)\ .filter(device=device, mgmt_only=True)\
.select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device', .select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
'circuit_termination__circuit') 'circuit_termination__circuit').prefetch_related('ip_addresses')
device_bays = natsorted( device_bays = natsorted(
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'), DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
key=attrgetter('name') key=attrgetter('name')
) )
# Gather relevant device objects
ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface', 'vrf')\
.order_by('address')
services = Service.objects.filter(device=device) services = Service.objects.filter(device=device)
secrets = device.secrets.all() secrets = device.secrets.all()
@ -743,7 +739,6 @@ def device(request, pk):
'interfaces': interfaces, 'interfaces': interfaces,
'mgmt_interfaces': mgmt_interfaces, 'mgmt_interfaces': mgmt_interfaces,
'device_bays': device_bays, 'device_bays': device_bays,
'ip_addresses': ip_addresses,
'services': services, 'services': services,
'secrets': secrets, 'secrets': secrets,
'related_devices': related_devices, 'related_devices': related_devices,
@ -1599,7 +1594,7 @@ def ipaddress_assign(request, pk):
return redirect('dcim:device', pk=device.pk) return redirect('dcim:device', pk=device.pk)
else: else:
form = forms.IPAddressForm(device) form = forms.IPAddressForm(device, initial=request.GET)
return render(request, 'dcim/ipaddress_assign.html', { return render(request, 'dcim/ipaddress_assign.html', {
'device': device, 'device': device,

View File

@ -313,6 +313,16 @@ li.occupied + li.available {
border-top: 1px solid #474747; border-top: 1px solid #474747;
} }
/* Devices */
table.component-list tr.ipaddress td {
background-color: #eeffff;
padding-bottom: 4px;
padding-top: 4px;
}
table.component-list tr.ipaddress:hover td {
background-color: #e6f7f7;
}
/* Misc */ /* Misc */
.banner-bottom { .banner-bottom {
margin-bottom: 50px; margin-bottom: 50px;

View File

@ -194,35 +194,6 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>IP Addresses</strong>
</div>
{% if ip_addresses %}
<table class="table table-hover panel-body">
{% for ip in ip_addresses %}
{% include 'dcim/inc/ipaddress.html' %}
{% endfor %}
</table>
{% elif interfaces or mgmt_interfaces %}
<div class="panel-body text-muted">
None assigned
</div>
{% else %}
<div class="panel-body">
<a href="{% url 'dcim:interface_add' pk=device.pk %}">Create an interface</a> to assign an IP.
</div>
{% endif %}
{% if perms.ipam.add_ipaddress %}
{% if interfaces or mgmt_interfaces %}
<div class="panel-footer text-right">
<a href="{% url 'dcim:ipaddress_assign' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign IP address
</a>
</div>
{% endif %}
{% endif %}
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Services</strong> <strong>Services</strong>
@ -250,7 +221,7 @@
<div class="panel-heading"> <div class="panel-heading">
<strong>Critical Connections</strong> <strong>Critical Connections</strong>
</div> </div>
<table class="table table-hover panel-body"> <table class="table table-hover panel-body component-list">
{% for iface in mgmt_interfaces %} {% for iface in mgmt_interfaces %}
{% include 'dcim/inc/interface.html' with icon='wrench' %} {% include 'dcim/inc/interface.html' with icon='wrench' %}
{% empty %} {% empty %}
@ -375,7 +346,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<table class="table table-hover panel-body"> <table class="table table-hover panel-body component-list">
{% for devicebay in device_bays %} {% for devicebay in device_bays %}
{% include 'dcim/inc/devicebay.html' with selectable=True %} {% include 'dcim/inc/devicebay.html' with selectable=True %}
{% empty %} {% empty %}
@ -416,6 +387,9 @@
<div class="panel-heading"> <div class="panel-heading">
<strong>Interfaces</strong> <strong>Interfaces</strong>
<div class="pull-right"> <div class="pull-right">
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
</button>
{% if perms.dcim.change_interface and interfaces|length > 1 %} {% if perms.dcim.change_interface and interfaces|length > 1 %}
<button class="btn btn-default btn-xs toggle"> <button class="btn btn-default btn-xs toggle">
<span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all <span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
@ -428,7 +402,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<table class="table table-hover panel-body"> <table class="table table-hover panel-body component-list">
{% for iface in interfaces %} {% for iface in interfaces %}
{% include 'dcim/inc/interface.html' with selectable=True %} {% include 'dcim/inc/interface.html' with selectable=True %}
{% empty %} {% empty %}
@ -485,7 +459,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<table class="table table-hover panel-body"> <table class="table table-hover panel-body component-list">
{% for csp in cs_ports %} {% for csp in cs_ports %}
{% include 'dcim/inc/consoleserverport.html' with selectable=True %} {% include 'dcim/inc/consoleserverport.html' with selectable=True %}
{% empty %} {% empty %}
@ -537,7 +511,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<table class="table table-hover panel-body"> <table class="table table-hover panel-body component-list">
{% for po in power_outlets %} {% for po in power_outlets %}
{% include 'dcim/inc/poweroutlet.html' with selectable=True %} {% include 'dcim/inc/poweroutlet.html' with selectable=True %}
{% empty %} {% empty %}
@ -628,6 +602,18 @@ $(".powerport-toggle").click(function() {
$(".interface-toggle").click(function() { $(".interface-toggle").click(function() {
return toggleConnection($(this), "dcim/interface-connections/"); return toggleConnection($(this), "dcim/interface-connections/");
}); });
// Toggle the display of IP addresses under interfaces
$('button.toggle-ips').click(function() {
var selected = $(this).attr('selected');
if (selected) {
$('table.component-list tr.ipaddress').hide();
} else {
$('table.component-list tr.ipaddress').show();
}
$(this).attr('selected', !selected);
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
return false;
});
</script> </script>
<script src="{% static 'js/graphs.js' %}"></script> <script src="{% static 'js/graphs.js' %}"></script>
<script src="{% static 'js/secrets.js' %}"></script> <script src="{% static 'js/secrets.js' %}"></script>

View File

@ -1,4 +1,4 @@
<tr{% if iface.connection and not iface.connection.connection_status %} class="info"{% endif %}> <tr class="interface{% if iface.connection and not iface.connection.connection_status %} info{% endif %}">
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
<td class="pk"> <td class="pk">
<input name="pk" type="checkbox" value="{{ iface.pk }}" /> <input name="pk" type="checkbox" value="{{ iface.pk }}" />
@ -16,9 +16,6 @@
<br /><small class="text-muted">{{ iface.member_interfaces.all|join:", "|default:"No members" }}</small> <br /><small class="text-muted">{{ iface.member_interfaces.all|join:", "|default:"No members" }}</small>
{% endif %} {% endif %}
</td> </td>
<td>
<small>{{ iface.mac_address|default:'' }}</small>
</td>
{% if iface.is_lag %} {% if iface.is_lag %}
<td colspan="2" class="text-muted">LAG interface</td> <td colspan="2" class="text-muted">LAG interface</td>
{% elif iface.is_virtual %} {% elif iface.is_virtual %}
@ -53,7 +50,7 @@
<span class="text-muted">Not connected</span> <span class="text-muted">Not connected</span>
</td> </td>
{% endif %} {% endif %}
<td class="text-right"> <td colspan="2" class="text-right">
{% if show_graphs %} {% if show_graphs %}
{% if iface.circuit_termination or iface.connection %} {% if iface.circuit_termination or iface.connection %}
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface_graphs' pk=iface.pk %}" title="Show graphs"> <button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface_graphs' pk=iface.pk %}" title="Show graphs">
@ -61,6 +58,11 @@
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if perms.ipam.add_ipaddress %}
<a href="{% url 'dcim:ipaddress_assign' pk=device.pk %}?interface={{ iface.pk }}" class="btn btn-xs btn-success" title="Assign IP address">
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
</a>
{% endif %}
{% if perms.dcim.change_interface %} {% if perms.dcim.change_interface %}
{% if not iface.is_virtual %} {% if not iface.is_virtual %}
{% if iface.connection %} {% if iface.connection %}
@ -73,19 +75,19 @@
<i class="fa fa-plug" aria-hidden="true"></i> <i class="fa fa-plug" aria-hidden="true"></i>
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Delete connection"> <a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Disconnect">
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i> <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
</a> </a>
{% elif iface.circuit_termination and perms.circuits.change_circuittermination %} {% elif iface.circuit_termination and perms.circuits.change_circuittermination %}
<button class="btn btn-warning btn-xs interface-toggle connected" disabled="disabled" title="Circuits cannot be marked as planned or connected"> <button class="btn btn-warning btn-xs interface-toggle connected" disabled="disabled" title="Circuits cannot be marked as planned or connected">
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i> <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
</button> </button>
<a href="{% url 'circuits:circuittermination_edit' pk=iface.circuit_termination.pk %}" class="btn btn-danger btn-xs" title="Edit circuit termination"> <a href="{% url 'circuits:circuittermination_edit' pk=iface.circuit_termination.pk %}" class="btn btn-danger btn-xs" title="Edit circuit termination">
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i> <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
</a> </a>
{% else %} {% else %}
<a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface_a={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect"> <a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface_a={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect">
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i> <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
</a> </a>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -106,3 +108,41 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% for ip in iface.ip_addresses.all %}
<tr class="ipaddress">
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
<td></td>
{% endif %}
<td colspan="2">
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
{% if ip.description %}
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
{% endif %}
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
<span class="label label-success">Primary</span>
{% endif %}
</td>
<td class="text-right">
{% if ip.vrf %}
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
{% else %}
<span class="text-muted">Global</span>
{% endif %}
</td>
<td>
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
</td>
<td class="text-right">
{% if perms.ipam.edit_ipaddress %}
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
</a>
{% endif %}
{% if perms.ipam.delete_ipaddress %}
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
</a>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@ -1,21 +0,0 @@
<tr>
<td>
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
</td>
<td>
{{ ip.vrf|default:"Global" }}
</td>
<td>{{ ip.interface }}</td>
<td>
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
<span class="label label-success">Primary</span>
{% endif %}
</td>
<td class="text-right">
{% if perms.ipam.delete_ipaddress %}
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
</a>
{% endif %}
</td>
</tr>