Parcourir la source

Added views for VM interfaces

Jeremy Stretch il y a 7 ans
Parent
commit
e81e33af38

+ 65 - 0
netbox/templates/virtualization/inc/vminterface.html

@@ -0,0 +1,65 @@
+<tr class="interface{% if not iface.enabled %} danger{% endif %}">
+    {% if selectable and perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
+        <td class="pk">
+            <input name="pk" type="checkbox" value="{{ iface.pk }}" />
+        </td>
+    {% endif %}
+    <td>
+        <i class="fa fa-fw fa-square"></i> <span>{{ iface.name }}</span>
+        {% if iface.description %}
+            <i class="fa fa-fw fa-comment-o" title="{{ iface.description }}"></i>
+        {% endif %}
+    </td>
+    <td>{{ iface.mtu|default:"" }}</td>
+    <td>{{ iface.mac_address|default:"" }}</td>
+    <td class="text-right">
+        {% if perms.virtualization.change_vminterface %}
+            <a href="{% url 'virtualization:vminterface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
+                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
+            </a>
+        {% endif %}
+        {% if perms.virtualization.delete_vminterface %}
+            <a href="{% url 'virtualization:vminterface_delete' pk=iface.pk %}" class="btn btn-danger btn-xs" title="Delete interface">
+                <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
+            </a>
+        {% endif %}
+    </td>
+</tr>
+{% for ip in iface.ip_addresses.all %}
+    <tr class="ipaddress">
+        {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
+            <td></td>
+        {% endif %}
+        <td colspan="3">
+            <a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
+            {% if ip.description %}
+                <i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
+            {% endif %}
+            {% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
+                <span class="label label-success">Primary</span>
+            {% endif %}
+        </td>
+        <td class="text-right">
+            {% if ip.vrf %}
+                <a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
+            {% else %}
+                <span class="text-muted">Global</span>
+            {% endif %}
+        </td>
+        <td>
+            <span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
+        </td>
+        <td class="text-right">
+            {% if perms.ipam.change_ipaddress %}
+                <a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
+                    <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
+                </a>
+            {% endif %}
+            {% if perms.ipam.delete_ipaddress %}
+                <a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                    <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
+                </a>
+            {% endif %}
+        </td>
+    </tr>
+{% endfor %}

+ 61 - 2
netbox/templates/virtualization/virtualmachine.html

@@ -41,7 +41,7 @@
 <h1>{% block title %}{{ vm }}{% endblock %}</h1>
 {% include 'inc/created_updated.html' with obj=vm %}
 <div class="row">
-	<div class="col-md-7">
+	<div class="col-md-5">
         <div class="panel panel-default">
             <div class="panel-heading">
                 <strong>Virtual Machine</strong>
@@ -132,7 +132,66 @@
             </div>
         </div>
     </div>
-    <div class="col-md-5">
+    <div class="col-md-7">
+        {% if perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
+            <form method="post">
+            {% csrf_token %}
+            <input type="hidden" name="virtual_machine" value="{{ vm.pk }}" />
+        {% endif %}
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                <strong>Interfaces</strong>
+                <div class="pull-right">
+                    <button class="btn btn-default btn-xs toggle-ips" selected="selected">
+                        <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
+                    </button>
+                    {% if perms.virtualization.change_vminterface and interfaces|length > 1 %}
+                        <button class="btn btn-default btn-xs toggle">
+                            <span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
+                        </button>
+                    {% endif %}
+                    {% if perms.virtualization.add_vminterface and interfaces|length > 10 %}
+                        <a href="{% url 'virtualization:vminterface_add' pk=vm.pk %}" class="btn btn-primary btn-xs">
+                            <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
+                        </a>
+                    {% endif %}
+                </div>
+            </div>
+            <table id="interfaces_table" class="table table-hover panel-body component-list">
+                {% for iface in interfaces %}
+                    {% include 'virtualization/inc/vminterface.html' with selectable=True %}
+                {% empty %}
+                    <tr>
+                        <td colspan="4">No interfaces defined</td>
+                    </tr>
+                {% endfor %}
+            </table>
+            {% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
+                <div class="panel-footer">
+                    {% if interfaces and perms.virtualization.change_vminterface %}
+                        <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' pk=vm.pk %}" class="btn btn-warning btn-xs">
+                            <span class="glyphicon glyphicon-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' pk=vm.pk %}" class="btn btn-danger btn-xs">
+                            <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
+                        </button>
+                    {% endif %}
+                    {% if perms.virtualization.add_vminterface %}
+                        <div class="pull-right">
+                            <a href="{% url 'virtualization:vminterface_add' pk=vm.pk %}" class="btn btn-primary btn-xs">
+                                <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
+                            </a>
+                        </div>
+                        <div class="clearfix"></div>
+                    {% endif %}
+                 </div>
+            {% endif %}
+        </div>
+        {% if perms.virtualization.delete_vminterface %}
+            </form>
+        {% endif %}
 	</div>
 </div>
 {% endblock %}

+ 44 - 0
netbox/templates/virtualization/virtualmachine_component_add.html

@@ -0,0 +1,44 @@
+{% extends '_base.html' %}
+{% load helpers %}
+{% load form_helpers %}
+
+{% block title %}Create {{ component_type }} ({{ parent }}){% endblock %}
+
+{% block content %}
+<form action="." method="post" class="form form-horizontal">
+    {% csrf_token %}
+    <div class="row">
+        <div class="col-md-6 col-md-offset-3">
+            {% if form.non_field_errors %}
+                <div class="panel panel-danger">
+                    <div class="panel-heading"><strong>Errors</strong></div>
+                    <div class="panel-body">
+                        {{ form.non_field_errors }}
+                    </div>
+                </div>
+            {% endif %}
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <strong>{{ component_type|bettertitle }}</strong>
+                </div>
+                <div class="panel-body">
+                    <div class="form-group">
+                        <label class="col-md-3 control-label required">Virtual Machine</label>
+                        <div class="col-md-9">
+                            <p class="form-control-static">{{ parent }}</p>
+                        </div>
+                    </div>
+                    {% render_form form %}
+                </div>
+            </div>
+		    <div class="form-group">
+                <div class="col-md-9 col-md-offset-3">
+                    <button type="submit" name="_create" class="btn btn-primary">Create</button>
+                    <button type="submit" name="_addanother" class="btn btn-primary">Create and Add More</button>
+                    <a href="{{ return_url }}" class="btn btn-default">Cancel</a>
+                </div>
+		    </div>
+        </div>
+    </div>
+</form>
+{% endblock %}

+ 47 - 2
netbox/virtualization/forms.py

@@ -3,11 +3,15 @@ from __future__ import unicode_literals
 from django import forms
 from django.db.models import Count
 
+from dcim.formfields import MACAddressFormField
 from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
-from utilities.forms import APISelect, BootstrapMixin, ChainedModelChoiceField, FilterChoiceField, SlugField
-from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
+from utilities.forms import (
+    APISelect, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedModelChoiceField, ComponentForm,
+    ExpandableNameField, FilterChoiceField, SlugField,
+)
+from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
 #
@@ -157,3 +161,44 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
         queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
         label='Cluster'
     )
+
+
+#
+# VM interfaces
+#
+
+class VMInterfaceForm(BootstrapMixin, forms.ModelForm):
+
+    class Meta:
+        model = VMInterface
+        fields = ['virtual_machine', 'name', 'enabled', 'mac_address', 'mtu', 'description']
+        widgets = {
+            'virtual_machine': forms.HiddenInput(),
+        }
+
+
+class VMInterfaceCreateForm(ComponentForm):
+    name_pattern = ExpandableNameField(label='Name')
+    enabled = forms.BooleanField(required=False)
+    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
+    mac_address = MACAddressFormField(required=False, label='MAC Address')
+    description = forms.CharField(max_length=100, required=False)
+
+    def __init__(self, *args, **kwargs):
+
+        # Set interfaces enabled by default
+        kwargs['initial'] = kwargs.get('initial', {}).copy()
+        kwargs['initial'].update({'enabled': True})
+
+        super(VMInterfaceCreateForm, self).__init__(*args, **kwargs)
+
+
+class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(queryset=VMInterface.objects.all(), widget=forms.MultipleHiddenInput)
+    virtual_machine = forms.ModelChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.HiddenInput)
+    enabled = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
+    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
+    description = forms.CharField(max_length=100, required=False)
+
+    class Meta:
+        nullable_fields = ['mtu', 'description']

+ 1 - 0
netbox/virtualization/models.py

@@ -225,6 +225,7 @@ class VMInterface(models.Model):
     class Meta:
         ordering = ['virtual_machine', 'name']
         unique_together = ['virtual_machine', 'name']
+        verbose_name = 'VM interface'
 
     def __str__(self):
         return self.name

+ 11 - 0
netbox/virtualization/tables.py

@@ -83,3 +83,14 @@ class VirtualMachineTable(BaseTable):
     class Meta(BaseTable.Meta):
         model = VirtualMachine
         fields = ('pk', 'name', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')
+
+
+#
+# VM components
+#
+
+class VMInterfaceTable(BaseTable):
+
+    class Meta(BaseTable.Meta):
+        model = VMInterface
+        fields = ('name', 'enabled', 'description')

+ 8 - 0
netbox/virtualization/urls.py

@@ -38,4 +38,12 @@ urlpatterns = [
     url(r'^virtual-machines/(?P<pk>\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
     url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
 
+    # VM interfaces
+    # url(r'^virtual-machines/interfaces/add/$', views.VMBulkAddVMInterfaceView.as_view(), name='vm_bulk_add_vminterface'),
+    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/add/$', views.VMInterfaceCreateView.as_view(), name='vminterface_add'),
+    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/edit/$', views.VMInterfaceBulkEditView.as_view(), name='vminterface_bulk_edit'),
+    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/delete/$', views.VMInterfaceBulkDeleteView.as_view(), name='vminterface_bulk_delete'),
+    url(r'^vm-interfaces/(?P<pk>\d+)/edit/$', views.VMInterfaceEditView.as_view(), name='vminterface_edit'),
+    url(r'^vm-interfaces/(?P<pk>\d+)/delete/$', views.VMInterfaceDeleteView.as_view(), name='vminterface_delete'),
+
 ]

+ 42 - 33
netbox/virtualization/views.py

@@ -155,9 +155,11 @@ class VirtualMachineView(View):
     def get(self, request, pk):
 
         vm = get_object_or_404(VirtualMachine.objects.select_related('tenant__group'), pk=pk)
+        interfaces = VMInterface.objects.filter(virtual_machine=vm)
 
         return render(request, 'virtualization/virtualmachine.html', {
             'vm': vm,
+            'interfaces': interfaces,
         })
 
 
@@ -200,36 +202,43 @@ class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView):
 # VM interfaces
 #
 
-# class VMInterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
-#     permission_required = 'virtualization.add_vminterface'
-#     parent_model = VirtualMachine
-#     parent_field = 'vm'
-#     model = VMInterface
-#     form = forms.VMInterfaceCreateForm
-#     model_form = forms.VMInterfaceForm
-#
-#
-# class VMInterfaceEditView(PermissionRequiredMixin, ComponentEditView):
-#     permission_required = 'virtualization.change_vminterface'
-#     model = VMInterface
-#     form_class = forms.VMInterfaceForm
-#
-#
-# class VMInterfaceDeleteView(PermissionRequiredMixin, ComponentDeleteView):
-#     permission_required = 'virtualization.delete_vminterface'
-#     model = VMInterface
-#
-#
-# class VMInterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
-#     permission_required = 'virtualization.change_vminterface'
-#     cls = VMInterface
-#     parent_cls = VirtualMachine
-#     table = tables.VMInterfaceTable
-#     form = forms.VMInterfaceBulkEditForm
-#
-#
-# class VMInterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
-#     permission_required = 'virtualization.delete_vminterface'
-#     cls = VMInterface
-#     parent_cls = VirtualMachine
-#     table = tables.VMInterfaceTable
+class VMInterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
+    permission_required = 'virtualization.add_vminterface'
+    parent_model = VirtualMachine
+    parent_field = 'virtual_machine'
+    model = VMInterface
+    form = forms.VMInterfaceCreateForm
+    model_form = forms.VMInterfaceForm
+    template_name = 'virtualization/virtualmachine_component_add.html'
+
+
+class VMInterfaceEditView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'virtualization.change_vminterface'
+    model = VMInterface
+    form_class = forms.VMInterfaceForm
+
+    def get_return_url(self, request, obj):
+        return obj.virtual_machine.get_absolute_url()
+
+
+class VMInterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
+    permission_required = 'virtualization.delete_vminterface'
+    model = VMInterface
+
+    def get_return_url(self, request, obj):
+        return obj.virtual_machine.get_absolute_url()
+
+
+class VMInterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'virtualization.change_vminterface'
+    cls = VMInterface
+    parent_cls = VirtualMachine
+    table = tables.VMInterfaceTable
+    form = forms.VMInterfaceBulkEditForm
+
+
+class VMInterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'virtualization.delete_vminterface'
+    cls = VMInterface
+    parent_cls = VirtualMachine
+    table = tables.VMInterfaceTable