Parcourir la source

Added virtualization filters

Jeremy Stretch il y a 7 ans
Parent
commit
9acd792abe

+ 65 - 4
netbox/templates/virtualization/virtualmachine.html

@@ -17,7 +17,7 @@
                 <input type="text" name="q" class="form-control" placeholder="Search virtual machines" />
                 <span class="input-group-btn">
                     <button type="submit" class="btn btn-primary">
-                        <span class="fa fa-search" aria-hidden="true"></span>
+                        <span class="fa fa-search"></span>
                     </button>
                 </span>
             </div>
@@ -27,13 +27,13 @@
 <div class="pull-right">
     {% if perms.virtualization.change_virtualmachine %}
 		<a href="{% url 'virtualization:virtualmachine_edit' pk=vm.pk %}" class="btn btn-warning">
-			<span class="fa fa-pencil" aria-hidden="true"></span>
+			<span class="fa fa-pencil"></span>
 			Edit this VM
 		</a>
     {% endif %}
     {% if perms.virtualization.delete_virtualmachine %}
 		<a href="{% url 'virtualization:virtualmachine_delete' pk=vm.pk %}" class="btn btn-danger">
-			<span class="fa fa-trash" aria-hidden="true"></span>
+			<span class="fa fa-trash"></span>
 			Delete this VM
 		</a>
     {% endif %}
@@ -53,7 +53,68 @@
                 </tr>
                 <tr>
                     <td>Cluster</td>
-                    <td><a href="{{ vm.cluster.get_absolute_url }}">{{ vm.cluster }}</a></td>
+                    <td>
+                        {% if vm.cluster.group %}
+                            <a href="{{ vm.cluster.group.get_absolute_url }}">{{ vm.cluster.group }}</a>
+                            <i class="fa fa-angle-right"></i>
+                        {% endif %}
+                        <a href="{{ vm.cluster.get_absolute_url }}">{{ vm.cluster }}</a>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Cluster Type</td>
+                    <td>{{ vm.cluster.type }}</td>
+                </tr>
+                <tr>
+                    <td>Tenant</td>
+                    <td>
+                        {% if vm.tenant %}
+                            {% if vm.tenant.group %}
+                                <a href="{{ vm.tenant.group.get_absolute_url }}">{{ vm.tenant.group.name }}</a>
+                                <i class="fa fa-angle-right"></i>
+                            {% endif %}
+                            <a href="{{ vm.tenant.get_absolute_url }}">{{ vm.tenant }}</a>
+                        {% else %}
+                            <span class="text-muted">None</span>
+                        {% endif %}
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                <strong>Resources</strong>
+            </div>
+            <table class="table table-hover panel-body attr-table">
+                <tr>
+                    <td>Virtual CPUs</td>
+                    <td>
+                        {% if vm.vcpus %}
+                            {{ vm.vcpus }}
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
+                    <td>Memory</td>
+                    <td>
+                        {% if vm.memory %}
+                            {{ vm.memory }} MB
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
+                    <td>Disk Space</td>
+                    <td>
+                        {% if vm.disk %}
+                            {{ vm.disk }} GB
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
                 </tr>
             </table>
         </div>

+ 6 - 2
netbox/templates/virtualization/virtualmachine_edit.html

@@ -6,13 +6,17 @@
         <div class="panel-heading"><strong>Virtual Machine</strong></div>
         <div class="panel-body">
             {% render_field form.name %}
+            {% render_field form.cluster_group %}
+            {% render_field form.cluster %}
             {% render_field form.platform %}
         </div>
     </div>
     <div class="panel panel-default">
-        <div class="panel-heading"><strong>Cluster</strong></div>
+        <div class="panel-heading"><strong>Resources</strong></div>
         <div class="panel-body">
-            {% render_field form.cluster %}
+            {% render_field form.vcpus %}
+            {% render_field form.memory %}
+            {% render_field form.disk %}
         </div>
     </div>
     <div class="panel panel-default">

+ 30 - 1
netbox/templates/virtualization/virtualmachine_list.html

@@ -12,7 +12,7 @@
             Import virtual machines
         </a>
     {% endif %}
-    {% include 'inc/export_button.html' with obj_type='virtualmachines' %}
+    {% include 'inc/export_button.html' with obj_type='virtual machines' %}
 </div>
 <h1>{% block title %}Virtual Machines{% endblock %}</h1>
 <div class="row">
@@ -24,3 +24,32 @@
     </div>
 </div>
 {% endblock %}
+
+{% block javascript %}
+<script type="text/javascript">
+$(document).ready(function() {
+
+    var cluster_group_list = $('#id_cluster_group');
+    var cluster_list = $('#id_cluster_id');
+
+    // Update cluster options based on selected group
+    cluster_group_list.change(function() {
+        var selected_groups = $(this).val();
+        if (selected_groups) {
+            cluster_list.empty();
+            $.ajax({
+                url: netbox_api_path + 'virtualization/clusters/?limit=500&group=' + selected_groups.join('&group='),
+                dataType: 'json',
+                success: function (response, status) {
+                    $.each(response["results"], function (index, cluster) {
+                        var option = $("<option></option>").attr("value", cluster.id).text(cluster.name);
+                        cluster_list.append(option);
+                    });
+                }
+            });
+        }
+    });
+
+});
+</script>
+{% endblock %}

+ 3 - 0
netbox/virtualization/api/views.py

@@ -4,6 +4,7 @@ from rest_framework.viewsets import ModelViewSet
 
 from extras.api.views import CustomFieldModelViewSet
 from utilities.api import WritableSerializerMixin
+from virtualization import filters
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from . import serializers
 
@@ -26,6 +27,7 @@ class ClusterViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Cluster.objects.select_related('type', 'group')
     serializer_class = serializers.ClusterSerializer
     write_serializer_class = serializers.WritableClusterSerializer
+    filter_class = filters.ClusterFilter
 
 
 #
@@ -36,6 +38,7 @@ class VirtualMachineViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = VirtualMachine.objects.all()
     serializer_class = serializers.VirtualMachineSerializer
     write_serializer_class = serializers.WritableVirtualMachineSerializer
+    filter_class = filters.VirtualMachineFilter
 
 
 class VMInterfaceViewSet(WritableSerializerMixin, ModelViewSet):

+ 99 - 0
netbox/virtualization/filters.py

@@ -0,0 +1,99 @@
+from __future__ import unicode_literals
+
+import django_filters
+from django.db.models import Q
+
+from dcim.models import Platform
+from extras.filters import CustomFieldFilterSet
+from tenancy.models import Tenant
+from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
+from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
+
+
+class ClusterFilter(CustomFieldFilterSet):
+    id__in = NumericInFilter(name='id', lookup_expr='in')
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+    group_id = NullableModelMultipleChoiceFilter(
+        queryset=ClusterGroup.objects.all(),
+        label='Parent group (ID)',
+    )
+    group = NullableModelMultipleChoiceFilter(
+        queryset=ClusterGroup.objects.all(),
+        to_field_name='slug',
+        label='Parent group (slug)',
+    )
+    type_id = django_filters.ModelMultipleChoiceFilter(
+        queryset=ClusterType.objects.all(),
+        label='Cluster type (ID)',
+    )
+    type = django_filters.ModelMultipleChoiceFilter(
+        name='type__slug',
+        queryset=ClusterType.objects.all(),
+        to_field_name='slug',
+        label='Cluster type (slug)',
+    )
+
+    class Meta:
+        model = Cluster
+        fields = ['name']
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(name__icontains=value) |
+            Q(comments__icontains=value)
+        )
+
+
+class VirtualMachineFilter(CustomFieldFilterSet):
+    id__in = NumericInFilter(name='id', lookup_expr='in')
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+    cluster_group_id = NullableModelMultipleChoiceFilter(
+        name='cluster__group',
+        queryset=ClusterGroup.objects.all(),
+        label='Cluster group (ID)',
+    )
+    cluster_group = NullableModelMultipleChoiceFilter(
+        name='cluster__group__slug',
+        queryset=ClusterGroup.objects.all(),
+        to_field_name='slug',
+        label='Cluster group (slug)',
+    )
+    tenant_id = NullableModelMultipleChoiceFilter(
+        queryset=Tenant.objects.all(),
+        label='Tenant (ID)',
+    )
+    tenant = NullableModelMultipleChoiceFilter(
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        label='Tenant (slug)',
+    )
+    platform_id = NullableModelMultipleChoiceFilter(
+        queryset=Platform.objects.all(),
+        label='Platform (ID)',
+    )
+    platform = NullableModelMultipleChoiceFilter(
+        name='platform',
+        queryset=Platform.objects.all(),
+        to_field_name='slug',
+        label='Platform (slug)',
+    )
+
+    class Meta:
+        model = VirtualMachine
+        fields = ['name', 'cluster']
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(name__icontains=value) |
+            Q(comments__icontains=value)
+        )

+ 62 - 4
netbox/virtualization/forms.py

@@ -1,11 +1,12 @@
 from __future__ import unicode_literals
 
 from django import forms
+from django.db.models import Count
 
-from extras.forms import CustomFieldBulkEditForm, CustomFieldForm
+from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
-from utilities.forms import BootstrapMixin, SlugField
+from utilities.forms import APISelect, BootstrapMixin, ChainedModelChoiceField, FilterChoiceField, SlugField
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
@@ -67,15 +68,58 @@ class ClusterCSVForm(forms.ModelForm):
         fields = ['name', 'type', 'group']
 
 
+class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
+    model = Cluster
+    q = forms.CharField(required=False, label='Search')
+    group = FilterChoiceField(
+        queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),
+        to_field_name='slug',
+        null_option=(0, 'None'),
+        required=False,
+    )
+    type = FilterChoiceField(
+        queryset=ClusterType.objects.annotate(filter_count=Count('clusters')),
+        to_field_name='slug',
+        required=False,
+    )
+
+
 #
 # Virtual Machines
 #
 
 class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
+    cluster_group = forms.ModelChoiceField(
+        queryset=ClusterGroup.objects.all(),
+        required=False,
+        widget=forms.Select(
+            attrs={'filter-for': 'cluster', 'nullable': 'true'}
+        )
+    )
+    cluster = ChainedModelChoiceField(
+        queryset=Cluster.objects.all(),
+        chains=(
+            ('group', 'cluster_group'),
+        ),
+        widget=APISelect(
+            api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'
+        )
+    )
 
     class Meta:
         model = VirtualMachine
-        fields = ['name', 'cluster', 'tenant', 'platform', 'comments']
+        fields = ['name', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
+
+    def __init__(self, *args, **kwargs):
+
+        # Initialize helper selector
+        instance = kwargs.get('instance')
+        if instance.pk and instance.cluster is not None:
+            initial = kwargs.get('initial', {}).copy()
+            initial['cluster_group'] = instance.cluster.group
+            kwargs['initial'] = initial
+
+        super(VirtualMachineForm, self).__init__(*args, **kwargs)
 
 
 class VirtualMachineCSVForm(forms.ModelForm):
@@ -89,7 +133,7 @@ class VirtualMachineCSVForm(forms.ModelForm):
     )
 
     class Meta:
-        fields = ['cluster', 'name', 'tenant']
+        fields = ['cluster', 'name', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
 
 
 class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
@@ -99,3 +143,17 @@ class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
 
     class Meta:
         nullable_fields = ['tenant']
+
+
+class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
+    model = VirtualMachine
+    q = forms.CharField(required=False, label='Search')
+    cluster_group = FilterChoiceField(
+        queryset=ClusterGroup.objects.all(),
+        to_field_name='slug',
+        null_option=(0, 'None'),
+    )
+    cluster_id = FilterChoiceField(
+        queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
+        label='Cluster'
+    )

+ 5 - 5
netbox/virtualization/migrations/0001_initial.py

@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.4 on 2017-08-16 19:22
+# Generated by Django 1.11.4 on 2017-08-16 19:27
 from __future__ import unicode_literals
 
 import dcim.fields
@@ -13,9 +13,9 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        ('dcim', '0041_napalm_integration'),
         ('ipam', '0018_remove_service_uniqueness_constraint'),
         ('tenancy', '0003_unicode_literals'),
+        ('dcim', '0041_napalm_integration'),
     ]
 
     operations = [
@@ -63,9 +63,9 @@ class Migration(migrations.Migration):
                 ('created', models.DateField(auto_now_add=True)),
                 ('last_updated', models.DateTimeField(auto_now=True)),
                 ('name', models.CharField(max_length=64, unique=True)),
-                ('vcpus', models.PositiveSmallIntegerField(blank=True, verbose_name='vCPUs')),
-                ('memory', models.PositiveIntegerField(blank=True, verbose_name='Memory (MB)')),
-                ('disk', models.PositiveIntegerField(blank=True, verbose_name='Disk (GB)')),
+                ('vcpus', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='vCPUs')),
+                ('memory', models.PositiveIntegerField(blank=True, null=True, verbose_name='Memory (MB)')),
+                ('disk', models.PositiveIntegerField(blank=True, null=True, verbose_name='Disk (GB)')),
                 ('comments', models.TextField(blank=True)),
                 ('cluster', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='virtual_machines', to='virtualization.Cluster')),
                 ('platform', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='virtual_machines', to='dcim.Platform')),

+ 3 - 0
netbox/virtualization/models.py

@@ -161,14 +161,17 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
     )
     vcpus = models.PositiveSmallIntegerField(
         blank=True,
+        null=True,
         verbose_name='vCPUs'
     )
     memory = models.PositiveIntegerField(
         blank=True,
+        null=True,
         verbose_name='Memory (MB)'
     )
     disk = models.PositiveIntegerField(
         blank=True,
+        null=True,
         verbose_name='Disk (GB)'
     )
     comments = models.TextField(

+ 2 - 1
netbox/virtualization/tables.py

@@ -77,8 +77,9 @@ class ClusterTable(BaseTable):
 class VirtualMachineTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn()
+    cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
     tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
 
     class Meta(BaseTable.Meta):
         model = VirtualMachine
-        fields = ('pk', 'name', 'tenant')
+        fields = ('pk', 'name', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')

+ 6 - 3
netbox/virtualization/views.py

@@ -11,6 +11,7 @@ from utilities.views import (
     ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
+from . import filters
 from . import forms
 from . import tables
 
@@ -84,6 +85,8 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 class ClusterListView(ObjectListView):
     queryset = Cluster.objects.annotate(vm_count=Count('virtual_machines'))
     table = tables.ClusterTable
+    filter = filters.ClusterFilter
+    filter_form = forms.ClusterFilterForm
     template_name = 'virtualization/cluster_list.html'
 
 
@@ -138,8 +141,8 @@ class ClusterBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 
 class VirtualMachineListView(ObjectListView):
     queryset = VirtualMachine.objects.select_related('tenant')
-    # filter = filters.VirtualMachineFilter
-    # filter_form = forms.VirtualMachineFilterForm
+    filter = filters.VirtualMachineFilter
+    filter_form = forms.VirtualMachineFilterForm
     table = tables.VirtualMachineTable
     template_name = 'virtualization/virtualmachine_list.html'
 
@@ -184,7 +187,7 @@ class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView):
     permission_required = 'virtualization.change_virtualmachine'
     cls = VirtualMachine
     queryset = VirtualMachine.objects.select_related('tenant')
-    # filter = filters.VirtualMachineFilter
+    filter = filters.VirtualMachineFilter
     table = tables.VirtualMachineTable
     form = forms.VirtualMachineBulkEditForm
     default_return_url = 'virtualization:virtualmachine_list'