Commit 9e2f21e1 by Kálmán Viktor

Merge branch 'feature-working-vm-create'

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
parents 586cb8ad 9a95ba6b
...@@ -33,6 +33,7 @@ class VmCustomizeForm(forms.Form): ...@@ -33,6 +33,7 @@ class VmCustomizeForm(forms.Form):
cpu_priority = forms.IntegerField() cpu_priority = forms.IntegerField()
cpu_count = forms.IntegerField() cpu_count = forms.IntegerField()
ram_size = forms.IntegerField() ram_size = forms.IntegerField()
amount = forms.IntegerField(min_value=0, initial=1)
disks = forms.ModelMultipleChoiceField( disks = forms.ModelMultipleChoiceField(
queryset=None, required=True) queryset=None, required=True)
...@@ -68,12 +69,21 @@ class VmCustomizeForm(forms.Form): ...@@ -68,12 +69,21 @@ class VmCustomizeForm(forms.Form):
self.initial['template'] = self.template.pk self.initial['template'] = self.template.pk
self.initial['customized'] = self.template.pk self.initial['customized'] = self.template.pk
# set widget for amount
self.fields['amount'].widget = NumberInput()
self.helper = FormHelper(self) self.helper = FormHelper(self)
self.helper.form_show_labels = False
# don't show labels for the sliders
self.helper.form_show_labels = True
self.fields['cpu_count'].label = ""
self.fields['ram_size'].label = ""
self.fields['cpu_priority'].label = ""
self.helper.layout = Layout( self.helper.layout = Layout(
Field("template", type="hidden"), Field("template", type="hidden"),
Field("customized", type="hidden"), Field("customized", type="hidden"),
Div( # buttons Div(
Div( Div(
AnyTag( # tip: don't try to use Button class AnyTag( # tip: don't try to use Button class
"button", "button",
...@@ -84,16 +94,17 @@ class VmCustomizeForm(forms.Form): ...@@ -84,16 +94,17 @@ class VmCustomizeForm(forms.Form):
HTML(" Start"), HTML(" Start"),
css_id="vm-create-customized-start", css_id="vm-create-customized-start",
css_class="btn btn-success", css_class="btn btn-success",
style="float: right; margin-top: 24px;",
), ),
css_class="col-sm-11 text-right", Field("name", style="max-width: 350px;"),
css_class="col-sm-12",
), ),
css_class="row", css_class="row",
), ),
Div( Div(
Div( Div(
Field("name"), Field("amount", min="1", style="max-width: 60px;"),
css_class="col-sm-5", css_class="col-sm-10",
), ),
css_class="row", css_class="row",
), ),
...@@ -185,32 +196,36 @@ class VmCustomizeForm(forms.Form): ...@@ -185,32 +196,36 @@ class VmCustomizeForm(forms.Form):
HTML(_("No disks are added!")), HTML(_("No disks are added!")),
css_id="vm-create-disk-list", css_id="vm-create-disk-list",
), ),
AnyTag( Div(
"h3", HTML(""),
Div( style="clear: both;",
AnyTag(
"select",
css_class="form-control",
css_id="vm-create-disk-add-select",
),
Div(
AnyTag(
"a",
AnyTag(
"i",
css_class="icon-plus-sign",
),
href="#",
css_id="vm-create-disk-add-button",
css_class="btn btn-success",
),
css_class="input-group-btn"
),
css_class="input-group",
style="max-width: 330px;",
),
css_id="vm-create-disk-add",
), ),
# AnyTag(
# "h3",
# Div(
# AnyTag(
# "select",
# css_class="form-control",
# css_id="vm-create-disk-add-select",
# ),
# Div(
# AnyTag(
# "a",
# AnyTag(
# "i",
# css_class="icon-plus-sign",
# ),
# href="#",
# css_id="vm-create-disk-add-button",
# css_class="btn btn-success",
# ),
# css_class="input-group-btn"
# ),
# css_class="input-group",
# style="max-width: 330px;",
# ),
# css_id="vm-create-disk-add",
# ),
css_class="no-js-hidden", css_class="no-js-hidden",
), ),
css_class="col-sm-8", css_class="col-sm-8",
......
...@@ -410,7 +410,7 @@ footer a, footer a:hover, footer a:visited { ...@@ -410,7 +410,7 @@ footer a, footer a:hover, footer a:visited {
color: white; color: white;
text-decoration: underline; text-decoration: underline;
} }
.template-disk-list { .template-disk-list {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;
...@@ -423,3 +423,16 @@ footer a, footer a:hover, footer a:visited { ...@@ -423,3 +423,16 @@ footer a, footer a:hover, footer a:visited {
#node-info-pane { #node-info-pane {
margin-bottom: 20px; margin-bottom: 20px;
} }
.index-vm-list-name {
display: inline-block;
max-width: 70%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
float: left;
}
#dashboard-vm-list a small {
padding-left: 10px;
}
...@@ -3,7 +3,7 @@ $(function () { ...@@ -3,7 +3,7 @@ $(function () {
var template = $(this).data("template"); var template = $(this).data("template");
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: '/dashboard/vm/create/', url: '/dashboard/vm/create/' + (typeof template === "undefined" ? '' : '?template=' + template),
success: function(data) { success: function(data) {
$('body').append(data); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
...@@ -12,9 +12,6 @@ $(function () { ...@@ -12,9 +12,6 @@ $(function () {
$('#create-modal').on('hidden.bs.modal', function() { $('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove(); $('#create-modal').remove();
}); });
if(template) {
$('#vm-create-template-select option[value="' + template + '"]').prop("selected", true).trigger("change");
}
} }
}); });
return false; return false;
...@@ -191,6 +188,9 @@ $(function () { ...@@ -191,6 +188,9 @@ $(function () {
'name': result[i].name.toLowerCase(), 'name': result[i].name.toLowerCase(),
'state': result[i].state, 'state': result[i].state,
'fav': result[i].fav, 'fav': result[i].fav,
'host': result[i].host,
'icon': result[i].icon,
'status': result[i].status,
}); });
} }
}); });
...@@ -207,7 +207,9 @@ $(function () { ...@@ -207,7 +207,9 @@ $(function () {
} }
search_result.sort(compareVmByFav); search_result.sort(compareVmByFav);
for(var i=0; i<5 && i<search_result.length; i++) for(var i=0; i<5 && i<search_result.length; i++)
html += generateVmHTML(search_result[i].pk, search_result[i].name, search_result[i].fav); html += generateVmHTML(search_result[i].pk, search_result[i].name,
search_result[i].host, search_result[i].icon,
search_result[i].status, search_result[i].fav);
if(search_result.length == 0) if(search_result.length == 0)
html += '<div class="list-group-item">No result</div>'; html += '<div class="list-group-item">No result</div>';
$("#dashboard-vm-list").html(html); $("#dashboard-vm-list").html(html);
...@@ -233,21 +235,33 @@ $(function () { ...@@ -233,21 +235,33 @@ $(function () {
}); });
}); });
function generateVmHTML(pk, name, fav) { function generateVmHTML(pk, name, host, icon, _status, fav) {
return '<a href="/dashboard/vm/' + pk + '/" class="list-group-item">' + return '<a href="/dashboard/vm/' + pk + '/" class="list-group-item">' +
'<i class="icon-play-sign"></i> ' + name + '<span class="index-vm-list-name">' +
'<div class="pull-right dashboard-vm-favourite" data-vm="' + pk +'">' + '<i class="' + icon + '" title="' + _status + '"></i> ' + name +
'<i class="title-favourite icon-star' + (fav ? "" : "-empty") + ' text-primary" title="" data-original-title="' + '</span>' +
(fav ? "Un": "Mark as ") + 'favourite"></i>' + '<small class="text-muted"> ' + host + '</small>' +
'</div>' + '<div class="pull-right dashboard-vm-favourite" data-vm="' + pk + '">' +
'</a>'; (fav ? '<i class="icon-star text-primary title-favourite" title="Unfavourite"></i>' :
'<i class="icon-star-empty text-primary title-favourite" title="Mark as favorite"></i>' ) +
'</div>' +
'<div style="clear: both;"></div>' +
'</a>';
} }
/* copare vm-s by fav, pk order */
function compareVmByFav(a, b) { function compareVmByFav(a, b) {
if(a.fav) if(a.fav && b.fav) {
return a.pk < b.pk ? -1 : 1;
}
else if(a.fav && !b.fav) {
return -1; return -1;
else }
else if(!a.fav && b.fav) {
return 1; return 1;
}
else
return a.pk < b.pk ? -1 : 1;
} }
function addSliderMiscs() { function addSliderMiscs() {
......
...@@ -14,7 +14,6 @@ function vmCreateLoaded() { ...@@ -14,7 +14,6 @@ function vmCreateLoaded() {
$(".customize-vm").click(function() { $(".customize-vm").click(function() {
var template = $(this).data("template-pk"); var template = $(this).data("template-pk");
console.log(template);
$.get("/dashboard/vm/create/?template=" + template, function(data) { $.get("/dashboard/vm/create/?template=" + template, function(data) {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#create-modal'); r.next('div').remove(); r.remove();
...@@ -142,7 +141,6 @@ function vmCustomizeLoaded() { ...@@ -142,7 +141,6 @@ function vmCustomizeLoaded() {
text = raw_text.replace("unmanaged -", "&#xf0c1;"); text = raw_text.replace("unmanaged -", "&#xf0c1;");
} }
var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + pk + '">' + text + '</option>'; var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + pk + '">' + text + '</option>';
if($('#vm-create-network-list span').length < 1) { if($('#vm-create-network-list span').length < 1) {
$("#vm-create-network-list").html(""); $("#vm-create-network-list").html("");
...@@ -152,8 +150,14 @@ function vmCustomizeLoaded() { ...@@ -152,8 +150,14 @@ function vmCustomizeLoaded() {
} else { } else {
$('#vm-create-network-add-select').append(html); $('#vm-create-network-add-select').append(html);
} }
}); });
// if all networks are added add a dummy and disable the add button
if($("#vm-create-network-add-select option").length < 1) {
$("#vm-create-network-add-select").html('<option value="-1">No more networks!</option>');
$('#vm-create-network-add-button').attr('disabled', true);
}
/* build up network list */ /* build up network list */
$('#vm-create-network-add-vlan option').each(function() { $('#vm-create-network-add-vlan option').each(function() {
...@@ -197,24 +201,14 @@ function vmCustomizeLoaded() { ...@@ -197,24 +201,14 @@ function vmCustomizeLoaded() {
/* remove disk */ /* remove disk */
// event for disk remove button (icon, X) // event for disk remove button (icon, X)
$('body').on('click', '.vm-create-remove-disk', function() { $('body').on('click', '.vm-create-remove-disk', function() {
var disk_pk = ($(this).parent('span').prop('id')).replace('vlan-', '') var disk_pk = ($(this).parent('span').prop('id')).replace('disk-', '')
$(this).parent('span').fadeOut(500, function() { $(this).parent('span').fadeOut(500, function() {
/* if ther are no more disks disabled the add button */
if($('#vm-create-disk-add-select option')[0].value == -1) {
$('#vm-create-disk-add-button').attr('disabled', false);
$('#vm-create-disk-add-select').html('');
}
/* remove the disk label */ /* remove the disk label */
$(this).remove(); $(this).remove();
var disk_name = $(this).text(); var disk_name = $(this).text();
$('#vm-create-disk-add-select').append($('<option>', {
value: disk_pk,
text: disk_name
}));
/* remove the selection from the multiple select */ /* remove the selection from the multiple select */
$('#vm-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', false); $('#vm-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', false);
if ($('#vm-create-disk-list').children('span').length < 1) { if ($('#vm-create-disk-list').children('span').length < 1) {
...@@ -257,7 +251,11 @@ function vmCustomizeLoaded() { ...@@ -257,7 +251,11 @@ function vmCustomizeLoaded() {
data: $('form').serialize(), data: $('form').serialize(),
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
if(data.redirect) { if(data.redirect) {
window.location.replace(data.redirect + '#activity'); /* it won't redirect to the same page */
if(window.location.pathname == data.redirect) {
window.location.reload();
}
window.location.href = data.redirect + '#activity';
} }
else { else {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#create-modal'); r.next('div').remove(); r.remove();
...@@ -295,5 +293,6 @@ function vmCreateNetworkLabel(pk, name, managed) { ...@@ -295,5 +293,6 @@ function vmCreateNetworkLabel(pk, name, managed) {
function vmCreateDiskLabel(pk, name) { function vmCreateDiskLabel(pk, name) {
return '<span id="vlan-' + pk + '" class="label label-primary"><i class="icon-file"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-disk"><i class="icon-remove-sign"></i></a></span> '; var style = "float: left; margin: 5px 5px 5px 0;";
return '<span id="disk-' + pk + '" class="label label-primary" style="' + style + '"><i class="icon-file"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-disk"><i class="icon-remove-sign"></i></a></span> ';
} }
...@@ -58,12 +58,17 @@ ...@@ -58,12 +58,17 @@
</li> </li>
</ul> </ul>
<div style="margin-top: 20px; padding: 0 15px; width: 100%"> <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> {% if perms.vm_set_resources %}
<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> {% trans "Customize" %}</a>
{% endif %}
<form class="pull-right text-right" method="POST" action="{% url "dashboard.views.vm-create" %}"> <form class="pull-right text-right" method="POST" action="{% url "dashboard.views.vm-create" %}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="template" value="{{ t.pk }}"/> <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> <button class="btn btn-success btn-xs vm-create-start" data-template-pk="{{ t.pk }}" type="submit">
<i class="icon-play"></i> {% trans "Start" %}
</button>
</form> </form>
<div style="clear: both;"></div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -16,8 +16,11 @@ ...@@ -16,8 +16,11 @@
<div id="dashboard-vm-list"> <div id="dashboard-vm-list">
{% for i in instances %} {% for i in instances %}
<a href="{{ i.get_absolute_url }}" class="list-group-item"> <a href="{{ i.get_absolute_url }}" class="list-group-item">
<i class="{{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i> {{ i.name }} <span class="index-vm-list-name">
<small class="text-muted">{{ i.primary_host.hostname }}</small> <i class="{{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i>
{{ i.name }}
</span>
<small class="text-muted"> {{ i.primary_host.hostname }}</small>
<div class="pull-right dashboard-vm-favourite" data-vm="{{ i.pk }}"> <div class="pull-right dashboard-vm-favourite" data-vm="{{ i.pk }}">
{% if i.fav %} {% if i.fav %}
<i class="icon-star text-primary title-favourite" title="{% trans "Unfavourite" %}"></i> <i class="icon-star text-primary title-favourite" title="{% trans "Unfavourite" %}"></i>
...@@ -25,6 +28,7 @@ ...@@ -25,6 +28,7 @@
<i class="icon-star-empty text-primary title-favourite" title="{% trans "Mark as favorite" %}"></i> <i class="icon-star-empty text-primary title-favourite" title="{% trans "Mark as favorite" %}"></i>
{% endif %} {% endif %}
</div> </div>
<div style="clear: both;"></div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
...@@ -50,19 +54,24 @@ ...@@ -50,19 +54,24 @@
<div class="panel-body" id="vm-graph-view" style="display: none"> <div class="panel-body" id="vm-graph-view" style="display: none">
<p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-max="{{ request.user.profile.instance_limit }}" data-width="100" data-height="100" data-readOnly="true" value="{{ instances|length|add:more_instances }}"></p> <p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-max="{{ request.user.profile.instance_limit }}" data-width="100" data-height="100" data-readOnly="true" value="{{ instances|length|add:more_instances }}"></p>
<p><span class="bigbig">{% blocktrans with count=running_vm_num %}<big>{{ count }}</big> running{% endblocktrans %}</span> <p><span class="bigbig">{% blocktrans with count=running_vm_num %}<big>{{ count }}</big> running{% endblocktrans %}</span>
<ul class="list-inline"> <ul class="list-inline" style="max-height: 95px; overflow: hidden;">
{% for vm in running_vms %} {% for vm in running_vms %}
<li class="label label-success"> <li style="display: inline-block; padding: 2px;">
<a href="vm.get_absolute_url" title="{{vm.primary_host.get_fqdn}}"><i class="{{vm.get_status_icon}}"></i> {{vm.name}}</a> <a href="{{vm.get_absolute_url}}" title="{{vm.primary_host.get_fqdn}}" class="label label-success">
</li> <i class="{{vm.get_status_icon}}"></i> {{vm.name}}
</a>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</p> </p>
<p class="big text-warning">{% blocktrans with count=stopped_vm_num %}<big>{{ count }}</big> stopped{% endblocktrans %}</p>
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="text-right"> <div>
<a href="{% url "dashboard.views.vm-list" %}" class="btn btn-primary btn-xs"><i class="icon-chevron-sign-right"></i> <strong>{{ instances|length|add:more_instances }}</strong> machines total</a> <a style="float: right; margin-top: 17px;" href="{% url "dashboard.views.vm-list" %}" class="btn btn-primary btn-xs">
<i class="icon-chevron-sign-right"></i>
<strong>{{ instances|length|add:more_instances }}</strong> machines total
</a>
<p class="big text-warning">{% blocktrans with count=stopped_vm_num %}<big>{{ count }}</big> stopped{% endblocktrans %}</p>
</div> </div>
</div> </div>
</div> </div>
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<div class="panel-body"> <div class="panel-body">
{% include template %} {% include template %}
</div> </div>
</div>
</div> </div>
{% endblock %} {% endblock %}
......
...@@ -530,6 +530,24 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -530,6 +530,24 @@ class VmDetailTest(LoginMixin, TestCase):
response = c.get("/dashboard/template/111111/") response = c.get("/dashboard/template/111111/")
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_permitted_customized_vm_create(self):
c = Client()
self.login(c, "superuser")
instance_count = Instance.objects.all().count()
response = c.post("/dashboard/vm/create/", {
'name': 'vm',
'amount': 2,
'customized': 1,
'template': 1,
'cpu_priority': 1, 'cpu_count': 1, 'ram_size': 1,
'network': [],
'disks': [Disk.objects.get(id=1).pk],
})
self.assertEqual(response.status_code, 302)
self.assertEqual(instance_count + 2, Instance.objects.all().count())
class VmDetailVncTest(LoginMixin, TestCase): class VmDetailVncTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
......
...@@ -921,7 +921,9 @@ class VmList(LoginRequiredMixin, ListView): ...@@ -921,7 +921,9 @@ class VmList(LoginRequiredMixin, ListView):
instances = [{ instances = [{
'pk': i.pk, 'pk': i.pk,
'name': i.name, 'name': i.name,
'state': i.state, 'icon': i.get_status_icon(),
'host': "" if not i.primary_host else i.primary_host.hostname,
'status': i.get_status_display(),
'fav': i.pk in favs} for i in instances] 'fav': i.pk in favs} for i in instances]
return HttpResponse( return HttpResponse(
json.dumps(list(instances)), # instances is ValuesQuerySet json.dumps(list(instances)), # instances is ValuesQuerySet
...@@ -1098,9 +1100,9 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -1098,9 +1100,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
if not template.has_level(request.user, 'user'): if not template.has_level(request.user, 'user'):
raise PermissionDenied() raise PermissionDenied()
inst = Instance.create_from_template( instances = [Instance.create_from_template(
template=template, owner=user) template=template, owner=user)]
return self.__deploy(request, inst) return self.__deploy(request, instances)
def __create_customized(self, request, *args, **kwargs): def __create_customized(self, request, *args, **kwargs):
user = request.user user = request.user
...@@ -1129,17 +1131,33 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -1129,17 +1131,33 @@ class VmCreate(LoginRequiredMixin, TemplateView):
networks = [InterfaceTemplate(vlan=l, managed=l.managed) networks = [InterfaceTemplate(vlan=l, managed=l.managed)
for l in post['networks']] for l in post['networks']]
disks = post['disks'] disks = post['disks']
inst = Instance.create_from_template(
template=template, owner=user, networks=networks, ikwargs.update({
disks=disks, **ikwargs) 'template': template,
return self.__deploy(request, inst) 'owner': user,
'networks': networks,
'disks': disks,
})
amount = post['amount']
instances = Instance.mass_create_from_template(amount=amount,
**ikwargs)
return self.__deploy(request, instances)
else: else:
raise PermissionDenied() raise PermissionDenied()
def __deploy(self, request, instance, *args, **kwargs): def __deploy(self, request, instances, *args, **kwargs):
instance.deploy_async(user=request.user) for i in instances:
messages.success(request, _('VM successfully created!')) i.deploy_async(user=request.user)
path = instance.get_absolute_url()
if len(instances) > 1:
messages.success(request, _("Successfully created %d VMs!" %
len(instances)))
path = reverse("dashboard.index")
else:
messages.success(request, _("VM successfully created!"))
path = instances[0].get_absolute_url()
if request.is_ajax(): if request.is_ajax():
return HttpResponse(json.dumps({'redirect': path}), return HttpResponse(json.dumps({'redirect': path}),
content_type="application/json") content_type="application/json")
......
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