diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md
index 1e1b906aa..6500e36e3 100644
--- a/docs/release-notes/version-2.11.md
+++ b/docs/release-notes/version-2.11.md
@@ -87,6 +87,7 @@ A new Cloud model has been introduced to represent the boundary of a network tha
* [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models
* [#5375](https://github.com/netbox-community/netbox/issues/5375) - Add `speed` attribute to console port models
* [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
+* [#5425](https://github.com/netbox-community/netbox/issues/5425) - Create separate tabs for VMs and devices under the cluster view
* [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields
* [#5608](https://github.com/netbox-community/netbox/issues/5608) - Add REST API endpoint for custom links
* [#5610](https://github.com/netbox-community/netbox/issues/5610) - Add REST API endpoint for webhooks
diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html
index c065d52e1..e21e82eed 100644
--- a/netbox/templates/virtualization/cluster.html
+++ b/netbox/templates/virtualization/cluster.html
@@ -1,117 +1,81 @@
-{% extends 'generic/object.html' %}
-{% load buttons %}
-{% load custom_links %}
+{% extends 'virtualization/cluster/base.html' %}
{% load helpers %}
{% load plugins %}
-{% block breadcrumbs %}
-
-
-
- {% include 'inc/custom_fields_panel.html' %}
- {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='virtualization:cluster_list' %}
-
-
- Comments
-
-
- {% if object.comments %}
- {{ object.comments|render_markdown }}
- {% else %}
- None
- {% endif %}
-
-
- {% plugin_left_page object %}
+
+
-
-
-
- Host Devices
-
- {% if perms.virtualization.change_cluster %}
-
- {% endif %}
-
- {% plugin_right_page object %}
-
+ {% include 'inc/custom_fields_panel.html' %}
+
+
+ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='virtualization:cluster_list' %}
+
+
+ Comments
+
+
+ {% if object.comments %}
+ {{ object.comments|render_markdown }}
+ {% else %}
+ None
+ {% endif %}
+
+
+ {% plugin_left_page object %}
+
-
- {% plugin_full_width_page object %}
-
+
+ {% plugin_full_width_page object %}
+
{% endblock %}
diff --git a/netbox/templates/virtualization/cluster/base.html b/netbox/templates/virtualization/cluster/base.html
new file mode 100644
index 000000000..3e90eb17d
--- /dev/null
+++ b/netbox/templates/virtualization/cluster/base.html
@@ -0,0 +1,55 @@
+{% extends 'generic/object.html' %}
+{% load buttons %}
+{% load helpers %}
+{% load custom_links %}
+{% load plugins %}
+
+{% block breadcrumbs %}
+
{{ object.type }}
+ {% if object.group %}
+
{{ object.group }}
+ {% endif %}
+
{{ object }}
+{% endblock %}
+
+{% block buttons %}
+ {% if perms.virtualization.change_cluster and perms.virtualization.add_virtualmachine %}
+
+ Add Virtual Machine
+
+ {% endif %}
+ {% if perms.virtualization.change_cluster %}
+
+ Assign Device
+
+ {% endif %}
+ {{ block.super }}
+{% endblock %}
+
+{% block tabs %}
+
+{% endblock %}
diff --git a/netbox/templates/virtualization/cluster/devices.html b/netbox/templates/virtualization/cluster/devices.html
new file mode 100644
index 000000000..f0de81e39
--- /dev/null
+++ b/netbox/templates/virtualization/cluster/devices.html
@@ -0,0 +1,25 @@
+{% extends 'virtualization/cluster/base.html' %}
+{% load helpers %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/netbox/templates/virtualization/cluster/virtual_machines.html b/netbox/templates/virtualization/cluster/virtual_machines.html
new file mode 100644
index 000000000..5c8902bc7
--- /dev/null
+++ b/netbox/templates/virtualization/cluster/virtual_machines.html
@@ -0,0 +1,15 @@
+{% extends 'virtualization/cluster/base.html' %}
+{% load helpers %}
+
+{% block content %}
+
+
+
+
+ Virtual Machines
+
+ {% include 'responsive_table.html' with table=virtualmachines_table %}
+
+
+
+{% endblock %}
diff --git a/netbox/templates/virtualization/virtualmachine/base.html b/netbox/templates/virtualization/virtualmachine/base.html
index 00212aadd..88f7da1de 100644
--- a/netbox/templates/virtualization/virtualmachine/base.html
+++ b/netbox/templates/virtualization/virtualmachine/base.html
@@ -36,6 +36,11 @@
Config Context
{% endif %}
+ {% if perms.extras.view_journalentry %}
+
+ Journal
+
+ {% endif %}
{% if perms.extras.view_objectchange %}
Change Log
diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py
index 0e1dee9e4..c319f03ca 100644
--- a/netbox/virtualization/tests/test_views.py
+++ b/netbox/virtualization/tests/test_views.py
@@ -127,6 +127,20 @@ class ClusterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'comments': 'New comments',
}
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_cluster_virtualmachines(self):
+ cluster = Cluster.objects.first()
+
+ url = reverse('virtualization:cluster_virtualmachines', kwargs={'pk': cluster.pk})
+ self.assertHttpStatus(self.client.get(url), 200)
+
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_cluster_devices(self):
+ cluster = Cluster.objects.first()
+
+ url = reverse('virtualization:cluster_devices', kwargs={'pk': cluster.pk})
+ self.assertHttpStatus(self.client.get(url), 200)
+
class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = VirtualMachine
@@ -199,7 +213,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
- def test_device_interfaces(self):
+ def test_virtualmachine_interfaces(self):
virtualmachine = VirtualMachine.objects.first()
vminterfaces = (
VMInterface(virtual_machine=virtualmachine, name='Interface 1'),
diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py
index 457b73024..d1f8e76e3 100644
--- a/netbox/virtualization/urls.py
+++ b/netbox/virtualization/urls.py
@@ -37,6 +37,8 @@ urlpatterns = [
path('clusters/edit/', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'),
path('clusters/delete/', views.ClusterBulkDeleteView.as_view(), name='cluster_bulk_delete'),
path('clusters//', views.ClusterView.as_view(), name='cluster'),
+ path('clusters//devices/', views.ClusterDevicesView.as_view(), name='cluster_devices'),
+ path('clusters//virtual-machines/', views.ClusterVirtualMachinesView.as_view(), name='cluster_virtualmachines'),
path('clusters//edit/', views.ClusterEditView.as_view(), name='cluster_edit'),
path('clusters//delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
path('clusters//changelog/', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}),
diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py
index 75f2368fb..4a6ad66b5 100644
--- a/netbox/virtualization/views.py
+++ b/netbox/virtualization/views.py
@@ -155,16 +155,38 @@ class ClusterListView(generic.ObjectListView):
class ClusterView(generic.ObjectView):
queryset = Cluster.objects.all()
+
+class ClusterVirtualMachinesView(generic.ObjectView):
+ queryset = Cluster.objects.all()
+ template_name = 'virtualization/cluster/virtual_machines.html'
+
+ def get_extra_context(self, request, instance):
+ virtualmachines = VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=instance)
+ virtualmachines_table = tables.VirtualMachineTable(virtualmachines, orderable=False)
+ if request.user.has_perm('virtualization.change_cluster'):
+ virtualmachines_table.columns.show('pk')
+
+ return {
+ 'virtualmachines_table': virtualmachines_table,
+ 'active_tab': 'virtual-machines',
+ }
+
+
+class ClusterDevicesView(generic.ObjectView):
+ queryset = Cluster.objects.all()
+ template_name = 'virtualization/cluster/devices.html'
+
def get_extra_context(self, request, instance):
devices = Device.objects.restrict(request.user, 'view').filter(cluster=instance).prefetch_related(
'site', 'rack', 'tenant', 'device_type__manufacturer'
)
- device_table = DeviceTable(list(devices), orderable=False)
+ devices_table = DeviceTable(list(devices), orderable=False)
if request.user.has_perm('virtualization.change_cluster'):
- device_table.columns.show('pk')
+ devices_table.columns.show('pk')
return {
- 'device_table': device_table,
+ 'devices_table': devices_table,
+ 'active_tab': 'devices',
}