Commit 10fc46ee by Kálmán Viktor

dashboard: new vm create modal

parent 66c1cfd4
......@@ -23,48 +23,53 @@ VLANS = Vlan.objects.all()
DISKS = Disk.objects.exclude(type="qcow2-snap")
class VmCreateForm(forms.Form):
template = forms.ModelChoiceField(queryset=InstanceTemplate.objects.all(),
empty_label="Select pls")
class VmCustomizeForm(forms.Form):
name = forms.CharField()
cpu_priority = forms.IntegerField()
cpu_count = forms.IntegerField()
ram_size = forms.IntegerField()
disks = forms.ModelMultipleChoiceField(
queryset=DISKS,
required=False
)
queryset=None, required=True)
networks = forms.ModelMultipleChoiceField(
queryset=VLANS, required=False)
queryset=None, required=False)
template = forms.CharField()
customized = forms.CharField() # dummy flag field
def __init__(self, *args, **kwargs):
super(VmCreateForm, self).__init__(*args, **kwargs)
self.user = kwargs.pop("user", None)
self.template = kwargs.pop("template", None)
super(VmCustomizeForm, self).__init__(*args, **kwargs)
# set displayed disk and network list
self.fields['disks'].queryset = Disk.get_objects_with_level(
'user', self.user).exclude(type="qcow2-snap")
self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user)
# set initial for disk and network list
self.initial['disks'] = self.template.disks.all()
self.initial['networks'] = InterfaceTemplate.objects.filter(
template=self.template).values_list("vlan", flat=True)
# set initial for resources
self.initial['cpu_priority'] = self.template.priority
self.initial['cpu_count'] = self.template.num_cores
self.initial['ram_size'] = self.template.ram_size
# initial name and template pk
self.initial['name'] = self.template.name
self.initial['template'] = self.template.pk
self.initial['customized'] = self.template.pk
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
Field('template', id="vm-create-template-select",
css_class="select form-control"),
css_class="col-sm-10",
),
css_class="row",
),
Field("template", type="hidden"),
Field("customized", type="hidden"),
Div( # buttons
Div(
AnyTag(
"a",
HTML("%s " % _("Advanced")),
AnyTag(
"i",
css_class="vm-create-advanced-icon icon-caret-down"
),
css_class="btn btn-info vm-create-advanced-btn",
),
css_class="col-sm-5",
),
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
......@@ -72,15 +77,21 @@ class VmCreateForm(forms.Form):
css_class="icon-play"
),
HTML(" Start"),
css_id="vm-create-submit",
css_id="vm-create-customized-start",
css_class="btn btn-success",
),
css_class="col-sm-5 text-right",
css_class="col-sm-11 text-right",
),
css_class="row",
),
Div(
Div(
Field("name"),
css_class="col-sm-5",
),
css_class="row",
),
Div( # vm-create-advanced
Div(
Div(
AnyTag(
......@@ -102,7 +113,8 @@ class VmCreateForm(forms.Form):
Field('cpu_priority', id="vm-cpu-priority-slider",
css_class="vm-slider",
data_slider_min="0", data_slider_max="100",
data_slider_step="1", data_slider_value="20",
data_slider_step="1",
data_slider_value=self.template.priority,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
......@@ -120,7 +132,8 @@ class VmCreateForm(forms.Form):
Field('cpu_count', id="vm-cpu-count-slider",
css_class="vm-slider",
data_slider_min="1", data_slider_max="8",
data_slider_step="1", data_slider_value="2",
data_slider_step="1",
data_slider_value=self.template.num_cores,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
......@@ -138,7 +151,8 @@ class VmCreateForm(forms.Form):
Field('ram_size', id="vm-ram-size-slider",
css_class="vm-slider",
data_slider_min="128", data_slider_max="4096",
data_slider_step="128", data_slider_value="512",
data_slider_step="128",
data_slider_value=self.template.ram_size,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
......@@ -257,8 +271,6 @@ class VmCreateForm(forms.Form):
),
css_class="row"
), # end of network
css_class="vm-create-advanced"
),
)
......
......@@ -2,24 +2,72 @@ var vlans = [];
var disks = [];
$(function() {
vmCreateLoaded();
vmCustomizeLoaded();
});
function vmCreateLoaded() {
$('.vm-create-advanced').hide();
$('.vm-create-advanced-btn').click(function() {
$('.vm-create-advanced').stop().slideToggle();
if ($('.vm-create-advanced-icon').hasClass('icon-caret-down')) {
$('.vm-create-advanced-icon').removeClass('icon-caret-down').addClass('icon-caret-up');
} else {
$('.vm-create-advanced-icon').removeClass('icon-caret-up').addClass('icon-caret-down');
$(".vm-create-template-details").hide();
$(".vm-create-template-summary").click(function() {
$(this).next(".vm-create-template-details").slideToggle();
});
$(".customize-vm").click(function() {
var template = $(this).data("template-pk");
console.log(template);
$.get("/dashboard/vm/create/?template=" + template, function(data) {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
});
return false;
});
/* start vm button clicks */
$('.vm-create-start').click(function() {
template = $(this).data("template-pk");
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: {'template': template},
success: function(data, textStatus, xhr) {
if(data.redirect) {
window.location.replace(data.redirect + '#activity');
}
else {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
}
},
error: function(xhr, textStatus, error) {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('#vm-create-template-select').change(function() {
vmCreateTemplateChange(this);
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
return false;
});
}
function vmCustomizeLoaded() {
/* network thingies */
/* add network */
......@@ -86,15 +134,24 @@ function vmCreateLoaded() {
/* copy networks from hidden select */
$('#vm-create-network-add-vlan option').each(function() {
var managed = $(this).text().indexOf("mana") == 0;
var text = $(this).text();
var raw_text = $(this).text();
var pk = $(this).val();
if(managed) {
text = text.replace("managed -", "");
text = raw_text.replace("managed -", "");
} else {
text = text.replace("unmanaged -", "");
text = raw_text.replace("unmanaged -", "");
}
var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + pk + '">' + text + '</option>';
if($('#vm-create-network-list span').length < 1) {
$("#vm-create-network-list").html("");
}
if($(this).is(":selected")) {
$("#vm-create-network-list").append(vmCreateNetworkLabel(pk, raw_text.replace("unmanaged -", "").replace("managed -", ""), managed));
} else {
$('#vm-create-network-add-select').append(html);
}
});
......@@ -168,8 +225,20 @@ function vmCreateLoaded() {
});
/* copy disks from hidden select */
$('#vm-create-disk-add-select').html($('#vm-create-disk-add-form').html());
$('#vm-create-disk-add-form option').each(function() {
var text = $(this).text();
var pk = $(this).val();
var html = '<option value="' + pk + '">' + text + '</option>';
if($('#vm-create-disk-list span').length < 1) {
$("#vm-create-disk-list").html("");
}
if($(this).is(":selected")) {
$("#vm-create-disk-list").append(vmCreateDiskLabel(pk, text));
} else {
$('#vm-create-disk-add-select').append(html);
}
});
/* build up disk list */
$('#vm-create-disk-add-select option').each(function() {
......@@ -179,8 +248,8 @@ function vmCreateLoaded() {
});
});
/* add button */
$('#vm-create-submit').click(function() {
/* start vm button clicks */
$('#vm-create-customized-start').click(function() {
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
......@@ -219,97 +288,6 @@ function vmCreateLoaded() {
$('.js-hidden').hide();
}
function vmCreateTemplateChange(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
$('#vm-cpu-priority-slider').slider("setValue", data['priority']);
$('#vm-cpu-count-slider').slider("setValue", data['num_cores']);
$('#vm-ram-size-slider').slider("setValue", data['ram_size']);
/* slider doesn't have change event ........................ */
refreshSliders();
/* clear selections */
$("#vm-create-network-add-vlan").find('option').prop('selected', false);
$('#vm-create-disk-add-form').find('option').prop('selected', false);
/* clear the network select */
$("#vm-create-network-add-select").html('');
/* 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]
$('#vm-create-network-list').append(
vmCreateNetworkLabel(nn.vlan_pk, nn.vlan, nn.managed)
);
$('#vm-create-network-add-vlan option[value="' + nn.vlan_pk + '"]').prop('selected', true);
added_vlans.push(nn.vlan_pk);
}
/* remove already added vlans from dropdown or add new ones */
$('#vm-create-network-add-select').html('');
// this is working because the vlans array already has the icon's hex code
for(var i=0; i < vlans.length; i++)
if(added_vlans.indexOf(vlans[i].pk) == -1) {
var html = '<option data-managed="' + (vlans[i].managed ? 1 : 0) + '" value="' + vlans[i].pk + '">' + vlans[i].name + '</option>';
$('#vm-create-network-add-select').append(html);
}
/* enable the network add button if there are not added vlans */
if(added_vlans.length != vlans.length) {
$('#vm-create-network-add-button').attr('disabled', false);
} else {
$('#vm-create-network-add-select').html('<option value="-1">No more networks!</option>');
$('#vm-create-network-add-button').attr('disabled', true);
}
/* if there are no added vlans print it out */
if(added_vlans.length < 1) {
$('#vm-create-network-list').html("Not added to any network!");
}
/* append disks */
$('#vm-create-disk-list').html('');
var added_disks = []
for(var d = 0; d<data['disks'].length; d++) {
dd = data['disks'][d]
$('#vm-create-disk-list').append(
vmCreateDiskLabel(dd.pk, dd.name)
);
$('#vm-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 */
$('#vm-create-disk-add-select').html('');
for(var i=0; i < disks.length; i++)
if(added_disks.indexOf(disks[i].pk) == -1)
$('#vm-create-disk-add-select').append($('<option>', {
value: disks[i].pk,
text: disks[i].name
}));
/* enable the disk add button if there are not added disks */
if(added_disks.length != disks.length) {
$('#vm-create-disk-add-button').attr('disabled', false);
} else {
$('#vm-create-disk-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
$('#vm-create-disk-add-button').attr('disabled', true);
}
}
}
});
}
function vmCreateNetworkLabel(pk, name, managed) {
return '<span id="vlan-' + pk + '" class="label label-' + (managed ? 'primary' : 'default') + '"><i class="icon-' + (managed ? 'globe' : 'link') + '"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-network"><i class="icon-remove-sign"></i></a></span> ';
......
{% load sizefieldtags %}
<div class="vm-create-template-list">
{% for t in templates %}
<div class="vm-create-template">
<div class="vm-create-template-summary">
{{ t.name }}
<span class="pull-right"><i class="icon-{{ t.os_type }}"></i> {{ t.system }}</span>
</div>
<div class="vm-create-template-details">
<ul>
<li>
<i class="icon-gears"></i> CPU
<div class="progress pull-right">
<div class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="{{ t.num_cores }}" aria-valuemin="0" aria-valuemax="8" style="width: 80%">
<span class="progress-bar-text">{{ t.num_cores }} cores</span>
</div>
</div>
</li>
<li>
<i class="icon-ticket"></i> Memory
<div class="progress pull-right">
<div class="progress-bar progress-bar-info" role="progressbar"
aria-valuenow="{{ t.ram_size }}" aria-valuemin="0" aria-valuemax="4096"
style="width: 80%">
<span class="progress-bar-text">{{ t.ram_size }} MB</span>
</div>
</div>
</li>
<li>
<i class="icon-file"></i> Disks
<span style="float: right;">
{% for d in t.disks.all %}{{ d.name }} ({{ d.size|filesize }}){% if not forloop.last %}, {% endif %}{% endfor %}
</span>
</li>
<li>
<i class="icon-globe"></i> Network:
<span style="float: right;">
{% for i in t.interface_set.all %}{{ i.vlan.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
</span>
</li>
<li>
<i class="icon-tag"></i> Típus: {{ t.lease.name }}
<span style="float: right;">
<i class="icon-pause"></i> {{ t.lease.get_readable_suspend_time }}
<i class="icon-remove"></i> {{ t.lease.get_readable_delete_time }}
</span>
</li>
<li>
<i class="icon-hand-right"></i> Description:
<span style="float: right; max-width: 350px;">
{{ t.description }}
</span>
<div class="clearfix"></div>
</li>
</ul>
<div style="margin-top: 20px; padding: 0 15px; width: 100%">
<a class="btn btn-primary btn-xs customize-vm" data-template-pk="{{ t.pk }}" href="{% url "dashboard.views.vm-create" %}?template={{ t.pk }}"><i class="icon-wrench"></i> Customize</a>
<form class="pull-right text-right" method="POST" action="{% url "dashboard.views.vm-create" %}">
{% csrf_token %}
<input type="hidden" name="template" value="{{ t.pk }}"/>
<button class="btn btn-success btn-xs vm-create-start" data-template-pk="{{ t.pk }}" type="submit"><i class="icon-play"></i> Start</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
<style>
.row {
margin-bottom: 15px;
}
.vm-create-template {
max-width: 800px;
border: 1px solid black;
border-bottom: none;
}
.vm-create-template-list .vm-create-template:last-child {
border-bottom: 1px solid black;
}
.vm-create-template-summary {
padding: 15px;
cursor: pointer;
}
.vm-create-template:nth-child(odd) .vm-create-template-summary {
background: #F5F5F5;
}
.vm-create-template-list .vm-create-template-summary:hover {
background: #D2D2D2;
}
.vm-create-template-details {
border-top: 1px dashed #D3D3D3;
padding: 15px;
}
.vm-create-template-details ul {
list-style: none;
padding: 0 15px;
}
.vm-create-template-details li {
border-bottom: 1px dotted #aaa;
padding: 5px 0px;
}
.progress {
position: relative;
width: 200px;
height: 12px;
margin-bottom: 0px;
margin-top: 5px;
}
.progress-bar-text {
position: absolute;
display: block;
width: 100%;
color: white;
/* outline */
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
font-size: 10px;
}
</style>
{% block "extra-js" %}
<script>
$('.progress-bar').each(function() {
var min = $(this).attr('aria-valuemin');
var max = $(this).attr('aria-valuemax');
var now = $(this).attr('aria-valuenow');
var siz = (now-min)*100/(max-min);
$(this).css('width', siz+'%');
});
</script>
{% endblock %}
{% load crispy_forms_tags %}
{% load sizefieldtags %}
{% crispy vm_create_form %}
<script src="/static/dashboard/vm-create.js"></script>
<div class="modal fade" id="create-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
{% if box_title and ajax_title %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{{ box_title }}</h4>
</div>
{% endif %}
<div class="modal-body">
{% include template %}
</div>
......
......@@ -15,7 +15,7 @@
{% endblock %}
{% block extra_js %}
{% if template == "dashboard/vm-create.html" %}
{% if template == "dashboard/_vm-create-1.html" %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
{% endif %}
{% endblock %}
{% load crispy_forms_tags %}
<style>
.row {
margin-bottom: 15px;
}
</style>
<form method="POST" action="/dashboard/vm/create/">
{% csrf_token %}
{% crispy vm_create_form %}
</form>
......@@ -28,7 +28,8 @@ from django_tables2 import SingleTableView
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from .forms import (
VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm, DiskAddForm,
VmCustomizeForm, TemplateForm, LeaseForm, NodeForm, HostForm,
DiskAddForm,
)
from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable,)
......@@ -36,7 +37,6 @@ from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity, Node, instance_activity, Lease,
Interface)
from firewall.models import Vlan, Host, Rule
from storage.models import Disk
from dashboard.models import Favourite
logger = logging.getLogger(__name__)
......@@ -952,7 +952,7 @@ class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
class VmCreate(LoginRequiredMixin, TemplateView):
form_class = VmCreateForm
form_class = VmCustomizeForm
form = None
def get_template_names(self):
......@@ -962,51 +962,66 @@ class VmCreate(LoginRequiredMixin, TemplateView):
return ['dashboard/nojs-wrapper.html']
def get(self, request, form=None, *args, **kwargs):
if form is None:
form = self.form_class()
form.fields['disks'].queryset = Disk.get_objects_with_level(
'user', request.user).exclude(type="qcow2-snap")
form.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', request.user)
form_error = form is not None
template = (form.template.pk if form_error
else request.GET.get("template"))
templates = InstanceTemplate.get_objects_with_level('user',
request.user)
form.fields['template'].queryset = templates
if form is None and template:
form = self.form_class(user=request.user,
template=templates.get(pk=template))
context = self.get_context_data(**kwargs)
if template:
context.update({
'template': 'dashboard/vm-create.html',
'box_title': 'Create a VM',
'template': 'dashboard/_vm-create-2.html',
'box_title': _('Customize VM'),
'ajax_title': False,
'vm_create_form': form,
'template_o': templates.get(pk=template),
})
else:
context.update({
'template': 'dashboard/_vm-create-1.html',
'box_title': _('Create a VM'),
'ajax_title': False,
'templates': templates.all(),
})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
def __create_normal(self, request, *args, **kwargs):
user = request.user
template = InstanceTemplate.objects.get(
pk=request.POST.get("template"))
# permission check
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
inst = Instance.create_from_template(
template=template, owner=user)
return self.__deploy(request, inst)
def __create_customized(self, request, *args, **kwargs):
user = request.user
form = self.form_class(
request.POST, user=request.user,
template=InstanceTemplate.objects.get(
pk=request.POST.get("template")
)
)
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
post = form.cleaned_data
user = request.user
try:
limit = user.profile.instance_limit
except Exception as e:
logger.debug('No profile or instance limit: %s', e)
else:
current = Instance.active.filter(owner=user).count()
logger.debug('current use: %d, limit: %d', current, limit)
if limit < current:
messages.error(request,
_('Instance limit (%d) exceeded.') % limit)
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': '/'}),
content_type="application/json")
else:
return redirect('/')
template = post['template']
if not template.has_level(request.user, 'user'):
template = InstanceTemplate.objects.get(pk=post['template'])
# permission check
if not template.has_level(user, 'user'):
raise PermissionDenied()
if request.user.has_perm('vm.set_resources'):
ikwargs = {
'name': post['name'],
'num_cores': post['cpu_count'],
'ram_size': post['ram_size'],
'priority': post['cpu_priority'],
......@@ -1017,17 +1032,45 @@ class VmCreate(LoginRequiredMixin, TemplateView):
inst = Instance.create_from_template(
template=template, owner=user, networks=networks,
disks=disks, **ikwargs)
return self.__deploy(request, inst)
else:
inst = Instance.create_from_template(
template=template, owner=user)
inst.deploy_async(user=request.user)
raise PermissionDenied()
def __deploy(self, request, instance, *args, **kwargs):
instance.deploy_async(user=request.user)
messages.success(request, _('VM successfully created!'))
path = inst.get_absolute_url()
path = instance.get_absolute_url()
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': path}),
content_type="application/json")
else:
return redirect(path)
return redirect("%s#activity" % path)
def post(self, request, *args, **kwargs):
user = request.user
# limit chekcs
try:
limit = user.profile.instance_limit
except Exception as e:
logger.debug('No profile or instance limit: %s', e)
else: