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

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.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']

View File

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

View File

@ -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')

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+)/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):
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