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):
cpu_priority = forms.IntegerField()
cpu_count = forms.IntegerField()
ram_size = forms.IntegerField()
amount = forms.IntegerField(min_value=0, initial=1)
disks = forms.ModelMultipleChoiceField(
queryset=None, required=True)
......@@ -68,12 +69,21 @@ class VmCustomizeForm(forms.Form):
self.initial['template'] = self.template.pk
self.initial['customized'] = self.template.pk
# set widget for amount
self.fields['amount'].widget = NumberInput()
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(
Field("template", type="hidden"),
Field("customized", type="hidden"),
Div( # buttons
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
......@@ -84,16 +94,17 @@ class VmCustomizeForm(forms.Form):
HTML(" Start"),
css_id="vm-create-customized-start",
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",
),
Div(
Div(
Field("name"),
css_class="col-sm-5",
Field("amount", min="1", style="max-width: 60px;"),
css_class="col-sm-10",
),
css_class="row",
),
......@@ -185,32 +196,36 @@ class VmCustomizeForm(forms.Form):
HTML(_("No disks are added!")),
css_id="vm-create-disk-list",
),
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",
Div(
HTML(""),
style="clear: both;",
),
# 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="col-sm-8",
......
......@@ -410,7 +410,7 @@ footer a, footer a:hover, footer a:visited {
color: white;
text-decoration: underline;
}
.template-disk-list {
list-style: none;
padding-left: 0;
......@@ -423,3 +423,16 @@ footer a, footer a:hover, footer a:visited {
#node-info-pane {
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 () {
var template = $(this).data("template");
$.ajax({
type: 'GET',
url: '/dashboard/vm/create/',
url: '/dashboard/vm/create/' + (typeof template === "undefined" ? '' : '?template=' + template),
success: function(data) {
$('body').append(data);
vmCreateLoaded();
......@@ -12,9 +12,6 @@ $(function () {
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
if(template) {
$('#vm-create-template-select option[value="' + template + '"]').prop("selected", true).trigger("change");
}
}
});
return false;
......@@ -191,6 +188,9 @@ $(function () {
'name': result[i].name.toLowerCase(),
'state': result[i].state,
'fav': result[i].fav,
'host': result[i].host,
'icon': result[i].icon,
'status': result[i].status,
});
}
});
......@@ -207,7 +207,9 @@ $(function () {
}
search_result.sort(compareVmByFav);
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)
html += '<div class="list-group-item">No result</div>';
$("#dashboard-vm-list").html(html);
......@@ -233,21 +235,33 @@ $(function () {
});
});
function generateVmHTML(pk, name, fav) {
return '<a href="/dashboard/vm/' + pk + '/" class="list-group-item">' +
'<i class="icon-play-sign"></i> ' + name +
'<div class="pull-right dashboard-vm-favourite" data-vm="' + pk +'">' +
'<i class="title-favourite icon-star' + (fav ? "" : "-empty") + ' text-primary" title="" data-original-title="' +
(fav ? "Un": "Mark as ") + 'favourite"></i>' +
'</div>' +
'</a>';
function generateVmHTML(pk, name, host, icon, _status, fav) {
return '<a href="/dashboard/vm/' + pk + '/" class="list-group-item">' +
'<span class="index-vm-list-name">' +
'<i class="' + icon + '" title="' + _status + '"></i> ' + name +
'</span>' +
'<small class="text-muted"> ' + host + '</small>' +
'<div class="pull-right dashboard-vm-favourite" data-vm="' + pk + '">' +
(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) {
if(a.fav)
if(a.fav && b.fav) {
return a.pk < b.pk ? -1 : 1;
}
else if(a.fav && !b.fav) {
return -1;
else
}
else if(!a.fav && b.fav) {
return 1;
}
else
return a.pk < b.pk ? -1 : 1;
}
function addSliderMiscs() {
......
......@@ -14,7 +14,6 @@ function vmCreateLoaded() {
$(".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();
......@@ -142,7 +141,6 @@ function vmCustomizeLoaded() {
text = raw_text.replace("unmanaged -", "&#xf0c1;");
}
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("");
......@@ -152,8 +150,14 @@ function vmCustomizeLoaded() {
} else {
$('#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 */
$('#vm-create-network-add-vlan option').each(function() {
......@@ -197,24 +201,14 @@ function vmCustomizeLoaded() {
/* remove disk */
// event for disk remove button (icon, X)
$('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() {
/* 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 */
$(this).remove();
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 */
$('#vm-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', false);
if ($('#vm-create-disk-list').children('span').length < 1) {
......@@ -257,7 +251,11 @@ function vmCustomizeLoaded() {
data: $('form').serialize(),
success: function(data, textStatus, xhr) {
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 {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
......@@ -295,5 +293,6 @@ function vmCreateNetworkLabel(pk, name, managed) {
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 @@
</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>
{% 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" %}">
{% 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>
<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>
<div style="clear: both;"></div>
</div>
</div>
</div>
......
......@@ -16,8 +16,11 @@
<div id="dashboard-vm-list">
{% for i in instances %}
<a href="{{ i.get_absolute_url }}" class="list-group-item">
<i class="{{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i> {{ i.name }}
<small class="text-muted">{{ i.primary_host.hostname }}</small>
<span class="index-vm-list-name">
<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 }}">
{% if i.fav %}
<i class="icon-star text-primary title-favourite" title="{% trans "Unfavourite" %}"></i>
......@@ -25,6 +28,7 @@
<i class="icon-star-empty text-primary title-favourite" title="{% trans "Mark as favorite" %}"></i>
{% endif %}
</div>
<div style="clear: both;"></div>
</a>
{% endfor %}
</div>
......@@ -50,19 +54,24 @@
<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><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 %}
<li class="label label-success">
<a href="vm.get_absolute_url" title="{{vm.primary_host.get_fqdn}}"><i class="{{vm.get_status_icon}}"></i> {{vm.name}}</a>
</li>
<li style="display: inline-block; padding: 2px;">
<a href="{{vm.get_absolute_url}}" title="{{vm.primary_host.get_fqdn}}" class="label label-success">
<i class="{{vm.get_status_icon}}"></i> {{vm.name}}
</a>
</li>
{% endfor %}
</ul>
</p>
<p class="big text-warning">{% blocktrans with count=stopped_vm_num %}<big>{{ count }}</big> stopped{% endblocktrans %}</p>
<div class="clearfix"></div>
<div class="text-right">
<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>
<div>
<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>
......@@ -11,6 +11,7 @@
<div class="panel-body">
{% include template %}
</div>
</div>
</div>
{% endblock %}
......
......@@ -530,6 +530,24 @@ class VmDetailTest(LoginMixin, TestCase):
response = c.get("/dashboard/template/111111/")
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):
fixtures = ['test-vm-fixture.json', 'node.json']
......
......@@ -921,7 +921,9 @@ class VmList(LoginRequiredMixin, ListView):
instances = [{
'pk': i.pk,
'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]
return HttpResponse(
json.dumps(list(instances)), # instances is ValuesQuerySet
......@@ -1098,9 +1100,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
inst = Instance.create_from_template(
template=template, owner=user)
return self.__deploy(request, inst)
instances = [Instance.create_from_template(
template=template, owner=user)]
return self.__deploy(request, instances)
def __create_customized(self, request, *args, **kwargs):
user = request.user
......@@ -1129,17 +1131,33 @@ class VmCreate(LoginRequiredMixin, TemplateView):
networks = [InterfaceTemplate(vlan=l, managed=l.managed)
for l in post['networks']]
disks = post['disks']
inst = Instance.create_from_template(
template=template, owner=user, networks=networks,
disks=disks, **ikwargs)
return self.__deploy(request, inst)
ikwargs.update({
'template': template,
'owner': user,
'networks': networks,
'disks': disks,
})
amount = post['amount']
instances = Instance.mass_create_from_template(amount=amount,
**ikwargs)
return self.__deploy(request, instances)
else:
raise PermissionDenied()
def __deploy(self, request, instance, *args, **kwargs):
instance.deploy_async(user=request.user)
messages.success(request, _('VM successfully created!'))
path = instance.get_absolute_url()
def __deploy(self, request, instances, *args, **kwargs):
for i in instances:
i.deploy_async(user=request.user)
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():
return HttpResponse(json.dumps({'redirect': path}),
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