diff --git a/circle/circle/settings/base.py b/circle/circle/settings/base.py index c8c5454..e90c6ba 100644 --- a/circle/circle/settings/base.py +++ b/circle/circle/settings/base.py @@ -463,3 +463,5 @@ STORE_URL = get_env_variable("STORE_URL", "") SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^ (getnode() % 983)) & 0xffff) + +MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024) diff --git a/circle/circle/settings/production.py b/circle/circle/settings/production.py index a083089..5d6fe6f 100644 --- a/circle/circle/settings/production.py +++ b/circle/circle/settings/production.py @@ -70,20 +70,14 @@ SERVER_EMAIL = EMAIL_HOST_USER ########## CACHE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches -try: - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': get_env_variable('DJANGO_MEMCACHED'), - } - } -except ImproperlyConfigured: - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': SITE_NAME, - } +from urlparse import urlsplit + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': urlsplit(get_env_variable('CACHE_URI')).netloc, } +} ########## END CACHE CONFIGURATION diff --git a/circle/common/models.py b/circle/common/models.py index 608fc06..d63beb3 100644 --- a/circle/common/models.py +++ b/circle/common/models.py @@ -420,6 +420,7 @@ create_readable = HumanReadableObject.create class HumanReadableException(HumanReadableObject, Exception): """HumanReadableObject that is an Exception so can used in except clause. """ + def __init__(self, level=None, *args, **kwargs): super(HumanReadableException, self).__init__(*args, **kwargs) if level is not None: @@ -450,6 +451,7 @@ def humanize_exception(message, exception=None, level=None, **params): ... Welcome! """ + Ex = type("HumanReadable" + type(exception).__name__, (HumanReadableException, type(exception)), exception.__dict__) diff --git a/circle/dashboard/fixtures/test-vm-fixture.json b/circle/dashboard/fixtures/test-vm-fixture.json index 1690748..2e10104 100644 --- a/circle/dashboard/fixtures/test-vm-fixture.json +++ b/circle/dashboard/fixtures/test-vm-fixture.json @@ -1382,7 +1382,7 @@ "pw": "ads", "time_of_suspend": null, "ram_size": 200, - "priority": 4, + "priority": 10, "active_since": null, "template": null, "access_method": "nx", @@ -1412,7 +1412,7 @@ "pw": "ads", "time_of_suspend": null, "ram_size": 200, - "priority": 4, + "priority": 10, "active_since": null, "template": null, "access_method": "nx", @@ -1518,7 +1518,7 @@ "ram_size": 1024, "modified": "2014-01-24T00:58:19.654Z", "system": "bubuntu", - "priority": 20, + "priority": 10, "access_method": "ssh", "raw_data": "", "arch": "x86_64", diff --git a/circle/dashboard/forms.py b/circle/dashboard/forms.py index a565b64..56f21a3 100644 --- a/circle/dashboard/forms.py +++ b/circle/dashboard/forms.py @@ -30,7 +30,7 @@ from django.core.exceptions import PermissionDenied, ValidationError import autocomplete_light from crispy_forms.helper import FormHelper from crispy_forms.layout import ( - Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK, + Layout, Div, BaseInput, Field, HTML, Submit, TEMPLATE_PACK, ) from crispy_forms.utils import render_field @@ -51,7 +51,7 @@ from vm.models import ( from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.auth.models import Permission from .models import Profile, GroupProfile -from circle.settings.base import LANGUAGES +from circle.settings.base import LANGUAGES, MAX_NODE_RAM from django.utils.translation import string_concat from .virtvalidator import domain_validator @@ -59,6 +59,13 @@ from .virtvalidator import domain_validator LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")")) for l in LANGUAGES) +priority_choices = ( + (10, _("idle")), + (30, _("normal")), + (80, _("server")), + (100, _("realtime")), +) + class VmSaveForm(forms.Form): name = forms.CharField(max_length=100, label=_('Name'), @@ -72,19 +79,62 @@ class VmSaveForm(forms.Form): class VmCustomizeForm(forms.Form): - name = forms.CharField() - cpu_priority = forms.IntegerField() - cpu_count = forms.IntegerField() - ram_size = forms.IntegerField() - amount = forms.IntegerField(min_value=0, initial=1) + name = forms.CharField(widget=forms.TextInput(attrs={ + 'class': "form-control", + 'style': "max-width: 350px", + 'required': "", + })) + + cpu_count = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags cpu-count-input", + 'min': 1, + 'max': 10, + 'required': "", + }), + min_value=1, max_value=10, + ) + + ram_size = forms.IntegerField(widget=forms.TextInput(attrs={ + 'class': "form-control input-tags ram-input", + 'min': 128, + 'pattern': "\d+", + 'max': MAX_NODE_RAM, + 'step': 128, + 'required': "", + }), + min_value=128, max_value=MAX_NODE_RAM, + ) + + cpu_priority = forms.ChoiceField( + priority_choices, widget=forms.Select(attrs={ + 'class': "form-control input-tags cpu-priority-input", + }) + ) + + amount = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control", + 'min': "1", + 'style': "max-width: 60px", + 'required': "", + }), initial=1, min_value=1) disks = forms.ModelMultipleChoiceField( - queryset=None, required=False) + queryset=None, required=False, + widget=forms.SelectMultiple(attrs={ + 'class': "form-control", + 'id': "vm-create-disk-add-form", + }) + ) networks = forms.ModelMultipleChoiceField( - queryset=None, required=False) + queryset=None, required=False, + widget=forms.SelectMultiple(attrs={ + 'class': "form-control", + 'id': "vm-create-network-add-vlan", + }) + ) - template = forms.CharField() - customized = forms.CharField() # dummy flag field + template = forms.CharField(widget=forms.HiddenInput()) + customized = forms.CharField(widget=forms.HiddenInput()) def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) @@ -111,230 +161,6 @@ 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) - - # 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( - Div( - AnyTag( # tip: don't try to use Button class - "button", - AnyTag( - "i", - css_class="fa fa-play" - ), - HTML(" Start"), - css_id="vm-create-customized-start", - css_class="btn btn-success", - style="float: right; margin-top: 24px;", - ), - Field("name", style="max-width: 350px;"), - css_class="col-sm-12", - ), - css_class="row", - ), - Div( - Div( - Field("amount", min="1", style="max-width: 60px;"), - css_class="col-sm-10", - ), - css_class="row", - ), - Div( - Div( - AnyTag( - 'h2', - HTML(_("Resources")), - ), - css_class="col-sm-12", - ), - css_class="row", - ), - Div( # cpu priority - Div( - HTML('<label for="vm-cpu-priority-slider">' - '<i class="fa fa-trophy"></i> CPU priority' - '</label>'), - css_class="col-sm-3" - ), - Div( - 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=self.template.priority, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # cpu count - Div( - HTML('<label for="cpu-count-slider">' - '<i class="fa fa-cogs"></i> CPU count' - '</label>'), - css_class="col-sm-3" - ), - Div( - 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=self.template.num_cores, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # ram size - Div( - HTML('<label for="ram-slider">' - '<i class="fa fa-ticket"></i> RAM amount' - '</label>'), - css_class="col-sm-3" - ), - Div( - 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=self.template.ram_size, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # disks - Div( - AnyTag( - "h2", - HTML("Disks") - ), - css_class="col-sm-4", - ), - Div( - Div( - Field("disks", css_class="form-control", - id="vm-create-disk-add-form"), - css_class="js-hidden", - style="padding-top: 15px; max-width: 450px;", - ), - Div( - AnyTag( - "h3", - HTML(_("No disks are added!")), - css_id="vm-create-disk-list", - ), - 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", - style="padding-top: 3px;", - ), - css_class="row", - ), # end of disks - Div( # network - Div( - AnyTag( - "h2", - HTML(_("Network")), - ), - css_class="col-sm-4", - ), - Div( - Div( # js-hidden - Field( - "networks", - css_class="form-control", - id="vm-create-network-add-vlan", - ), - css_class="js-hidden", - style="padding-top: 15px; max-width: 450px;", - ), - Div( # no-js-hidden - AnyTag( - "h3", - HTML(_("Not added to any network!")), - css_id="vm-create-network-list", - ), - AnyTag( - "h3", - Div( - AnyTag( - "select", - css_class=("form-control " - "font-awesome-font"), - css_id="vm-create-network-add-select", - ), - Div( - AnyTag( - "a", - AnyTag( - "i", - css_class="fa fa-plus-circle", - ), - css_id=("vm-create-network-add" - "-button"), - css_class="btn btn-success", - ), - css_class="input-group-btn", - ), - css_class="input-group", - style="max-width: 330px;", - ), - css_class="vm-create-network-add" - ), - css_class="no-js-hidden", - ), - css_class="col-sm-8", - style="padding-top: 3px;", - ), - css_class="row" - ), # end of network - ) - class GroupCreateForm(forms.ModelForm): @@ -581,6 +407,29 @@ class TemplateForm(forms.ModelForm): networks = forms.ModelMultipleChoiceField( queryset=None, required=False, label=_("Networks")) + num_cores = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags cpu-count-input", + 'min': 1, + 'max': 10, + 'required': "", + }), + min_value=1, max_value=10, + ) + + ram_size = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags ram-input", + 'min': 128, + 'max': MAX_NODE_RAM, + 'step': 128, + 'required': "", + }), + min_value=128, max_value=MAX_NODE_RAM, + ) + + priority = forms.ChoiceField(priority_choices, widget=forms.Select(attrs={ + 'class': "form-control input-tags cpu-priority-input", + })) + def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) super(TemplateForm, self).__init__(*args, **kwargs) @@ -612,18 +461,27 @@ class TemplateForm(forms.ModelForm): field.widget.attrs['disabled'] = 'disabled' if not self.instance.pk and len(self.errors) < 1: - self.instance.priority = 20 - self.instance.ram_size = 512 - self.instance.num_cores = 2 + self.initial['num_cores'] = 1 + self.initial['priority'] = 10 + self.initial['ram_size'] = 512 + self.initial['max_ram_size'] = 512 + + lease_queryset = ( + Lease.get_objects_with_level("operator", self.user).distinct() + | Lease.objects.filter(pk=self.instance.lease_id).distinct()) - self.fields["lease"].queryset = Lease.get_objects_with_level( - "operator", self.user) + self.fields["lease"].queryset = lease_queryset + + self.fields['raw_data'].validators.append(domain_validator) def clean_owner(self): if self.instance.pk is not None: return User.objects.get(pk=self.instance.owner.pk) return self.user + def clean_max_ram_size(self): + return self.cleaned_data.get("ram_size", 0) + def _clean_fields(self): try: old = InstanceTemplate.objects.get(pk=self.instance.pk) @@ -685,77 +543,14 @@ class TemplateForm(forms.ModelForm): submit_kwargs['disabled'] = None helper = FormHelper() - helper.layout = Layout( - Field("name"), - Fieldset( - _("Resource configuration"), - Div( # cpu count - Div( - Field('num_cores', id="vm-cpu-count-slider", - css_class="vm-slider", - data_slider_min="1", data_slider_max="8", - data_slider_step="1", - data_slider_value=self.instance.num_cores, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # cpu priority - Div( - Field('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=self.instance.priority, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( - Div( - 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=self.instance.ram_size, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row", - ), - Field('max_ram_size', type="hidden", value="0"), - Field('arch'), - ), - Fieldset( - _("Virtual machine settings"), - Field('access_method'), - Field('boot_menu'), - Field('raw_data'), - Field('req_traits'), - Field('description'), - Field("parent", type="hidden"), - Field("system"), - ), - Fieldset( - _("External resources"), - Field("networks"), - Field("lease"), - Field("tags"), - ), - ) - helper.add_input(Submit('submit', 'Save changes', **submit_kwargs)) return helper class Meta: model = InstanceTemplate exclude = ('state', 'disks', ) widgets = { - 'system': forms.TextInput + 'system': forms.TextInput, + 'max_ram_size': forms.HiddenInput } @@ -1305,3 +1100,32 @@ class GroupPermissionForm(forms.ModelForm): helper.add_input(Submit("submit", _("Save"), css_class="btn btn-success", )) return helper + + +class VmResourcesForm(forms.ModelForm): + num_cores = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags cpu-count-input", + 'min': 1, + 'max': 10, + 'required': "", + }), + min_value=1, max_value=10, + ) + + ram_size = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags ram-input", + 'min': 128, + 'max': MAX_NODE_RAM, + 'step': 128, + 'required': "", + }), + min_value=128, max_value=MAX_NODE_RAM, + ) + + priority = forms.ChoiceField(priority_choices, widget=forms.Select(attrs={ + 'class': "form-control input-tags cpu-priority-input", + })) + + class Meta: + model = Instance + fields = ('num_cores', 'priority', 'ram_size', ) diff --git a/circle/dashboard/static/dashboard/dashboard.css b/circle/dashboard/static/dashboard/dashboard.css index c276966..65fb297 100644 --- a/circle/dashboard/static/dashboard/dashboard.css +++ b/circle/dashboard/static/dashboard/dashboard.css @@ -186,42 +186,6 @@ html { text-decoration: none !important; } -.slider { - display: inline-block; -} -.slider .track { - height: 20px; - top: 50%; -} -.slider > .dragger, .slider > .dragger:hover { - border-radius: 0px; - -moz-border-radius: 0px; - -webkit-border-radius: 0px; - width: 8px; - height: 24px; - margin-top: -12px!important; - text-shadow: 0 1px 0 #fff; - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); - background-repeat: repeat-x; - border-color: #2d6ca2; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); -} -.slider > .dragger:hover { - background-color: #3071a9; - background-image: none; - border-color: #2d6ca2; -} - -.slider > .highlight-track { - height: 20px; - top: 50%; -} -.slider + .output { - -} .rule-table tr >:nth-child(1) { text-align: right; } @@ -848,3 +812,54 @@ textarea[name="list-new-namelist"] { #show-all-activities-container { margin: 20px 0 0 10px; } + +#vm-details-resources-form { + margin-top: 15px; +} + +#vm-details-resources-form .row, .resources-sliders .row { + margin-bottom: 15px; +} + +#vm-create-disk-add-form { + max-width: 450px; + margin-top: 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; +} diff --git a/circle/dashboard/static/dashboard/dashboard.js b/circle/dashboard/static/dashboard/dashboard.js index af5aab8..2269083 100644 --- a/circle/dashboard/static/dashboard/dashboard.js +++ b/circle/dashboard/static/dashboard/dashboard.js @@ -435,28 +435,63 @@ function compareVmByFav(a, b) { return a.pk < b.pk ? -1 : 1; } +$(document).on('shown.bs.tab', 'a[href="#resources"]', function (e) { + $(".cpu-priority-input").trigger("change"); + $(".cpu-count-input, .ram-input").trigger("input"); +}) + function addSliderMiscs() { - $('.vm-slider').each(function() { - $("<span>").addClass("output").html($(this).val()).insertAfter($(this)); - }); - - $('.vm-slider').slider() - .on('slide', function(e) { - $(this).val(e.value); - $(this).parent('div').nextAll("span").html(e.value) + // set max values based on inputs + var cpu_count_range = "0, " + $(".cpu-count-input").prop("max"); + var ram_range = "0, " + $(".ram-input").prop("max"); + $(".cpu-count-slider").data("slider-range", cpu_count_range); + $(".ram-slider").data("slider-range", ram_range); + + $(".vm-slider").simpleSlider(); + $(".cpu-priority-slider").bind("slider:changed", function (event, data) { + var value = data.value + 0; + + $('.cpu-priority-input option[value="' + value + '"]').attr("selected", "selected"); }); - refreshSliders(); -} + $(".cpu-priority-input").change(function() { + var val = $(":selected", $(this)).val(); + $(".cpu-priority-slider").simpleSlider("setValue", val); + }); + + $(".cpu-count-slider").bind("slider:changed", function (event, data) { + var value = data.value + 0; + $(".cpu-count-input").val(parseInt(value)); + }); -// ehhh -function refreshSliders() { - $('.vm-slider').each(function() { - $(this).val($(this).slider().data('slider').getValue()); - $(this).parent('div').nextAll("span").html($(this).val()); + $(".cpu-count-input").bind("input", function() { + var val = parseInt($(this).val()); + if(!val) return; + $(".cpu-count-slider").simpleSlider("setValue", val); }); + + + var ram_fire = false; + $(".ram-slider").bind("slider:changed", function (event, data) { + if(ram_fire) { + ram_fire = false; + return; + } + + var value = data.value + 0; + $(".ram-input").val(value); + }); + + $(".ram-input").bind("input", function() { + var val = $(this).val(); + ram_fire = true; + $(".ram-slider").simpleSlider("setValue", parseInt(val)); + }); + $(".cpu-priority-input").trigger("change"); + $(".cpu-count-input, .ram-input").trigger("input"); } + /* deletes the VM with the pk * if dir is true, then redirect to the dashboard landing page * else it adds a success message */ diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/.gitignore b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/.gitignore deleted file mode 100644 index 4381f42..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -*.zip -gh-pages \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/README.md b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/README.md deleted file mode 100644 index 1ab4e1c..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/README.md +++ /dev/null @@ -1,27 +0,0 @@ -jQuery Simple Slider: Unobtrusive Numerical Slider -================================================== - -SimpleSlider is a jQuery plugin for turning your text inputs into draggable -numerical sliders. - -It has no external dependencies other than jQuery, and you don't need to write -a single line of JavaScript to get it to work. - - -How to Use ------------ - -Include the javascript file in your page: - - <script src="simple-slider.js"></script> - -Turn your text input into a slider: - - <input type="text" data-slider="true"> - - -Documentation, Features and Demos ---------------------------------- -Full details and documentation can be found on the project page here: - -<http://loopj.com/jquery-simple-slider/> \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/demo.html b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/demo.html deleted file mode 100644 index 1bf42ec..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/demo.html +++ /dev/null @@ -1,80 +0,0 @@ -<html> -<head> - <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> - - <script src="js/simple-slider.js"></script> - - <link href="css/simple-slider.css" rel="stylesheet" type="text/css" /> - <link href="css/simple-slider-volume.css" rel="stylesheet" type="text/css" /> - - <!-- These styles are only used for this page, not required for the slider --> - <style> - body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } - [class^=slider] { display: inline-block; margin-bottom: 30px; } - .output { color: #888; font-size: 14px; padding-top: 1px; margin-left: 5px; vertical-align: top;} - h1 { font-size: 20px; } - h2 { clear: both; margin: 0; margin-bottom: 5px; font-size: 16px; } - p { font-size: 15px; margin-bottom: 30px; } - h2:first-of-type { margin-top: 0; } - </style> -</head> - -<body> - <h1>jQuery Simple Slider Examples</h1> - <p> - Here are a few examples of the functionality available in Simple Slider. - </p> - - - <h2>Basic Example</h2> - <input type="text" data-slider="true"> - - <h2>Basic Example (Themed)</h2> - <input type="text" data-slider="true" data-slider-theme="volume"> - - <h2>Predefined Value</h2> - <input type="text" value="0.2" data-slider="true"> - - <h2>Steps</h2> - <input type="text" data-slider="true" data-slider-step="0.1"> - - <h2>Range</h2> - <input type="text" data-slider="true" data-slider-range="10,1000"> - - <h2>Range & Steps</h2> - <input type="text" data-slider="true" data-slider-range="100,500" data-slider-step="100"> - - <h2>Range, Steps & Snap</h2> - <input type="text" data-slider="true" data-slider-range="100,500" data-slider-step="100" data-slider-snap="true"> - - <h2>Predefined List of Values</h2> - <input type="text" data-slider="true" data-slider-values="0,100,500,800,2000"> - - <h2>Predefined List & Snap</h2> - <input type="text" data-slider="true" data-slider-values="0,100,500,800,2000" data-slider-snap="true"> - - <h2>Predefined List, Equal Steps & Snap</h2> - <input type="text" data-slider="true" data-slider-values="0,100,500,800,2000" data-slider-equal-steps="true" data-slider-snap="true"> - - <h2>Highlighted</h2> - <input type="text" data-slider="true" value="0.8" data-slider-highlight="true"> - - <h2>Highlighted (Themed)</h2> - <input type="text" data-slider="true" value="0.4" data-slider-highlight="true" data-slider-theme="volume"> - - <script> - $("[data-slider]") - .each(function () { - var input = $(this); - $("<span>") - .addClass("output") - .insertAfter($(this)); - }) - .bind("slider:ready slider:changed", function (event, data) { - $(this) - .nextAll(".output:first") - .html(data.value.toFixed(3)); - }); - </script> -</body> -</html> diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/grunt.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/grunt.js deleted file mode 100644 index eea6217..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/grunt.js +++ /dev/null @@ -1,68 +0,0 @@ -module.exports = function(grunt) { - grunt.initConfig({ - pkg: '<json:package.json>', - - meta: { - banner: - '/*\n' + - ' * <%= pkg.title || pkg.name %>: <%= pkg.description %>\n' + - ' * Version <%= pkg.version %>\n' + - ' *\n' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> (<%= pkg.author.url %>)\n' + - ' *\n' + - ' * Licensed under the <%= pkg.licenses[0].type %> license (<%= pkg.licenses[0].url %>)\n' + - ' *\n' + - ' */\n' - }, - - coffee: { - compile: { - files: { - 'js/<%= pkg.name %>.js': 'js/*.coffee' - }, - - options: { - bare: true - } - } - }, - - watch: { - coffee: { - files: ['js/*.coffee'], - tasks: 'coffee growl:coffee' - } - }, - - growl: { - coffee: { - title: 'CoffeeScript', - message: 'Compiled successfully' - } - }, - - min: { - dist: { - src: ['<banner:meta.banner>', 'js/<%= pkg.name %>.js'], - dest: 'js/<%= pkg.name %>.min.js' - } - }, - - compress: { - zip: { - files: { - "<%= pkg.name %>-<%= pkg.version %>.zip": ["js/**", "demo.html", "README.md"] - } - } - } - }); - - // Lib tasks. - grunt.loadNpmTasks('grunt-contrib'); - grunt.loadNpmTasks('grunt-growl'); - - // Default task. - grunt.registerTask('build', 'coffee min'); - grunt.registerTask('serve', 'server watch:coffee'); - grunt.registerTask('default', 'build'); -}; \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.coffee b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.coffee deleted file mode 100644 index 9c3d40c..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.coffee +++ /dev/null @@ -1,361 +0,0 @@ -### - jQuery Simple Slider - - Copyright (c) 2012 James Smith (http://loopj.com) - - Licensed under the MIT license (http://mit-license.org/) -### - -(($, window) -> - - # - # Main slider class - # - - class SimpleSlider - # Build a slider object. - # Exposed via el.numericalSlider(options) - constructor: (@input, options) -> - # Load in the settings - @defaultOptions = - animate: true - snapMid: false - classPrefix: null - classSuffix: null - theme: null - highlight: false - - @settings = $.extend({}, @defaultOptions, options) - @settings.classSuffix = "-#{@settings.theme}" if @settings.theme - - # Hide the original input - @input.hide() - - # Create the slider canvas - @slider = $("<div>") - .addClass("slider"+(@settings.classSuffix || "")) - .css - position: "relative" - userSelect: "none" - boxSizing: "border-box" - .insertBefore @input - @slider.attr("id", @input.attr("id") + "-slider") if @input.attr("id") - - @track = @createDivElement("track") - .css - width: "100%" - - if @settings.highlight - # Create the highlighting track on top of the track - @highlightTrack = @createDivElement("highlight-track") - .css - width: "0" - - # Create the slider drag target - @dragger = @createDivElement("dragger") - - # Adjust dimensions now elements are in the DOM - @slider.css - minHeight: @dragger.outerHeight() - marginLeft: @dragger.outerWidth()/2 - marginRight: @dragger.outerWidth()/2 - - @track.css - marginTop: @track.outerHeight()/-2 - - if @settings.highlight - @highlightTrack.css - marginTop: @track.outerHeight()/-2 - - @dragger.css - marginTop: @dragger.outerWidth()/-2 - marginLeft: @dragger.outerWidth()/-2 - - # Hook up drag/drop mouse events - @track - .mousedown (e) => - @trackEvent(e) - - if @settings.highlight - @highlightTrack - .mousedown (e) => - @trackEvent(e) - - @dragger - .mousedown (e) => - return unless e.which == 1 - - # We've started moving - @dragging = true - @dragger.addClass "dragging" - - # Update the slider position - @domDrag(e.pageX, e.pageY) - - false - - $("body") - .mousemove (e) => - if @dragging - # Update the slider position - @domDrag(e.pageX, e.pageY) - - # Always show a pointer when dragging - $("body").css cursor: "pointer" - - - .mouseup (e) => - if @dragging - # Finished dragging - @dragging = false - @dragger.removeClass "dragging" - - # Revert the cursor - $("body").css cursor: "auto" - - # Set slider initial position - @pagePos = 0 - - # Fill in initial slider value - if @input.val() == "" - @value = @getRange().min - @input.val(@value) - else - @value = @nearestValidValue(@input.val()) - - @setSliderPositionFromValue(@value) - - # We are ready to go - ratio = @valueToRatio(@value) - @input.trigger "slider:ready", - value: @value - ratio: ratio - position: ratio * @slider.outerWidth() - el: @slider - - # Create the basis of the track-div(s) - createDivElement: (classname) -> - item = $("<div>") - .addClass(classname) - .css - position: "absolute" - top: "50%" - userSelect: "none" - cursor: "pointer" - .appendTo @slider - return item - - - # Set the ratio (value between 0 and 1) of the slider. - # Exposed via el.slider("setRatio", ratio) - setRatio: (ratio) -> - # Range-check the ratio - ratio = Math.min(1, ratio) - ratio = Math.max(0, ratio) - - # Work out the value - value = @ratioToValue(ratio) - - # Update the position of the slider on the screen - @setSliderPositionFromValue(value) - - # Trigger value changed events - @valueChanged(value, ratio, "setRatio") - - # Set the value of the slider - # Exposed via el.slider("setValue", value) - setValue: (value) -> - # Snap value to nearest step or allowedValue - value = @nearestValidValue(value) - - # Work out the ratio - ratio = @valueToRatio(value) - - # Update the position of the slider on the screen - @setSliderPositionFromValue(value) - - # Trigger value changed events - @valueChanged(value, ratio, "setValue") - - # Respond to an event on a track - trackEvent: (e) -> - return unless e.which == 1 - - @domDrag(e.pageX, e.pageY, true) - @dragging = true - false - - # Respond to a dom drag event - domDrag: (pageX, pageY, animate=false) -> - # Normalize position within allowed range - pagePos = pageX - @slider.offset().left - pagePos = Math.min(@slider.outerWidth(), pagePos) - pagePos = Math.max(0, pagePos) - - # If the element position has changed, do stuff - if @pagePos != pagePos - @pagePos = pagePos - - # Set the percentage value of the slider - ratio = pagePos / @slider.outerWidth() - - # Trigger value changed events - value = @ratioToValue(ratio) - @valueChanged(value, ratio, "domDrag") - - # Update the position of the slider on the screen - if @settings.snap - @setSliderPositionFromValue(value, animate) - else - @setSliderPosition(pagePos, animate) - - # Set the slider position given a slider canvas position - setSliderPosition: (position, animate=false) -> - if animate and @settings.animate - @dragger.animate left: position, 200 - @highlightTrack.animate width: position, 200 if @settings.highlight - else - @dragger.css left: position - @highlightTrack.css width: position if @settings.highlight - - # Set the slider position given a value - setSliderPositionFromValue: (value, animate=false) -> - # Get the slide ratio from the value - ratio = @valueToRatio(value) - - # Set the slider position - @setSliderPosition(ratio * @slider.outerWidth(), animate) - - # Get the valid range of values - getRange: -> - if @settings.allowedValues - min: Math.min(@settings.allowedValues...) - max: Math.max(@settings.allowedValues...) - else if @settings.range - min: parseFloat(@settings.range[0]) - max: parseFloat(@settings.range[1]) - else - min: 0 - max: 1 - - # Find the nearest valid value, checking allowedValues and step settings - nearestValidValue: (rawValue) -> - range = @getRange() - - # Range-check the value - rawValue = Math.min(range.max, rawValue) - rawValue = Math.max(range.min, rawValue) - - # Apply allowedValues or step settings - if @settings.allowedValues - closest = null - $.each @settings.allowedValues, -> - if closest == null || Math.abs(this - rawValue) < Math.abs(closest - rawValue) - closest = this - - return closest - else if @settings.step - maxSteps = (range.max - range.min) / @settings.step - steps = Math.floor((rawValue - range.min) / @settings.step) - steps += 1 if (rawValue - range.min) % @settings.step > @settings.step / 2 and steps < maxSteps - - return steps * @settings.step + range.min - else - return rawValue - - # Convert a value to a ratio - valueToRatio: (value) -> - if @settings.equalSteps - # Get slider ratio for equal-step - for allowedVal, idx in @settings.allowedValues - if !closest? || Math.abs(allowedVal - value) < Math.abs(closest - value) - closest = allowedVal - closestIdx = idx - - if @settings.snapMid - (closestIdx+0.5)/@settings.allowedValues.length - else - (closestIdx)/(@settings.allowedValues.length - 1) - - else - # Get slider ratio for continuous values - range = @getRange() - (value - range.min) / (range.max - range.min) - - # Convert a ratio to a valid value - ratioToValue: (ratio) -> - if @settings.equalSteps - steps = @settings.allowedValues.length - step = Math.round(ratio * steps - 0.5) - idx = Math.min(step, @settings.allowedValues.length - 1) - - @settings.allowedValues[idx] - else - range = @getRange() - rawValue = ratio * (range.max - range.min) + range.min - - @nearestValidValue(rawValue) - - # Trigger value changed events - valueChanged: (value, ratio, trigger) -> - return if value.toString() == @value.toString() - - # Save the new value - @value = value - - # Construct event data and fire event - eventData = - value: value - ratio: ratio - position: ratio * @slider.outerWidth() - trigger: trigger - el: @slider - - @input - .val(value) - .trigger($.Event("change", eventData)) - .trigger("slider:changed", eventData) - - - # - # Expose as jQuery Plugin - # - - $.extend $.fn, simpleSlider: (settingsOrMethod, params...) -> - publicMethods = ["setRatio", "setValue"] - - $(this).each -> - if settingsOrMethod and settingsOrMethod in publicMethods - obj = $(this).data("slider-object") - - obj[settingsOrMethod].apply(obj, params) - else - settings = settingsOrMethod - $(this).data "slider-object", new SimpleSlider($(this), settings) - - - # - # Attach unobtrusive JS hooks - # - - $ -> - $("[data-slider]").each -> - $el = $(this) - - # Build options object from data attributes - settings = {} - - allowedValues = $el.data "slider-values" - settings.allowedValues = (parseFloat(x) for x in allowedValues.split(",")) if allowedValues - settings.range = $el.data("slider-range").split(",") if $el.data("slider-range") - settings.step = $el.data("slider-step") if $el.data("slider-step") - settings.snap = $el.data("slider-snap") - settings.equalSteps = $el.data("slider-equal-steps") - settings.theme = $el.data("slider-theme") if $el.data("slider-theme") - settings.highlight = $el.data("slider-highlight") if $el.attr("data-slider-highlight") - settings.animate = $el.data("slider-animate") if $el.data("slider-animate")? - - # Activate the plugin - $el.simpleSlider settings - -) @jQuery or @Zepto, this diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js deleted file mode 100644 index b6a7341..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * jQuery Simple Slider: Unobtrusive Numerical Slider - * Version 1.0.0 - * - * Copyright (c) 2013 James Smith (http://loopj.com) - * - * Licensed under the MIT license (http://mit-license.org/) - * - */ - -var __slice=[].slice,__indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++)if(t in this&&this[t]===e)return t;return-1};(function(e,t){var n;return n=function(){function t(t,n){var r,i=this;this.input=t,this.defaultOptions={animate:!0,snapMid:!1,classPrefix:null,classSuffix:null,theme:null,highlight:!1},this.settings=e.extend({},this.defaultOptions,n),this.settings.theme&&(this.settings.classSuffix="-"+this.settings.theme),this.input.hide(),this.slider=e("<div>").addClass("slider"+(this.settings.classSuffix||"")).css({position:"relative",userSelect:"none",boxSizing:"border-box"}).insertBefore(this.input),this.input.attr("id")&&this.slider.attr("id",this.input.attr("id")+"-slider"),this.track=this.createDivElement("track").css({width:"100%"}),this.settings.highlight&&(this.highlightTrack=this.createDivElement("highlight-track").css({width:"0"})),this.dragger=this.createDivElement("dragger"),this.slider.css({minHeight:this.dragger.outerHeight(),marginLeft:this.dragger.outerWidth()/2,marginRight:this.dragger.outerWidth()/2}),this.track.css({marginTop:this.track.outerHeight()/-2}),this.settings.highlight&&this.highlightTrack.css({marginTop:this.track.outerHeight()/-2}),this.dragger.css({marginTop:this.dragger.outerWidth()/-2,marginLeft:this.dragger.outerWidth()/-2}),this.track.mousedown(function(e){return i.trackEvent(e)}),this.settings.highlight&&this.highlightTrack.mousedown(function(e){return i.trackEvent(e)}),this.dragger.mousedown(function(e){if(e.which!==1)return;return i.dragging=!0,i.dragger.addClass("dragging"),i.domDrag(e.pageX,e.pageY),!1}),e("body").mousemove(function(t){if(i.dragging)return i.domDrag(t.pageX,t.pageY),e("body").css({cursor:"pointer"})}).mouseup(function(t){if(i.dragging)return i.dragging=!1,i.dragger.removeClass("dragging"),e("body").css({cursor:"auto"})}),this.pagePos=0,this.input.val()===""?(this.value=this.getRange().min,this.input.val(this.value)):this.value=this.nearestValidValue(this.input.val()),this.setSliderPositionFromValue(this.value),r=this.valueToRatio(this.value),this.input.trigger("slider:ready",{value:this.value,ratio:r,position:r*this.slider.outerWidth(),el:this.slider})}return t.prototype.createDivElement=function(t){var n;return n=e("<div>").addClass(t).css({position:"absolute",top:"50%",userSelect:"none",cursor:"pointer"}).appendTo(this.slider),n},t.prototype.setRatio=function(e){var t;return e=Math.min(1,e),e=Math.max(0,e),t=this.ratioToValue(e),this.setSliderPositionFromValue(t),this.valueChanged(t,e,"setRatio")},t.prototype.setValue=function(e){var t;return e=this.nearestValidValue(e),t=this.valueToRatio(e),this.setSliderPositionFromValue(e),this.valueChanged(e,t,"setValue")},t.prototype.trackEvent=function(e){if(e.which!==1)return;return this.domDrag(e.pageX,e.pageY,!0),this.dragging=!0,!1},t.prototype.domDrag=function(e,t,n){var r,i,s;n==null&&(n=!1),r=e-this.slider.offset().left,r=Math.min(this.slider.outerWidth(),r),r=Math.max(0,r);if(this.pagePos!==r)return this.pagePos=r,i=r/this.slider.outerWidth(),s=this.ratioToValue(i),this.valueChanged(s,i,"domDrag"),this.settings.snap?this.setSliderPositionFromValue(s,n):this.setSliderPosition(r,n)},t.prototype.setSliderPosition=function(e,t){t==null&&(t=!1);if(t&&this.settings.animate){this.dragger.animate({left:e},200);if(this.settings.highlight)return this.highlightTrack.animate({width:e},200)}else{this.dragger.css({left:e});if(this.settings.highlight)return this.highlightTrack.css({width:e})}},t.prototype.setSliderPositionFromValue=function(e,t){var n;return t==null&&(t=!1),n=this.valueToRatio(e),this.setSliderPosition(n*this.slider.outerWidth(),t)},t.prototype.getRange=function(){return this.settings.allowedValues?{min:Math.min.apply(Math,this.settings.allowedValues),max:Math.max.apply(Math,this.settings.allowedValues)}:this.settings.range?{min:parseFloat(this.settings.range[0]),max:parseFloat(this.settings.range[1])}:{min:0,max:1}},t.prototype.nearestValidValue=function(t){var n,r,i,s;return i=this.getRange(),t=Math.min(i.max,t),t=Math.max(i.min,t),this.settings.allowedValues?(n=null,e.each(this.settings.allowedValues,function(){if(n===null||Math.abs(this-t)<Math.abs(n-t))return n=this}),n):this.settings.step?(r=(i.max-i.min)/this.settings.step,s=Math.floor((t-i.min)/this.settings.step),(t-i.min)%this.settings.step>this.settings.step/2&&s<r&&(s+=1),s*this.settings.step+i.min):t},t.prototype.valueToRatio=function(e){var t,n,r,i,s,o,u,a;if(this.settings.equalSteps){a=this.settings.allowedValues;for(i=o=0,u=a.length;o<u;i=++o){t=a[i];if(typeof n=="undefined"||n===null||Math.abs(t-e)<Math.abs(n-e))n=t,r=i}return this.settings.snapMid?(r+.5)/this.settings.allowedValues.length:r/(this.settings.allowedValues.length-1)}return s=this.getRange(),(e-s.min)/(s.max-s.min)},t.prototype.ratioToValue=function(e){var t,n,r,i,s;return this.settings.equalSteps?(s=this.settings.allowedValues.length,i=Math.round(e*s-.5),t=Math.min(i,this.settings.allowedValues.length-1),this.settings.allowedValues[t]):(n=this.getRange(),r=e*(n.max-n.min)+n.min,this.nearestValidValue(r))},t.prototype.valueChanged=function(t,n,r){var i;if(t.toString()===this.value.toString())return;return this.value=t,i={value:t,ratio:n,position:n*this.slider.outerWidth(),trigger:r,el:this.slider},this.input.val(t).trigger(e.Event("change",i)).trigger("slider:changed",i)},t}(),e.extend(e.fn,{simpleSlider:function(){var t,r,i;return i=arguments[0],t=2<=arguments.length?__slice.call(arguments,1):[],r=["setRatio","setValue"],e(this).each(function(){var s,o;return i&&__indexOf.call(r,i)>=0?(s=e(this).data("slider-object"),s[i].apply(s,t)):(o=i,e(this).data("slider-object",new n(e(this),o)))})}}),e(function(){return e("[data-slider]").each(function(){var t,n,r,i;return t=e(this),r={},n=t.data("slider-values"),n&&(r.allowedValues=function(){var e,t,r,s;r=n.split(","),s=[];for(e=0,t=r.length;e<t;e++)i=r[e],s.push(parseFloat(i));return s}()),t.data("slider-range")&&(r.range=t.data("slider-range").split(",")),t.data("slider-step")&&(r.step=t.data("slider-step")),r.snap=t.data("slider-snap"),r.equalSteps=t.data("slider-equal-steps"),t.data("slider-theme")&&(r.theme=t.data("slider-theme")),t.attr("data-slider-highlight")&&(r.highlight=t.data("slider-highlight")),t.data("slider-animate")!=null&&(r.animate=t.data("slider-animate")),t.simpleSlider(r)})})})(this.jQuery||this.Zepto,this); \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/package.json b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/package.json deleted file mode 100644 index c1918f3..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name" : "simple-slider", - "title" : "jQuery Simple Slider", - "description" : "Unobtrusive Numerical Slider", - "version" : "1.0.0", - "homepage" : "http://loopj.com/jquery-simple-slider", - "keywords" : [], - "author" : { - "name" : "James Smith", - "email" : "james@loopj.com", - "url" : "http://loopj.com" - }, - "repository" : { - "type" : "git", - "url" : "https://github.com/loopj/jquery-simple-slider.git" - }, - "bugs" : { - "url" : "https://github.com/loopj/jquery-simple-slider/issues" - }, - "licenses": [{ - "type": "MIT", - "url" : "http://mit-license.org/" - }], - "devDependencies" : { - "grunt" : "0.3.x", - "grunt-contrib": "0.2.x", - "grunt-growl": "git://github.com/loopj/grunt-growl.git#master" - }, - "scripts": { - "test": "grunt" - } -} \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/css/simple-slider.css b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/css/simple-slider.css similarity index 67% rename from circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/css/simple-slider.css rename to circle/dashboard/static/dashboard/loopj-jquery-simple-slider/css/simple-slider.css index ca9bf6b..953ebf2 100644 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/css/simple-slider.css +++ b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/css/simple-slider.css @@ -53,3 +53,50 @@ border-color: #496805; } + + +.slider { + display: inline-block; +} +.slider .track { + height: 20px; + top: 50%; +} +.slider > .dragger, .slider > .dragger:hover { + border-radius: 0px; + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + width: 8px; + height: 24px; + margin-top: -12px!important; + text-shadow: 0 1px 0 #fff; + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); + background-repeat: repeat-x; + border-color: #2d6ca2; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); +} +.slider > .dragger:hover { + background-color: #3071a9; + background-image: none; + border-color: #2d6ca2; +} + +.slider > .highlight-track { + height: 20px; + top: 50%; +} +.slider + .output { + +} + +.slider > .track, .slider > .highlight-track { +border-radius: 5px; +} + +/* review this later */ +.slider { + width: 100%; +} diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.js similarity index 80% rename from circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.js rename to circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.js index 1b917f6..5426cbd 100644 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.js +++ b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.js @@ -1,7 +1,10 @@ /* jQuery Simple Slider - Copyright (c) 2012 James Smith (http://loopj.com) + Copyright (c) 2012, 2013 James Smith (http://loopj.com) + Copyright (c) 2013 Maarten van Grootel (http://maatenvangrootel.nl) + Copyright (c) 2013 Nathan Hunzaker (http://natehunzaker.com) + Copyright (c) 2013 Erik J. Nedwidek (http://github.com/nedwidek) Licensed under the MIT license (http://mit-license.org/) */ @@ -23,8 +26,12 @@ var __slice = [].slice, classPrefix: null, classSuffix: null, theme: null, - highlight: false + highlight: false, + showScale: false }; + if(typeof options == 'undefined') { + options = this.loadDataOptions(); + } this.settings = $.extend({}, this.defaultOptions, options); if (this.settings.theme) { this.settings.classSuffix = "-" + this.settings.theme; @@ -106,6 +113,20 @@ var __slice = [].slice, } this.setSliderPositionFromValue(this.value); ratio = this.valueToRatio(this.value); + if (this.settings.showScale) { + this.scale = this.createDivElement("scale"); + this.minScale = this.createSpanElement("min-scale", this.scale); + this.maxScale = this.createSpanElement("max-scale", this.scale); + + range = this.getRange(); + + this.minScale.html(range.min); + this.maxScale.html(range.max); + + this.scale.css('marginTop', function(index, currentValue) { + return (parseInt(currentValue, 10) + this.previousSibling.offsetHeight / 2) + 'px'; + }); + } this.input.trigger("slider:ready", { value: this.value, ratio: ratio, @@ -114,6 +135,44 @@ var __slice = [].slice, }); } + SimpleSlider.prototype.loadDataOptions = function() { + var options = {}; + allowedValues = this.input.data("slider-values"); + if (allowedValues) { + options.allowedValues = (function() { + var _i, _len, _ref, _results; + _ref = allowedValues.split(","); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + x = _ref[_i]; + _results.push(parseFloat(x)); + } + return _results; + })(); + } + if (this.input.data("slider-range")) { + options.range = this.input.data("slider-range").split(","); + } + if (this.input.data("slider-step")) { + options.step = this.input.data("slider-step"); + } + options.snap = this.input.data("slider-snap"); + options.equalSteps = this.input.data("slider-equal-steps"); + if (this.input.data("slider-theme")) { + options.theme = this.input.data("slider-theme"); + } + if (this.input.attr("data-slider-highlight")) { + options.highlight = this.input.data("slider-highlight"); + } + if (this.input.data("slider-animate") != null) { + options.animate = this.input.data("slider-animate"); + } + if (this.input.data("slider-showscale") != null) { + options.showScale = this.input.data("slider-showscale"); + } + return options; + } + SimpleSlider.prototype.createDivElement = function(classname) { var item; item = $("<div>").addClass(classname).css({ @@ -125,6 +184,12 @@ var __slice = [].slice, return item; }; + SimpleSlider.prototype.createSpanElement = function(classname, parent) { + var item; + item = $("<span>").addClass(classname).appendTo(parent); + return item; + }; + SimpleSlider.prototype.setRatio = function(ratio) { var value; ratio = Math.min(1, ratio); @@ -322,42 +387,14 @@ var __slice = [].slice, }); } }); + + /* return $(function() { return $("[data-slider]").each(function() { var $el, allowedValues, settings, x; $el = $(this); - settings = {}; - allowedValues = $el.data("slider-values"); - if (allowedValues) { - settings.allowedValues = (function() { - var _i, _len, _ref, _results; - _ref = allowedValues.split(","); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - x = _ref[_i]; - _results.push(parseFloat(x)); - } - return _results; - })(); - } - if ($el.data("slider-range")) { - settings.range = $el.data("slider-range").split(","); - } - if ($el.data("slider-step")) { - settings.step = $el.data("slider-step"); - } - settings.snap = $el.data("slider-snap"); - settings.equalSteps = $el.data("slider-equal-steps"); - if ($el.data("slider-theme")) { - settings.theme = $el.data("slider-theme"); - } - if ($el.attr("data-slider-highlight")) { - settings.highlight = $el.data("slider-highlight"); - } - if ($el.data("slider-animate") != null) { - settings.animate = $el.data("slider-animate"); - } - return $el.simpleSlider(settings); + return $el.simpleSlider(); }); }); + */ })(this.jQuery || this.Zepto, this); diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.min.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.min.js new file mode 100644 index 0000000..fbdb6e3 --- /dev/null +++ b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.min.js @@ -0,0 +1,11 @@ +/* + * jQuery Simple Slider: Unobtrusive Numerical Slider + * Version 1.0.0 + * + * Copyright (c) 2013 James Smith (http://loopj.com) + * + * Licensed under the MIT license (http://mit-license.org/) + * + */ + +var __slice=[].slice,__indexOf=[].indexOf||function(c){for(var b=0,a=this.length;b<a;b++){if(b in this&&this[b]===c){return b}}return -1};(function(c,a){var b;b=(function(){function d(e,f){var g,h=this;this.input=e;this.defaultOptions={animate:true,snapMid:false,classPrefix:null,classSuffix:null,theme:null,highlight:false,showScale:false};if(typeof f=="undefined"){f=this.loadDataOptions()}this.settings=c.extend({},this.defaultOptions,f);if(this.settings.theme){this.settings.classSuffix="-"+this.settings.theme}this.input.hide();this.slider=c("<div>").addClass("slider"+(this.settings.classSuffix||"")).css({position:"relative",userSelect:"none",boxSizing:"border-box"}).insertBefore(this.input);if(this.input.attr("id")){this.slider.attr("id",this.input.attr("id")+"-slider")}this.track=this.createDivElement("track").css({width:"100%"});if(this.settings.highlight){this.highlightTrack=this.createDivElement("highlight-track").css({width:"0"})}this.dragger=this.createDivElement("dragger");this.slider.css({minHeight:this.dragger.outerHeight(),marginLeft:this.dragger.outerWidth()/2,marginRight:this.dragger.outerWidth()/2});this.track.css({marginTop:this.track.outerHeight()/-2});if(this.settings.highlight){this.highlightTrack.css({marginTop:this.track.outerHeight()/-2})}this.dragger.css({marginTop:this.dragger.outerWidth()/-2,marginLeft:this.dragger.outerWidth()/-2});this.track.mousedown(function(i){return h.trackEvent(i)});if(this.settings.highlight){this.highlightTrack.mousedown(function(i){return h.trackEvent(i)})}this.dragger.mousedown(function(i){if(i.which!==1){return}h.dragging=true;h.dragger.addClass("dragging");h.domDrag(i.pageX,i.pageY);return false});c("body").mousemove(function(i){if(h.dragging){h.domDrag(i.pageX,i.pageY);return c("body").css({cursor:"pointer"})}}).mouseup(function(i){if(h.dragging){h.dragging=false;h.dragger.removeClass("dragging");return c("body").css({cursor:"auto"})}});this.pagePos=0;if(this.input.val()===""){this.value=this.getRange().min;this.input.val(this.value)}else{this.value=this.nearestValidValue(this.input.val())}this.setSliderPositionFromValue(this.value);g=this.valueToRatio(this.value);if(this.settings.showScale){this.scale=this.createDivElement("scale");this.minScale=this.createSpanElement("min-scale",this.scale);this.maxScale=this.createSpanElement("max-scale",this.scale);range=this.getRange();this.minScale.html(range.min);this.maxScale.html(range.max);this.scale.css("marginTop",function(i,j){return(parseInt(j,10)+this.previousSibling.offsetHeight/2)+"px"})}this.input.trigger("slider:ready",{value:this.value,ratio:g,position:g*this.slider.outerWidth(),el:this.slider})}d.prototype.loadDataOptions=function(){var e={};allowedValues=this.input.data("slider-values");if(allowedValues){e.allowedValues=(function(){var i,g,h,f;h=allowedValues.split(",");f=[];for(i=0,g=h.length;i<g;i++){x=h[i];f.push(parseFloat(x))}return f})()}if(this.input.data("slider-range")){e.range=this.input.data("slider-range").split(",")}if(this.input.data("slider-step")){e.step=this.input.data("slider-step")}e.snap=this.input.data("slider-snap");e.equalSteps=this.input.data("slider-equal-steps");if(this.input.data("slider-theme")){e.theme=this.input.data("slider-theme")}if(this.input.attr("data-slider-highlight")){e.highlight=this.input.data("slider-highlight")}if(this.input.data("slider-animate")!=null){e.animate=this.input.data("slider-animate")}if(this.input.data("slider-showscale")!=null){e.showScale=this.input.data("slider-showscale")}return e};d.prototype.createDivElement=function(f){var e;e=c("<div>").addClass(f).css({position:"absolute",top:"50%",userSelect:"none",cursor:"pointer"}).appendTo(this.slider);return e};d.prototype.createSpanElement=function(g,e){var f;f=c("<span>").addClass(g).appendTo(e);return f};d.prototype.setRatio=function(e){var f;e=Math.min(1,e);e=Math.max(0,e);f=this.ratioToValue(e);this.setSliderPositionFromValue(f);return this.valueChanged(f,e,"setRatio")};d.prototype.setValue=function(f){var e;f=this.nearestValidValue(f);e=this.valueToRatio(f);this.setSliderPositionFromValue(f);return this.valueChanged(f,e,"setValue")};d.prototype.trackEvent=function(f){if(f.which!==1){return}this.domDrag(f.pageX,f.pageY,true);this.dragging=true;return false};d.prototype.domDrag=function(i,g,e){var f,h,j;if(e==null){e=false}f=i-this.slider.offset().left;f=Math.min(this.slider.outerWidth(),f);f=Math.max(0,f);if(this.pagePos!==f){this.pagePos=f;h=f/this.slider.outerWidth();j=this.ratioToValue(h);this.valueChanged(j,h,"domDrag");if(this.settings.snap){return this.setSliderPositionFromValue(j,e)}else{return this.setSliderPosition(f,e)}}};d.prototype.setSliderPosition=function(e,f){if(f==null){f=false}if(f&&this.settings.animate){this.dragger.animate({left:e},200);if(this.settings.highlight){return this.highlightTrack.animate({width:e},200)}}else{this.dragger.css({left:e});if(this.settings.highlight){return this.highlightTrack.css({width:e})}}};d.prototype.setSliderPositionFromValue=function(g,e){var f;if(e==null){e=false}f=this.valueToRatio(g);return this.setSliderPosition(f*this.slider.outerWidth(),e)};d.prototype.getRange=function(){if(this.settings.allowedValues){return{min:Math.min.apply(Math,this.settings.allowedValues),max:Math.max.apply(Math,this.settings.allowedValues)}}else{if(this.settings.range){return{min:parseFloat(this.settings.range[0]),max:parseFloat(this.settings.range[1])}}else{return{min:0,max:1}}}};d.prototype.nearestValidValue=function(i){var h,g,f,e;f=this.getRange();i=Math.min(f.max,i);i=Math.max(f.min,i);if(this.settings.allowedValues){h=null;c.each(this.settings.allowedValues,function(){if(h===null||Math.abs(this-i)<Math.abs(h-i)){return h=this}});return h}else{if(this.settings.step){g=(f.max-f.min)/this.settings.step;e=Math.floor((i-f.min)/this.settings.step);if((i-f.min)%this.settings.step>this.settings.step/2&&e<g){e+=1}return e*this.settings.step+f.min}else{return i}}};d.prototype.valueToRatio=function(l){var f,e,j,m,i,g,k,h;if(this.settings.equalSteps){h=this.settings.allowedValues;for(m=g=0,k=h.length;g<k;m=++g){f=h[m];if(!(typeof e!=="undefined"&&e!==null)||Math.abs(f-l)<Math.abs(e-l)){e=f;j=m}}if(this.settings.snapMid){return(j+0.5)/this.settings.allowedValues.length}else{return j/(this.settings.allowedValues.length-1)}}else{i=this.getRange();return(l-i.min)/(i.max-i.min)}};d.prototype.ratioToValue=function(h){var e,g,j,i,f;if(this.settings.equalSteps){f=this.settings.allowedValues.length;i=Math.round(h*f-0.5);e=Math.min(i,this.settings.allowedValues.length-1);return this.settings.allowedValues[e]}else{g=this.getRange();j=h*(g.max-g.min)+g.min;return this.nearestValidValue(j)}};d.prototype.valueChanged=function(h,f,e){var g;if(h.toString()===this.value.toString()){return}this.value=h;g={value:h,ratio:f,position:f*this.slider.outerWidth(),trigger:e,el:this.slider};return this.input.val(h).trigger(c.Event("change",g)).trigger("slider:changed",g)};return d})();c.extend(c.fn,{simpleSlider:function(){var e,d,f;f=arguments[0],e=2<=arguments.length?__slice.call(arguments,1):[];d=["setRatio","setValue"];return c(this).each(function(){var h,g;if(f&&__indexOf.call(d,f)>=0){h=c(this).data("slider-object");return h[f].apply(h,e)}else{g=f;return c(this).data("slider-object",new b(c(this),g))}})}});return c(function(){return c("[data-slider]").each(function(){var e,g,f,d;e=c(this);return e.simpleSlider()})})})(this.jQuery||this.Zepto,this); diff --git a/circle/dashboard/static/dashboard/vm-create.js b/circle/dashboard/static/dashboard/vm-create.js index b0926f1..8806993 100644 --- a/circle/dashboard/static/dashboard/vm-create.js +++ b/circle/dashboard/static/dashboard/vm-create.js @@ -2,13 +2,17 @@ var vlans = []; var disks = []; $(function() { - vmCustomizeLoaded(); + if($(".vm-create-template-list").length) { + vmCreateLoaded(); + } else { + vmCustomizeLoaded(); + } }); function vmCreateLoaded() { $(".vm-create-template-details").hide(); - $(".vm-create-template-summary").click(function() { + $(".vm-create-template-summary").unbind("click").click(function() { $(this).next(".vm-create-template-details").slideToggle(); }); @@ -64,9 +68,18 @@ function vmCreateLoaded() { return false; }); + $('.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+'%'); + }); + } function vmCustomizeLoaded() { + $("[title]").tooltip(); /* network thingies */ /* add network */ @@ -92,7 +105,7 @@ function vmCustomizeLoaded() { /* add dummy text if no more networks are available */ if($('#vm-create-network-add-select option').length < 1) { $('#vm-create-network-add-button').attr('disabled', true); - $('#vm-create-network-add-select').html('<option value="-1">No more networks!</option>'); + $('#vm-create-network-add-select').html('<option value="-1">' + gettext("No more networks.") + '</option>'); } return false; @@ -124,7 +137,7 @@ function vmCustomizeLoaded() { /* remove the selection from the multiple select */ $('#vm-create-network-add-vlan option[value="' + vlan_pk + '"]').prop('selected', false); if ($('#vm-create-network-list').children('span').length < 1) { - $('#vm-create-network-list').append('Not added to any network!'); + $('#vm-create-network-list').append(gettext("Not added to any network")); } }); return false; @@ -155,7 +168,7 @@ function vmCustomizeLoaded() { // 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-select").html('<option value="-1">' + gettext("No more networks.") + '</option>'); $('#vm-create-network-add-button').attr('disabled', true); } @@ -170,54 +183,6 @@ function vmCustomizeLoaded() { /* ----- end of networks thingies ----- */ - - /* add disk */ - $('#vm-create-disk-add-button').click(function() { - var disk_pk = $('#vm-create-disk-add-select :selected').val(); - var name = $('#vm-create-disk-add-select :selected').text(); - - if ($('#vm-create-disk-list').children('span').length < 1) { - $('#vm-create-disk-list').html(''); - } - $('#vm-create-disk-list').append( - vmCreateDiskLabel(disk_pk, name) - ); - - /* select the disk from the multiple select */ - $('#vm-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', true); - - $('option:selected', $('#vm-create-disk-add-select')).remove(); - - /* add dummy text if no more disks are available */ - if($('#vm-create-disk-add-select option').length < 1) { - $('#vm-create-disk-add-button').attr('disabled', true); - $('#vm-create-disk-add-select').html('<option value="-1">We are out of <options> hehe</option>'); - } - - return false; - }); - - - /* 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('disk-', '') - - $(this).parent('span').fadeOut(500, function() { - /* remove the disk label */ - $(this).remove(); - - var disk_name = $(this).text(); - - /* 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) { - $('#vm-create-disk-list').append('No disks are added!'); - } - }); - return false; - }); - /* copy disks from hidden select */ $('#vm-create-disk-add-form option').each(function() { var text = $(this).text(); @@ -244,6 +209,14 @@ function vmCustomizeLoaded() { /* start vm button clicks */ $('#vm-create-customized-start').click(function() { + var error = false; + $(".cpu-count-input, .ram-input, #id_name, #id_amount ").each(function() { + if(!$(this)[0].checkValidity()) { + error = true; + } + }); + if(error) return true; + $.ajax({ url: '/dashboard/vm/create/', headers: {"X-CSRFToken": getCookie('csrftoken')}, @@ -284,6 +257,7 @@ function vmCustomizeLoaded() { /* for no js stuff */ $('.no-js-hidden').show(); $('.js-hidden').hide(); + } @@ -294,5 +268,5 @@ function vmCreateNetworkLabel(pk, name, managed) { function vmCreateDiskLabel(pk, name) { var style = "float: left; margin: 5px 5px 5px 0;"; - return '<span id="disk-' + pk + '" class="label label-primary" style="' + style + '"><i class="fa fa-file"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-disk"><i class="fa fa-times-circle"></i></a></span> '; + return '<span id="disk-' + pk + '" class="label label-primary" style="' + style + '"><i class="fa fa-file"></i> ' + name + '</span> '; } diff --git a/circle/dashboard/static/dashboard/vm-details.js b/circle/dashboard/static/dashboard/vm-details.js index b446168..3129f9d 100644 --- a/circle/dashboard/static/dashboard/vm-details.js +++ b/circle/dashboard/static/dashboard/vm-details.js @@ -1,5 +1,6 @@ var show_all = false; var in_progress = false; +var activity_hash = 5; $(function() { /* do we need to check for new activities */ @@ -27,6 +28,15 @@ $(function() { /* save resources */ $('#vm-details-resources-save').click(function() { + var error = false; + $(".cpu-count-input, .ram-input").each(function() { + if(!$(this)[0].checkValidity()) { + error = true; + } + }); + if(error) return true; + + $('i.fa-floppy-o', this).removeClass("fa-floppy-o").addClass("fa-refresh fa-spin"); var vm = $(this).data("vm"); $.ajax({ @@ -34,8 +44,12 @@ $(function() { url: "/dashboard/vm/" + vm + "/op/resources_change/", data: $('#vm-details-resources-form').serialize(), success: function(data, textStatus, xhr) { + if(data.success) { + $('a[href="#activity"]').trigger("click"); + } else { + addMessage(data.messages.join("<br />"), "danger"); + } $("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o"); - $('a[href="#activity"]').trigger("click"); }, error: function(xhr, textStatus, error) { $("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o"); @@ -346,17 +360,6 @@ function decideActivityRefresh() { return check; } -/* unescapes html got via the request, also removes whitespaces and replaces all ' with " */ -function unescapeHTML(html) { - return html.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&').replace(/–/g, "–").replace(/\//g, "").replace(/'/g, '"').replace(/'/g, "'").replace(/ /g, ''); -} - -/* the html page contains some tags that were modified via js (titles for example), we delete these - also some html tags are closed with / */ -function changeHTML(html) { - return html.replace(/data-original-title/g, "title").replace(/title=""/g, "").replace(/\//g, '').replace(/ /g, ''); -} - function checkNewActivity(runs) { var instance = location.href.split('/'); instance = instance[instance.length - 2]; @@ -365,19 +368,24 @@ function checkNewActivity(runs) { url: '/dashboard/vm/' + instance + '/activity/', data: {'show_all': show_all}, success: function(data) { - if(show_all) { /* replace on longer string freezes the spinning stuff */ + var new_activity_hash = (data['activities'] + "").hashCode(); + if(new_activity_hash != activity_hash) { $("#activity-refresh").html(data['activities']); - } else { - a = unescapeHTML(data['activities']); - b = changeHTML($("#activity-refresh").html()); - if(a != b) - $("#activity-refresh").html(data['activities']); } + activity_hash = new_activity_hash; + $("#ops").html(data['ops']); $("#disk-ops").html(data['disk_ops']); $("[title]").tooltip(); - $("#vm-details-state i").prop("class", "fa " + data['icon']); + /* changing the status text */ + var icon = $("#vm-details-state i"); + if(data['is_new_state']) { + if(!icon.hasClass("fa-spin")) + icon.prop("class", "fa fa-spinner fa-spin"); + } else { + icon.prop("class", "fa " + data['icon']); + } $("#vm-details-state span").html(data['human_readable_status'].toUpperCase()); if(data['status'] == "RUNNING") { $("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled"); @@ -385,12 +393,12 @@ function checkNewActivity(runs) { $("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled"); } - if(data['status'] == "STOPPED") { - $(".enabled-when-stopped").prop("disabled", false); - $(".hide-when-stopped").hide(); + if(data['status'] == "STOPPED" || data['status'] == "PENDING") { + $(".change-resources-button").prop("disabled", false); + $(".change-resources-help").hide(); } else { - $(".enabled-when-stopped").prop("disabled", true); - $(".hide-when-stopped").show(); + $(".change-resources-button").prop("disabled", true); + $(".change-resources-help").show(); } if(runs > 0 && decideActivityRefresh()) { @@ -408,3 +416,14 @@ function checkNewActivity(runs) { } }); } + +String.prototype.hashCode = function() { + var hash = 0, i, chr, len; + if (this.length == 0) return hash; + for (i = 0, len = this.length; i < len; i++) { + chr = this.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +}; diff --git a/circle/dashboard/templates/base.html b/circle/dashboard/templates/base.html index 7227817..aede030 100644 --- a/circle/dashboard/templates/base.html +++ b/circle/dashboard/templates/base.html @@ -68,6 +68,7 @@ </body> <script src="//code.jquery.com/jquery-1.11.1.min.js"></script> + <script src="{{ STATIC_URL }}dashboard/loopj-jquery-simple-slider/js/simple-slider.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> <script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script> {% include 'autocomplete_light/static.html' %} diff --git a/circle/dashboard/templates/dashboard/_resources-sliders.html b/circle/dashboard/templates/dashboard/_resources-sliders.html new file mode 100644 index 0000000..27dd1a5 --- /dev/null +++ b/circle/dashboard/templates/dashboard/_resources-sliders.html @@ -0,0 +1,65 @@ +{% load i18n %} +{% load sizefieldtags %} +{% load crispy_forms_tags %} + +<div class="row"> + <div class="col-sm-3" style="font-weight: bold;"> + <i class="fa fa-trophy"></i> {% trans "CPU priority" %} + </div> + <div class="col-sm-6"> + <input type="text" class="vm-slider cpu-priority-slider" + disabled placeholder="{% trans "Enable JS for fancy sliders" %}" + data-slider="true" data-slider-highlight="true" data-slider-range="10, 100" + data-slider-values="0, 10, 30, 80, 100" data-slider-snap="true"/> + </div> + <div class="col-sm-3"> + <div class="input-group"> + {{ field_priority }} + <span class="input-group-addon input-tags"> + <i class="fa fa-question" title="{% trans "The priority of the CPU" %}"></i> + </span> + </div> + </div> +</div> +<div class="row"> + <div class="col-sm-3" style="font-weight: bold;"> + <i class="fa fa-cogs"></i> {% trans "CPU count" %} + </div> + <div class="col-sm-6"> + <input type="text" class="vm-slider cpu-count-slider" + disabled placeholder="{% trans "Enable JS for fancy sliders" %}" + data-slider="true" data-slider-highlight="true" data-slider-range="0, 10" + data-slider-step="true" data-slider-snap="true"/> + </div> + <div class="col-sm-3"> + <div class="input-group"> + {{ field_num_cores }} + <span class="input-group-addon input-tags"> + <i class="fa fa-question" title="{% trans "Number of CPU cores" %}"></i> + </span> + </div> + </div> +</div> + +<div class="row"> + <div class="col-sm-3" style="font-weight: bold;"> + <i class="fa fa-ticket"></i> {% trans "RAM amount" %} + </div> + <div class="col-sm-6"> + <input type="text" class="vm-slider ram-slider" + disabled placeholder="{% trans "Enable JS for fancy sliders" %}" + data-slider="true" data-slider-highlight="true" data-slider-range="0, 8192" + data-slider-step="128" data-slider-snap="true"/> + </div> + <div class="col-sm-3"> + <div class="input-group"> + {{ field_ram_size }} + <span class="input-group-addon input-tags"> + MiB + </span> + <span class="input-group-addon input-tags"> + <i title="{% trans "RAM size in mebibytes" %}" class="fa fa-question"></i> + </span> + </div> + </div> +</div> diff --git a/circle/dashboard/templates/dashboard/_template-create.html b/circle/dashboard/templates/dashboard/_template-create.html index 2737e3f..7b65f67 100644 --- a/circle/dashboard/templates/dashboard/_template-create.html +++ b/circle/dashboard/templates/dashboard/_template-create.html @@ -1,17 +1,49 @@ {% load i18n %} {% load crispy_forms_tags %} -{% if leases < 1 %} + +<form action="" method="POST"> +{% with form=form %} + {% include "display-form-errors.html" %} +{% endwith %} + +{% csrf_token %} + +{{ form.name|as_crispy_field }} + +<fieldset class="resources-sliders"> + <legend>{% trans "Resource configuration" %}</legend> + {% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %} + {{ form.max_ram_size|as_crispy_field }} +</fieldset> + +<fieldset> + <legend>{% trans "Virtual machine settings" %}</legend> +{{ form.arch|as_crispy_field }} +{{ form.access_method|as_crispy_field }} +{{ form.boot_menu|as_crispy_field }} +{{ form.raw_data|as_crispy_field }} +{{ form.req_traits|as_crispy_field }} +{{ form.description|as_crispy_field }} +{{ form.system|as_crispy_field }} +</fieldset> +<fieldset> + <legend>{% trans "External resources" %}</legend> +{{ form.networks|as_crispy_field }} +{{ form.lease|as_crispy_field }} + +{% if show_lease_create %} <div class="alert alert-warning"> - {% trans "You haven't created any leases yet, but you need one to create a template!" %} + {% trans "You haven't created any leases yet, but you need one to create a template." %} <a href="{% url "dashboard.views.lease-create" %}">{% trans "Create a new lease now." %}</a> </div> {% endif %} +{{ form.tags|as_crispy_field }} +</fieldset> + +<input type="submit" value="{% trans "Create new template" %}" class="btn btn-success"> +</form> -{% with form=form %} - {% include "display-form-errors.html" %} -{% endwith %} -{% crispy form %} <style> fieldset { @@ -21,8 +53,3 @@ font-weight: bold; } </style> -<script> - $(function() { - $("#hint_id_num_cores, #hint_id_priority, #hint_id_ram_size").hide(); - }); -</script> diff --git a/circle/dashboard/templates/dashboard/_vm-create-1.html b/circle/dashboard/templates/dashboard/_vm-create-1.html index 4b19b71..9d275a2 100644 --- a/circle/dashboard/templates/dashboard/_vm-create-1.html +++ b/circle/dashboard/templates/dashboard/_vm-create-1.html @@ -58,7 +58,7 @@ </li> </ul> <div style="margin-top: 20px; padding: 0 15px; width: 100%"> - {% if perms.vm_set_resources %} + {% 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="fa fa-wrench"></i> {% trans "Customize" %}</a> {% endif %} <form class="pull-right text-right" method="POST" action="{% url "dashboard.views.vm-create" %}"> @@ -76,48 +76,6 @@ </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; @@ -139,15 +97,3 @@ 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 %} diff --git a/circle/dashboard/templates/dashboard/_vm-create-2.html b/circle/dashboard/templates/dashboard/_vm-create-2.html index c793d52..3d3a62c 100644 --- a/circle/dashboard/templates/dashboard/_vm-create-2.html +++ b/circle/dashboard/templates/dashboard/_vm-create-2.html @@ -1,6 +1,94 @@ {% load crispy_forms_tags %} +{% load i18n %} {% load sizefieldtags %} -{% crispy vm_create_form %} +{% include "display-form-errors.html" with form=vm_create_form %} +<form method="POST"> +{% csrf_token %} -<script src="/static/dashboard/vm-create.js"></script> +{{ vm_create_form.template }} +{{ vm_create_form.customized }} + +<div class="row"> + <div class="col-sm-12"> + <div class="form-group"> + <button id="vm-create-customized-start" class="btn btn-success" + style="float: right; margin-top: 24px;"> + <i class="fa fa-play"></i> + {% trans "Start" %} + </button> + <label>{% trans "Name" %}*</label> + {{ vm_create_form.name }} + </div> + </div> +</div> + +<div class="row"> + <div class="col-sm-10"> + <div class="form-group"> + <label>{% trans "Amount" %}*</label> + {{ vm_create_form.amount }} + </div> + </div> +</div> + +<div class="row"> + <div class="col-sm-12"> + <h2>{% trans "Resources" %}</h2> + </div> +</div> + +<!-- resources --> +<div class="resources-sliders" style="max-width: 720px;"> +{% include "dashboard/_resources-sliders.html" with field_priority=vm_create_form.cpu_priority field_num_cores=vm_create_form.cpu_count field_ram_size=vm_create_form.ram_size %} +</div> + +<div class="row"> + <div class="col-sm-4"> + <h2>{% trans "Disks" %}</h2> + </div> + <div class="col-sm-8"> + <div class="js-hidden"> + {{ vm_create_form.disks }} + </div> + <div class="no-js-hidden"> + <h3 id="vm-create-disk-list">{% trans "No disks are added." %}</h3> + <div style="clear: both;"></div> + </div> + </div> +</div> + + +<div class="row"> + <div class="col-sm-4"> + <h2>{% trans "Network" %}</h2> + </div> + <div class="col-sm-8" style="padding-top: 3px;"> + <div class="js-hidden" style="padding-top: 15px; max-width: 450px;"> + {{ vm_create_form.networks }} + </div> + <div class="no-js-hidden"> + <h3 id="vm-create-network-list"> + {% trans "Not added to any network." %} + </h3> + <h3 id="vm-create-network-add"> + <div class="input-group" style="max-width: 330px;"> + <select class="form-control font-awesome-font" id="vm-create-network-add-select"> + </select> + <div class="input-group-btn"> + <a id="vm-create-network-add-button" class="btn btn-success"> + <i class="fa fa-plus-circle"></i> + </a> + </div><!-- .input-group-btn --> + </div><!-- .input-group --> + </h3><!-- #vm-create-network-add --> + </div><!-- .no-js-hidden --> + </div><!-- .col-sm-8 --> +</div><!-- .row --> + +</form> +<script> +try { + vmCustomizeLoaded(); +} catch(e) {} +</script> diff --git a/circle/dashboard/templates/dashboard/base.html b/circle/dashboard/templates/dashboard/base.html index 3f0cc6e..a4b32e6 100644 --- a/circle/dashboard/templates/dashboard/base.html +++ b/circle/dashboard/templates/dashboard/base.html @@ -5,7 +5,7 @@ {% block extra_link %} - <link rel="stylesheet" href="{{ STATIC_URL }}dashboard/bootstrap-slider/slider.css"/> + <link rel="stylesheet" href="{{ STATIC_URL }}dashboard/loopj-jquery-simple-slider/css/simple-slider.css"/> <link href="{{ STATIC_URL }}dashboard/bootstrap-tour.min.css" rel="stylesheet"> <link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet"> {% endblock %} @@ -55,6 +55,5 @@ {% block extra_script %} <script src="{{ STATIC_URL }}dashboard/js/jquery.knob.js"></script> - <script src="{{ STATIC_URL}}dashboard/bootstrap-slider/bootstrap-slider.js"></script> <script src="{{ STATIC_URL }}dashboard/dashboard.js"></script> {% endblock %} diff --git a/circle/dashboard/templates/dashboard/group-create.html b/circle/dashboard/templates/dashboard/group-create.html index 29a270c..35e47a9 100644 --- a/circle/dashboard/templates/dashboard/group-create.html +++ b/circle/dashboard/templates/dashboard/group-create.html @@ -1,9 +1,4 @@ {% load crispy_forms_tags %} -<style> - .row { - margin-bottom: 15px; - } -</style> <form method="POST" action="{% url "dashboard.views.group-create" %}"> {% csrf_token %} diff --git a/circle/dashboard/templates/dashboard/lease-edit.html b/circle/dashboard/templates/dashboard/lease-edit.html index 0186ec1..519f0bd 100644 --- a/circle/dashboard/templates/dashboard/lease-edit.html +++ b/circle/dashboard/templates/dashboard/lease-edit.html @@ -82,9 +82,9 @@ </tr> {% endfor %} <tr><td><i class="icon-plus"></i></td> - <td><input type="text" class="form-control" name="perm-new-name" + <td><input type="text" class="form-control" name="name" placeholder="{% trans "Name of group or user" %}"></td> - <td><select class="form-control" name="perm-new"> + <td><select class="form-control" name="level"> {% for id, name in acl.levels %} <option value="{{id}}">{{name}}</option> {% endfor %} diff --git a/circle/dashboard/templates/dashboard/node-create.html b/circle/dashboard/templates/dashboard/node-create.html index 640e951..68c63f3 100644 --- a/circle/dashboard/templates/dashboard/node-create.html +++ b/circle/dashboard/templates/dashboard/node-create.html @@ -1,11 +1,12 @@ {% load crispy_forms_tags %} +<form method="POST" action="/dashboard/node/create/" id="node-create-form"> +{% csrf_token %} +{% crispy formset formset.form.helper %} +</form> + <style> - .row { - margin-bottom: 15px; + #node-create-form .row { + margin-bottom: 10px; } </style> -<form method="POST" action="/dashboard/node/create/"> -{% csrf_token %} -{% crispy formset formset.form.helper %} -</form> diff --git a/circle/dashboard/templates/dashboard/nojs-wrapper.html b/circle/dashboard/templates/dashboard/nojs-wrapper.html index cbba2a1..e5f87c2 100644 --- a/circle/dashboard/templates/dashboard/nojs-wrapper.html +++ b/circle/dashboard/templates/dashboard/nojs-wrapper.html @@ -16,7 +16,7 @@ {% endblock %} {% block extra_js %} - {% if template == "dashboard/_vm-create-1.html" %} + {% if template == "dashboard/_vm-create-1.html" or template == "dashboard/_vm-create-2.html" %} <script src="{{ STATIC_URL }}dashboard/vm-create.js"></script> {% endif %} {% endblock %} diff --git a/circle/dashboard/templates/dashboard/store/upload.html b/circle/dashboard/templates/dashboard/store/upload.html index 795ae6c..13ab887 100644 --- a/circle/dashboard/templates/dashboard/store/upload.html +++ b/circle/dashboard/templates/dashboard/store/upload.html @@ -19,7 +19,7 @@ <div class="panel-body"> <div style="text-align: center; margin: 0 0 20px 0;"> <div class="label label-info" style="padding: 5px;"> - {% trans "Curently uploading to" %}: {{ directory }} + {% trans "Currently uploading to" %}: {{ directory }} </div> </div> <form method="POST" action="{{ action }}" enctype="multipart/form-data"> diff --git a/circle/dashboard/templates/dashboard/template-edit.html b/circle/dashboard/templates/dashboard/template-edit.html index 36bd8a7..ba88e73 100644 --- a/circle/dashboard/templates/dashboard/template-edit.html +++ b/circle/dashboard/templates/dashboard/template-edit.html @@ -15,10 +15,41 @@ <h3 class="no-margin"><i class="fa fa-puzzle-piece"></i> {% trans "Edit template" %}</h3> </div> <div class="panel-body"> + <form action="" method="POST"> {% with form=form %} {% include "display-form-errors.html" %} {% endwith %} - {% crispy form %} + + {% csrf_token %} + + {{ form.name|as_crispy_field }} + + <fieldset class="resources-sliders"> + <legend>{% trans "Resource configuration" %}</legend> + {% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %} + {{ form.max_ram_size|as_crispy_field }} + </fieldset> + + <fieldset> + <legend>{% trans "Virtual machine settings" %}</legend> + {{ form.arch|as_crispy_field }} + {{ form.access_method|as_crispy_field }} + {{ form.boot_menu|as_crispy_field }} + {{ form.raw_data|as_crispy_field }} + {{ form.req_traits|as_crispy_field }} + {{ form.description|as_crispy_field }} + {{ form.system|as_crispy_field }} + </fieldset> + <fieldset> + <legend>{% trans "External resources" %}</legend> + {{ form.networks|as_crispy_field }} + {{ form.lease|as_crispy_field }} + + {{ form.tags|as_crispy_field }} + </fieldset> + + <input type="submit" value="{% trans "Save changes" %}" class="btn btn-primary"> + </form> </div> </div> </div> diff --git a/circle/dashboard/templates/dashboard/vm-detail.html b/circle/dashboard/templates/dashboard/vm-detail.html index ee943b3..43f747a 100644 --- a/circle/dashboard/templates/dashboard/vm-detail.html +++ b/circle/dashboard/templates/dashboard/vm-detail.html @@ -63,7 +63,11 @@ <div class="col-md-4" id="vm-info-pane"> <div class="big"> <span id="vm-details-state" class="label label-success"> - <i class="fa {{ instance.get_status_icon }}"></i> + <i class="fa + {% if is_new_state %} + fa-spinner fa-spin + {% else %} + {{ instance.get_status_icon }}{% endif %}"></i> <span>{{ instance.get_status_display|upper }}</span> </span> </div> @@ -75,6 +79,8 @@ <dd> {% if instance.get_connect_port %} {{ instance.get_connect_host }}:<strong>{{ instance.get_connect_port }}</strong> + {% elif instance.interface_set.count < 1%} + <strong>{% trans "The VM doesn't have any network interface." %}</strong> {% else %} <strong>{% trans "The required port for this protocol is not forwarded." %}</strong> {% endif %} @@ -109,8 +115,7 @@ <div class="input-group" id="dashboard-vm-details-connect-command"> <span class="input-group-addon input-tags">{% trans "Command" %}</span> <input type="text" spellcheck="false" - value="{% if instance.get_connect_command %}{{ instance.get_connect_command }}{% else %} - {% trans "Connection is not possible." %}{% endif %}" + value="{% if instance.get_connect_command %}{{ instance.get_connect_command }}{% else %}{% trans "Connection is not possible." %}{% endif %}" id="vm-details-connection-string" class="form-control input-tags" /> <span class="input-group-addon input-tags" id="vm-details-connection-string-copy"> <i class="fa fa-copy" title="{% trans "Select all" %}"></i> diff --git a/circle/dashboard/templates/dashboard/vm-detail/resources.html b/circle/dashboard/templates/dashboard/vm-detail/resources.html index 77f0d49..209cc72 100644 --- a/circle/dashboard/templates/dashboard/vm-detail/resources.html +++ b/circle/dashboard/templates/dashboard/vm-detail/resources.html @@ -2,55 +2,26 @@ {% load sizefieldtags %} {% load crispy_forms_tags %} -<form id="vm-details-resources-form" method="POST" action=""> +<form method="POST" action="{{ op.resources_change.get_url }}" id="vm-details-resources-form"> {% csrf_token %} -<p class="row"> - <div class="col-sm-3"> - <label for="vm-cpu-priority-slider"><i class="fa fa-trophy"></i> {% trans "CPU priority" %}</label> - </div> - <div class="col-sm-9"> - <input name="cpu-priority" type="text" id="vm-cpu-priority-slider" class="vm-slider" value="{{ instance.priority }}" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="{{ instance.priority }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> - </div> -</p> - - -<p class="row"> - <div class="col-sm-3"> - <label for="cpu-count-slider"><i class="fa fa-cogs"></i> {% trans "CPU count" %}</label> - </div> - <div class="col-sm-9"> - <input name="cpu-count" type="text" id="vm-cpu-count-slider" class="vm-slider" value=" {{ instance.num_cores }}" data-slider-min="0" data-slider-max="8" data-slider-step="1" data-slider-value="{{ instance.num_cores }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> - </div> -</p> - - -<p class="row"> - <div class="col-sm-3"> - <label for="ram-slider"><i class="fa fa-ticket"></i> {% trans "RAM amount" %}</label> - </div> - <div class="col-sm-9"> - <input name="ram-size" type="text" id="vm-ram-size-slider" class="vm-slider" value="{{ instance.ram_size }}" data-slider-min="128" data-slider-max="4096" data-slider-step="128" data-slider-value="{{ instance.ram_size }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> MiB - </div> -</p> + {% include "dashboard/_resources-sliders.html" with field_priority=resources_form.priority field_num_cores=resources_form.num_cores field_ram_size=resources_form.ram_size %} {% if can_change_resources %} -<p class="row"> - <div class="col-sm-12"> - <button type="submit" class="btn btn-success btn-sm enabled-when-stopped" id="vm-details-resources-save" - data-vm="{{ instance.pk }}" - {% if not op.resources_change %}disabled{% endif %}> + <button type="submit" class="btn btn-success btn-sm change-resources-button" + id="vm-details-resources-save" data-vm="{{ instance.pk }}" + {% if op.resources_change.disabled %}disabled{% endif %}> <i class="fa fa-floppy-o"></i> {% trans "Save resources" %} </button> - <span class="hide-when-stopped" - {% if op.resources_change %}style="display: none;"{% endif %} + <span class="change-resources-help" + {% if not op.resources_change.disabled %}style="display: none;"{% endif %} >{% trans "Stop your VM to change resources." %}</span> - </div> -</p> {% endif %} </form> + <hr /> + <div class="row" id="vm-details-resources-disk"> <div class="col-sm-11"> <h3> diff --git a/circle/dashboard/tests/test_views.py b/circle/dashboard/tests/test_views.py index 58aa971..8e416a7 100644 --- a/circle/dashboard/tests/test_views.py +++ b/circle/dashboard/tests/test_views.py @@ -282,10 +282,12 @@ class VmDetailTest(LoginMixin, TestCase): c = Client() self.login(c, 'superuser') kwargs = InstanceTemplate.objects.get(id=1).__dict__.copy() - kwargs.update(name='t2', lease=1, disks=1, raw_data='tst2') + kwargs.update(name='t2', lease=1, disks=1, + raw_data='<devices></devices>') response = c.post('/dashboard/template/1/', kwargs) self.assertEqual(response.status_code, 302) - self.assertEqual(InstanceTemplate.objects.get(id=1).raw_data, 'tst2') + self.assertEqual(InstanceTemplate.objects.get(id=1).raw_data, + "<devices></devices>") def test_permitted_lease_delete_w_template_using_it(self): c = Client() @@ -565,7 +567,7 @@ class VmDetailTest(LoginMixin, TestCase): 'amount': 2, 'customized': 1, 'template': 1, - 'cpu_priority': 1, 'cpu_count': 1, 'ram_size': 1, + 'cpu_priority': 10, 'cpu_count': 1, 'ram_size': 128, 'network': [], }) diff --git a/circle/dashboard/views.py b/circle/dashboard/views.py index 8be5cda..e0f8ccb 100644 --- a/circle/dashboard/views.py +++ b/circle/dashboard/views.py @@ -1,5 +1,4 @@ # Copyright 2014 Budapest University of Technology and Economics (BME IK) - # # This file is part of CIRCLE Cloud. # @@ -71,7 +70,7 @@ from .forms import ( VmSaveForm, UserKeyForm, VmRenewForm, CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm, TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm, - VmAddInterfaceForm, + VmResourcesForm, VmAddInterfaceForm, ) from .tables import ( @@ -87,7 +86,7 @@ from storage.models import Disk from firewall.models import Vlan, Host, Rule from .models import Favourite, Profile, GroupProfile, FutureMember -from .store_api import Store, NoStoreException +from .store_api import Store, NoStoreException, NotOkException logger = logging.getLogger(__name__) saml_available = hasattr(settings, "SAML_CONFIG") @@ -311,6 +310,10 @@ class VmDetailView(CheckedDetailView): activities = activities[:10] context['activities'] = activities context['show_show_all'] = show_show_all + latest = instance.get_latest_activity_in_progress() + context['is_new_state'] = (latest and + latest.resultant_state is not None and + instance.status != latest.resultant_state) context['vlans'] = Vlan.get_objects_with_level( 'user', self.request.user @@ -328,6 +331,8 @@ class VmDetailView(CheckedDetailView): context['ipv6_port'] = instance.get_connect_port(use_ipv6=True) # resources forms + context['resources_form'] = VmResourcesForm(instance=instance) + if self.request.user.is_superuser: context['traits_form'] = TraitsForm(instance=instance) context['raw_data_form'] = RawDataForm(instance=instance) @@ -352,8 +357,6 @@ class VmDetailView(CheckedDetailView): return v(request) raise Http404() - raise Http404() - def __set_name(self, request): self.object = self.get_object() if not self.object.has_level(request.user, 'owner'): @@ -789,22 +792,35 @@ class VmResourcesChangeView(VmOperationView): op = 'resources_change' icon = "save" show_in_toolbar = False + wait_for_result = 0.5 def post(self, request, extra=None, *args, **kwargs): if extra is None: extra = {} - resources = { - 'num_cores': "cpu-count", - 'priority': "cpu-priority", - 'ram_size': "ram-size", - "max_ram_size": "ram-size", # TODO - } - for k, v in resources.iteritems(): - extra[k] = request.POST.get(v) + instance = get_object_or_404(Instance, pk=kwargs['pk']) - return super(VmResourcesChangeView, self).post(request, extra, - *args, **kwargs) + form = VmResourcesForm(request.POST, instance=instance) + if not form.is_valid(): + for f in form.errors: + messages.error(request, "<strong>%s</strong>: %s" % ( + f, form.errors[f].as_text() + )) + if request.is_ajax(): # this is not too nice + store = messages.get_messages(request) + store.used = True + return HttpResponse( + json.dumps({'success': False, + 'messages': [unicode(m) for m in store]}), + content_type="application=json" + ) + else: + return redirect(instance.get_absolute_url() + "#resources") + else: + extra = form.cleaned_data + extra['max_ram_size'] = extra['ram_size'] + return super(VmResourcesChangeView, self).post(request, extra, + *args, **kwargs) class TokenOperationView(OperationView): @@ -1359,10 +1375,13 @@ class TemplateCreate(SuccessMessageMixin, CreateView): def get_context_data(self, *args, **kwargs): context = super(TemplateCreate, self).get_context_data(*args, **kwargs) + num_leases = Lease.get_objects_with_level("user", + self.request.user).count() + can_create_leases = self.request.user.has_perm("create_leases") context.update({ 'box_title': _("Create a new base VM"), 'template': "dashboard/_template-create.html", - 'leases': Lease.objects.count() + 'show_lease_create': num_leases < 1 and can_create_leases }) return context @@ -2441,6 +2460,9 @@ def vm_activity(request, pk): response['human_readable_status'] = instance.get_status_display() response['status'] = instance.status response['icon'] = instance.get_status_icon() + latest = instance.get_latest_activity_in_progress() + response['is_new_state'] = (latest and latest.resultant_state is not None + and instance.status != latest.resultant_state) context = { 'instance': instance, @@ -3162,6 +3184,10 @@ class StoreList(LoginRequiredMixin, TemplateView): except NoStoreException: messages.warning(self.request, _("No store.")) return redirect("/") + except NotOkException: + messages.warning(self.request, _("Store has some problems now." + " Try again later.")) + return redirect("/") def create_up_directory(self, directory): path = normpath(join('/', directory, '..')) diff --git a/circle/firewall/tasks/local_tasks.py b/circle/firewall/tasks/local_tasks.py index 2a06967..3085aca 100644 --- a/circle/firewall/tasks/local_tasks.py +++ b/circle/firewall/tasks/local_tasks.py @@ -98,5 +98,5 @@ def reloadtask(type='Host', timeout=15): }[type] logger.info("Reload %s on next periodic iteration applying change to %s.", ", ".join(reload), type) - if all(cache.add("%s_lock" % i, True, 30) for i in reload): + if all([cache.add("%s_lock" % i, 'true', 30) for i in reload]): reloadtask_worker.apply_async(queue='localhost.man', countdown=5) diff --git a/circle/locale/hu/LC_MESSAGES/django.po b/circle/locale/hu/LC_MESSAGES/django.po index b7e79e8..9cd88d4 100644 --- a/circle/locale/hu/LC_MESSAGES/django.po +++ b/circle/locale/hu/LC_MESSAGES/django.po @@ -3273,7 +3273,7 @@ msgid "" "[%(filename)s] because it has never beendeployed." msgstr "" "A kér művelet nem hajtható végre a(z) „%(name)s” (%(pk)s) " -"[(filename)s] lemezen, mivel nem volt még csatolva." +"[%(filename)s] lemezen, mivel nem volt még csatolva." #: storage/models.py:159 #, python-format diff --git a/circle/storage/models.py b/circle/storage/models.py index 3600b7a..54a1eb8 100644 --- a/circle/storage/models.py +++ b/circle/storage/models.py @@ -33,7 +33,9 @@ from sizefield.models import FileSizeField from .tasks import local_tasks, storage_tasks from celery.exceptions import TimeoutError -from common.models import WorkerNotFound, HumanReadableException +from common.models import ( + WorkerNotFound, HumanReadableException, humanize_exception +) logger = logging.getLogger(__name__) @@ -221,7 +223,7 @@ class Disk(TimeStampedModel): return { 'qcow2-norm': 'virtio', 'qcow2-snap': 'virtio', - 'iso': 'scsi', + 'iso': 'ide', 'raw-ro': 'virtio', 'raw-rw': 'virtio', }[self.type] @@ -404,10 +406,11 @@ class Disk(TimeStampedModel): try: result = remote.get(timeout=5) break - except TimeoutError: + except TimeoutError as e: if task is not None and task.is_aborted(): AbortableAsyncResult(remote.id).abort() - raise Exception("Download aborted by user.") + raise humanize_exception(ugettext_noop( + "Operation aborted by user."), e) disk.size = result['size'] disk.type = result['type'] disk.is_ready = True @@ -477,11 +480,12 @@ class Disk(TimeStampedModel): try: remote.get(timeout=5) break - except TimeoutError: + except TimeoutError as e: if task is not None and task.is_aborted(): AbortableAsyncResult(remote.id).abort() disk.destroy() - raise Exception("Save as aborted by use.") + raise humanize_exception(ugettext_noop( + "Operation aborted by user."), e) disk.is_ready = True disk.save() return disk diff --git a/circle/vm/models/common.py b/circle/vm/models/common.py index 9adf9d4..c86c0b1 100644 --- a/circle/vm/models/common.py +++ b/circle/vm/models/common.py @@ -18,6 +18,7 @@ from __future__ import absolute_import, unicode_literals from datetime import timedelta, datetime +from django.core.validators import MinValueValidator from django.db.models import Model, CharField, IntegerField, permalink from django.utils.translation import ugettext_lazy as _ from django.utils.timesince import timeuntil @@ -37,16 +38,20 @@ class BaseResourceConfigModel(Model): """ num_cores = IntegerField(verbose_name=_('number of cores'), help_text=_('Number of virtual CPU cores ' - 'available to the virtual machine.')) + 'available to the virtual machine.'), + validators=[MinValueValidator(0)]) ram_size = IntegerField(verbose_name=_('RAM size'), - help_text=_('Mebibytes of memory.')) + help_text=_('Mebibytes of memory.'), + validators=[MinValueValidator(0)]) max_ram_size = IntegerField(verbose_name=_('maximal RAM size'), help_text=_('Upper memory size limit ' - 'for balloning.')) + 'for balloning.'), + validators=[MinValueValidator(0)]) arch = CharField(max_length=10, verbose_name=_('architecture'), choices=ARCHITECTURES) priority = IntegerField(verbose_name=_('priority'), - help_text=_('CPU priority.')) + help_text=_('CPU priority.'), + validators=[MinValueValidator(0)]) class Meta: abstract = True diff --git a/circle/vm/models/instance.py b/circle/vm/models/instance.py index 274f64f..19b87da 100644 --- a/circle/vm/models/instance.py +++ b/circle/vm/models/instance.py @@ -41,7 +41,9 @@ from model_utils.models import TimeStampedModel, StatusModel from taggit.managers import TaggableManager from acl.models import AclBase -from common.models import create_readable, HumanReadableException +from common.models import ( + create_readable, HumanReadableException, humanize_exception +) from common.operations import OperatedMixin from ..tasks import vm_tasks, agent_tasks from .activity import (ActivityInProgressError, instance_activity, @@ -877,10 +879,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, while True: try: return remote.get(timeout=step) - except TimeoutError: + except TimeoutError as e: if task is not None and task.is_aborted(): AbortableAsyncResult(remote.id).abort() - raise Exception("Shutdown aborted by user.") + raise humanize_exception(ugettext_noop( + "Operation aborted by user."), e) def suspend_vm(self, timeout=230): queue_name = self.get_remote_queue_name('vm', 'slow') @@ -957,7 +960,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, for a in acts: if (latest == a.activity_code and - merged_acts[-1].result == a.result and + merged_acts[-1].result_data == a.result_data and a.finished and merged_acts[-1].finished and a.user == merged_acts[-1].user and (merged_acts[-1].finished - a.finished).days < 7 and @@ -975,3 +978,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, return vm_tasks.screenshot.apply_async(args=[self.vm_name], queue=queue_name ).get(timeout=timeout) + + def get_latest_activity_in_progress(self): + try: + return InstanceActivity.objects.filter( + instance=self, succeeded=None, parent=None).latest("started") + except InstanceActivity.DoesNotExist: + return None diff --git a/circle/vm/operations.py b/circle/vm/operations.py index ddb3ca9..b3f3412 100644 --- a/circle/vm/operations.py +++ b/circle/vm/operations.py @@ -913,7 +913,8 @@ class ResourcesOperation(InstanceOperation): required_perms = ('vm.change_resources', ) accept_states = ('STOPPED', 'PENDING', ) - def _operation(self, user, num_cores, ram_size, max_ram_size, priority): + def _operation(self, user, activity, + num_cores, ram_size, max_ram_size, priority): self.instance.num_cores = num_cores self.instance.ram_size = ram_size @@ -923,6 +924,12 @@ class ResourcesOperation(InstanceOperation): self.instance.full_clean() self.instance.save() + activity.result = create_readable(ugettext_noop( + "Priority: %(priority)s, Num cores: %(num_cores)s, " + "Ram size: %(ram_size)s"), priority=priority, num_cores=num_cores, + ram_size=ram_size + ) + register_operation(ResourcesOperation) diff --git a/circle/vm/tasks/local_agent_tasks.py b/circle/vm/tasks/local_agent_tasks.py index 42a2425..7cd5052 100644 --- a/circle/vm/tasks/local_agent_tasks.py +++ b/circle/vm/tasks/local_agent_tasks.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. +from common.models import create_readable from manager.mancelery import celery from vm.tasks.agent_tasks import (restart_networking, change_password, set_time, set_hostname, start_access_server, @@ -87,8 +88,9 @@ def agent_started(vm, version=None): try: with act.sub_activity( 'update', - readable_name=ugettext_noop('update to %(version)s'), - version=settings.AGENT_VERSION + readable_name=create_readable( + ugettext_noop('update to %(version)s'), + version=settings.AGENT_VERSION) ): update.apply_async( queue=queue,