diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 0ff5e78f4..a702127f4 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -151,8 +151,25 @@ {% endif %} +
+
+ Virtual Machines +
+ {% include 'responsive_table.html' with table=virtual_machine_table %} + {% if perms.virtualization.change_cluster %} + + {% endif %} +
{% plugin_right_page cluster %} - +
diff --git a/netbox/templates/virtualization/cluster_add_virtualmachines.html b/netbox/templates/virtualization/cluster_add_virtualmachines.html new file mode 100644 index 000000000..48339ebdd --- /dev/null +++ b/netbox/templates/virtualization/cluster_add_virtualmachines.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} +{% load static %} +{% load form_helpers %} + +{% block content %} +
+ {% csrf_token %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
+
+

{% block title %}Add Virtual Machines to Cluster {{ cluster }}{% endblock %}

+ {% if form.non_field_errors %} +
+
Errors
+
+ {{ form.non_field_errors }} +
+
+ {% endif %} +
+
Virtual Machine Selection
+
+ {% render_form form %} +
+
+
+
+
+
+ + Cancel +
+
+
+{% endblock %} diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index ce4ca3e3c..64ec8a919 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -276,6 +276,28 @@ class ClusterRemoveDevicesForm(ConfirmationForm): ) +class ClusterAddVirtualMachinesForm(BootstrapMixin, forms.Form): + virtualmachines = DynamicModelMultipleChoiceField( + queryset=VirtualMachine.objects.all(), + ) + + class Meta: + fields = [ + 'virtualmachines', + ] + + def __init__(self, cluster, *args, **kwargs): + + self.cluster = cluster + + super().__init__(*args, **kwargs) + + self.fields['virtualmachines'].choices = [] + + def clean(self): + super().clean() + + # # Virtual Machines # diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 3d6f07566..451bca41e 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -38,6 +38,7 @@ urlpatterns = [ path('clusters//changelog/', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}), path('clusters//devices/add/', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'), path('clusters//devices/remove/', views.ClusterRemoveDevicesView.as_view(), name='cluster_remove_devices'), + path('clusters//virtual-machines/add/', views.ClusterAddVirtualMachinesView.as_view(), name='cluster_add_virtualmachines'), # Virtual machines path('virtual-machines/', views.VirtualMachineListView.as_view(), name='virtualmachine_list'), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 2b7ae3a13..267eb076d 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -14,6 +14,7 @@ from utilities.views import ( BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, BulkRenameView, ComponentCreateView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView, ) +from virtualization.tables import VirtualMachineTable from . import filters, forms, tables from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface @@ -103,11 +104,18 @@ class ClusterView(ObjectView): queryset = Cluster.objects.all() def get(self, request, pk): + virtual_machines = VirtualMachine.objects.restrict(request.user) + self.queryset = self.queryset.prefetch_related( - Prefetch('virtual_machines', queryset=VirtualMachine.objects.restrict(request.user)) + Prefetch('virtual_machines', queryset=virtual_machines) ) cluster = get_object_or_404(self.queryset, pk=pk) + + virtual_machine_table = VirtualMachineTable(list(virtual_machines.filter(cluster=cluster)), orderable=False) + if request.user.has_perm('virtualization.change_cluster'): + virtual_machine_table.columns.show('pk') + devices = Device.objects.restrict(request.user, 'view').filter(cluster=cluster).prefetch_related( 'site', 'rack', 'tenant', 'device_type__manufacturer' ) @@ -118,6 +126,7 @@ class ClusterView(ObjectView): return render(request, 'virtualization/cluster.html', { 'cluster': cluster, 'device_table': device_table, + 'virtual_machine_table': virtual_machine_table, }) @@ -232,6 +241,47 @@ class ClusterRemoveDevicesView(ObjectEditView): }) +class ClusterAddVirtualMachinesView(ObjectEditView): + queryset = Cluster.objects.all() + form = forms.ClusterAddVirtualMachinesForm + template_name = 'virtualization/cluster_add_virtualmachines.html' + + def get(self, request, pk): + cluster = get_object_or_404(self.queryset, pk=pk) + form = self.form(cluster, initial=request.GET) + + return render(request, self.template_name, { + 'cluster': cluster, + 'form': form, + 'return_url': reverse('virtualization:cluster', kwargs={'pk': pk}), + }) + + def post(self, request, pk): + cluster = get_object_or_404(self.queryset, pk=pk) + form = self.form(cluster, request.POST) + + if form.is_valid(): + + virtualmachine_pks = form.cleaned_data['virtualmachines'] + with transaction.atomic(): + + # Assign the selected VirtualMachines to the Cluster + for virtualmachine in VirtualMachine.objects.filter(pk__in=virtualmachine_pks): + virtualmachine.cluster = cluster + virtualmachine.save() + + messages.success(request, "Added {} virtual machines to cluster {}".format( + len(virtualmachine_pks), cluster + )) + return redirect(cluster.get_absolute_url()) + + return render(request, self.template_name, { + 'cluster': cluster, + 'form': form, + 'return_url': cluster.get_absolute_url(), + }) + + # # Virtual machines #