Added views for VM interfaces

This commit is contained in:
Jeremy Stretch 2017-08-18 14:37:19 -04:00
parent a7c56eab86
commit e81e33af38
8 changed files with 279 additions and 37 deletions

View File

@ -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 %}

View File

@ -41,7 +41,7 @@
<h1>{% block title %}{{ vm }}{% endblock %}</h1> <h1>{% block title %}{{ vm }}{% endblock %}</h1>
{% include 'inc/created_updated.html' with obj=vm %} {% include 'inc/created_updated.html' with obj=vm %}
<div class="row"> <div class="row">
<div class="col-md-7"> <div class="col-md-5">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Virtual Machine</strong> <strong>Virtual Machine</strong>
@ -132,7 +132,66 @@
</div> </div>
</div> </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>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -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 %}

View File

@ -3,11 +3,15 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.db.models import Count from django.db.models import Count
from dcim.formfields import MACAddressFormField
from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import APISelect, BootstrapMixin, ChainedModelChoiceField, FilterChoiceField, SlugField from utilities.forms import (
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine 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')), queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
label='Cluster' 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']

View File

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

View File

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

View File

@ -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+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'), 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'),
] ]

View File

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