Просмотр исходного кода

Closes #5425: Create separate tabs for VMs and devices under the cluster view

Jeremy Stretch 4 лет назад
Родитель
Сommit
b070be1c41

+ 1 - 0
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

+ 70 - 106
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 %}
-  <li><a href="{{ object.type.get_absolute_url }}">{{ object.type }}</a></li>
-  {% if object.group %}
-    <li><a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a></li>
-  {% endif %}
-  <li>{{ object }}</li>
-{% endblock %}
-
 {% block content %}
 <div class="row">
-	<div class="col-md-5">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <strong>Cluster</strong>
-            </div>
-            <table class="table table-hover panel-body attr-table">
-                <tr>
-                    <td>Name</td>
-                    <td>{{ object.name }}</td>
-                </tr>
-                <tr>
-                    <td>Type</td>
-                    <td><a href="{{ object.type.get_absolute_url }}">{{ object.type }}</a></td>
-                </tr>
-                <tr>
-                    <td>Group</td>
-                    <td>
-                        {% if object.group %}
-                            <a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <td>Tenant</td>
-                    <td>
-                        {% if object.tenant %}
-                            <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <td>Site</td>
-                    <td>
-                        {% if object.site %}
-                            <a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <td>Virtual Machines</td>
-                    <td><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ object.pk }}">{{ object.virtual_machines.count }}</a></td>
-                </tr>
-            </table>
-        </div>
-        {% include 'inc/custom_fields_panel.html' %}
-        {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='virtualization:cluster_list' %}
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <strong>Comments</strong>
-            </div>
-            <div class="panel-body rendered-markdown">
-                {% if object.comments %}
-                    {{ object.comments|render_markdown }}
-                {% else %}
-                    <span class="text-muted">None</span>
-                {% endif %}
-            </div>
-        </div>
-        {% plugin_left_page object %}
-    </div>
-    <div class="col-md-7">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <strong>Host Devices</strong>
-            </div>
-            {% if perms.virtualization.change_cluster %}
-                <form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
-                {% csrf_token %}
+	<div class="col-md-6">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Cluster</strong>
+      </div>
+      <table class="table table-hover panel-body attr-table">
+        <tr>
+          <td>Name</td>
+          <td>{{ object.name }}</td>
+        </tr>
+        <tr>
+          <td>Type</td>
+          <td><a href="{{ object.type.get_absolute_url }}">{{ object.type }}</a></td>
+        </tr>
+        <tr>
+          <td>Group</td>
+          <td>
+            {% if object.group %}
+              <a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
+            {% else %}
+              <span class="text-muted">None</span>
+            {% endif %}
+          </td>
+        </tr>
+        <tr>
+          <td>Tenant</td>
+          <td>
+            {% if object.tenant %}
+              <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
+            {% else %}
+              <span class="text-muted">None</span>
             {% endif %}
-            {% include 'responsive_table.html' with table=device_table %}
-            {% if perms.virtualization.change_cluster %}
-                <div class="panel-footer noprint">
-                    <div class="pull-right">
-                        <a href="{% url 'virtualization:cluster_add_devices' pk=object.pk %}?site={{ object.site.pk }}" class="btn btn-primary btn-xs">
-                            <span class="mdi mdi-plus-thick" aria-hidden="true"></span>
-                            Add devices
-                        </a>
-                    </div>
-                    <button type="submit" name="_remove" class="btn btn-danger primary btn-xs">
-                        <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span>
-                        Remove devices
-                    </button>
-                </div>
-                </form>
+          </td>
+        </tr>
+        <tr>
+          <td>Site</td>
+          <td>
+            {% if object.site %}
+              <a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a>
+            {% else %}
+              <span class="text-muted">None</span>
             {% endif %}
-        </div>
-        {% plugin_right_page object %}
-	</div>
+          </td>
+        </tr>
+        <tr>
+          <td>Virtual Machines</td>
+          <td><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ object.pk }}">{{ object.virtual_machines.count }}</a></td>
+        </tr>
+      </table>
+    </div>
+    {% include 'inc/custom_fields_panel.html' %}
+  </div>
+  <div class="col-md-6">
+    {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='virtualization:cluster_list' %}
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Comments</strong>
+      </div>
+      <div class="panel-body rendered-markdown">
+        {% if object.comments %}
+          {{ object.comments|render_markdown }}
+        {% else %}
+          <span class="text-muted">None</span>
+        {% endif %}
+      </div>
+    </div>
+    {% plugin_left_page object %}
+  </div>
 </div>
 <div class="row">
-    <div class="col-md-12">
-        {% plugin_full_width_page object %}
-    </div>
+  <div class="col-md-12">
+    {% plugin_full_width_page object %}
+  </div>
 </div>
 {% endblock %}

+ 55 - 0
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 %}
+  <li><a href="{{ object.type.get_absolute_url }}">{{ object.type }}</a></li>
+  {% if object.group %}
+    <li><a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a></li>
+  {% endif %}
+  <li>{{ object }}</li>
+{% endblock %}
+
+{% block buttons %}
+  {% if perms.virtualization.change_cluster and perms.virtualization.add_virtualmachine %}
+    <a href="{% url 'virtualization:virtualmachine_add' %}?cluster={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
+      <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Virtual Machine
+    </a>
+  {% endif %}
+  {% if perms.virtualization.change_cluster %}
+    <a href="{% url 'virtualization:cluster_add_devices' pk=object.pk %}?site={{ object.site.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
+      <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Assign Device
+    </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 }}">Cluster</a>
+    </li>
+    {% with virtualmachine_count=object.virtual_machines.count %}
+      <li role="presentation" {% if active_tab == 'virtual-machines' %} class="active"{% endif %}>
+        <a href="{% url 'virtualization:cluster_virtualmachines' pk=object.pk %}">Virtual Machines {% badge virtualmachine_count %}</a>
+      </li>
+    {% endwith %}
+    {% with device_count=object.devices.count %}
+      <li role="presentation" {% if active_tab == 'devices' %} class="active"{% endif %}>
+        <a href="{% url 'virtualization:cluster_devices' pk=object.pk %}">Devices {% badge device_count %}</a>
+      </li>
+    {% endwith %}
+    {% if perms.extras.view_journalentry %}
+      <li role="presentation"{% if active_tab == 'journal' %} class="active"{% endif %}>
+        <a href="{% url 'virtualization:cluster_journal' pk=object.pk %}">Journal</a>
+      </li>
+    {% endif %}
+    {% if perms.extras.view_objectchange %}
+      <li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
+        <a href="{% url 'virtualization:cluster_changelog' pk=object.pk %}">Change Log</a>
+      </li>
+    {% endif %}
+  </ul>
+{% endblock %}

+ 25 - 0
netbox/templates/virtualization/cluster/devices.html

@@ -0,0 +1,25 @@
+{% extends 'virtualization/cluster/base.html' %}
+{% load helpers %}
+
+{% block content %}
+<div class="row">
+  <div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Host Devices</strong>
+      </div>
+      <form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
+      {% csrf_token %}
+      {% include 'responsive_table.html' with table=devices_table %}
+        {% if perms.virtualization.change_cluster %}
+          <div class="panel-footer noprint">
+            <button type="submit" name="_remove" class="btn btn-danger primary btn-xs">
+              <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Remove devices
+            </button>
+          </div>
+        {% endif %}
+      </form>
+    </div>
+  </div>
+</div>
+{% endblock %}

+ 15 - 0
netbox/templates/virtualization/cluster/virtual_machines.html

@@ -0,0 +1,15 @@
+{% extends 'virtualization/cluster/base.html' %}
+{% load helpers %}
+
+{% block content %}
+<div class="row">
+  <div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Virtual Machines</strong>
+      </div>
+      {% include 'responsive_table.html' with table=virtualmachines_table %}
+    </div>
+  </div>
+</div>
+{% endblock %}

+ 5 - 0
netbox/templates/virtualization/virtualmachine/base.html

@@ -36,6 +36,11 @@
         <a href="{% url 'virtualization:virtualmachine_configcontext' pk=object.pk %}">Config Context</a>
       </li>
     {% endif %}
+    {% if perms.extras.view_journalentry %}
+      <li role="presentation"{% if active_tab == 'journal' %} class="active"{% endif %}>
+        <a href="{% url 'virtualization:virtualmachine_journal' pk=object.pk %}">Journal</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>

+ 15 - 1
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'),

+ 2 - 0
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/<int:pk>/', views.ClusterView.as_view(), name='cluster'),
+    path('clusters/<int:pk>/devices/', views.ClusterDevicesView.as_view(), name='cluster_devices'),
+    path('clusters/<int:pk>/virtual-machines/', views.ClusterVirtualMachinesView.as_view(), name='cluster_virtualmachines'),
     path('clusters/<int:pk>/edit/', views.ClusterEditView.as_view(), name='cluster_edit'),
     path('clusters/<int:pk>/delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
     path('clusters/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}),

+ 25 - 3
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',
         }