Commit db31f971 by Kálmán Viktor

Merge branch 'master' into custom-connect-command

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
parents d0ae4fc4 4268ac9d
......@@ -368,9 +368,9 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
from shutilwhich import which
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
# INSTALLED_APPS += ( # needed only for testing djangosaml2
# 'djangosaml',
# )
INSTALLED_APPS += (
'djangosaml2',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'djangosaml2.backends.Saml2Backend',
......
......@@ -27,6 +27,7 @@ from warnings import warn
from django.contrib import messages
from django.contrib.auth.models import User
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import (
CharField, DateTimeField, ForeignKey, NullBooleanField
......@@ -413,6 +414,10 @@ class HumanReadableObject(object):
self._set_values(user_text_template, admin_text_template, params)
def _set_values(self, user_text_template, admin_text_template, params):
if isinstance(user_text_template, Promise):
user_text_template = user_text_template._proxy____args[0]
if isinstance(admin_text_template, Promise):
admin_text_template = admin_text_template._proxy____args[0]
self.user_text_template = user_text_template
self.admin_text_template = admin_text_template
self.params = params
......@@ -451,6 +456,12 @@ class HumanReadableObject(object):
self.user_text_template, unicode(self.params))
return self.user_text_template
def get_text(self, user):
if user and user.is_superuser:
return self.get_admin_text()
else:
return self.get_user_text()
def to_dict(self):
return {"user_text_template": self.user_text_template,
"admin_text_template": self.admin_text_template,
......@@ -481,13 +492,34 @@ class HumanReadableException(HumanReadableObject, Exception):
self.level = "error"
def send_message(self, request, level=None):
if request.user and request.user.is_superuser:
msg = self.get_admin_text()
else:
msg = self.get_user_text()
msg = self.get_text(request.user)
getattr(messages, level or self.level)(request, msg)
def fetch_human_exception(exception, user=None):
"""Fetch user readable message from exception.
>>> r = humanize_exception("foo", Exception())
>>> fetch_human_exception(r, User())
u'foo'
>>> fetch_human_exception(r).get_text(User())
u'foo'
>>> fetch_human_exception(Exception(), User())
u'Unknown error'
>>> fetch_human_exception(PermissionDenied(), User())
u'Permission Denied'
"""
if not isinstance(exception, HumanReadableException):
if isinstance(exception, PermissionDenied):
exception = create_readable(ugettext_noop("Permission Denied"))
else:
exception = create_readable(ugettext_noop("Unknown error"),
ugettext_noop("Unknown error: %(ex)s"),
ex=unicode(exception))
return exception.get_text(user) if user else exception
def humanize_exception(message, exception=None, level=None, **params):
"""Return new dynamic-class exception which is based on
HumanReadableException and the original class with the dict of exception.
......
......@@ -18,10 +18,10 @@
from inspect import getargspec
from logging import getLogger
from .models import activity_context, has_suffix
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.utils.translation import ugettext_noop
from .models import activity_context, has_suffix, humanize_exception
logger = getLogger(__name__)
......@@ -31,6 +31,7 @@ class Operation(object):
"""
async_queue = 'localhost.man'
required_perms = None
superuser_required = False
do_not_call_in_templates = True
abortable = False
has_percentage = False
......@@ -143,13 +144,26 @@ class Operation(object):
def check_precond(self):
pass
def check_auth(self, user):
if self.required_perms is None:
@classmethod
def check_perms(cls, user):
"""Check if user is permitted to run this operation on any instance
"""
if cls.required_perms is None:
raise ImproperlyConfigured(
"Set required_perms to () if none needed.")
if not user.has_perms(self.required_perms):
if not user.has_perms(cls.required_perms):
raise PermissionDenied("%s doesn't have the required permissions."
% user)
if cls.superuser_required and not user.is_superuser:
raise humanize_exception(ugettext_noop(
"Superuser privileges are required."), PermissionDenied())
def check_auth(self, user):
"""Check if user is permitted to run this operation on this instance
"""
self.check_perms(user)
def create_activity(self, parent, user, kwargs):
raise NotImplementedError
......@@ -185,14 +199,17 @@ class OperatedMixin(object):
def __getattr__(self, name):
# NOTE: __getattr__ is only called if the attribute doesn't already
# exist in your __dict__
cls = self.__class__
return self.get_operation_class(name)(self)
@classmethod
def get_operation_class(cls, name):
ops = getattr(cls, operation_registry_name, {})
op = ops.get(name)
if op:
return op(self)
return op
else:
raise AttributeError("%r object has no attribute %r" %
(self.__class__.__name__, name))
(cls.__name__, name))
def get_available_operations(self, user):
"""Yield Operations that match permissions of user and preconditions.
......
......@@ -636,12 +636,8 @@ class LeaseForm(forms.ModelForm):
Field('name'),
Field("suspend_interval_seconds", type="hidden", value="0"),
Field("delete_interval_seconds", type="hidden", value="0"),
HTML(string_concat("<label>", _("Suspend in"), "</label>")),
Div(
Div(
HTML(_("Suspend in")),
css_class="input-group-addon",
style="width: 100px;",
),
NumberField("suspend_hours", css_class="form-control"),
Div(
HTML(_("hours")),
......@@ -664,12 +660,8 @@ class LeaseForm(forms.ModelForm):
),
css_class="input-group interval-input",
),
HTML(string_concat("<label>", _("Delete in"), "</label>")),
Div(
Div(
HTML(_("Delete in")),
css_class="input-group-addon",
style="width: 100px;",
),
NumberField("delete_hours", css_class="form-control"),
Div(
HTML(_("hours")),
......@@ -693,7 +685,7 @@ class LeaseForm(forms.ModelForm):
css_class="input-group interval-input",
)
)
helper.add_input(Submit("submit", "Save changes"))
helper.add_input(Submit("submit", _("Save changes")))
return helper
class Meta:
......@@ -705,6 +697,8 @@ class VmRenewForm(forms.Form):
force = forms.BooleanField(required=False, label=_(
"Set expiration times even if they are shorter than "
"the current value."))
save = forms.BooleanField(required=False, label=_(
"Save selected lease."))
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
......@@ -716,6 +710,32 @@ class VmRenewForm(forms.Form):
empty_label=None, label=_('Length')))
if len(choices) < 2:
self.fields['lease'].widget = HiddenInput()
self.fields['save'].widget = HiddenInput()
@property
def helper(self):
helper = FormHelper(self)
helper.form_tag = False
return helper
class VmStateChangeForm(forms.Form):
interrupt = forms.BooleanField(required=False, label=_(
"Forcibly interrupt all running activities."),
help_text=_("Set all activities to finished state, "
"but don't interrupt any tasks."))
new_state = forms.ChoiceField(Instance.STATUS, label=_(
"New status"))
def __init__(self, *args, **kwargs):
show_interrupt = kwargs.pop('show_interrupt')
status = kwargs.pop('status')
super(VmStateChangeForm, self).__init__(*args, **kwargs)
if not show_interrupt:
self.fields['interrupt'].widget = HiddenInput()
self.fields['new_state'].initial = status
@property
def helper(self):
......@@ -1164,9 +1184,9 @@ class VmResourcesForm(forms.ModelForm):
vm_search_choices = (
(0, _("owned")),
(1, _("shared")),
(2, _("all")),
("owned", _("owned")),
("shared", _("shared")),
("all", _("all")),
)
......@@ -1185,5 +1205,5 @@ class VmListSearchForm(forms.Form):
# set initial value, otherwise it would be overwritten by request.GET
if not self.data.get("stype"):
data = self.data.copy()
data['stype'] = 2
data['stype'] = "all"
self.data = data
......@@ -874,3 +874,75 @@ textarea[name="list-new-namelist"] {
text-align: center;
vertical-align: middle;
}
#vm-list-table .migrating-icon {
-webkit-animation: passing 2s linear infinite;
animation: passing 2s linear infinite;
}
@-webkit-keyframes passing {
0% {
-webkit-transform: translateX(50%);
transform: translateX(50%);
opacity: 0;
}
50% {
-webkit-transform: translateX(0%);
transform: translateX(0%);
opacity: 1;
}
100% {
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
opacity: 0;
}
}
@keyframes passing {
0% {
-webkit-transform: translateX(50%);
-ms-transform: translateX(50%);
transform: translateX(50%);
opacity: 0;
}
50% {
-webkit-transform: translateX(0%);
-ms-transform: translateX(0%);
transform: translateX(0%);
opacity: 1;
}
100% {
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
opacity: 0;
}
}
.mass-migrate-node {
cursor: pointer;
}
.mass-op-panel {
padding: 6px 10px;
}
.mass-op-panel .check {
color: #449d44;
}
.mass-op-panel .minus {
color: #d9534f;
}
.mass-op-panel .status-icon {
font-size: .8em;
}
#vm-list-search, #vm-mass-ops {
margin-top: 8px;
}
......@@ -262,7 +262,7 @@ $(function () {
$("#dashboard-vm-search-form").submit(function() {
var vm_list_items = $("#dashboard-vm-list .list-group-item");
if(vm_list_items.length == 1) {
if(vm_list_items.length == 1 && vm_list_items.first().prop("href")) {
window.location.href = vm_list_items.first().prop("href");
return false;
}
......@@ -488,14 +488,19 @@ function addSliderMiscs() {
ram_fire = true;
$(".ram-slider").simpleSlider("setValue", parseInt(val));
});
$(".cpu-priority-input").trigger("change");
$(".cpu-count-input, .ram-input").trigger("input");
setDefaultSliderValues();
$(".cpu-priority-slider").simpleSlider("setDisabled", $(".cpu-priority-input").prop("disabled"));
$(".cpu-count-slider").simpleSlider("setDisabled", $(".cpu-count-input").prop("disabled"));
$(".ram-slider").simpleSlider("setDisabled", $(".ram-input").prop("disabled"));
}
function setDefaultSliderValues() {
$(".cpu-priority-input").trigger("change");
$(".ram-input, .cpu-count-input").trigger("input");
}
/* deletes the VM with the pk
* if dir is true, then redirect to the dashboard landing page
......
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
var shiftKey = 16;
var selected = [];
$(function() {
$(document).keydown(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = true;
if (e.keyCode == shiftKey) shiftDown = true;
}).keyup(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = false;
if (e.keyCode == shiftKey) shiftDown = false;
});
$('.group-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('group-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1);
} else {
selected.push($(this).index());
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
end = $(this).index();
if(start > end) {
var tmp = start - 1; start = end; end = tmp - 1;
}
for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) {
selected.push(i);
setRowColor($('.group-list-table tbody tr').eq(i));
}
}
}
retval = false;
} else {
$('.group-list-selected').removeClass('group-list-selected');
$(this).addClass('group-list-selected');
selected = [$(this).index()];
}
// reset btn disables
$('.group-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 1) {
$('.group-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.group-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
}
} else {
$('.group-list-group-control a').attr('disabled', true);
}
return retval;
});
$('#group-list-group-migrate').click(function() {
console.log(collectIds(selected));
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
return false;
}
});
/* rename */
$("#group-list-rename-button, .group-details-rename-button").click(function() {
$("#group-list-column-name", $(this).closest("tr")).hide();
......@@ -113,51 +34,4 @@ $(function() {
return false;
});
/* group actions */
/* select all */
$('#group-list-group-select-all').click(function() {
$('.group-list-table tbody tr').each(function() {
var index = $(this).index();
if(selected.indexOf(index) < 0) {
selected.push(index);
$(this).addClass('group-list-selected');
}
});
if(selected.length > 0)
$('.group-list-group-control a').attr('disabled', false);
return false;
});
/* mass vm delete */
$('#group-list-group-delete').click(function() {
addModalConfirmation(massDeleteVm,
{
'url': '/dashboard/group/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
}
}
);
return false;
});
});
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.group-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('node-', ''));
}
return ids;
}
function setRowColor(row) {
if(!row.hasClass('group-list-selected')) {
row.addClass('group-list-selected');
} else {
row.removeClass('group-list-selected');
}
}
......@@ -28,6 +28,9 @@ function vmCreateLoaded() {
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
$("#create-modal").on("shown.bs.modal", function() {
setDefaultSliderValues();
});
});
return false;
});
......@@ -217,6 +220,8 @@ function vmCustomizeLoaded() {
});
if(error) return true;
$(this).find("i").prop("class", "fa fa-spinner fa-spin");
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
......
......@@ -14,6 +14,7 @@ $(function() {
$('.vm-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if(!$(this).data("vm-pk")) return;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('vm-list-selected')) {
......@@ -46,86 +47,20 @@ $(function() {
selected = [{'index': $(this).index(), 'vm': $(this).data("vm-pk")}];
}
// reset btn disables
$('.vm-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 0) {
$('.vm-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.vm-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
}
$('#vm-mass-ops .mass-operation').attr('disabled', false);
} else {
$('.vm-list-group-control a').attr('disabled', true);
$('#vm-mass-ops .mass-operation').attr('disabled', true);
}
return retval;
});
$('#vm-list-group-migrate').click(function() {
// pass?
});
$('.vm-list-details').popover({
'placement': 'auto',
'html': true,
'trigger': 'hover'
});
$('.vm-list-connect').popover({
'placement': 'left',
'html': true,
'trigger': 'click'
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
return false;
}
});
/* rename */
$("#vm-list-rename-button, .vm-details-rename-button").click(function() {
$("#vm-list-column-name", $(this).closest("tr")).hide();
$("#vm-list-rename", $(this).closest("tr")).css('display', 'inline');
$("#vm-list-rename-name", $(this).closest("tr")).focus();
});
/* rename ajax */
$('.vm-list-rename-submit').click(function() {
var row = $(this).closest("tr")
var name = $('#vm-list-rename-name', row).val();
var url = '/dashboard/vm/' + row.children("td:first-child").text().replace(" ", "") + '/';
$.ajax({
method: 'POST',
url: url,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#vm-list-column-name", row).html(
$("<a/>", {
'class': "real-link",
href: "/dashboard/vm/" + data['vm_pk'] + "/",
text: data['new_name']
})
).show();
$('#vm-list-rename', row).hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
/* group actions */
/* select all */
......@@ -133,27 +68,69 @@ $(function() {
$('.vm-list-table tbody tr').each(function() {
var index = $(this).index();
var vm = $(this).data("vm-pk");
if(!isAlreadySelected(vm)) {
if(vm && !isAlreadySelected(vm)) {
selected.push({'index': index, 'vm': vm});
$(this).addClass('vm-list-selected');
}
});
if(selected.length > 0)
$('.vm-list-group-control a').attr('disabled', false);
$('#vm-mass-ops .mass-operation').attr('disabled', false);
return false;
});
/* mass vm delete */
$('#vm-list-group-delete').click(function() {
addModalConfirmation(massDeleteVm,
{
'url': '/dashboard/vm/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
/* mass operations */
$("#vm-mass-ops").on('click', '.mass-operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin');
params = "?" + selected.map(function(a){return "vm=" + a.vm}).join("&");
$.ajax({
type: 'GET',
url: $(this).attr('href') + params,
success: function(data) {
icon.removeClass("fa-spinner fa-spin");
$('body').append(data);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
$("[title]").tooltip({'placement': "left"});
}
});
return false;
});
$("body").on("click", "#op-form-send", function() {
var url = $(this).closest("form").prop("action");
$(this).find("i").prop("class", "fa fa-fw fa-spinner fa-spin");
$.ajax({
url: url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: $(this).closest('form').serialize(),
success: function(data, textStatus, xhr) {
/* hide the modal we just submitted */
$('#confirmation-modal').modal("hide");
updateStatuses(1);
/* if there are messages display them */
if(data.messages && data.messages.length > 0) {
addMessage(data.messages.join("<br />"), "danger");
}
},
error: function(xhr, textStatus, error) {
$('#confirmation-modal').modal("hide");
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " " + xhr.statusText, "danger");
}
}
);
});
return false;
});
......@@ -181,8 +158,65 @@ $(function() {
$(".vm-list-table th a").on("click", function(event) {
event.preventDefault();
});
$(document).on("click", ".mass-migrate-node", function() {
$(this).find('input[type="radio"]').prop("checked", true);
});
if(checkStatusUpdate()) {
updateStatuses(1);
}
});
function checkStatusUpdate() {
icons = $("#vm-list-table tbody td.state i");
if(icons.hasClass("fa-spin") || icons.hasClass("migrating-icon")) {
return true;
}
}
function updateStatuses(runs) {
$.get("/dashboard/vm/list/?compact", function(result) {
$("#vm-list-table tbody tr").each(function() {
vm = $(this).data("vm-pk");
status_td = $(this).find("td.state");
status_icon = status_td.find("i");
status_text = status_td.find("span");
if(vm in result) {
if(result[vm].in_status_change) {
if(!status_icon.hasClass("fa-spin")) {
status_icon.prop("class", "fa fa-fw fa-spinner fa-spin");
}