Commit 7ec740a5 by Őry Máté

Merge remote-tracking branch 'origin/master' into feature-userguide

parents 448a2141 fec75c1e
......@@ -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)
......@@ -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
......
......@@ -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__)
......
......@@ -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",
......
......@@ -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', )
......@@ -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;
}
......@@ -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 */
......
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
<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 &amp; Steps</h2>
<input type="text" data-slider="true" data-slider-range="100,500" data-slider-step="100">
<h2>Range, Steps &amp; 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 &amp; 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 &amp; 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>
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
###
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
/*
* 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
{
"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
......@@ -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%;
}
/*
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);
/*
* 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);
......@@ -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 &lt;options&gt; 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> ';
}
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(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&').replace(/&ndash;/g, "–").replace(/\//g, "").replace(/'/g, '"').replace(/&#39;/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;
};
......@@ -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' %}
......
{% 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>
{% 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>
......@@ -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 %}
{% 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>
......@@ -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 %}
{% load crispy_forms_tags %}
<style>
.row {
margin-bottom: 15px;
}
</style>
<form method="POST" action="{% url "dashboard.views.group-create" %}">
{% csrf_token %}
......
......@@ -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 %}
......
{% 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>
......@@ -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 %}
......@@ -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">
......
......@@ -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>
......
......@@ -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>
......
......@@ -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>
......
......@@ -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': [],
})
......
# 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, '..'))
......
......@@ -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)
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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
......@@ -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)
......
......@@ -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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment