mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
Closes #6014: Move virtual machine interfaces list to a separate view
This commit is contained in:
parent
1544823d73
commit
0364d8cd43
@ -95,6 +95,7 @@ A new cloud model has been introduced for representing the boundary of a network
|
|||||||
* [#5873](https://github.com/netbox-community/netbox/issues/5873) - Use numeric IDs in all object URLs
|
* [#5873](https://github.com/netbox-community/netbox/issues/5873) - Use numeric IDs in all object URLs
|
||||||
* [#5990](https://github.com/netbox-community/netbox/issues/5990) - Deprecated `display_field` parameter for custom script ObjectVar and MultiObjectVar fields
|
* [#5990](https://github.com/netbox-community/netbox/issues/5990) - Deprecated `display_field` parameter for custom script ObjectVar and MultiObjectVar fields
|
||||||
* [#5995](https://github.com/netbox-community/netbox/issues/5995) - Dropped backward compatibility for `queryset` parameter on ObjectVar and MultiObjectVar (use `model` instead)
|
* [#5995](https://github.com/netbox-community/netbox/issues/5995) - Dropped backward compatibility for `queryset` parameter on ObjectVar and MultiObjectVar (use `model` instead)
|
||||||
|
* [#6014](https://github.com/netbox-community/netbox/issues/6014) - Moved virtual machine interfaces list to a separate view
|
||||||
|
|
||||||
### REST API Changes
|
### REST API Changes
|
||||||
|
|
||||||
|
@ -1,44 +1,10 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'virtualization/virtualmachine/base.html' %}
|
||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
{% load custom_links %}
|
{% load custom_links %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
{% if object.cluster %}
|
|
||||||
<li><a href="{{ object.cluster.get_absolute_url }}">{{ object.cluster }}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li>{{ object }}</li>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block buttons %}
|
|
||||||
{% if perms.virtualization.add_vminterface %}
|
|
||||||
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
|
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Interfaces
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
|
||||||
<a href="{{ object.get_absolute_url }}">Virtual Machine</a>
|
|
||||||
</li>
|
|
||||||
{% if perms.extras.view_configcontext %}
|
|
||||||
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
|
||||||
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=object.pk %}">Config Context</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.extras.view_objectchange %}
|
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
|
||||||
<a href="{% url 'virtualization:virtualmachine_changelog' pk=object.pk %}">Change Log</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -236,54 +202,6 @@
|
|||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="virtual_machine" value="{{ object.pk }}" />
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong>Interfaces</strong>
|
|
||||||
<div class="pull-right noprint">
|
|
||||||
{% if request.user.is_authenticated %}
|
|
||||||
<button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target="#VirtualMachineVMInterfaceTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="pull-right col-md-2 noprint">
|
|
||||||
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'responsive_table.html' with table=vminterface_table %}
|
|
||||||
{% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
|
|
||||||
<div class="panel-footer noprint">
|
|
||||||
{% if interfaces and perms.virtualization.change_vminterface %}
|
|
||||||
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-xs">
|
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-xs">
|
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if interfaces and perms.virtualization.delete_vminterface %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger btn-xs">
|
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.virtualization.add_vminterface %}
|
|
||||||
<div class="pull-right">
|
|
||||||
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-xs">
|
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% table_config_form vminterface_table %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'secrets/inc/private_key_modal.html' %}
|
{% include 'secrets/inc/private_key_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
45
netbox/templates/virtualization/virtualmachine/base.html
Normal file
45
netbox/templates/virtualization/virtualmachine/base.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load custom_links %}
|
||||||
|
{% load plugins %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li><a href="{% url 'virtualization:virtualmachine_list' %}">Virtual Machines</a></li>
|
||||||
|
<li><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ object.cluster.pk }}">{{ object.cluster }}</a></li>
|
||||||
|
<li>{{ object }}</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttons %}
|
||||||
|
{% if perms.virtualization.add_vminterface %}
|
||||||
|
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Interfaces
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
|
<a href="{{ object.get_absolute_url }}">Virtual Machine</a>
|
||||||
|
</li>
|
||||||
|
{% with interface_count=object.interfaces.count %}
|
||||||
|
{% if interface_count %}
|
||||||
|
<li role="presentation" {% if active_tab == 'interfaces' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% if perms.extras.view_configcontext %}
|
||||||
|
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=object.pk %}">Config Context</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.extras.view_objectchange %}
|
||||||
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'virtualization:virtualmachine_changelog' pk=object.pk %}">Change Log</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,54 @@
|
|||||||
|
{% extends 'virtualization/virtualmachine/base.html' %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Interfaces</strong>
|
||||||
|
<div class="pull-right noprint">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target="#VirtualMachineVMInterfaceTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="pull-right col-md-2 noprint">
|
||||||
|
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% render_table interface_table 'inc/table.html' %}
|
||||||
|
<div class="panel-footer noprint">
|
||||||
|
{% if perms.virtualization.change_vminterface %}
|
||||||
|
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtualmachine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.virtualization.delete_vminterface %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.virtualization.add_vminterface %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'virtualization:vminterface_add' %}?virtualmachine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form interface_table %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="{% static 'js/connection_toggles.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
|
<script src="{% static 'js/interface_filtering.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
|
<script src="{% static 'js/tableconfig.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
|
{% endblock %}
|
@ -1,3 +1,5 @@
|
|||||||
|
from django.test import override_settings
|
||||||
|
from django.urls import reverse
|
||||||
from netaddr import EUI
|
from netaddr import EUI
|
||||||
|
|
||||||
from dcim.choices import InterfaceModeChoices
|
from dcim.choices import InterfaceModeChoices
|
||||||
@ -196,6 +198,19 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'comments': 'New comments',
|
'comments': 'New comments',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_interfaces(self):
|
||||||
|
virtualmachine = VirtualMachine.objects.first()
|
||||||
|
vminterfaces = (
|
||||||
|
VMInterface(virtual_machine=virtualmachine, name='Interface 1'),
|
||||||
|
VMInterface(virtual_machine=virtualmachine, name='Interface 2'),
|
||||||
|
VMInterface(virtual_machine=virtualmachine, name='Interface 3'),
|
||||||
|
)
|
||||||
|
VMInterface.objects.bulk_create(vminterfaces)
|
||||||
|
|
||||||
|
url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
|
@ -51,6 +51,7 @@ urlpatterns = [
|
|||||||
path('virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'),
|
path('virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'),
|
||||||
path('virtual-machines/delete/', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'),
|
path('virtual-machines/delete/', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'),
|
||||||
path('virtual-machines/<int:pk>/', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
path('virtual-machines/<int:pk>/', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
||||||
|
path('virtual-machines/<int:pk>/interfaces/', views.VirtualMachineInterfacesView.as_view(), name='virtualmachine_interfaces'),
|
||||||
path('virtual-machines/<int:pk>/edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
path('virtual-machines/<int:pk>/edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
||||||
path('virtual-machines/<int:pk>/delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
path('virtual-machines/<int:pk>/delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
||||||
path('virtual-machines/<int:pk>/config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),
|
path('virtual-machines/<int:pk>/config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),
|
||||||
|
@ -322,6 +322,30 @@ class VirtualMachineView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualMachineInterfacesView(generic.ObjectView):
|
||||||
|
queryset = VirtualMachine.objects.all()
|
||||||
|
template_name = 'virtualization/virtualmachine/interfaces.html'
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
interfaces = instance.interfaces.restrict(request.user, 'view').prefetch_related(
|
||||||
|
Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
|
||||||
|
'tags',
|
||||||
|
)
|
||||||
|
interface_table = tables.VirtualMachineVMInterfaceTable(
|
||||||
|
data=interfaces,
|
||||||
|
user=request.user,
|
||||||
|
orderable=False
|
||||||
|
)
|
||||||
|
if request.user.has_perm('virtualization.change_vminterface') or \
|
||||||
|
request.user.has_perm('virtualization.delete_vminterface'):
|
||||||
|
interface_table.columns.show('pk')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'interface_table': interface_table,
|
||||||
|
'active_tab': 'interfaces',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineConfigContextView(ObjectConfigContextView):
|
class VirtualMachineConfigContextView(ObjectConfigContextView):
|
||||||
queryset = VirtualMachine.objects.annotate_config_context_data()
|
queryset = VirtualMachine.objects.annotate_config_context_data()
|
||||||
base_template = 'virtualization/virtualmachine.html'
|
base_template = 'virtualization/virtualmachine.html'
|
||||||
|
Loading…
Reference in New Issue
Block a user