Implemented a view for interfaces

This commit is contained in:
Jeremy Stretch 2018-07-11 15:30:54 -04:00
parent 484a74defd
commit f048cf36ce
8 changed files with 366 additions and 14 deletions

View File

@ -1871,7 +1871,7 @@ class Interface(ComponentModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return self.parent.get_absolute_url() return reverse('dcim:interface', kwargs={'pk': self.pk})
def get_component_parent(self): def get_component_parent(self):
return self.device or self.virtual_machine return self.device or self.virtual_machine
@ -1967,6 +1967,10 @@ class Interface(ComponentModel):
def parent(self): def parent(self):
return self.device or self.virtual_machine return self.device or self.virtual_machine
@property
def is_connectable(self):
return self.form_factor not in NONCONNECTABLE_IFACE_TYPES
@property @property
def is_virtual(self): def is_virtual(self):
return self.form_factor in VIRTUAL_IFACE_TYPES return self.form_factor in VIRTUAL_IFACE_TYPES

View File

@ -199,6 +199,7 @@ urlpatterns = [
url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'), url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.InterfaceConnectionAddView.as_view(), name='interfaceconnection_add'),
url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'), url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.InterfaceConnectionDeleteView.as_view(), name='interfaceconnection_delete'),
url(r'^interfaces/(?P<pk>\d+)/$', views.InterfaceView.as_view(), name='interface'),
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'), url(r'^interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'),
url(r'^interfaces/(?P<pk>\d+)/assign-vlans/$', views.InterfaceAssignVLANsView.as_view(), name='interface_assign_vlans'), url(r'^interfaces/(?P<pk>\d+)/assign-vlans/$', views.InterfaceAssignVLANsView.as_view(), name='interface_assign_vlans'),
url(r'^interfaces/(?P<pk>\d+)/delete/$', views.InterfaceDeleteView.as_view(), name='interface_delete'), url(r'^interfaces/(?P<pk>\d+)/delete/$', views.InterfaceDeleteView.as_view(), name='interface_delete'),

View File

@ -21,6 +21,7 @@ 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 extras.views import ObjectConfigContextView from extras.views import ObjectConfigContextView
from ipam.models import Prefix, Service, VLAN from ipam.models import Prefix, Service, VLAN
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator from utilities.paginator import EnhancedPaginator
from utilities.views import ( from utilities.views import (
@ -1616,6 +1617,47 @@ class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
# Interfaces # Interfaces
# #
class InterfaceView(View):
def get(self, request, pk):
interface = get_object_or_404(Interface, pk=pk)
# Get connected interface
connected_interface = interface.connected_interface
if connected_interface is None and hasattr(interface, 'circuit_termination'):
peer_termination = interface.circuit_termination.get_peer_termination()
if peer_termination is not None:
connected_interface = peer_termination.interface
# Get assigned IP addresses
ipaddress_table = InterfaceIPAddressTable(
data=interface.ip_addresses.select_related('vrf', 'tenant'),
orderable=False
)
# Get assigned VLANs and annotate whether each is tagged or untagged
vlans = []
if interface.untagged_vlan is not None:
vlans.append(interface.untagged_vlan)
vlans[0].tagged = False
for vlan in interface.tagged_vlans.select_related('site', 'group', 'tenant', 'role'):
vlan.tagged = True
vlans.append(vlan)
vlan_table = InterfaceVLANTable(
interface=interface,
data=vlans,
orderable=False
)
return render(request, 'dcim/interface.html', {
'interface': interface,
'connected_interface': connected_interface,
'ipaddress_table': ipaddress_table,
'vlan_table': vlan_table,
})
class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView): class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
permission_required = 'dcim.add_interface' permission_required = 'dcim.add_interface'
parent_model = Device parent_model = Device

View File

@ -342,6 +342,20 @@ class IPAddressAssignTable(BaseTable):
orderable = False orderable = False
class InterfaceIPAddressTable(BaseTable):
"""
List IP addresses assigned to a specific Interface.
"""
address = tables.TemplateColumn(IPADDRESS_ASSIGN_LINK, verbose_name='IP Address')
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
status = tables.TemplateColumn(STATUS_LABEL)
tenant = tables.TemplateColumn(template_code=TENANT_LINK)
class Meta(BaseTable.Meta):
model = IPAddress
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
# #
# VLAN groups # VLAN groups
# #
@ -403,6 +417,27 @@ class VLANMemberTable(BaseTable):
fields = ('parent', 'name', 'untagged', 'actions') fields = ('parent', 'name', 'untagged', 'actions')
class InterfaceVLANTable(BaseTable):
"""
List VLANs assigned to a specific Interface.
"""
vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
tagged = BooleanColumn()
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
status = tables.TemplateColumn(STATUS_LABEL)
role = tables.TemplateColumn(VLAN_ROLE_LINK)
class Meta(BaseTable.Meta):
model = VLAN
fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
def __init__(self, interface, *args, **kwargs):
self.interface = interface
super(InterfaceVLANTable, self).__init__(*args, **kwargs)
# #
# Services # Services
# #

View File

@ -522,8 +522,7 @@
<th>Name</th> <th>Name</th>
<th>LAG</th> <th>LAG</th>
<th>Description</th> <th>Description</th>
<th>MTU</th> <th>Mode</th>
<th>MAC Address</th>
<th colspan="2">Connection</th> <th colspan="2">Connection</th>
<th></th> <th></th>
</tr> </tr>

View File

@ -11,7 +11,7 @@
<td> <td>
<span title="{{ iface.get_form_factor_display }}"> <span title="{{ iface.get_form_factor_display }}">
<i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}align-justify{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}exchange{% endif %}"></i> <i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}align-justify{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}exchange{% endif %}"></i>
{{ iface }} <a href="{{ iface.get_absolute_url }}">{{ iface }}</a>
</span> </span>
</td> </td>
@ -23,13 +23,10 @@
</td> </td>
{# Description #} {# Description #}
<td>{{ iface.description }}</td> <td>{{ iface.description|default:"&mdash;" }}</td>
{# MTU #} {# 802.1Q mode #}
<td>{{ iface.mtu|default:"" }}</td> <td>{{ iface.get_mode_display }}</td>
{# MAC address #}
<td>{{ iface.mac_address|default:"" }}</td>
{# Connection or type #} {# Connection or type #}
{% if iface.is_lag %} {% if iface.is_lag %}
@ -141,7 +138,7 @@
{% endif %} {% endif %}
{# IP addresses table #} {# IP addresses table #}
<td colspan="8" style="padding: 0"> <td colspan="7" style="padding: 0">
<table class="table table-condensed interface-ips"> <table class="table table-condensed interface-ips">
<thead> <thead>
<tr class="text-muted"> <tr class="text-muted">

View File

@ -0,0 +1,275 @@
{% extends '_base.html' %}
{% load helpers %}
{% block header %}
<div class="row">
<div class="col-md-12">
<ol class="breadcrumb">
{% if interface.device %}
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
{% else %}
<li><a href="{% url 'virtualization:virtualmachine_list' %}">Virtual Machines</a></li>
{% endif %}
<li><a href="{{ interface.parent.get_absolute_url }}">{{ interface.parent }}</a></li>
<li>{{ interface }}</li>
</ol>
</div>
</div>
<div class="pull-right">
{% if perms.dcim.change_interface %}
<a href="{% url 'dcim:interface_edit' pk=interface.pk %}" class="btn btn-warning">
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this interface
</a>
{% endif %}
{% if perms.dcim.delete_interface %}
<a href="{% url 'dcim:interface_delete' pk=interface.pk %}" class="btn btn-danger">
<span class="fa fa-trash" aria-hidden="true"></span> Delete this interface
</a>
{% endif %}
</div>
<h1>{% block title %}{{ interface.parent }} / {{ interface.name }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interface</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>{% if interface.device %}Device{% else %}Virtual Machine{% endif %}</td>
<td>
<a href="{{ interface.parent.get_absolute_url }}">{{ interface.parent }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ interface.name }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ interface.get_form_factor_display }}</td>
</tr>
<tr>
<td>Enabled</td>
<td>
{% if interface.enabled %}
<span class="text-success"><i class="fa fa-check"></i></span>
{% else %}
<span class="text-danger"><i class="fa fa-close"></i></span>
{% endif %}
</td>
</tr>
<tr>
<td>LAG</td>
<td>
{% if interface.lag%}
<a href="{{ interface.lag.get_absolute_url }}">{{ interface.lag }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
<tr>
<td>Description</td>
<td>
{% if interface.description %}
<span>{{ interface.description }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>MTU</td>
<td>
{% if interface.mtu %}
<span>{{ interface.mtu }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>MAC Address</td>
<td>
{% if interface.mac_address %}
<span>{{ interface.mac_address }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>802.1Q Mode</td>
<td>{{ interface.get_mode_display }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=interface.tags.all %}
</div>
<div class="col-md-6">
{% if interface.is_connectable %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connected Interface</strong>
</div>
{% if connected_interface %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>{% if connected_interface.device %}Device{% else %}Virtual Machine{% endif %}</td>
<td>
<a href="{{ connected_interface.parent.get_absolute_url }}">{{ connected_interface.parent }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ connected_interface.name }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ connected_interface.get_form_factor_display }}</td>
</tr>
<tr>
<td>Enabled</td>
<td>
{% if connected_interface.enabled %}
<span class="text-success"><i class="fa fa-check"></i></span>
{% else %}
<span class="text-danger"><i class="fa fa-close"></i></span>
{% endif %}
</td>
</tr>
<tr>
<td>LAG</td>
<td>
{% if connected_interface.lag%}
<a href="{{ connected_interface.lag.get_absolute_url }}">{{ connected_interface.lag }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
<tr>
<td>Description</td>
<td>
{% if connected_interface.description %}
<span>{{ connected_interface.description }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>MTU</td>
<td>
{% if connected_interface.mtu %}
<span>{{ connected_interface.mtu }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>MAC Address</td>
<td>
{% if connected_interface.mac_address %}
<span>{{ connected_interface.mac_address }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>802.1Q Mode</td>
<td>{{ connected_interface.get_mode_display }}</td>
</tr>
{% if interface.connection %}
<tr>
<td>Connection Status</td>
<td>
{% if interface.connection.connection_status %}
<span class="label label-success">{{ interface.connection.get_connection_status_display }}</span>
{% else %}
<span class="label label-info">{{ interface.connection.get_connection_status_display }}</span>
{% endif %}
</td>
</tr>
{% endif %}
</table>
{% else %}
<div class="panel-body text-muted">
No connected interface
</div>
{% endif %}
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Circuit Termination</strong>
</div>
<table class="table table-hover panel-body">
{% if interface.circuit_termination %}
<tr>
<td>Circuit</td>
<td><a href="{{ interface.circuit_termination.circuit.get_absolute_url }}">{{ interface.circuit_termination.circuit }}</a></td>
</tr>
<tr>
<td>Side</td>
<td>{{ interface.circuit_termination.term_side }}</td>
</tr>
{% else %}
<tr>
<td colspan="2" class="text-muted">None</td>
</tr>
{% endif %}
</table>
</div>
{% endif %}
{% if interface.is_lag %}
<div class="panel panel-default">
<div class="panel-heading"><strong>LAG Members</strong></div>
<table class="table table-hover table-headings panel-body">
<thead>
<tr>
<th>Parent</th>
<th>Interface</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% for member in interface.member_interfaces.all %}
<tr>
<td>
<a href="{{ member.parent.get_absolute_url }}">{{ member.parent }}</a>
</td>
<td>
<a href="{{ member.get_absolute_url }}">{{ member }}</a>
</td>
<td>
{{ member.get_form_factor_display }}
</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-muted">No member interfaces</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% include 'panel_table.html' with table=ipaddress_table heading="IP Addresses" %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% include 'panel_table.html' with table=vlan_table heading="VLANs" %}
</div>
</div>
{% endblock %}

View File

@ -261,8 +261,7 @@
<th>Name</th> <th>Name</th>
<th>LAG</th> <th>LAG</th>
<th>Description</th> <th>Description</th>
<th>MTU</th> <th>Mode</th>
<th>MAC Address</th>
<th colspan="2">Connection</th> <th colspan="2">Connection</th>
<th></th> <th></th>
</tr> </tr>
@ -272,7 +271,7 @@
{% include 'dcim/inc/interface.html' with device=virtualmachine %} {% include 'dcim/inc/interface.html' with device=virtualmachine %}
{% empty %} {% empty %}
<tr> <tr>
<td colspan="9" class="text-center text-muted">&mdash; No interfaces defined &mdash;</td> <td colspan="8" class="text-center text-muted">&mdash; No interfaces defined &mdash;</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>