1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #5075: Include a VLAN membership view for VM interfaces

This commit is contained in:
Jeremy Stretch
2020-09-23 11:48:32 -04:00
parent c0b94e4e8e
commit 1f0a4cc548
8 changed files with 77 additions and 24 deletions

View File

@ -13,6 +13,7 @@
### Bug Fixes
* [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release
* [#5075](https://github.com/netbox-community/netbox/issues/5075) - Include a VLAN membership view for VM interfaces
* [#5105](https://github.com/netbox-community/netbox/issues/5105) - Validation should fail when reassigning a primary IP from device to VM
* [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data
* [#5108](https://github.com/netbox-community/netbox/issues/5108) - Fix execution of reports via CLI

View File

@ -985,13 +985,20 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
def get_status_class(self):
return self.STATUS_CLASS_MAP[self.status]
def get_members(self):
# Return all interfaces assigned to this VLAN
def get_interfaces(self):
# Return all device interfaces assigned to this VLAN
return Interface.objects.filter(
Q(untagged_vlan_id=self.pk) |
Q(tagged_vlans=self.pk)
).distinct()
def get_vminterfaces(self):
# Return all VM interfaces assigned to this VLAN
return VMInterface.objects.filter(
Q(untagged_vlan_id=self.pk) |
Q(tagged_vlans=self.pk)
).distinct()
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
class Service(ChangeLoggedModel, CustomFieldModel):

View File

@ -4,6 +4,7 @@ from django_tables2.utils import Accessor
from dcim.models import Interface
from tenancy.tables import COL_TENANT
from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, TagColumn, ToggleColumn
from virtualization.models import VMInterface
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
RIR_UTILIZATION = """
@ -124,9 +125,11 @@ VLANGROUP_ADD_VLAN = """
{% endwith %}
"""
VLAN_MEMBER_UNTAGGED = """
VLAN_MEMBER_TAGGED = """
{% if record.untagged_vlan_id == vlan.pk %}
<i class="glyphicon glyphicon-ok">
<span class="text-danger"><i class="fa fa-close"></i></span>
{% else %}
<span class="text-success"><i class="fa fa-check"></i></span>
{% endif %}
"""
@ -553,15 +556,15 @@ class VLANDetailTable(VLANTable):
default_columns = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
class VLANMemberTable(BaseTable):
parent = tables.LinkColumn(
order_by=['device', 'virtual_machine']
)
class VLANMembersTable(BaseTable):
"""
Base table for Interface and VMInterface assignments
"""
name = tables.LinkColumn(
verbose_name='Interface'
)
untagged = tables.TemplateColumn(
template_code=VLAN_MEMBER_UNTAGGED,
tagged = tables.TemplateColumn(
template_code=VLAN_MEMBER_TAGGED,
orderable=False
)
actions = tables.TemplateColumn(
@ -570,9 +573,21 @@ class VLANMemberTable(BaseTable):
verbose_name=''
)
class VLANDevicesTable(VLANMembersTable):
device = tables.LinkColumn()
class Meta(BaseTable.Meta):
model = Interface
fields = ('parent', 'name', 'untagged', 'actions')
fields = ('device', 'name', 'tagged', 'actions')
class VLANVirtualMachinesTable(VLANMembersTable):
virtual_machine = tables.LinkColumn()
class Meta(BaseTable.Meta):
model = VMInterface
fields = ('virtual_machine', 'name', 'tagged', 'actions')
class InterfaceVLANTable(BaseTable):

View File

@ -90,7 +90,8 @@ urlpatterns = [
path('vlans/edit/', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'),
path('vlans/delete/', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'),
path('vlans/<int:pk>/', views.VLANView.as_view(), name='vlan'),
path('vlans/<int:pk>/members/', views.VLANMembersView.as_view(), name='vlan_members'),
path('vlans/<int:pk>/interfaces/', views.VLANInterfacesView.as_view(), name='vlan_interfaces'),
path('vlans/<int:pk>/vm-interfaces/', views.VLANVMInterfacesView.as_view(), name='vlan_vminterfaces'),
path('vlans/<int:pk>/edit/', views.VLANEditView.as_view(), name='vlan_edit'),
path('vlans/<int:pk>/delete/', views.VLANDeleteView.as_view(), name='vlan_delete'),
path('vlans/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vlan_changelog', kwargs={'model': VLAN}),

View File

@ -749,15 +749,13 @@ class VLANView(ObjectView):
})
class VLANMembersView(ObjectView):
class VLANInterfacesView(ObjectView):
queryset = VLAN.objects.all()
def get(self, request, pk):
vlan = get_object_or_404(self.queryset, pk=pk)
members = vlan.get_members().restrict(request.user, 'view').prefetch_related('device', 'virtual_machine')
members_table = tables.VLANMemberTable(members)
interfaces = vlan.get_interfaces().prefetch_related('device')
members_table = tables.VLANDevicesTable(interfaces)
paginate = {
'paginator_class': EnhancedPaginator,
@ -765,10 +763,31 @@ class VLANMembersView(ObjectView):
}
RequestConfig(request, paginate).configure(members_table)
return render(request, 'ipam/vlan_members.html', {
return render(request, 'ipam/vlan_interfaces.html', {
'vlan': vlan,
'members_table': members_table,
'active_tab': 'members',
'active_tab': 'interfaces',
})
class VLANVMInterfacesView(ObjectView):
queryset = VLAN.objects.all()
def get(self, request, pk):
vlan = get_object_or_404(self.queryset, pk=pk)
interfaces = vlan.get_vminterfaces().prefetch_related('virtual_machine')
members_table = tables.VLANVirtualMachinesTable(interfaces)
paginate = {
'paginator_class': EnhancedPaginator,
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
}
RequestConfig(request, paginate).configure(members_table)
return render(request, 'ipam/vlan_vminterfaces.html', {
'vlan': vlan,
'members_table': members_table,
'active_tab': 'vminterfaces',
})

View File

@ -52,8 +52,11 @@
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{% url 'ipam:vlan' pk=vlan.pk %}">VLAN</a>
</li>
<li role="presentation"{% if active_tab == 'members' %} class="active"{% endif %}>
<a href="{% url 'ipam:vlan_members' pk=vlan.pk %}">Members <span class="badge">{{ vlan.get_members.count }}</span></a>
<li role="presentation"{% if active_tab == 'interfaces' %} class="active"{% endif %}>
<a href="{% url 'ipam:vlan_interfaces' pk=vlan.pk %}">Device Interfaces <span class="badge">{{ vlan.get_interfaces.count }}</span></a>
</li>
<li role="presentation"{% if active_tab == 'vminterfaces' %} class="active"{% endif %}>
<a href="{% url 'ipam:vlan_vminterfaces' pk=vlan.pk %}">VM Interfaces <span class="badge">{{ vlan.get_vminterfaces.count }}</span></a>
</li>
{% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>

View File

@ -1,11 +1,9 @@
{% extends 'ipam/vlan.html' %}
{% block title %}{{ block.super }} - Members{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with table=members_table table_template='panel_table.html' heading='VLAN Members' parent=vlan %}
{% include 'utilities/obj_table.html' with table=members_table table_template='panel_table.html' heading='Device Interfaces' parent=vlan %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,9 @@
{% extends 'ipam/vlan.html' %}
{% block content %}
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with table=members_table table_template='panel_table.html' heading='Virtual Machine Interfaces' parent=vlan %}
</div>
</div>
{% endblock %}