Commit 8fd80cf1 by Oláh István Gergely

dashboard: add node create form

parent e029a56b
from datetime import timedelta
from django import forms
from vm.models import InstanceTemplate, Lease, InterfaceTemplate
from vm.models import InstanceTemplate, Lease, InterfaceTemplate, Node
from storage.models import Disk
from firewall.models import Vlan
# from django.core.urlresolvers import reverse_lazy
from firewall.models import Vlan, Host
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (Layout, Div, BaseInput,
Field, HTML, Submit, Fieldset)
......@@ -13,8 +11,12 @@ from crispy_forms.utils import render_field
from django.template import Context
from django.template.loader import render_to_string
from django.forms.widgets import TextInput
from django.forms import ModelForm
from crispy_forms.bootstrap import FormActions
from django.forms.models import BaseInlineFormSet
from django.utils.translation import ugettext as _
# from crispy_forms.bootstrap import FormActions
VLANS = Vlan.objects.all()
......@@ -292,6 +294,169 @@ class VmCreateForm(forms.Form):
)
class HostForm(forms.ModelForm):
def setowner(self, user):
self.instance.owner = user
def __init__(self, *args, **kwargs):
super(HostForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.form_tag = False
self.helper.layout = Layout(
Div(
Div( # host
Div(
AnyTag(
'h3',
HTML(_("Host")),
),
css_class="col-sm-3",
),
css_class="row",
),
Div( # host data
Div( # hostname
HTML('<label for="node-hostname-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div( # hostname
'hostname',
css_class="col-sm-9",
),
Div( # mac
HTML('<label for="node-mac-box">'
'MAC'
'</label>'),
css_class="col-sm-3",
),
Div(
'mac',
css_class="col-sm-9",
),
Div( # ip
HTML('<label for="node-ip-box">'
'IP'
'</label>'),
css_class="col-sm-3",
),
Div(
'ipv4',
css_class="col-sm-9",
),
Div( # vlan
HTML('<label for="node-vlan-box">'
'VLAN'
'</label>'),
css_class="col-sm-3",
),
Div(
'vlan',
css_class="col-sm-9",
),
css_class="row",
),
),
)
class Meta:
model = Host
fields = ['hostname', 'vlan', 'mac', 'ipv4', ]
class NodeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(NodeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
Div(
Div(
AnyTag(
'h3',
HTML(_("Node")),
),
css_class="col-sm-3",
),
css_class="row",
),
Div(
Div( # nodename
HTML('<label for="node-nodename-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div(
'name',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div( # priority
HTML('<label for="node-nodename-box">'
'Priority'
'</label>'),
css_class="col-sm-3",
),
Div(
'priority',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div( # enabled
HTML('<label for="node-nodename-box">'
'Enabled'
'</label>'),
css_class="col-sm-3",
),
Div(
'enabled',
css_class="col-sm-9",
),
css_class="row",
),
Div( # nested host
HTML("""{% load crispy_forms_tags %}
{% crispy hostform %}
""")
),
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="icon-play"
),
HTML("Start"),
css_id="node-create-submit",
css_class="btn btn-success",
),
css_class="col-sm-12 text-right",
),
css_class="row",
),
css_class="col-sm-11",
),
css_class="row",
),
)
class Meta:
model = Node
fields = ['name', 'priority', 'enabled', ]
class TemplateForm(forms.ModelForm):
managed_networks = forms.ModelMultipleChoiceField(
queryset=VLANS, required=False)
......
......@@ -7,9 +7,9 @@ $(function () {
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#vm-create-modal').modal('show');
$('#vm-create-modal').on('hidden.bs.modal', function() {
$('#vm-create-modal').remove();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
}
});
......@@ -24,9 +24,9 @@ $(function () {
$('body').append(data);
nodeCreateLoaded();
addSliderMiscs();
$('#node-create-modal').modal('show');
$('#node-create-modal').on('hidden.bs.modal', function() {
$('#node-create-modal').remove();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
}
});
......
var vlans = [];
var disks = [];
$(function() {
nodeCreateLoaded();
});
function nodeCreateLoaded() {
$('.node-create-advanced').hide();
$('.node-create-advanced-btn').click(function() {
$('.vm-create-advanced').stop().slideToggle();
if ($('.node-create-advanced-icon').hasClass('icon-caret-down')) {
$('.node-create-advanced-icon').removeClass('icon-caret-down').addClass('icon-caret-up');
} else {
$('.node-create-advanced-icon').removeClass('icon-caret-up').addClass('icon-caret-down');
}
});
$('#node-create-template-select').change(function() {
nodeCreateTemplateChange(this);
});
/* network thingies */
/* add network */
$('#node-create-network-add-button').click(function() {
var vlan_pk = $('#node-create-network-add-select :selected').val();
var managed = $('#node-create-network-add-checkbox-managed').prop('checked');
var name = $('#node-create-network-add-select :selected').text();
if ($('#node-create-network-list').children('span').length < 1) {
$('#node-create-network-list').html('');
}
$('#node-create-network-list').append(
nodeCreateNetworkLabel(vlan_pk, name, managed)
);
/* select the network from the managed/unmanaged multiple select */
if(managed) {
$('#node-create-network-add-managed option[value="' + vlan_pk + '"]').prop('selected', true);
} else {
$('#node-create-network-add-unmanaged option[value="' + vlan_pk + '"]').prop('selected', true);
}
$('option:selected', $('#node-create-network-add-select')).remove();
/* add dummy text if no more networks are available */
if($('#node-create-network-add-select option').length < 1) {
$('#node-create-network-add-button').attr('disabled', true);
$('#node-create-network-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
}
return false;
});
/* remove network */
// event for network remove button (icon, X)
$('body').on('click', '.node-create-remove-network', function() {
var vlan_pk = ($(this).parent('span').prop('id')).replace('vlan-', '')
// if it's "blue" then it's managed, kinda not cool
var managed = $(this).parent('span').hasClass('label-primary');
$(this).parent('span').fadeOut(500, function() {
/* if ther are no more vlans disabled the add button */
if($('#node-create-network-add-select option')[0].value == -1) {
$('#node-create-network-add-button').attr('disabled', false);
$('#node-create-network-add-select').html('');
}
/* remove the network label */
$(this).remove();
var vlan_name = $(this).text();
$('#node-create-network-add-select').append($('<option>', {
value: vlan_pk,
text: vlan_name
}));
/* remove the selection from the multiple select */
$('#node-create-network-add-' + (managed ? '' : 'un') + 'managed option[value="' + vlan_pk + '"]').prop('selected', false);
if ($('#node-create-network-list').children('span').length < 1) {
$('#node-create-network-list').append('Not added to any network!');
}
});
return false;
});
/* copy networks from hidden select */
$('#node-create-network-add-select').html($('#node-create-network-add-managed').html());
/* build up network list */
$('#node-create-network-add-select option').each(function() {
vlans.push({
'name': $(this).text(),
'pk': parseInt($(this).val())
});
});
/* ----- end of networks thingies ----- */
/* add disk */
$('#node-create-disk-add-button').click(function() {
var disk_pk = $('#node-create-disk-add-select :selected').val();
var name = $('#node-create-disk-add-select :selected').text();
if ($('#node-create-disk-list').children('span').length < 1) {
$('#node-create-disk-list').html('');
}
$('#node-create-disk-list').append(
nodeCreateDiskLabel(disk_pk, name)
);
/* select the disk from the multiple select */
$('#node-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', true);
$('option:selected', $('#node-create-disk-add-select')).remove();
/* add dummy text if no more disks are available */
if($('#node-create-disk-add-select option').length < 1) {
$('#node-create-disk-add-button').attr('disabled', true);
$('#node-create-disk-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
}
return false;
});
/* remove disk */
// event for disk remove button (icon, X)
$('body').on('click', '.node-create-remove-disk', function() {
var disk_pk = ($(this).parent('span').prop('id')).replace('vlan-', '')
$(this).parent('span').fadeOut(500, function() {
/* if ther are no more disks disabled the add button */
if($('#node-create-disk-add-select option')[0].value == -1) {
$('#node-create-disk-add-button').attr('disabled', false);
$('#node-create-disk-add-select').html('');
}
/* remove the disk label */
$(this).remove();
var disk_name = $(this).text();
$('#node-create-disk-add-select').append($('<option>', {
value: disk_pk,
text: disk_name
}));
/* remove the selection from the multiple select */
$('#node-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', false);
if ($('#node-create-disk-list').children('span').length < 1) {
$('#node-create-disk-list').append('No disks are added!');
}
});
return false;
});
/* copy disks from hidden select */
$('#node-create-disk-add-select').html($('#node-create-disk-add-form').html());
/* build up disk list */
$('#node-create-disk-add-select option').each(function() {
disks.push({
'name': $(this).text(),
'pk': parseInt($(this).val())
});
});
/* add button */
$('#node-create-submit').click(function() {
$.ajax({
url: '/dashboard/node/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: $('form').serialize(),
success: function(data, textStatus, xhr) {
if(data.pk) {
window.location.replace('/dashboard/node/' + data.pk + '/#activity');
}
},
error: function(xhr, textStatus, error) {
if (xhr.status == 500) {
alert("uhuhuhuhuhuh");
} else {
alert("unknown error");
}
}
});
return false;
});
/* no js compatibility */
$('.no-js-hidden').show();
$('.js-hidden').hide();
}
function nodeCreateTemplateChange(new_this) {
this.value = new_this.value;
if(this.value < 0) return;
$.ajax({
url: '/dashboard/template/' + this.value,
type: 'GET',
success: function(data, textStatus, xhr) {
if(xhr.status == 200) {
// set sliders
$('#node-cpu-priority-slider').slider("setValue", data['priority']);
$('#node-cpu-count-slider').slider("setValue", data['num_cores']);
$('#node-ram-size-slider').slider("setValue", data['ram_size']);
/* slider doesn't have change event ........................ */
refreshSliders();
/* clear selections */
$('select[id^="node-create-network-add"], select[id$="managed"]').find('option').prop('selected', false);
$('#node-create-disk-add-form').find('option').prop('selected', false);
/* append vlans from InterfaceTemplates */
$('#vm-create-network-list').html('');
var added_vlans = []
for(var n = 0; n<data['network'].length; n++) {
nn = data['network'][n]
$('#node-create-network-list').append(
nodeCreateNetworkLabel(nn.vlan_pk, nn.vlan, nn.managed)
);
$('#node-create-network-add-' + (nn.managed ? '' : 'un') + 'managed option[value="' + nn.vlan_pk + '"]').prop('selected', true);
added_vlans.push(nn.vlan_pk);
}
/* remove already added vlans from dropdown or add new ones */
$('#node-create-network-add-select').html('');
for(var i=0; i < vlans.length; i++)
if(added_vlans.indexOf(vlans[i].pk) == -1)
$('#node-create-network-add-select').append($('<option>', {
value: vlans[i].pk,
text: vlans[i].name
}));
/* enalbe the network add button if there are not added vlans */
if(added_vlans.length != vlans.length) {
$('#node-create-network-add-button').attr('disabled', false);
} else {
$('#node-create-network-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
$('#node-create-network-add-button').attr('disabled', true);
}
/* append disks */
$('#node-create-disk-list').html('');
var added_disks = []
for(var d = 0; d<data['disks'].length; d++) {
dd = data['disks'][d]
$('#node-create-disk-list').append(
nodeCreateDiskLabel(dd.pk, dd.name)
);
$('#node-create-disk-add-form option[value="' + dd.pk + '"]').prop('selected', true);
added_disks.push(dd.pk);
}
/* remove already added disks from dropdown or add new ones */
$('#node-create-disk-add-select').html('');
for(var i=0; i < disks.length; i++)
if(added_disks.indexOf(disks[i].pk) == -1)
$('#node-create-disk-add-select').append($('<option>', {
value: disks[i].pk,
text: disks[i].name
}));
/* enalbe the disk add button if there are not added disks */
if(added_disks.length != disks.length) {
$('#node-create-disk-add-button').attr('disabled', false);
} else {
$('#node-create-disk-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
$('#node-create-disk-add-button').attr('disabled', true);
}
}
}
});
}
......@@ -181,13 +181,13 @@ function vmCreateLoaded() {
window.location.replace(data.redirect + '#activity');
}
else {
var r = $('#vm-create-modal'); r.next('div').remove(); r.remove();
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#vm-create-modal').modal('show');
$('#vm-create-modal').on('hidden.bs.modal', function() {
$('#vm-create-modal').remove();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
}
},
......
<div class="modal fade" id="vm-create-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="create-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
......
{% load crispy_forms_tags %}
<style>
.row {
margin-bottom: 15px;
}
</style>
<form method="POST" action="/dashboard/vm/create/">
{% csrf_token %}
<div class="row">
<div class="col-sm-5">
<a class="btn btn-info vm-create-advanced-btn">Advanced <i class="vm-create-advanced-icon icon-caret-down"></i></a>
</div>
<div class="col-sm-5 text-right">
<button id="vm-create-submit" type="submit" class="btn btn-success "><i class="icon-play"></i> Start</button>
</div>
</div>
<div class="vm-create-advanced">
<div class="row">
<div class="col-sm-12">
<h2>Resources</h2>
</div>
<p class="row">
<div class="col-sm-3">
<label for="vm-cpu-priority-slider"><i class="icon-trophy"></i> CPU priority</label>
</div>
<div class="col-sm-9">
<input name="cpu-priority" type="text" id="vm-cpu-priority-slider" class="vm-slider" value="20" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="20" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> CPU count</label>
</div>
<div class="col-sm-9">
<input name="cpu-count" type="text" id="vm-cpu-count-slider" class="vm-slider" value="2" data-slider-min="0" data-slider-max="8" data-slider-step="1" data-slider-value="2" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="ram-slider"><i class="icon-ticket"></i> RAM amount</label>
</div>
<div class="col-sm-9">
<input name="ram-size" type="text" id="vm-ram-size-slider" class="vm-slider" value="512" data-slider-min="128" data-slider-max="4096" data-slider-step="128" data-slider-value="512" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> MiB
</div>
</p>
</div>
<!-- disk -->
<div class="row">
<div class="col-sm-4">
<h2>Disks</h2>
</div>
<div class="col-sm-8" style="padding-top: 3px;">
<div class="js-hidden" style="padding-top: 15px; max-width: 450px;">
<select class="form-control" id="vm-create-disk-add-form" multiple name="disks">
{% for d in disks %}
<option value="{{ d.pk }}">{{ d.name }}</option>
{% endfor %}
</select>
</div>
<div class="no-js-hidden">
<h3 id="vm-create-disk-list">
No disks are added!
</h3>
<h3 id="vm-create-disk-add">
<div class="input-group" style="max-width: 330px;">
<select class="form-control" id="vm-create-disk-add-select">
<!-- options should be copied via js from above -->
</select>
<div class="input-group-btn">
<!--<input type="submit" value="Add to network" class="btn btn-success"/>-->
<a href="#" id="vm-create-disk-add-button" class="btn btn-success"><i class="icon-plus-sign"></i></a>
</div>
</div>
</h3>
</div>
</div>
</div>
<!-- network -->
<div class="row">
<div class="col-sm-4">
<h2>Network</h2>
</div>
<style>
/* temporary inline css for dev */
a.hover-black {
color:white;
}
.hover-black:hover {
color: black /*#d9534f*/;
text-decoration: none;
}
.no-js-hidden {
display: none;
}
</style>
<div class="col-sm-8" style="padding-top: 3px;">
<div class="js-hidden" style="padding-top: 15px; max-width: 450px;">
<h4>Managed networks</h4>
<select class="form-control" id="vm-create-network-add-managed" multiple name="managed-vlans">
{% for v in vlans %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
<h4>Unmanaged networks</h4>
<select class="form-control" id="vm-create-network-add-unmanaged" multiple name="unmanaged-vlans">
{% for v in vlans %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
</div>
<div class="no-js-hidden">
<h3 id="vm-create-network-list">
Not added to any network!
</h3>
<h3 id="vm-create-network-add">
<div class="input-group" style="max-width: 330px;">
<select class="form-control" id="vm-create-network-add-select">
<!-- options should be copied via js from above -->
</select>
<span class="input-group-addon">
<input id="vm-create-network-add-checkbox-managed" type="checkbox" title data-original-title="Managed network?" style="-webkit-transform: scale(1.4, 1.4); margin-top: 4px;" checked/>
</span>
<div class="input-group-btn">
<!--<input type="submit" value="Add to network" class="btn btn-success"/>-->
<a href="#" id="vm-create-network-add-button" class="btn btn-success"><i class="icon-plus-sign"></i></a>
</div>
</div>
</h3>
</div>
</div>
</div>
</div>
<form method="POST" action="/dashboard/node/create/">
{% csrf_token %}
{% crispy formset formset.form.helper %}
</form>
......@@ -19,10 +19,11 @@ from django.views.generic import (TemplateView, DetailView, View, DeleteView,
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.forms.models import inlineformset_factory
from django_tables2 import SingleTableView
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from .forms import VmCreateForm, TemplateForm, LeaseForm
from .forms import VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm
from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
......@@ -517,17 +518,30 @@ class VmCreate(LoginRequiredMixin, TemplateView):
class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
form_class = HostForm
hostform = None
formset_class = inlineformset_factory(Host, Node, form=NodeForm, extra=1)
formset = None
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/modal-wrapper.html']
else:
return ['dashboard/nojs-wrapper.html']
def get(self, request, *args, **kwargs):
def get(self, request, hostform=None, formset=None, *args, **kwargs):
if hostform is None:
hostform = self.form_class()
if formset is None:
formset = self.formset_class(instance=Host())
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/node-create.html',
'box_title': 'Create a Node'
'box_title': 'Create a Node',
'hostform': hostform,
'formset': formset,
})
return self.render_to_response(context)
......@@ -535,60 +549,34 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
context = super(NodeCreate, self).get_context_data(**kwargs)
# TODO acl
context.update({
'templates': InstanceTemplate.objects.all(),
'vlans': Vlan.objects.all(),
'disks': Disk.objects.exclude(type="qcow2-snap")
})
return context
# TODO handle not ajax posts
def post(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
resp = {}
try:
ikwargs = {
'num_cores': int(request.POST.get('cpu-count')),
'ram_size': int(request.POST.get('ram-size')),
'priority': int(request.POST.get('cpu-priority')),
}
networks = [InterfaceTemplate(vlan=Vlan.objects.get(pk=l),
managed=True)
for l in request.POST.getlist('managed-vlans')
]
networks.extend([InterfaceTemplate(vlan=Vlan.objects.get(pk=l),
managed=False)
for l in request.POST.getlist('unmanaged-vlans')
])
disks = Disk.objects.filter(pk__in=request.POST.getlist('disks'))
template = InstanceTemplate.objects.get(
pk=request.POST.get('template-pk'))
inst = Instance.create_from_template(template=template,
owner=user, networks=networks,
disks=disks, **ikwargs)
inst.deploy_async(user=request.user)
resp['pk'] = inst.pk
messages.success(request, _('Node successfully created!'))
except InstanceTemplate.DoesNotExist:
resp['error'] = True
except Exception, e:
print e
resp['error'] = True
if not self.request.user.is_authenticated():
raise PermissionDenied()
hostform = self.form_class(request.POST)
formset = self.formset_class(request.POST, Host())
if not hostform.is_valid():
return self.get(request, hostform, formset, *args, **kwargs)
hostform.setowner(request.user)
savedform = hostform.save(commit=False)
formset = self.formset_class(request.POST, instance=savedform)
if not formset.is_valid():
return self.get(request, hostform, formset, *args, **kwargs)
savedform.save()
nodemodel = formset.save()
messages.success(request, _('Node successfully created!'))
path = nodemodel[0].get_absolute_url()
if request.is_ajax():
return HttpResponse(json.dumps(resp),
content_type="application/json",
status=500 if resp.get('error') else 200)
return HttpResponse(json.dumps({'redirect': path}),
content_type="application/json")
else:
return redirect(reverse_lazy('dashboard.views.detail', resp))
return redirect(path)
class VmDelete(LoginRequiredMixin, DeleteView):
......
......@@ -3,7 +3,7 @@ from logging import getLogger
from django.db.models import (
CharField, IntegerField, ForeignKey, BooleanField, ManyToManyField,
FloatField,
FloatField, permalink,
)
from django.utils.translation import ugettext_lazy as _
......@@ -143,3 +143,10 @@ class Node(TimeStampedModel):
def get_state_count(cls, online, enabled):
return len([1 for i in cls.objects.filter(enabled=enabled).all()
if i.online == online])
@permalink
def get_absolute_url(self):
return ('dashboard.views.node-detail', None, {'pk': self.id})
def pr():
print "irdki"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment