Commit 3b72505e by Bach Dániel

Merge branch 'master' into feature-store

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
	circle/dashboard/urls.py
	circle/dashboard/views.py
parents 822e58c5 89d48699
...@@ -260,6 +260,7 @@ THIRD_PARTY_APPS = ( ...@@ -260,6 +260,7 @@ THIRD_PARTY_APPS = (
'taggit', 'taggit',
'statici18n', 'statici18n',
'django_sshkey', 'django_sshkey',
'autocomplete_light',
) )
# Apps specific for this project go here. # Apps specific for this project go here.
......
...@@ -35,7 +35,11 @@ SOUTH_TESTS_MIGRATE = False ...@@ -35,7 +35,11 @@ SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += ( INSTALLED_APPS += (
'acl.tests', 'acl.tests',
'django_nose',
) )
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = ['--with-doctest']
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = { CACHES = {
'default': { 'default': {
......
...@@ -23,6 +23,7 @@ from logging import getLogger ...@@ -23,6 +23,7 @@ from logging import getLogger
from time import time from time import time
from warnings import warn from warnings import warn
from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.cache import cache from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
...@@ -46,17 +47,24 @@ class WorkerNotFound(Exception): ...@@ -46,17 +47,24 @@ class WorkerNotFound(Exception):
def activitycontextimpl(act, on_abort=None, on_commit=None): def activitycontextimpl(act, on_abort=None, on_commit=None):
try: try:
try:
yield act yield act
except HumanReadableException as e:
result = e
raise
except BaseException as e: except BaseException as e:
# BaseException is the common parent of Exception and # BaseException is the common parent of Exception and
# system-exiting exceptions, e.g. KeyboardInterrupt # system-exiting exceptions, e.g. KeyboardInterrupt
handler = None if on_abort is None else lambda a: on_abort(a, e) result = create_readable(
result = create_readable(ugettext_noop("Failure."), ugettext_noop("Failure."),
ugettext_noop("Unhandled exception: " ugettext_noop("Unhandled exception: %(error)s"),
"%(error)s"),
error=unicode(e)) error=unicode(e))
raise
except:
logger.exception("Failed activity %s" % unicode(act))
handler = None if on_abort is None else lambda a: on_abort(a, e)
act.finish(succeeded=False, result=result, event_handler=handler) act.finish(succeeded=False, result=result, event_handler=handler)
raise e raise
else: else:
act.finish(succeeded=True, event_handler=on_commit) act.finish(succeeded=True, event_handler=on_commit)
...@@ -70,11 +78,11 @@ activity_code_separator = '.' ...@@ -70,11 +78,11 @@ activity_code_separator = '.'
def has_prefix(activity_code, *prefixes): def has_prefix(activity_code, *prefixes):
"""Determine whether the activity code has the specified prefix. """Determine whether the activity code has the specified prefix.
E.g.: has_prefix('foo.bar.buz', 'foo.bar') == True >>> assert has_prefix('foo.bar.buz', 'foo.bar')
has_prefix('foo.bar.buz', 'foo', 'bar') == True >>> assert has_prefix('foo.bar.buz', 'foo', 'bar')
has_prefix('foo.bar.buz', 'foo.bar', 'buz') == True >>> assert has_prefix('foo.bar.buz', 'foo.bar', 'buz')
has_prefix('foo.bar.buz', 'foo', 'bar', 'buz') == True >>> assert has_prefix('foo.bar.buz', 'foo', 'bar', 'buz')
has_prefix('foo.bar.buz', 'foo', 'buz') == False >>> assert not has_prefix('foo.bar.buz', 'foo', 'buz')
""" """
equal = lambda a, b: a == b equal = lambda a, b: a == b
act_code_parts = split_activity_code(activity_code) act_code_parts = split_activity_code(activity_code)
...@@ -85,11 +93,11 @@ def has_prefix(activity_code, *prefixes): ...@@ -85,11 +93,11 @@ def has_prefix(activity_code, *prefixes):
def has_suffix(activity_code, *suffixes): def has_suffix(activity_code, *suffixes):
"""Determine whether the activity code has the specified suffix. """Determine whether the activity code has the specified suffix.
E.g.: has_suffix('foo.bar.buz', 'bar.buz') == True >>> assert has_suffix('foo.bar.buz', 'bar.buz')
has_suffix('foo.bar.buz', 'bar', 'buz') == True >>> assert has_suffix('foo.bar.buz', 'bar', 'buz')
has_suffix('foo.bar.buz', 'foo.bar', 'buz') == True >>> assert has_suffix('foo.bar.buz', 'foo.bar', 'buz')
has_suffix('foo.bar.buz', 'foo', 'bar', 'buz') == True >>> assert has_suffix('foo.bar.buz', 'foo', 'bar', 'buz')
has_suffix('foo.bar.buz', 'foo', 'buz') == False >>> assert not has_suffix('foo.bar.buz', 'foo', 'buz')
""" """
equal = lambda a, b: a == b equal = lambda a, b: a == b
act_code_parts = split_activity_code(activity_code) act_code_parts = split_activity_code(activity_code)
...@@ -196,6 +204,10 @@ class ActivityModel(TimeStampedModel): ...@@ -196,6 +204,10 @@ class ActivityModel(TimeStampedModel):
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
value = create_readable(user_text_template="", value = create_readable(user_text_template="",
admin_text_template=value) admin_text_template=value)
elif not hasattr(value, "to_dict"):
warn("Use HumanReadableObject.", DeprecationWarning, stacklevel=2)
value = create_readable(user_text_template="",
admin_text_template=unicode(value))
self.result_data = None if value is None else value.to_dict() self.result_data = None if value is None else value.to_dict()
...@@ -361,8 +373,9 @@ class HumanReadableObject(object): ...@@ -361,8 +373,9 @@ class HumanReadableObject(object):
@classmethod @classmethod
def create(cls, user_text_template, admin_text_template=None, **params): def create(cls, user_text_template, admin_text_template=None, **params):
return cls(user_text_template, return cls(user_text_template=user_text_template,
admin_text_template or user_text_template, params) admin_text_template=(admin_text_template
or user_text_template), params=params)
def set(self, user_text_template, admin_text_template=None, **params): def set(self, user_text_template, admin_text_template=None, **params):
self._set_values(user_text_template, self._set_values(user_text_template,
...@@ -375,12 +388,22 @@ class HumanReadableObject(object): ...@@ -375,12 +388,22 @@ class HumanReadableObject(object):
def get_admin_text(self): def get_admin_text(self):
if self.admin_text_template == "": if self.admin_text_template == "":
return "" return ""
try:
return _(self.admin_text_template) % self.params return _(self.admin_text_template) % self.params
except KeyError:
logger.exception("Can't render admin_text_template '%s' %% %s",
self.admin_text_template, unicode(self.params))
return self.get_user_text()
def get_user_text(self): def get_user_text(self):
if self.user_text_template == "": if self.user_text_template == "":
return "" return ""
try:
return _(self.user_text_template) % self.params return _(self.user_text_template) % self.params
except KeyError:
logger.exception("Can't render user_text_template '%s' %% %s",
self.user_text_template, unicode(self.params))
return self.user_text_template
def to_dict(self): def to_dict(self):
return {"user_text_template": self.user_text_template, return {"user_text_template": self.user_text_template,
...@@ -397,10 +420,28 @@ create_readable = HumanReadableObject.create ...@@ -397,10 +420,28 @@ create_readable = HumanReadableObject.create
class HumanReadableException(HumanReadableObject, Exception): class HumanReadableException(HumanReadableObject, Exception):
"""HumanReadableObject that is an Exception so can used in except clause. """HumanReadableObject that is an Exception so can used in except clause.
""" """
pass def __init__(self, level=None, *args, **kwargs):
super(HumanReadableException, self).__init__(*args, **kwargs)
if level is not None:
if hasattr(messages, level):
self.level = level
else:
raise ValueError(
"Level should be the name of an attribute of django."
"contrib.messages (and it should be callable with "
"(request, message)). Like 'error', 'warning'.")
else:
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()
getattr(messages, level or self.level)(request, msg)
def humanize_exception(message, exception=None, **params): def humanize_exception(message, exception=None, level=None, **params):
"""Return new dynamic-class exception which is based on """Return new dynamic-class exception which is based on
HumanReadableException and the original class with the dict of exception. HumanReadableException and the original class with the dict of exception.
...@@ -409,8 +450,10 @@ def humanize_exception(message, exception=None, **params): ...@@ -409,8 +450,10 @@ def humanize_exception(message, exception=None, **params):
... ...
Welcome! Welcome!
""" """
Ex = type("HumanReadable" + type(exception).__name__, Ex = type("HumanReadable" + type(exception).__name__,
(HumanReadableException, type(exception)), (HumanReadableException, type(exception)),
exception.__dict__) exception.__dict__)
return Ex.create(message, **params) ex = Ex.create(message, **params)
if level:
ex.level = level
return ex
import autocomplete_light
from django.utils.translation import ugettext as _
from .views import AclUpdateView
class AclUserAutocomplete(autocomplete_light.AutocompleteGenericBase):
search_fields = (
('^first_name', 'last_name', 'username', '^email', 'profile__org_id'),
('^name', 'groupprofile__org_id'),
)
autocomplete_js_attributes = {'placeholder': _("Name of group or user")}
choice_html_format = u'<span data-value="%s"><span>%s</span> %s</span>'
def choice_html(self, choice):
try:
name = choice.get_full_name()
except AttributeError:
name = _('group')
if name:
name = u'(%s)' % name
return self.choice_html_format % (
self.choice_value(choice), self.choice_label(choice), name)
def choices_for_request(self):
user = self.request.user
self.choices = (AclUpdateView.get_allowed_users(user),
AclUpdateView.get_allowed_groups(user))
return super(AclUserAutocomplete, self).choices_for_request()
autocomplete_light.register(AclUserAutocomplete)
...@@ -1322,7 +1322,7 @@ ...@@ -1322,7 +1322,7 @@
"user_permissions": [ "user_permissions": [
115 115
], ],
"password": "pbkdf2_sha256$10000$KIoeMs78MiOj$PnVXn3YJMehbOciBO32CMzqL0ZnQrzrdb7+b5dE13os=", "password": "md5$qLN4mQMOrsUJ$f07129fd1a289a0afb4e09f7a6816a4f",
"email": "test@example.org", "email": "test@example.org",
"date_joined": "2013-09-04T15:29:49.914Z" "date_joined": "2013-09-04T15:29:49.914Z"
} }
......
...@@ -27,6 +27,7 @@ from django.contrib.auth.models import User, Group ...@@ -27,6 +27,7 @@ from django.contrib.auth.models import User, Group
from django.core.validators import URLValidator from django.core.validators import URLValidator
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
import autocomplete_light
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK, Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK,
...@@ -44,7 +45,6 @@ from django.core.urlresolvers import reverse_lazy ...@@ -44,7 +45,6 @@ from django.core.urlresolvers import reverse_lazy
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from storage.models import Disk
from vm.models import ( from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
) )
...@@ -54,6 +54,7 @@ from .models import Profile, GroupProfile ...@@ -54,6 +54,7 @@ from .models import Profile, GroupProfile
from circle.settings.base import LANGUAGES from circle.settings.base import LANGUAGES
from django.utils.translation import string_concat from django.utils.translation import string_concat
from .virtvalidator import domain_validator
LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")")) LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")"))
for l in LANGUAGES) for l in LANGUAGES)
...@@ -78,7 +79,7 @@ class VmCustomizeForm(forms.Form): ...@@ -78,7 +79,7 @@ class VmCustomizeForm(forms.Form):
amount = forms.IntegerField(min_value=0, initial=1) amount = forms.IntegerField(min_value=0, initial=1)
disks = forms.ModelMultipleChoiceField( disks = forms.ModelMultipleChoiceField(
queryset=None, required=True) queryset=None, required=False)
networks = forms.ModelMultipleChoiceField( networks = forms.ModelMultipleChoiceField(
queryset=None, required=False) queryset=None, required=False)
...@@ -91,8 +92,7 @@ class VmCustomizeForm(forms.Form): ...@@ -91,8 +92,7 @@ class VmCustomizeForm(forms.Form):
super(VmCustomizeForm, self).__init__(*args, **kwargs) super(VmCustomizeForm, self).__init__(*args, **kwargs)
# set displayed disk and network list # set displayed disk and network list
self.fields['disks'].queryset = Disk.get_objects_with_level( self.fields['disks'].queryset = self.template.disks.all()
'user', self.user).exclude(type="qcow2-snap")
self.fields['networks'].queryset = Vlan.get_objects_with_level( self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user) 'user', self.user)
...@@ -596,6 +596,10 @@ class TemplateForm(forms.ModelForm): ...@@ -596,6 +596,10 @@ class TemplateForm(forms.ModelForm):
n = self.instance.interface_set.values_list("vlan", flat=True) n = self.instance.interface_set.values_list("vlan", flat=True)
self.initial['networks'] = n self.initial['networks'] = n
if self.instance.pk and not self.instance.has_level(self.user,
'owner'):
self.allowed_fields = ()
else:
self.allowed_fields = ( self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags') 'name', 'access_method', 'description', 'system', 'tags')
if self.user.has_perm('vm.change_template_resources'): if self.user.has_perm('vm.change_template_resources'):
...@@ -675,6 +679,11 @@ class TemplateForm(forms.ModelForm): ...@@ -675,6 +679,11 @@ class TemplateForm(forms.ModelForm):
@property @property
def helper(self): def helper(self):
submit_kwargs = {}
if self.instance.pk and not self.instance.has_level(self.user,
'owner'):
submit_kwargs['disabled'] = None
helper = FormHelper() helper = FormHelper()
helper.layout = Layout( helper.layout = Layout(
Field("name"), Field("name"),
...@@ -739,7 +748,7 @@ class TemplateForm(forms.ModelForm): ...@@ -739,7 +748,7 @@ class TemplateForm(forms.ModelForm):
Field("tags"), Field("tags"),
), ),
) )
helper.add_input(Submit('submit', 'Save changes')) helper.add_input(Submit('submit', 'Save changes', **submit_kwargs))
return helper return helper
class Meta: class Meta:
...@@ -900,7 +909,8 @@ class VmRenewForm(forms.Form): ...@@ -900,7 +909,8 @@ class VmRenewForm(forms.Form):
self.fields['lease'] = forms.ModelChoiceField(queryset=choices, self.fields['lease'] = forms.ModelChoiceField(queryset=choices,
initial=default, initial=default,
required=True, required=False,
empty_label=None,
label=_('Length')) label=_('Length'))
if len(choices) < 2: if len(choices) < 2:
self.fields['lease'].widget = HiddenInput() self.fields['lease'].widget = HiddenInput()
...@@ -944,6 +954,25 @@ class VmDownloadDiskForm(forms.Form): ...@@ -944,6 +954,25 @@ class VmDownloadDiskForm(forms.Form):
return helper return helper
class VmAddInterfaceForm(forms.Form):
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
super(VmAddInterfaceForm, self).__init__(*args, **kwargs)
field = forms.ModelChoiceField(
queryset=choices, required=True, label=_('Vlan'))
if not choices:
field.widget.attrs['disabled'] = 'disabled'
field.empty_label = _('No more networks.')
self.fields['vlan'] = field
@property
def helper(self):
helper = FormHelper(self)
helper.form_tag = False
return helper
class CircleAuthenticationForm(AuthenticationForm): class CircleAuthenticationForm(AuthenticationForm):
# fields: username, password # fields: username, password
...@@ -1178,6 +1207,11 @@ class UserCreationForm(OrgUserCreationForm): ...@@ -1178,6 +1207,11 @@ class UserCreationForm(OrgUserCreationForm):
return user return user
class AclUserAddForm(forms.Form):
name = forms.CharField(widget=autocomplete_light.TextWidget(
'AclUserAutocomplete', attrs={'class': 'form-control'}))
class UserKeyForm(forms.ModelForm): class UserKeyForm(forms.ModelForm):
name = forms.CharField(required=True, label=_('Name')) name = forms.CharField(required=True, label=_('Name'))
key = forms.CharField( key = forms.CharField(
...@@ -1223,6 +1257,9 @@ class TraitsForm(forms.ModelForm): ...@@ -1223,6 +1257,9 @@ class TraitsForm(forms.ModelForm):
class RawDataForm(forms.ModelForm): class RawDataForm(forms.ModelForm):
raw_data = forms.CharField(validators=[domain_validator],
widget=forms.Textarea(attrs={'rows': 5}),
required=False)
class Meta: class Meta:
model = Instance model = Instance
......
...@@ -77,7 +77,7 @@ class Notification(TimeStampedModel): ...@@ -77,7 +77,7 @@ class Notification(TimeStampedModel):
def send(cls, user, subject, template, context, def send(cls, user, subject, template, context,
valid_until=None, subject_context=None): valid_until=None, subject_context=None):
hro = create_readable(template, user=user, **context) hro = create_readable(template, user=user, **context)
subject = create_readable(subject, subject_context or context) subject = create_readable(subject, **(subject_context or context))
return cls.objects.create(to=user, return cls.objects.create(to=user,
subject_data=subject.to_dict(), subject_data=subject.to_dict(),
message_data=hro.to_dict(), message_data=hro.to_dict(),
...@@ -161,6 +161,11 @@ class Profile(Model): ...@@ -161,6 +161,11 @@ class Profile(Model):
def __unicode__(self): def __unicode__(self):
return self.get_display_name() return self.get_display_name()
class Meta:
permissions = (
('use_autocomplete', _('Can use autocomplete.')),
)
class FutureMember(Model): class FutureMember(Model):
org_id = CharField(max_length=64, help_text=_( org_id = CharField(max_length=64, help_text=_(
......
...@@ -844,3 +844,7 @@ textarea[name="list-new-namelist"] { ...@@ -844,3 +844,7 @@ textarea[name="list-new-namelist"] {
height: 20px; height: 20px;
position: absolute; position: absolute;
} }
#show-all-activities-container {
margin: 20px 0 0 10px;
}
...@@ -56,8 +56,6 @@ $(function () { ...@@ -56,8 +56,6 @@ $(function () {
url: '/dashboard/template/choose/', url: '/dashboard/template/choose/',
success: function(data) { success: function(data) {
$('body').append(data); $('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#create-modal').modal('show'); $('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() { $('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove(); $('#create-modal').remove();
...@@ -372,6 +370,11 @@ $(function () { ...@@ -372,6 +370,11 @@ $(function () {
return false; return false;
}); });
/* don't close notifications window on missclick */
$(document).on("click", ".notification-messages", function() {
return false;
});
$("#notification-button a").click(function() { $("#notification-button a").click(function() {
$('.notification-messages').load("/dashboard/notifications/"); $('.notification-messages').load("/dashboard/notifications/");
$('#notification-button a span[class*="badge-pulse"]').remove(); $('#notification-button a span[class*="badge-pulse"]').remove();
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
$(function() { $(function() {
/* vm operations */ /* vm operations */
$('#ops, #vm-details-resources-disk').on('click', '.operation.btn', function(e) { $('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
$.ajax({ $.ajax({
...@@ -50,6 +50,9 @@ $(function() { ...@@ -50,6 +50,9 @@ $(function() {
*/ */
if(data.success) { if(data.success) {
$('a[href="#activity"]').trigger("click"); $('a[href="#activity"]').trigger("click");
if(data.with_reload) {
location.reload();
}
/* if there are messages display them */ /* if there are messages display them */
if(data.messages && data.messages.length > 0) { if(data.messages && data.messages.length > 0) {
......
var show_all = false;
var in_progress = false;
$(function() { $(function() {
/* do we need to check for new activities */ /* do we need to check for new activities */
if(decideActivityRefresh()) { if(decideActivityRefresh()) {
checkNewActivity(false, 1); if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
} }
$('a[href="#activity"]').click(function(){ $('a[href="#activity"]').click(function(){
$('a[href="#activity"] i').addClass('fa-spin'); $('a[href="#activity"] i').addClass('fa-spin');
checkNewActivity(false, 1); if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
});
$("#activity-refresh").on("click", "#show-all-activities", function() {
$(this).find("i").addClass("fa-spinner fa-spin");
show_all = !show_all;
$('a[href="#activity"]').trigger("click");
return false;
}); });
/* save resources */ /* save resources */
...@@ -134,11 +151,6 @@ $(function() { ...@@ -134,11 +151,6 @@ $(function() {
return false; return false;
}); });
/* show help */
$(".vm-details-help-button").click(function() {
$(".vm-details-help").stop().slideToggle();
});
/* for interface remove buttons */ /* for interface remove buttons */
$('.interface-remove').click(function() { $('.interface-remove').click(function() {
var interface_pk = $(this).data('interface-pk'); var interface_pk = $(this).data('interface-pk');
...@@ -295,6 +307,10 @@ $(function() { ...@@ -295,6 +307,10 @@ $(function() {
$("#vm-details-connection-string").focus(); $("#vm-details-connection-string").focus();
}); });
$("a.operation-password_reset").click(function() {
if(Boolean($(this).data("disabled"))) return false;
});
}); });
...@@ -315,12 +331,13 @@ function removePort(data) { ...@@ -315,12 +331,13 @@ function removePort(data) {
} }
}); });
} }
function decideActivityRefresh() { function decideActivityRefresh() {
var check = false; var check = false;
/* if something is still spinning */ /* if something is still spinning */
if($('.timeline .activity:first i:first').hasClass('fa-spin')) if($('.timeline .activity i').hasClass('fa-spin'))
check = true; check = true;
/* if there is only one activity */ /* if there is only one activity */
if($('#activity-timeline div[class="activity"]').length < 2) if($('#activity-timeline div[class="activity"]').length < 2)
...@@ -340,25 +357,25 @@ function changeHTML(html) { ...@@ -340,25 +357,25 @@ function changeHTML(html) {
return html.replace(/data-original-title/g, "title").replace(/title=""/g, "").replace(/\//g, '').replace(/ /g, ''); return html.replace(/data-original-title/g, "title").replace(/title=""/g, "").replace(/\//g, '').replace(/ /g, '');
} }
function checkNewActivity(only_status, runs) { function checkNewActivity(runs) {
// set default only_status to false
only_status = typeof only_status !== 'undefined' ? only_status : false;
var instance = location.href.split('/'); instance = instance[instance.length - 2]; var instance = location.href.split('/'); instance = instance[instance.length - 2];
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: '/dashboard/vm/' + instance + '/activity/', url: '/dashboard/vm/' + instance + '/activity/',
data: {'only_status': only_status}, data: {'show_all': show_all},
success: function(data) { success: function(data) {
if(!only_status) { if(show_all) { /* replace on longer string freezes the spinning stuff */
$("#activity-refresh").html(data['activities']);
} else {
a = unescapeHTML(data['activities']); a = unescapeHTML(data['activities']);
b = changeHTML($("#activity-timeline").html()); b = changeHTML($("#activity-refresh").html());
if(a != b) if(a != b)
$("#activity-timeline").html(data['activities']); $("#activity-refresh").html(data['activities']);
}
$("#ops").html(data['ops']); $("#ops").html(data['ops']);
$("#disk-ops").html(data['disk_ops']); $("#disk-ops").html(data['disk_ops']);
$("[title]").tooltip(); $("[title]").tooltip();
}
$("#vm-details-state i").prop("class", "fa " + data['icon']); $("#vm-details-state i").prop("class", "fa " + data['icon']);
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase()); $("#vm-details-state span").html(data['human_readable_status'].toUpperCase());
...@@ -378,14 +395,16 @@ function checkNewActivity(only_status, runs) { ...@@ -378,14 +395,16 @@ function checkNewActivity(only_status, runs) {
if(runs > 0 && decideActivityRefresh()) { if(runs > 0 && decideActivityRefresh()) {
setTimeout( setTimeout(
function() {checkNewActivity(only_status, runs + 1)}, function() {checkNewActivity(runs + 1)},
1000 + Math.exp(runs * 0.05) 1000 + Math.exp(runs * 0.05)
); );
} else {
in_progress = false;
} }
$('a[href="#activity"] i').removeClass('fa-spin'); $('a[href="#activity"] i').removeClass('fa-spin');
}, },
error: function() { error: function() {
in_progress = false;
} }
}); });
}