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:
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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}),
|
||||||
|
@ -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',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 %}>
|
||||||
|
@ -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 %}
|
9
netbox/templates/ipam/vlan_vminterfaces.html
Normal file
9
netbox/templates/ipam/vlan_vminterfaces.html
Normal 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 %}
|
Reference in New Issue
Block a user