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 ### Bug Fixes
* [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release * [#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 * [#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 * [#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 * [#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): def get_status_class(self):
return self.STATUS_CLASS_MAP[self.status] return self.STATUS_CLASS_MAP[self.status]
def get_members(self): def get_interfaces(self):
# Return all interfaces assigned to this VLAN # Return all device interfaces assigned to this VLAN
return Interface.objects.filter( return Interface.objects.filter(
Q(untagged_vlan_id=self.pk) | Q(untagged_vlan_id=self.pk) |
Q(tagged_vlans=self.pk) Q(tagged_vlans=self.pk)
).distinct() ).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') @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
class Service(ChangeLoggedModel, CustomFieldModel): class Service(ChangeLoggedModel, CustomFieldModel):

View File

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

View File

@ -90,7 +90,8 @@ urlpatterns = [
path('vlans/edit/', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'), path('vlans/edit/', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'),
path('vlans/delete/', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'), 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>/', 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>/edit/', views.VLANEditView.as_view(), name='vlan_edit'),
path('vlans/<int:pk>/delete/', views.VLANDeleteView.as_view(), name='vlan_delete'), 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}), 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() queryset = VLAN.objects.all()
def get(self, request, pk): def get(self, request, pk):
vlan = get_object_or_404(self.queryset, pk=pk) vlan = get_object_or_404(self.queryset, pk=pk)
members = vlan.get_members().restrict(request.user, 'view').prefetch_related('device', 'virtual_machine') interfaces = vlan.get_interfaces().prefetch_related('device')
members_table = tables.VLANDevicesTable(interfaces)
members_table = tables.VLANMemberTable(members)
paginate = { paginate = {
'paginator_class': EnhancedPaginator, 'paginator_class': EnhancedPaginator,
@ -765,10 +763,31 @@ class VLANMembersView(ObjectView):
} }
RequestConfig(request, paginate).configure(members_table) RequestConfig(request, paginate).configure(members_table)
return render(request, 'ipam/vlan_members.html', { return render(request, 'ipam/vlan_interfaces.html', {
'vlan': vlan, 'vlan': vlan,
'members_table': members_table, '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 %}> <li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{% url 'ipam:vlan' pk=vlan.pk %}">VLAN</a> <a href="{% url 'ipam:vlan' pk=vlan.pk %}">VLAN</a>
</li> </li>
<li role="presentation"{% if active_tab == 'members' %} class="active"{% endif %}> <li role="presentation"{% if active_tab == 'interfaces' %} class="active"{% endif %}>
<a href="{% url 'ipam:vlan_members' pk=vlan.pk %}">Members <span class="badge">{{ vlan.get_members.count }}</span></a> <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> </li>
{% if perms.extras.view_objectchange %} {% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}> <li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>

View File

@ -1,11 +1,9 @@
{% extends 'ipam/vlan.html' %} {% extends 'ipam/vlan.html' %}
{% block title %}{{ block.super }} - Members{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-12"> <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>
</div> </div>
{% endblock %} {% 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 %}