Commit 8fd80cf1 by Oláh István Gergely

dashboard: add node create form

parent e029a56b
from datetime import timedelta from datetime import timedelta
from django import forms 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 storage.models import Disk
from firewall.models import Vlan from firewall.models import Vlan, Host
# from django.core.urlresolvers import reverse_lazy
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import (Layout, Div, BaseInput, from crispy_forms.layout import (Layout, Div, BaseInput,
Field, HTML, Submit, Fieldset) Field, HTML, Submit, Fieldset)
...@@ -13,8 +11,12 @@ from crispy_forms.utils import render_field ...@@ -13,8 +11,12 @@ from crispy_forms.utils import render_field
from django.template import Context from django.template import Context
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.forms.widgets import TextInput 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 django.utils.translation import ugettext as _
# from crispy_forms.bootstrap import FormActions
VLANS = Vlan.objects.all() VLANS = Vlan.objects.all()
...@@ -292,6 +294,169 @@ class VmCreateForm(forms.Form): ...@@ -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): class TemplateForm(forms.ModelForm):
managed_networks = forms.ModelMultipleChoiceField( managed_networks = forms.ModelMultipleChoiceField(
queryset=VLANS, required=False) queryset=VLANS, required=False)
......
...@@ -7,9 +7,9 @@ $(function () { ...@@ -7,9 +7,9 @@ $(function () {
$('body').append(data); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#vm-create-modal').modal('show'); $('#create-modal').modal('show');
$('#vm-create-modal').on('hidden.bs.modal', function() { $('#create-modal').on('hidden.bs.modal', function() {
$('#vm-create-modal').remove(); $('#create-modal').remove();
}); });
} }
}); });
...@@ -24,9 +24,9 @@ $(function () { ...@@ -24,9 +24,9 @@ $(function () {
$('body').append(data); $('body').append(data);
nodeCreateLoaded(); nodeCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#node-create-modal').modal('show'); $('#create-modal').modal('show');
$('#node-create-modal').on('hidden.bs.modal', function() { $('#create-modal').on('hidden.bs.modal', function() {
$('#node-create-modal').remove(); $('#create-modal').remove();
}); });
} }
}); });
......
...@@ -181,13 +181,13 @@ function vmCreateLoaded() { ...@@ -181,13 +181,13 @@ function vmCreateLoaded() {
window.location.replace(data.redirect + '#activity'); window.location.replace(data.redirect + '#activity');
} }
else { 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); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#vm-create-modal').modal('show'); $('#create-modal').modal('show');
$('#vm-create-modal').on('hidden.bs.modal', function() { $('#create-modal').on('hidden.bs.modal', function() {
$('#vm-create-modal').remove(); $('#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-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
......
{% load crispy_forms_tags %}
<style> <style>
.row { .row {
margin-bottom: 15px; margin-bottom: 15px;
} }
</style> </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 --> <form method="POST" action="/dashboard/node/create/">
<div class="row"> {% csrf_token %}
<div class="col-sm-4"> {% crispy formset formset.form.helper %}
<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> </form>
...@@ -19,10 +19,11 @@ from django.views.generic import (TemplateView, DetailView, View, DeleteView, ...@@ -19,10 +19,11 @@ from django.views.generic import (TemplateView, DetailView, View, DeleteView,
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.forms.models import inlineformset_factory
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin 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, from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable) TemplateListTable, LeaseListTable)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate, from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
...@@ -517,17 +518,30 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -517,17 +518,30 @@ class VmCreate(LoginRequiredMixin, TemplateView):
class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, 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): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
return ['dashboard/modal-wrapper.html'] return ['dashboard/modal-wrapper.html']
else: else:
return ['dashboard/nojs-wrapper.html'] 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 = self.get_context_data(**kwargs)
context.update({ context.update({
'template': 'dashboard/node-create.html', '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) return self.render_to_response(context)
...@@ -535,60 +549,34 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): ...@@ -535,60 +549,34 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
context = super(NodeCreate, self).get_context_data(**kwargs) context = super(NodeCreate, self).get_context_data(**kwargs)
# TODO acl # TODO acl
context.update({ context.update({
'templates': InstanceTemplate.objects.all(),
'vlans': Vlan.objects.all(),
'disks': Disk.objects.exclude(type="qcow2-snap")
}) })
return context return context
# TODO handle not ajax posts # TODO handle not ajax posts
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if self.request.user.is_authenticated(): if not self.request.user.is_authenticated():
user = self.request.user raise PermissionDenied()
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
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(): if request.is_ajax():
return HttpResponse(json.dumps(resp), return HttpResponse(json.dumps({'redirect': path}),
content_type="application/json", content_type="application/json")
status=500 if resp.get('error') else 200)
else: else:
return redirect(reverse_lazy('dashboard.views.detail', resp)) return redirect(path)
class VmDelete(LoginRequiredMixin, DeleteView): class VmDelete(LoginRequiredMixin, DeleteView):
......
...@@ -3,7 +3,7 @@ from logging import getLogger ...@@ -3,7 +3,7 @@ from logging import getLogger
from django.db.models import ( from django.db.models import (
CharField, IntegerField, ForeignKey, BooleanField, ManyToManyField, CharField, IntegerField, ForeignKey, BooleanField, ManyToManyField,
FloatField, FloatField, permalink,
) )
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -143,3 +143,10 @@ class Node(TimeStampedModel): ...@@ -143,3 +143,10 @@ class Node(TimeStampedModel):
def get_state_count(cls, online, enabled): def get_state_count(cls, online, enabled):
return len([1 for i in cls.objects.filter(enabled=enabled).all() return len([1 for i in cls.objects.filter(enabled=enabled).all()
if i.online == online]) 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