Commit 82870d16 by Csók Tamás

Merge remote-tracking branch 'origin/master' into issue-218

parents 0b76aed5 b0fd61ef
......@@ -35,9 +35,12 @@ circle/*.key
circle/*.pem
# collected static files:
circle/static
circle/static_collected
circle/bower_components
# jsi18n files
jsi18n
scripts.rc
# less
*.css
{
"name": "circle",
"version": "0.0.0",
"license": "GPL",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap": "~3.2.0",
"fontawesome": "~4.2.0",
"jquery": "~2.1.1",
"no-vnc": "*",
"jquery-knob": "~1.2.9",
"jquery-simple-slider": "https://github.com/BME-IK/jquery-simple-slider.git",
"bootbox": "~4.3.0",
"intro.js": "0.9.0"
}
}
......@@ -160,10 +160,88 @@ STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
########## END STATIC FILE CONFIGURATION
STATICFILES_DIRS = [normpath(join(SITE_ROOT, 'bower_components'))]
p = normpath(join(SITE_ROOT, '../../site-circle/static'))
if exists(p):
STATICFILES_DIRS = (p, )
STATICFILES_DIRS.append(p)
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
PIPELINE_COMPILERS = (
'pipeline.compilers.less.LessCompiler',
)
PIPELINE_CSS_COMPRESSOR = 'pipeline.compressors.yuglify.YuglifyCompressor'
PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.slimit.SlimItCompressor'
PIPELINE_DISABLE_WRAPPER = True
PIPELINE_LESS_ARGUMENTS = u'--include-path={}'.format(':'.join(STATICFILES_DIRS))
PIPELINE_CSS = {
"all": {"source_filenames": (
"compile_bootstrap.less",
"bootstrap/dist/css/bootstrap-theme.css",
"fontawesome/css/font-awesome.css",
"jquery-simple-slider/css/simple-slider.css",
"intro.js/introjs.css",
"template.less",
"dashboard/dashboard.less",
"network/network.less",
"autocomplete_light/style.css",
),
"output_filename": "all.css",
}
}
PIPELINE_JS = {
"all": {"source_filenames": (
# "jquery/dist/jquery.js", # included separately
"bootbox/bootbox.js",
"bootstrap/dist/js/bootstrap.js",
"intro.js/intro.js",
"jquery-knob/dist/jquery.knob.min.js",
"jquery-simple-slider/js/simple-slider.js",
"dashboard/dashboard.js",
"dashboard/group-details.js",
"dashboard/group-list.js",
"dashboard/js/stupidtable.min.js", # no bower file
"dashboard/node-create.js",
"dashboard/node-details.js",
"dashboard/node-list.js",
"dashboard/profile.js",
"dashboard/store.js",
"dashboard/template-list.js",
"dashboard/vm-common.js",
"dashboard/vm-create.js",
"dashboard/vm-list.js",
"js/host.js",
"js/network.js",
"js/switch-port.js",
"autocomplete_light/autocomplete.js",
"autocomplete_light/widget.js",
"autocomplete_light/addanother.js",
"autocomplete_light/text_widget.js",
"autocomplete_light/remote.js",
),
"output_filename": "all.js",
},
"vm-detail": {"source_filenames": (
"dashboard/vm-details.js",
"no-vnc/include/util.js",
"no-vnc/include/webutil.js",
"no-vnc/include/base64.js",
"no-vnc/include/websock.js",
"no-vnc/include/des.js",
"no-vnc/include/keysym.js",
"no-vnc/include/keysymdef.js",
"no-vnc/include/keyboard.js",
"no-vnc/include/input.js",
"no-vnc/include/display.js",
"no-vnc/include/jsunzip.js",
"no-vnc/include/rfb.js",
"dashboard/vm-console.js",
"dashboard/vm-tour.js",
),
"output_filename": "vm-detail.js",
},
}
########## SECRET CONFIGURATION
......@@ -266,6 +344,7 @@ THIRD_PARTY_APPS = (
'statici18n',
'django_sshkey',
'autocomplete_light',
'pipeline',
)
# Apps specific for this project go here.
......
......@@ -109,3 +109,7 @@ CRISPY_FAIL_SILENTLY = not DEBUG
if DEBUG:
from django.dispatch import Signal
Signal.send_robust = Signal.send
PIPELINE_DISABLED_COMPILERS = (
'pipeline.compilers.less.LessCompiler',
)
......@@ -58,3 +58,6 @@ for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
# Forbid store usage
STORE_URL = ""
# buildbot doesn't love pipeline
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
from django.core.management.base import BaseCommand
from common.management.commands.watch import LessUtils
class Command(BaseCommand):
help = "Compiles all LESS files."
def handle(self, *args, **kwargs):
print("Compiling LESS")
LessUtils.initial_compile()
import subprocess
import os
import pyinotify
from django.core.management.base import BaseCommand
from django.conf import settings
STATIC_FILES = u'--include-path={}'.format(':'.join(settings.STATICFILES_DIRS))
IGNORED_FOLDERS = ("static_collected", "bower_components", )
class LessUtils(object):
@staticmethod
def less_path_to_css_path(pathname):
return "%s.css" % pathname[:-1 * len(".less")]
@staticmethod
def compile_less(less_pathname, css_pathname):
cmd = ["lessc", STATIC_FILES, less_pathname, css_pathname]
print("\n%s" % ("=" * 30))
print("Compiling: %s" % os.path.basename(less_pathname))
try:
subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
print(e.output)
else:
print("Successfully compiled:\n%s\n->\n%s" % (
less_pathname, css_pathname))
@staticmethod
def initial_compile():
""" Walks through the project looking for LESS files
and compiles them into CSS.
"""
for root, dirs, files in os.walk(settings.SITE_ROOT):
for f in files:
if not f.endswith(".less"):
continue
relpath = os.path.relpath(root, settings.SITE_ROOT)
if relpath.startswith(IGNORED_FOLDERS):
continue
less_pathname = "%s/%s" % (root, f)
css_pathname = LessUtils.less_path_to_css_path(less_pathname)
LessUtils.compile_less(less_pathname, css_pathname)
@staticmethod
def start_watch():
""" Watches for changes in LESS files recursively from the
project's root and compiles the files
"""
wm = pyinotify.WatchManager()
class EventHandler(pyinotify.ProcessEvent):
def process_IN_MODIFY(self, event):
if not event.name.endswith(".less"):
return
relpath = os.path.relpath(event.pathname, settings.SITE_ROOT)
if relpath.startswith(IGNORED_FOLDERS):
return
css_pathname = LessUtils.less_path_to_css_path(event.pathname)
LessUtils.compile_less(event.pathname, css_pathname)
handler = EventHandler()
notifier = pyinotify.Notifier(wm, handler)
wm.add_watch(settings.SITE_ROOT, pyinotify.IN_MODIFY, rec=True)
notifier.loop()
class Command(BaseCommand):
help = "Compiles all LESS files then watches for changes."
def handle(self, *args, **kwargs):
# for first run compile everything
print("Initial LESS compiles")
LessUtils.initial_compile()
print("\n%s\n" % ("=" * 30))
print("End of initial LESS compiles\n")
# after first run watch less files
LessUtils.start_watch()
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import
from optparse import make_option
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from firewall.models import (Vlan, VlanGroup, Domain, Firewall, Rule,
SwitchPort, EthernetDevice, Host)
from storage.models import DataStore
from vm.models import Lease
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--force', action="store_true"),
make_option('--portal-ip'),
make_option('--external-net'),
make_option('--management-net'),
make_option('--vm-net'),
make_option('--external-if'),
make_option('--management-if'),
make_option('--trunk-if'),
make_option('--datastore-queue'),
make_option('--firewall-queue'),
make_option('--admin-user'),
make_option('--admin-pass'),
)
def create(self, model, field, **kwargs):
value = kwargs[field]
qs = model.objects.filter(**{field: value})[:1]
if not qs.exists():
obj = model.objects.create(**kwargs)
self.changed.append('New %s: %s' % (model, obj))
return obj
else:
return qs[0]
# http://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html
def print_state(self):
changed = "yes" if len(self.changed) else "no"
print "\nchanged=%s comment='%s'" % (changed, ", ".join(self.changed))
def handle(self, *args, **options):
self.changed = []
if (DataStore.objects.exists() and Vlan.objects.exists()
and not options['force']):
return self.print_state()
admin = self.create(User, 'username', username=options['admin_user'],
is_superuser=True, is_staff=True)
admin.set_password(options['admin_pass'])
admin.save()
self.create(DataStore, 'path', path='/datastore', name='default',
hostname=options['datastore_queue'])
# leases
self.create(Lease, 'name', name='lab',
suspend_interval_seconds=3600 * 5,
delete_interval_seconds=3600 * 24 * 7)
self.create(Lease, 'name', name='project',
suspend_interval_seconds=3600 * 24 * 30,
delete_interval_seconds=3600 * 24 * 30 * 6)
self.create(Lease, 'name', name='server',
suspend_interval_seconds=3600 * 24 * 365,
delete_interval_seconds=3600 * 24 * 365 * 3)
domain = self.create(Domain, 'name', name='example.com', owner=admin)
# vlans
net = self.create(Vlan, 'name', name='net', vid=4,
network4=options['external_net'], domain=domain)
man = self.create(Vlan, 'name', name='man', vid=3, dhcp_pool='manual',
network4=options['management_net'], domain=domain,
snat_ip=options['external_net'].split('/')[0])
man.snat_to.add(net)
man.snat_to.add(man)
vm = self.create(Vlan, 'name', name='vm', vid=2, dhcp_pool='manual',
network4=options['vm_net'], domain=domain,
snat_ip=options['external_net'].split('/')[0])
vm.snat_to.add(net)
vm.snat_to.add(vm)
# default vlan groups
vg_all = self.create(VlanGroup, 'name', name='all')
vg_all.vlans.add(vm, man, net)
vg_pf = self.create(VlanGroup, 'name', name='portforward')
vg_pf.vlans.add(vm, man, net)
vg_net = self.create(VlanGroup, 'name', name='net')
vg_net.vlans.add(net)
# portal host
portal = self.create(Host, 'hostname', hostname='portal', vlan=man,
mac='11:22:33:44:55:66', owner=admin,
shared_ip=True, external_ipv4=man.snat_ip,
ipv4=options['portal_ip'])
portal.add_port(proto='tcp', public=443, private=443)
portal.add_port(proto='tcp', public=22, private=22)
# firewall rules
fw = self.create(Firewall, 'name', name=options['firewall_queue'])
self.create(Rule, 'description', description='default output rule',
direction='out', action='accept',
foreign_network=vg_all, firewall=fw)
self.create(Rule, 'description', description='default input rule',
direction='in', action='accept',
foreign_network=vg_all, firewall=fw)
# vlan rules
self.create(Rule, 'description', description='allow vm->net',
direction='out', action='accept',
foreign_network=vg_net, vlan=vm)
self.create(Rule, 'description', description='allow man->net',
direction='out', action='accept',
foreign_network=vg_net, vlan=man)
# switch
# uplink interface
sp_net = self.create(SwitchPort, 'untagged_vlan', untagged_vlan=net)
self.create(EthernetDevice, 'switch_port', switch_port=sp_net,
name=options['external_if'])
# management interface
if options['management_if']:
sp_man = self.create(
SwitchPort, 'untagged_vlan', untagged_vlan=man)
self.create(EthernetDevice, 'switch_port', switch_port=sp_man,
name=options['management_if'])
# vm interface
sp_trunk = self.create(
SwitchPort, 'tagged_vlans', untagged_vlan=man, tagged_vlans=vg_all)
self.create(EthernetDevice, 'switch_port', switch_port=sp_trunk,
name=options['trunk_if'])
return self.print_state()
// Core variables and mixins
@import "bootstrap/less/variables.less";
// Custom variables
@navbar-height: 45px;
@import "bootstrap/less/mixins.less";
// Reset and dependencies
@import "bootstrap/less/normalize.less";
@import "bootstrap/less/print.less";
// we don't use these, also they make collectstatic fail ...
// @import "bootstrap/less/glyphicons.less";
// Core CSS
@import "bootstrap/less/scaffolding.less";
@import "bootstrap/less/type.less";
@import "bootstrap/less/code.less";
@import "bootstrap/less/grid.less";
@import "bootstrap/less/tables.less";
@import "bootstrap/less/forms.less";
@import "bootstrap/less/buttons.less";
// Components
@import "bootstrap/less/component-animations.less";
@import "bootstrap/less/dropdowns.less";
@import "bootstrap/less/button-groups.less";
@import "bootstrap/less/input-groups.less";
@import "bootstrap/less/navs.less";
@import "bootstrap/less/navbar.less";
@import "bootstrap/less/breadcrumbs.less";
@import "bootstrap/less/pagination.less";
@import "bootstrap/less/pager.less";
@import "bootstrap/less/labels.less";
@import "bootstrap/less/badges.less";
@import "bootstrap/less/jumbotron.less";
@import "bootstrap/less/thumbnails.less";
@import "bootstrap/less/alerts.less";
@import "bootstrap/less/progress-bars.less";
@import "bootstrap/less/media.less";
@import "bootstrap/less/list-group.less";
@import "bootstrap/less/panels.less";
@import "bootstrap/less/responsive-embed.less";
@import "bootstrap/less/wells.less";
@import "bootstrap/less/close.less";
// Components w/ JavaScript
@import "bootstrap/less/modals.less";
@import "bootstrap/less/tooltip.less";
@import "bootstrap/less/popovers.less";
@import "bootstrap/less/carousel.less";
// Utility classes
@import "bootstrap/less/utilities.less";
@import "bootstrap/less/responsive-utilities.less";
/*!
* Slider for Bootstrap
*
* Copyright 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.slider {
display: inline-block;
vertical-align: middle;
position: relative;
&.slider-horizontal {
width: 210px;
height: @baseLineHeight;
.slider-track {
height: @baseLineHeight/2;
width: 100%;
margin-top: -@baseLineHeight/4;
top: 50%;
left: 0;
}
.slider-selection {
height: 100%;
top: 0;
bottom: 0;
}
.slider-handle {
margin-left: -@baseLineHeight/2;
margin-top: -@baseLineHeight/4;
&.triangle {
border-width: 0 @baseLineHeight/2 @baseLineHeight/2 @baseLineHeight/2;
width: 0;
height: 0;
border-bottom-color: #0480be;
margin-top: 0;
}
}
}
&.slider-vertical {
height: 210px;
width: @baseLineHeight;
.slider-track {
width: @baseLineHeight/2;
height: 100%;
margin-left: -@baseLineHeight/4;
left: 50%;
top: 0;
}
.slider-selection {
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
.slider-handle {
margin-left: -@baseLineHeight/4;
margin-top: -@baseLineHeight/2;
&.triangle {
border-width: @baseLineHeight/2 0 @baseLineHeight/2 @baseLineHeight/2;
width: 1px;
height: 1px;
border-left-color: #0480be;
margin-left: 0;
}
}
}
input {
display: none;
}
.tooltip-inner {
white-space: nowrap;
}
}
.slider-track {
position: absolute;
cursor: pointer;
#gradient > .vertical(#f5f5f5, #f9f9f9);
.box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
.border-radius(@baseBorderRadius);
}
.slider-selection {
position: absolute;
#gradient > .vertical(#f9f9f9, #f5f5f5);
.box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
.box-sizing(border-box);
.border-radius(@baseBorderRadius);
}
.slider-handle {
position: absolute;
width: @baseLineHeight;
height: @baseLineHeight;
#gradient > .vertical(#149bdf, #0480be);
.box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)");
opacity: 0.8;
border: 0px solid transparent;
&.round {
.border-radius(@baseLineHeight);
}
&.triangle {
background: transparent none;
}
}
\ No newline at end of file
/*!
* Slider for Bootstrap
*
* Copyright 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.slider {
display: inline-block;
vertical-align: middle;
position: relative;
}
.slider.slider-horizontal {
width: 210px;
height: 20px;
}
.slider.slider-horizontal .slider-track {
height: 10px;
width: 100%;
margin-top: -5px;
top: 50%;
left: 0;
}
.slider.slider-horizontal .slider-selection {
height: 100%;
top: 0;
bottom: 0;
}
.slider.slider-horizontal .slider-handle {
margin-left: -10px;
margin-top: -5px;
}
.slider.slider-horizontal .slider-handle.triangle {
border-width: 0 10px 10px 10px;
width: 0;
height: 0;
border-bottom-color: #0480be;
margin-top: 0;
}
.slider.slider-vertical {
height: 210px;
width: 20px;
}
.slider.slider-vertical .slider-track {
width: 10px;
height: 100%;
margin-left: -5px;
left: 50%;
top: 0;
}
.slider.slider-vertical .slider-selection {
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
.slider.slider-vertical .slider-handle {
margin-left: -5px;
margin-top: -10px;
}
.slider.slider-vertical .slider-handle.triangle {
border-width: 10px 0 10px 10px;
width: 1px;
height: 1px;
border-left-color: #0480be;
margin-left: 0;
}
.slider input {
display: none;
}
.slider .tooltip-inner {
white-space: nowrap;
}
.slider-track {
position: absolute;
cursor: pointer;
background-color: #f7f7f7;
background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.slider-selection {
position: absolute;
background-color: #f7f7f7;
background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5));
background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5);
background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5);
background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.slider-handle {
position: absolute;
width: 20px;
height: 20px;
background-color: #0e90d2;
background-image: -moz-linear-gradient(top, #149bdf, #0480be);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
background-image: -o-linear-gradient(top, #149bdf, #0480be);
background-image: linear-gradient(to bottom, #149bdf, #0480be);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
opacity: 0.8;
border: 0px solid transparent;
}
.slider-handle.round {
-webkit-border-radius: 20px;
-moz-border-radius: 20px;
border-radius: 20px;
}
.slider-handle.triangle {
background: transparent none;
}
/* custom */
.slider-handle, .slider-handle:hover {
background-color: #3071a9;
opacity: 1;
width: 8px;
height: 26px;
margin-top: -4px!important;
margin-left: -6px !important;
border-radius: 0px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
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-handle:hover {
background: #428bca;
background-image: none;
border-color: #2d6ca2;
}
.slider-track, .slider-selection {
height: 20px !important;
background: #ccc;
background: -webkit-linear-gradient(top, #bbb, #ddd);
background: -moz-linear-gradient(top, #bbb, #ddd);
background: linear-gradient(top, #bbb, #ddd);
-webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border: 1px solid #aaa;
}
.slider-selection {
background-color: #8DCA09;
background: -webkit-linear-gradient(top, #8DCA09, #72A307);
background: -moz-linear-gradient(top, #8DCA09, #72A307);
background: linear-gradient(top, #8DCA09, #72A307);
border-color: #496805;
}
.vm-slider {
width: 300px;
}
.output {
padding-left: 10px;
font-weight: bold;
}
......@@ -364,9 +364,11 @@ a.hover-black {
display: block;
}
.notification-messages {
#notification-messages {
padding: 10px 8px;
width: 350px;
right: 0;
left: auto;
}
.notification-message {
......@@ -375,7 +377,7 @@ a.hover-black {
border-bottom: 1px dotted #D3D3D3;
}
.notification-messages .notification-message:last-child {
#notification-messages .notification-message:last-child {
margin-bottom: 0px;
padding: 0px;
border-bottom: none;
......@@ -390,6 +392,15 @@ a.hover-black {
cursor: pointer;
}
#notification-button a.dropdown-toggle {
color: white;
font-size: 12px;
}
#notification-button {
margin-right: 15px;
}
#vm-migrate-node-list {
list-style: none;
}
......@@ -950,6 +961,48 @@ textarea[name="new_members"] {
#vm-list-search, #vm-mass-ops {
margin-top: 8px;
}
.list-group-item-last {
border-bottom: 1px solid #ddd !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 > .track, .slider > .highlight-track {
border-radius: 5px;
}
.slider {
width: 100%;
}
#vm-list-search-checkbox {
margin-top: -1px;
......@@ -1051,3 +1104,50 @@ textarea[name="new_members"] {
white-space: nowrap;
text-align: center;
}
/* for introjs
* newer version has this fixed
* but it doesn't work w bootstrap 3.2.0
*/
.introjs-helperLayer *,
.introjs-helperLayer *:before,
.introjs-helperLayer *:after {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
-ms-box-sizing: content-box;
-o-box-sizing: content-box;
box-sizing: content-box;
}
.node-list-table .monitor {
.progress {
position: relative;
width: 150px;
height: 16px;
margin-bottom: 4px;
margin-top: 0px;
background-image: linear-gradient(to bottom, #BBEBEB 0px, #F5F5F5 100%);
}
.progress-bar-text {
position: absolute;
top: -1px;
display: block;
width: 100%;
color: white;
/* outline */
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
font-size: 13px;
}
}
.infobtn {
cursor: help;
&:hover {
background-position: 0 0px;
}
}
......@@ -51,14 +51,14 @@
function removeMember(data) {
$.ajax({
type: 'POST',
url: data['url'],
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
data['tr'].fadeOut(function() {
data.tr.fadeOut(function() {
$(this).remove();});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
addMessage('Uh oh :(', 'danger');
}
});
}
......
......@@ -8,7 +8,7 @@ $(function() {
/* rename ajax */
$('.group-list-rename-submit').click(function() {
var row = $(this).closest("tr")
var row = $(this).closest("tr");
var name = $('#group-list-rename-name', row).val();
var url = '/dashboard/group/' + row.children("td:first-child").text().replace(" ", "") + '/';
$.ajax({
......@@ -21,8 +21,8 @@ $(function() {
$("#group-list-column-name", row).html(
$("<a/>", {
'class': "real-link",
href: "/dashboard/group/" + data['group_pk'] + "/",
text: data['new_name']
href: "/dashboard/group/" + data.group_pk + "/",
text: data.new_name
})
).show();
$('#group-list-rename', row).hide();
......
.introjs-overlay{position:absolute;z-index:999999;background-color:#000;opacity:0;background:-moz-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:-webkit-gradient(radial,center center,0px,center center,100%,color-stop(0%,rgba(0,0,0,0.4)),color-stop(100%,rgba(0,0,0,0.9)));background:-webkit-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:-o-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:-ms-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#66000000',endColorstr='#e6000000',GradientType=1);-ms-filter:"alpha(opacity=50)";filter:alpha(opacity=50);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-fixParent{z-index:auto !important;opacity:1.0 !important}.introjs-showElement,tr.introjs-showElement>td,tr.introjs-showElement>th{z-index:9999999 !important}.introjs-relativePosition,tr.introjs-showElement>td,tr.introjs-showElement>th{position:relative}.introjs-helperLayer{position:absolute;z-index:9999998;background-color:#FFF;background-color:rgba(255,255,255,.9);border:1px solid #777;border:1px solid rgba(0,0,0,.5);border-radius:4px;box-shadow:0 2px 15px rgba(0,0,0,.4);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-helperNumberLayer{position:absolute;top:-16px;left:-16px;z-index:9999999999 !important;padding:2px;font-family:Arial,verdana,tahoma;font-size:13px;font-weight:bold;color:white;text-align:center;text-shadow:1px 1px 1px rgba(0,0,0,.3);background:#ff3019;background:-webkit-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ff3019),color-stop(100%,#cf0404));background:-moz-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-ms-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-o-linear-gradient(top,#ff3019 0,#cf0404 100%);background:linear-gradient(to bottom,#ff3019 0,#cf0404 100%);width:20px;height:20px;line-height:20px;border:3px solid white;border-radius:50%;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3019',endColorstr='#cf0404',GradientType=0);filter:progid:DXImageTransform.Microsoft.Shadow(direction=135,strength=2,color=ff0000);box-shadow:0 2px 5px rgba(0,0,0,.4)}.introjs-arrow{border:5px solid white;content:'';position:absolute}.introjs-arrow.top{top:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:white;border-left-color:transparent}.introjs-arrow.top-right{top:-10px;right:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:white;border-left-color:transparent}.introjs-arrow.top-middle{top:-10px;left:50%;margin-left:-5px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:white;border-left-color:transparent}.introjs-arrow.right{right:-10px;top:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:white}.introjs-arrow.bottom{bottom:-10px;border-top-color:white;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}.introjs-arrow.left{left:-10px;top:10px;border-top-color:transparent;border-right-color:white;border-bottom-color:transparent;border-left-color:transparent}.introjs-tooltip{position:absolute;padding:10px;background-color:white;min-width:200px;max-width:300px;border-radius:3px;box-shadow:0 1px 10px rgba(0,0,0,.4);-webkit-transition:opacity .1s ease-out;-moz-transition:opacity .1s ease-out;-ms-transition:opacity .1s ease-out;-o-transition:opacity .1s ease-out;transition:opacity .1s ease-out}.introjs-tooltipbuttons{text-align:right}.introjs-button{position:relative;overflow:visible;display:inline-block;padding:.3em .8em;border:1px solid #d4d4d4;margin:0;text-decoration:none;text-shadow:1px 1px 0 #fff;font:11px/normal sans-serif;color:#333;white-space:nowrap;cursor:pointer;outline:0;background-color:#ececec;background-image:-webkit-gradient(linear,0 0,0 100%,from(#f4f4f4),to(#ececec));background-image:-moz-linear-gradient(#f4f4f4,#ececec);background-image:-o-linear-gradient(#f4f4f4,#ececec);background-image:linear-gradient(#f4f4f4,#ececec);-webkit-background-clip:padding;-moz-background-clip:padding;-o-background-clip:padding-box;-webkit-border-radius:.2em;-moz-border-radius:.2em;border-radius:.2em;zoom:1;*display:inline;margin-top:10px}.introjs-button:hover{border-color:#bcbcbc;text-decoration:none;box-shadow:0 1px 1px #e3e3e3}.introjs-button:focus,.introjs-button:active{background-image:-webkit-gradient(linear,0 0,0 100%,from(#ececec),to(#f4f4f4));background-image:-moz-linear-gradient(#ececec,#f4f4f4);background-image:-o-linear-gradient(#ececec,#f4f4f4);background-image:linear-gradient(#ececec,#f4f4f4)}.introjs-button::-moz-focus-inner{padding:0;border:0}.introjs-skipbutton{margin-right:5px;color:#7a7a7a}.introjs-prevbutton{-webkit-border-radius:.2em 0 0 .2em;-moz-border-radius:.2em 0 0 .2em;border-radius:.2em 0 0 .2em;border-right:0}.introjs-nextbutton{-webkit-border-radius:0 .2em .2em 0;-moz-border-radius:0 .2em .2em 0;border-radius:0 .2em .2em 0}.introjs-disabled,.introjs-disabled:hover,.introjs-disabled:focus{color:#9a9a9a;border-color:#d4d4d4;box-shadow:none;cursor:default;background-color:#f4f4f4;background-image:none;text-decoration:none}.introjs-bullets{text-align:center}.introjs-bullets ul{clear:both;margin:15px auto 0;padding:0;display:inline-block}.introjs-bullets ul li{list-style:none;float:left;margin:0 2px}.introjs-bullets ul li a{display:block;width:6px;height:6px;background:#ccc;border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;text-decoration:none}.introjs-bullets ul li a:hover{background:#999}.introjs-bullets ul li a.active{background:#999}.introjsFloatingElement{position:absolute;height:0;width:0;left:50%;top:50%}
.introjs-helperLayer *,
.introjs-helperLayer *:before,
.introjs-helperLayer *:after {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
-ms-box-sizing: content-box;
-o-box-sizing: content-box;
box-sizing: content-box;
}
.slider {
width: 300px;
}
.slider > .dragger {
background: #8DCA09;
background: -webkit-linear-gradient(top, #8DCA09, #72A307);
background: -moz-linear-gradient(top, #8DCA09, #72A307);
background: linear-gradient(top, #8DCA09, #72A307);
-webkit-box-shadow: inset 0 2px 2px rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.2);
-moz-box-shadow: inset 0 2px 2px rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.2);
box-shadow: inset 0 2px 2px rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.2);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border: 1px solid #496805;
width: 16px;
height: 16px;
}
.slider > .dragger:hover {
background: -webkit-linear-gradient(top, #8DCA09, #8DCA09);
}
.slider > .track, .slider > .highlight-track {
background: #ccc;
background: -webkit-linear-gradient(top, #bbb, #ddd);
background: -moz-linear-gradient(top, #bbb, #ddd);
background: linear-gradient(top, #bbb, #ddd);
-webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
border: 1px solid #aaa;
height: 4px;
}
.slider > .highlight-track {
background-color: #8DCA09;
background: -webkit-linear-gradient(top, #8DCA09, #72A307);
background: -moz-linear-gradient(top, #8DCA09, #72A307);
background: linear-gradient(top, #8DCA09, #72A307);
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: 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);
......@@ -17,7 +17,7 @@ $(function() {
success: function(data, textStatus, xhr) {
$("#node-details-h1-name").text(data['new_name']).show();
$('#node-details-rename').hide();
// addMessage(data['message'], "success");
// addMessage(data.message, "success");
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
......@@ -54,14 +54,14 @@ $(function() {
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re['message'].toLowerCase() == "success") {
if(re.message.toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re['message'], 'danger');
addMessage(re.message, 'danger');
}
});
......@@ -73,18 +73,18 @@ $(function() {
function changeNodeStatus(data) {
$.ajax({
type: 'POST',
url: data['url'],
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
if(!data['redirect']) {
if(!data.redirect) {
selected = [];
addMessage(re['message'], 'success');
addMessage(re.message, 'success');
} else {
window.location.replace('/dashboard');
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
addMessage('Uh oh :(', 'danger');
}
});
}
......@@ -6,13 +6,10 @@ $(function() {
// find disabled nodes, set danger (red) on the rows
function colortable()
{
var tr= $('.false').closest("tr");
tr.addClass('danger');
var tr= $('.true').closest("tr");
tr.removeClass('danger');
$('.false').closest("tr").addClass('danger');
$('.true').closest("tr").removeClass('danger');
}
function statuschangeSuccess(tr){
var tspan=tr.children('.enabled').children();
var buttons=tr.children('.actions').children('.btn-group').children('.dropdown-menu').children('li').children('.node-enable');
......
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
/*jslint white: false, bitwise: false, plusplus: false */
/*global console */
var Base64 = {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=',
encode: function (data) {
"use strict";
var result = '';
var toBase64Table = Base64.toBase64Table;
var length = data.length
var lengthpad = (length%3);
var i = 0, j = 0;
// Convert every three bytes to 4 ascii characters.
/* BEGIN LOOP */
for (i = 0; i < (length - 2); i += 3) {
result += toBase64Table[data[i] >> 2];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += toBase64Table[data[i+2] & 0x3f];
}
/* END LOOP */
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (lengthpad === 2) {
j = length - lengthpad;
result += toBase64Table[data[j] >> 2];
result += toBase64Table[((data[j] & 0x03) << 4) + (data[j+1] >> 4)];
result += toBase64Table[(data[j+1] & 0x0f) << 2];
result += toBase64Table[64];
} else if (lengthpad === 1) {
j = length - lengthpad;
result += toBase64Table[data[j] >> 2];
result += toBase64Table[(data[j] & 0x03) << 4];
result += toBase64Table[64];
result += toBase64Table[64];
}
return result;
},
/* Convert Base64 data to a string */
toBinaryTable : [
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
],
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var toBinaryTable = Base64.toBinaryTable;
var base64Pad = Base64.base64Pad;
var result, result_length, idx, i, c, padding;
var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended
var data_length = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; }
/* Every four characters is 3 resulting numbers */
result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
result = new Array(result_length);
// Convert one by one.
/* BEGIN LOOP */
for (idx = 0, i = offset; i < data.length; i++) {
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === base64Pad);
// Skip illegal characters and whitespace
if (c === -1) {
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue;
}
// Collect data into leftdata, update bitcount
leftdata = (leftdata << 6) | c;
leftbits += 6;
// If we have 8 or more bits, append 8 bits to the result
if (leftbits >= 8) {
leftbits -= 8;
// Append if not padding.
if (!padding) {
result[idx++] = (leftdata >> leftbits) & 0xff;
}
leftdata &= (1 << leftbits) - 1;
}
}
/* END LOOP */
// If there are any bits left, the base64 string was corrupted
if (leftbits) {
throw {name: 'Base64-Error',
message: 'Corrupted base64 string'};
}
return result;
}
}; /* End of Base64 namespace */
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*/
"use strict";
/*jslint browser: true, white: false */
/*global Util, VNC_frame_data, finish */
var rfb, mode, test_state, frame_idx, frame_length,
iteration, iterations, istart_time,
// Pre-declarations for jslint
send_array, next_iteration, queue_next_packet, do_packet;
// Override send_array
send_array = function (arr) {
// Stub out send_array
};
next_iteration = function () {
if (iteration === 0) {
frame_length = VNC_frame_data.length;
test_state = 'running';
} else {
rfb.disconnect();
}
if (test_state !== 'running') { return; }
iteration += 1;
if (iteration > iterations) {
finish();
return;
}
frame_idx = 0;
istart_time = (new Date()).getTime();
rfb.connect('test', 0, "bogus");
queue_next_packet();
};
queue_next_packet = function () {
var frame, foffset, toffset, delay;
if (test_state !== 'running') { return; }
frame = VNC_frame_data[frame_idx];
while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) {
//Util.Debug("Send frame " + frame_idx);
frame_idx += 1;
frame = VNC_frame_data[frame_idx];
}
if (frame === 'EOF') {
Util.Debug("Finished, found EOF");
next_iteration();
return;
}
if (frame_idx >= frame_length) {
Util.Debug("Finished, no more frames");
next_iteration();
return;
}
if (mode === 'realtime') {
foffset = frame.slice(1, frame.indexOf('{', 1));
toffset = (new Date()).getTime() - istart_time;
delay = foffset - toffset;
if (delay < 1) {
delay = 1;
}
setTimeout(do_packet, delay);
} else {
setTimeout(do_packet, 1);
}
};
var bytes_processed = 0;
do_packet = function () {
//Util.Debug("Processing frame: " + frame_idx);
var frame = VNC_frame_data[frame_idx],
start = frame.indexOf('{', 1) + 1;
bytes_processed += frame.length - start;
if (VNC_frame_encoding === 'binary') {
var u8 = new Uint8Array(frame.length - start);
for (var i = 0; i < frame.length - start; i++) {
u8[i] = frame.charCodeAt(start + i);
}
rfb.recv_message({'data' : u8});
} else {
rfb.recv_message({'data' : frame.slice(start)});
}
frame_idx += 1;
queue_next_packet();
};
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*global Util, window, document */
// Globals defined here
var WebUtil = {}, $D;
/*
* Simple DOM selector by ID
*/
if (!window.$D) {
window.$D = function (id) {
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
return document.all[id];
} else if (document.layers) {
return document.layers[id];
}
return undefined;
};
}
/*
* ------------------------------------------------------
* Namespaced in WebUtil
* ------------------------------------------------------
*/
// init log level reading the logging HTTP param
WebUtil.init_logging = function(level) {
if (typeof level !== "undefined") {
Util._log_level = level;
} else {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
}
Util.init_logging();
};
WebUtil.dirObj = function (obj, depth, parent) {
var i, msg = "", val = "";
if (! depth) { depth=2; }
if (! parent) { parent= ""; }
// Print the properties of the passed-in object
for (i in obj) {
if ((depth > 1) && (typeof obj[i] === "object")) {
// Recurse attributes that are objects
msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
} else {
//val = new String(obj[i]).replace("\n", " ");
if (typeof(obj[i]) === "undefined") {
val = "undefined";
} else {
val = obj[i].toString().replace("\n", " ");
}
if (val.length > 30) {
val = val.substr(0,30) + "...";
}
msg += parent + "." + i + ": " + val + "\n";
}
}
return msg;
};
// Read a query string variable
WebUtil.getQueryVar = function(name, defVal) {
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
return decodeURIComponent(match[1]);
} else {
return defVal;
}
};
/*
* Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
*/
// No days means only for this browser session
WebUtil.createCookie = function(name,value,days) {
var date, expires;
if (days) {
date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
expires = "; expires="+date.toGMTString();
}
else {
expires = "";
}
document.cookie = name+"="+value+expires+"; path=/";
};
WebUtil.readCookie = function(name, defaultValue) {
var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
for(i=0; i < ca.length; i += 1) {
c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
}
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
};
WebUtil.eraseCookie = function(name) {
WebUtil.createCookie(name,"",-1);
};
/*
* Setting handling.
*/
WebUtil.initSettings = function(callback) {
var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) {
WebUtil.settings = cfg;
console.log(WebUtil.settings);
if (callback) {
callback.apply(this, callbackArgs);
}
});
} else {
// No-op
if (callback) {
callback.apply(this, callbackArgs);
}
}
};
// No days means only for this browser session
WebUtil.writeSetting = function(name, value) {
if (window.chrome && window.chrome.storage) {
//console.log("writeSetting:", name, value);
if (WebUtil.settings[name] !== value) {
WebUtil.settings[name] = value;
window.chrome.storage.sync.set(WebUtil.settings);
}
} else {
localStorage.setItem(name, value);
}
};
WebUtil.readSetting = function(name, defaultValue) {
var value;
if (window.chrome && window.chrome.storage) {
value = WebUtil.settings[name];
} else {
value = localStorage.getItem(name);
}
if (typeof value === "undefined") {
value = null;
}
if (value === null && typeof defaultValue !== undefined) {
return defaultValue;
} else {
return value;
}
};
WebUtil.eraseSetting = function(name) {
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
delete WebUtil.settings[name];
} else {
localStorage.removeItem(name);
}
};
/*
* Alternate stylesheet selection
*/
WebUtil.getStylesheets = function() { var i, links, sheets = [];
links = document.getElementsByTagName("link");
for (i = 0; i < links.length; i += 1) {
if (links[i].title &&
links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
sheets.push(links[i]);
}
}
return sheets;
};
// No sheet means try and use value from cookie, null sheet used to
// clear all alternates.
WebUtil.selectStylesheet = function(sheet) {
var i, link, sheets = WebUtil.getStylesheets();
if (typeof sheet === 'undefined') {
sheet = 'default';
}
for (i=0; i < sheets.length; i += 1) {
link = sheets[i];
if (link.title === sheet) {
Util.Debug("Using stylesheet " + sheet);
link.disabled = false;
} else {
//Util.Debug("Skipping stylesheet " + link.title);
link.disabled = true;
}
}
return sheet;
};
......@@ -25,7 +25,7 @@ $(function() {
$('#store-upload-form button[type="submit"] i').addClass("fa-spinner fa-spin");
var current_dir = $("#store-upload-form").find('[name="current_dir"]').val();
$.get($("#store-upload-form").data("action") + "?current_dir=" + current_dir, function(result) {
$("#store-upload-form").get(0).setAttribute("action", result['url']);
$("#store-upload-form").get(0).setAttribute("action", result.url);
$("#store-upload-form").submit();
});
......
......@@ -49,16 +49,16 @@ $(function() {
function deleteTemplate(data) {
$.ajax({
type: 'POST',
url: data['url'],
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
addMessage(re['message'], 'success');
$('a[data-template-pk="' + data['template_pk'] + '"]').closest('tr').fadeOut(function() {
addMessage(re.message, 'success');
$('a[data-template-pk="' + data.template_pk + '"]').closest('tr').fadeOut(function() {
$(this).remove();
});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
addMessage('Uh oh :(', 'danger');
}
});
}
......@@ -68,16 +68,16 @@ function deleteTemplate(data) {
function deleteLease(data) {
$.ajax({
type: 'POST',
url: data['url'],
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
addMessage(re['message'], 'success');
$('a[data-lease-pk="' + data['lease_pk'] + '"]').closest('tr').fadeOut(function() {
addMessage(re.message, 'success');
$('a[data-lease-pk="' + data.lease_pk + '"]').closest('tr').fadeOut(function() {
$(this).remove();
});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
addMessage('Uh oh :(', 'danger');
}
});
}
......
$(function() {
"use strict";
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js"]);
// Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
// "input.js", "display.js", "jsunzip.js", "rfb.js"]);
var rfb;
function updateState(rfb, state, oldstate, msg) {
$('#_console .btn-toolbar button').attr('disabled', !(state === "normal"));
$('#_console .btn-toolbar button').attr('disabled', (state !== "normal"));
rfb.sendKey(0xffe3); // press and release ctrl to kill screensaver
if (typeof(msg) !== 'undefined') {
......@@ -20,7 +20,7 @@ $(function() {
rfb = 0;
}
$("#vm-info-pane").fadeIn();
$("#vm-detail-pane").removeClass("col-md-12");
$("#vm-detail-pane").removeClass("col-md-12").addClass("col-md-8");
});
$('#sendCtrlAltDelButton').click(function() {
rfb.sendCtrlAltDel(); return false;});
......@@ -35,20 +35,20 @@ $(function() {
var host, port, password, path;
$("#vm-info-pane").hide();
$("#vm-detail-pane").addClass("col-md-12");
$("#vm-detail-pane").removeClass("col-md-8").addClass("col-md-12");
WebUtil.init_logging('warn');
host = window.location.hostname;
if (window.location.port == 8080) {
port = 9999;
} else {
port = window.location.port == "" ? "443" : window.location.port;
port = window.location.port === "" ? "443" : window.location.port;
}
password = '';
$('#_console .btn-toolbar button').attr('disabled', true);
$('#noVNC_status').html('Retreiving authorization token.');
$.get(VNC_URL, function(data) {
if (data.indexOf('vnc') != 0) {
if (data.indexOf('vnc') !== 0) {
$('#noVNC_status').html('No authorization token received.');
}
else {
......@@ -58,13 +58,13 @@ $(function() {
'local_cursor': true,
'shared': true,
'view_only': false,
'updateState': updateState});
'onUpdateState': updateState});
rfb.connect(host, port, password, data);
}
}).fail(function(){
$('#noVNC_status').html("Can't connect to console.");
});
});
if (window.location.hash == "#console")
window.onscriptsload = function(){$('a[href$="console"]').click();};
if (window.location.hash === "#console")
$('a[href$="console"]').trigger("click");
});
......@@ -117,7 +117,7 @@ function vmCustomizeLoaded() {
/* remove network */
// event for network remove button (icon, X)
$('body').on('click', '.vm-create-remove-network', function() {
var vlan_pk = ($(this).parent('span').prop('id')).replace('vlan-', '')
var vlan_pk = ($(this).parent('span').prop('id')).replace('vlan-', '');
// if it's "blue" then it's managed, kinda not cool
var managed = $(this).parent('span').hasClass('label-primary');
......@@ -148,7 +148,7 @@ function vmCustomizeLoaded() {
/* copy networks from hidden select */
$('#vm-create-network-add-vlan option').each(function() {
var managed = $(this).text().indexOf("mana") == 0;
var managed = $(this).text().indexOf("mana") === 0;
var raw_text = $(this).text();
var pk = $(this).val();
if(managed) {
......@@ -180,7 +180,7 @@ function vmCustomizeLoaded() {
vlans.push({
'name': $(this).text().replace("unmanaged -", "&#xf0c1;").replace("managed -", "&#xf0ac;"),
'pk': parseInt($(this).val()),
'managed': $(this).text().indexOf("mana") == 0,
'managed': $(this).text().indexOf("mana") === 0,
});
});
......
var show_all = false;
var in_progress = false;
var activity_hash = 5;
var Websock_native; // not sure
var reload_vm_detail = false;
$(function() {
......@@ -74,14 +75,14 @@ $(function() {
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re['message'].toLowerCase() == "success") {
if(re.message.toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re['message'], 'danger');
addMessage(re.message, 'danger');
}
});
......@@ -183,7 +184,7 @@ $(function() {
function removeInterface(data) {
$.ajax({
type: 'POST',
url: data['url'],
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
/* remove the html element */
......@@ -191,7 +192,7 @@ $(function() {
location.reload();
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
addMessage('Uh oh :(', 'danger');
}
});
}
......@@ -221,12 +222,12 @@ $(function() {
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$(".vm-details-home-edit-name").text(data['new_name']).show();
$(".vm-details-home-edit-name").text(data.new_name).show();
$(".vm-details-home-edit-name").parent("div").show();
$(".vm-details-home-edit-name-click").show();
$(".vm-details-home-rename-form-div").hide();
// update the inputs too
$(".vm-details-rename-submit").parent("span").prev("input").val(data['new_name']);
$(".vm-details-rename-submit").parent("span").prev("input").val(data.new_name);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
......@@ -256,7 +257,7 @@ $(function() {
data: {'new_description': description},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
var new_desc = data['new_description'];
var new_desc = data.new_description;
/* we can't simply use $.text, because we need new lines */
var tagsToReplace = {
'&': "&amp;",
......@@ -273,7 +274,7 @@ $(function() {
$(".vm-details-home-edit-description-click").show();
$("#vm-details-home-description").hide();
// update the textareia
$("vm-details-home-description textarea").text(data['new_description']);
$("vm-details-home-description textarea").text(data.new_description);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
......@@ -340,15 +341,15 @@ $(function() {
function removePort(data) {
$.ajax({
type: 'POST',
url: data['url'],
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
$("a[data-rule=" + data['rule'] + "]").each(function() {
$("a[data-rule=" + data.rule + "]").each(function() {
$(this).closest("tr").fadeOut(500, function() {
$(this).remove();
});
});
addMessage(re['message'], "success");
addMessage(re.message, "success");
},
error: function(xhr, textStatus, error) {
......@@ -374,23 +375,23 @@ function checkNewActivity(runs) {
url: '/dashboard/vm/' + instance + '/activity/',
data: {'show_all': show_all},
success: function(data) {
var new_activity_hash = (data['activities'] + "").hashCode();
var new_activity_hash = (data.activities + "").hashCode();
if(new_activity_hash != activity_hash) {
$("#activity-refresh").html(data['activities']);
$("#activity-refresh").html(data.activities);
}
activity_hash = new_activity_hash;
$("#ops").html(data['ops']);
$("#disk-ops").html(data['disk_ops']);
$("#ops").html(data.ops);
$("#disk-ops").html(data.disk_ops);
$("[title]").tooltip();
/* changing the status text */
var icon = $("#vm-details-state i");
if(data['is_new_state']) {
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']);
icon.prop("class", "fa " + data.icon);
}
$("#vm-details-state").data("status", data['status']);
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase());
......@@ -406,7 +407,7 @@ function checkNewActivity(runs) {
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
}
if(data['status'] == "STOPPED" || data['status'] == "PENDING") {
if(data.status == "STOPPED" || data.status == "PENDING") {
$(".change-resources-button").prop("disabled", false);
$(".change-resources-help").hide();
} else {
......@@ -416,7 +417,7 @@ function checkNewActivity(runs) {
if(runs > 0 && decideActivityRefresh()) {
setTimeout(
function() {checkNewActivity(runs + 1)},
function() {checkNewActivity(runs + 1);},
1000 + Math.exp(runs * 0.05)
);
} else {
......@@ -433,7 +434,7 @@ function checkNewActivity(runs) {
String.prototype.hashCode = function() {
var hash = 0, i, chr, len;
if (this.length == 0) return hash;
if (this.length === 0) return hash;
for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
......
......@@ -25,7 +25,7 @@ $(function() {
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1]['index'] + 1;
start = selected[selected.length - 1].index + 1;
end = $(this).index();
if(start > end) {
......@@ -101,7 +101,7 @@ $(function() {
});
$("body").on("click", "#op-form-send", function() {
$("body").on("click", "#mass-op-form-send", function() {
var url = $(this).closest("form").prop("action");
$(this).find("i").prop("class", "fa fa-fw fa-spinner fa-spin");
......
......@@ -23,46 +23,6 @@ html {
padding-right: 15px;
}
/* values for 45px tall navbar */
.navbar {
min-height: 45px;
}
.navbar-brand {
height: 45px;
padding: 12.5px 12.5px;
}
.navbar-toggle {
margin-top: 5.5px;
margin-bottom: 5.5px;
}
.navbar-form {
margin-top: 5.5px;
margin-bottom: 5.5px;
}
.navbar-btn {
margin-top: 5.5px;
margin-bottom: 5.5px;
}
.navbar-btn.btn-sm {
margin-top: 7.5px;
margin-bottom: 7.5px;
}
.navbar-btn.btn-xs {
margin-top: 11.5px;
margin-bottom: 11.5px;
}
.navbar-text {
margin-top: 12.5px;
margin-bottom: 12.5px;
}
/* --- */
/* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) {
/* Let the jumbotron breathe */
......
{% load i18n %}
{% load staticfiles %}
{% load compressed %}
<!DOCTYPE html>
<html lang="{{lang}}">
<head>
......@@ -6,14 +8,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" type="image/png" href="{{ STATIC_URL}}dashboard/img/favicon.png"/>
<link rel="icon" type="image/png" href="{% static "dashboard/img/favicon.png" %}"/>
<title>{% block title %}{% block title-page %}{% endblock %} | {% block title-site %}CIRCLE{% endblock %}{% endblock %}</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ STATIC_URL }}/template.css">
{% compressed_css 'all' %}
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
......@@ -67,11 +66,9 @@
</footer>
</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 "jquery/dist/jquery.min.js" %}"></script>
<script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
{% include 'autocomplete_light/static.html' %}
{% compressed_js 'all' %}
{% block extra_script %}
{% endblock %}
......@@ -81,11 +78,4 @@
{% block extra_etc %}
{% endblock %}
<script>
yourlabs.TextWidget.prototype.getValue = function(choice) {
return choice.children().html();
}
</script>
</html>
......@@ -81,7 +81,7 @@
.progress {
position: relative;
width: 200px;
height: 12px;
height: 16px;
margin-bottom: 0px;
margin-top: 5px;
}
......
{% extends "base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-site %}Dashboard | CIRCLE{% endblock %}
{% block extra_link %}
<link rel="stylesheet" href="{{ STATIC_URL }}dashboard/loopj-jquery-simple-slider/css/simple-slider.css"/>
<link rel="stylesheet" href="{{ STATIC_URL }}dashboard/introjs/introjs.min.css">
<link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet">
{% block extra_link_2 %}{% endblock %}
{% endblock %}
......@@ -21,14 +20,14 @@
{% if user.is_authenticated and user.pk and not request.token_user %}
<ul class="nav navbar-nav pull-right">
<li class="dropdown" id="notification-button">
<a href="{% url "dashboard.views.notifications" %}" style="color: white; font-size: 12px;"
<a href="{% url "dashboard.views.notifications" %}"
class="dropdown-toggle" data-toggle="dropdown">
{% trans "Notifications" %}
{% if NEW_NOTIFICATIONS_COUNT > 0 %}
<span class="badge badge-pulse">{{ NEW_NOTIFICATIONS_COUNT }}</span>
{% endif %}
</a>
<ul class="dropdown-menu notification-messages">
<ul class="dropdown-menu" id="notification-messages">
<li>{% trans "Loading..." %}</li>
</ul>
</li>
......@@ -37,7 +36,7 @@
<a class="navbar-brand pull-right" href="{% url "logout" %}?next={% url "login" %}" style="color: white; font-size: 10px;">
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</a>
<a class="navbar-brand pull-right" href="{% url "dashboard.views.profile-preferences" %}" title="{% trans "User profile" %}" style="color: white; font-size: 10px;">
<a class="navbar-brand pull-right" href="{% url "dashboard.views.profile-preferences" %}" style="color: white; font-size: 10px;">
<i class="fa fa-user"></i>
{% include "dashboard/_display-name.html" with user=user show_org=True %}
</a>
......@@ -51,9 +50,3 @@
{% endif %}
{% endblock %}
{% block extra_script %}
<script src="{{ STATIC_URL }}dashboard/js/jquery.knob.js"></script>
<script src="{{ STATIC_URL }}dashboard/dashboard.js"></script>
{% endblock %}
......@@ -164,7 +164,3 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
<script type="text/javascript" src="{% static "dashboard/group-details.js" %}"></script>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
......@@ -23,7 +24,3 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/group-list.js"></script>
{% endblock %}
......@@ -2,7 +2,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right toolbar">
<span class="btn btn-default btn-xs infobtn" title="{% trans "List of groups that you have access to." %}"><i class="fa fa-info-circle"></i></span>
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of groups that you have access to." %}"><i class="fa fa-info-circle"></i></span>
</div>
<h3 class="no-margin"><i class="fa fa-group"></i> {% trans "Groups" %}</h3>
</div>
......@@ -17,13 +17,17 @@
</div>
<div href="#" class="list-group-item list-group-footer text-right">
<div class="row">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input id="dashboard-group-search-input" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="col-xs-6">
<form action="{% url "dashboard.views.group-list" %}" method="GET" id="dashboard-group-search-form">
<div class="input-group input-group-sm">
<input id="dashboard-group-search-input" name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
<div class="col-sm-6 text-right">
</form>
</div>
<div class="col-xs-6 text-right">
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.group-list" %}">
<i class="fa fa-chevron-circle-right"></i>
{% if more_groups > 0 %}
......
......@@ -8,7 +8,7 @@
<a href="#index-list-view" data-index-box="node" class="btn btn-default btn-xs disabled"
data-container="body"><i class="fa fa-list"></i></a>
</div>
<span class="btn btn-default btn-xs infobtn" title="{% trans "List of compute nodes, also called worker nodes or hypervisors, which run the virtual machines." %}">
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of compute nodes, also called worker nodes or hypervisors, which run the virtual machines." %}">
<i class="fa fa-info-circle"></i>
</span>
</div>
......@@ -56,7 +56,10 @@
<div href="#" class="list-group-item list-group-footer">
<div class="row">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<div class="col-xs-6">
<form action="{% url "dashboard.views.node-list" %}" method="GET"
id="dashboard-node-search-form">
<div class="input-group input-group-sm">
<input id="dashboard-node-search-input" type="text" class="form-control"
placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
......@@ -65,7 +68,9 @@
</button>
</div>
</div>
<div class="col-sm-6 text-right">
</form>
</div>
<div class="col-xs-6 text-right">
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.node-list" %}">
<i class="fa fa-chevron-circle-right"></i>
{% if more_nodes > 0 %}
......
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
<span class="btn btn-default btn-xs infobtn pull-right" title="{% trans "List of VM templates that are available for you. You can create new ones from scratch or customize existing ones (preferred)." %}">
<span class="btn btn-default btn-xs infobtn pull-right" data-container="body" title="{% trans "List of VM templates that are available for you. You can create new ones from scratch or customize existing ones (preferred)." %}">
<i class="fa fa-info-circle"></i>
</span>
<h3 class="no-margin"><i class="fa fa-puzzle-piece"></i> {% trans "Templates" %}
......@@ -16,7 +16,10 @@
<i class="fa fa-{{ t.os_type }}"></i> {{ t.name }}
</span>
<small class="text-muted index-template-list-system">{{ t.system }}</small>
<div class="pull-right vm-create" data-template="{{ t.pk }}"><i title="{% trans "Start vm instance" %}" class="fa fa-play"></i></div>
<div class="pull-right vm-create" data-template="{{ t.pk }}">
<i data-container="body" title="{% trans "Start VM instance" %}"
class="fa fa-play"></i>
</div>
<div class="clearfix"></div>
</a>
{% empty %}
......
......@@ -10,7 +10,7 @@
data-container="body"
title="{% trans "list view" %}"><i class="fa fa-list"></i></a>
</div>
<span class="btn btn-default btn-xs infobtn" title="{% trans "List of your current virtual machines. Favourited ones are ahead of others." %}"><i class="fa fa-info-circle"></i></span>
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of your current virtual machines. Favourited ones are ahead of others." %}"><i class="fa fa-info-circle"></i></span>
</div>
<h3 class="no-margin">
<i class="fa fa-desktop"></i> {% trans "Virtual machines" %}
......@@ -44,15 +44,11 @@
</div>
{% endfor %}
</div>
<style>
.list-group-item-last {
border-bottom: 1px solid #ddd !important;
}
</style>
<div href="#" class="list-group-item list-group-footer">
<div class="row">
<div class="col-xs-6">
<form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<div class="input-group input-group-sm">
<input id="dashboard-vm-search-input" type="text" class="form-control" name="s"
placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
......@@ -60,7 +56,8 @@
</div>
</div>
</form>
<div class="col-sm-6 text-right">
</div>
<div class="col-xs-6 text-right">
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.vm-list" %}">
<i class="fa fa-chevron-circle-right"></i>
{% if more_instances > 0 %}
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-page %}{% trans "Index" %}{% endblock %}
{% block extra_link_2 %}
<link rel="search"
type="application/opensearchdescription+xml"
href="{% url "dashboard.views.vm-opensearch" %}"
title="{% blocktrans with name=COMPANY_NAME %}{{name}} virtual machines{% endblocktrans %}" />
{% endblock %}
{% block content %}
<div class="body-content dashboard-index">
<div class="row">
......@@ -43,8 +51,3 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
<script src="{{ STATIC_URL }}dashboard/node-create.js"></script>
{% endblock %}
......@@ -50,7 +50,7 @@
<dd>{{ object.task_uuid|default:'n/a' }}</dd>
<dt>{% trans "status" %}</dt>
<dd>{{ object.get_status_id }}</dd>
<dd id="activity_status">{{ object.get_status_id }}</dd>
<dt>{% trans "result" %}</dt>
......
......@@ -31,7 +31,7 @@ Do you want to perform the <strong>{{op}}</strong> operation on the following {{
<div class="pull-right">
<a class="btn btn-default" href="{% url "dashboard.views.vm-list" %}"
data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }}" type="submit" id="op-form-send">
<button class="btn btn-{{ opview.effect }}" type="submit" id="mass-op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }}
</button>
</div>
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-page %}{{ node.name }} | {% trans "Node" %}{% endblock %}
......@@ -96,7 +97,3 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-details.js"></script>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
......@@ -39,7 +40,3 @@
</div><!-- .row -->
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-list.js"></script>
{% endblock %}
......@@ -2,7 +2,7 @@
{% load i18n %}
<i class="fa fa-gears"></i> {% trans "CPU" %}
<div class="progress pull-right">
<div class="progress pull-right">
<div class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="{{ record.cpu_usage|stringformat:"f" }}"
aria-valuemin="0" aria-valuemax="1"
......@@ -15,10 +15,12 @@
{% endif %}
</span>
</div>
</div>
</div>
<br>
<i class="fa fa-ticket"></i> {% trans "Memory" %}
<div class="progress pull-right">
<div class="progress pull-right">
<div class="progress-bar" role="progressbar"
aria-valuenow="{{ record.ram_usage|stringformat:"f" }}"
aria-valuemin="0" aria-valuemax="100"
......@@ -31,29 +33,4 @@
{% endif %}
</span>
</div>
</div>
<style>
.progress {
position: relative;
width: 150px;
height: 16px;
margin-bottom: 4px;
margin-top: 0px;
background-image: linear-gradient(to bottom, #BBEBEB 0px, #F5F5F5 100%);
}
.progress-bar-text {
position: absolute;
display: block;
width: 100%;
color: white;
/* outline */
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
font-size: 13px;
}
</style>
</div>
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% block content %}
<div class="body-content">
......@@ -14,9 +15,3 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
{% 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 %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load crispy_forms_tags %}
......@@ -111,7 +112,3 @@
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/profile.js"></script>
{% endblock %}
......@@ -3,10 +3,11 @@
<div class="panel panel-default">
<div class="panel-heading">
<span class="btn btn-default btn-xs infobtn pull-right store-action-button"
title="{% trans "A list of your most recent files." %}">
title="{% trans "A list of your most recent files." %}"
data-container="body">
<i class="fa fa-info-circle"></i>
</span>
<span class="btn btn-default btn-xs pull-right"
<span class="btn btn-default btn-xs pull-right" data-container="body"
title="
{% blocktrans with used=files.quota.readable_used soft=files.quota.readable_soft hard=files.quota.readable_hard %}
You are currently using {{ used }}, your soft limit is {{ soft }}, your hard limit is {{ hard }}.
......@@ -20,7 +21,8 @@
<div id="dashboard-files-toplist">
{% for t in files.toplist %}
{% if t.TYPE == "F" %}
<div class="list-group-item">
<div class="list-group-item
{% if forloop.last and files.toplist|length < 5 %}list-group-item-last{% endif %}">
<i class="fa fa-{{ t.icon }} dashboard-toplist-icon"></i>
<div class="store-list-item-name">
{{ t.NAME }}
......@@ -37,7 +39,8 @@
</div>
{% else %}
<a href="{% url "dashboard.views.store-list" %}?directory={{ t.path }}"
class="list-group-item">
class="list-group-item
{% if forloop.last and files.toplist|length < 5 %}list-group-item-last{% endif %}">
<i class="fa fa-{{ t.icon }} dashboard-toplist-icon"></i>
<div class="store-list-item-name">
{{ t.NAME }}
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-page %}{% trans "List" %} | {% trans "Store" %}{% endblock %}
......@@ -17,26 +18,23 @@
<div class="progress-bar" role="progressbar"
aria-valuenow="{{ quota.used }}" aria-valuemin="0" aria-valuemax="{{ quota.hard }}"
style="width: {% widthratio quota.used quota.hard 100 %}%; min-width: 150px;">
<div style="padding-top: 2px;">
<div>
{% blocktrans with used=quota.readable_used %}
{{ used }} used
{% endblocktrans %}
</div>
</div>
<div class="progress-marker" id="progress-marker-hard" data-placement="left"
<div class="progress-marker" id="progress-marker-hard" data-placement="top"
data-container="body"
title="{% trans "Hard limit" %}: {{ quota.readable_hard }}">
</div>
<div class="progress-marker" id="progress-marker-soft" style="background: orange;
left: {% widthratio quota.soft quota.hard 100 %}%"
title="{% trans "Soft limit" %}: {{ quota.readable_soft }}"
data-placement="top">
data-placement="top" data-container="body">
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/store.js"></script>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
......@@ -152,6 +153,4 @@
$("#hint_id_num_cores, #hint_id_priority, #hint_id_ram_size").hide();
});
</script>
<script src="{{ STATIC_URL }}dashboard/disk-list.js"></script>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
......@@ -75,8 +76,3 @@
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/template-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load compressed %}
{% block title-page %}{{ instance.name }} | vm{% endblock %}
......@@ -70,6 +72,13 @@
{{ instance.name }}
</div>
<small>{{ instance.primary_host.get_fqdn }}</small>
<small class="dashboard-vm-favourite" style="line-height: 39.6px;" data-vm="{{ instance.pk }}">
{% if fav %}
<i class="fa fa-star text-primary title-favourite" title="{% trans "Unfavourite" %}"></i>
{% else %}
<i class="fa fa-star-o text-primary title-favourite" title="{% trans "Mark as favorite" %}"></i>
{% endif %}
</small>
</h1>
<div style="clear: both;"></div>
</div>
......@@ -220,10 +229,5 @@
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/introjs/intro.min.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-details.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-common.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-console.js"></script>
<script src="{{ STATIC_URL }}dashboard/disk-list.js"></script>
<script src="{{ STATIC_URL }}dashboard/vm-tour.js"></script>
{% compressed_js 'vm-detail' %}
{% endblock %}
{% load i18n %}
{% load staticfiles %}
<div class="btn-toolbar">
{% if perms.vm.access_console %}
<button id="sendCtrlAltDelButton" class="btn btn-danger btn-sm">{% trans "Send Ctrl+Alt+Del" %}</button>
......@@ -22,9 +23,8 @@
<canvas id="noVNC_canvas" width="640px" height="20px">Canvas not supported.
</canvas>
<script src="{{ STATIC_URL }}dashboard/novnc/util.js"></script>
<script>
var INCLUDE_URI = '{{ STATIC_URL }}dashboard/novnc/';
var INCLUDE_URI = '{% static "no-vnc/include/" %}';
var VNC_URL = "{{ vnc_url }}";
</script>
{% endif %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-page %}{% trans "Virtual machines" %}{% endblock %}
......@@ -154,8 +155,3 @@
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/vm-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
{% endblock %}
{% load i18n %}<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>{% blocktrans with name=COMPANY_NAME %}{{name}} virtual machines{% endblocktrans %}</ShortName>
<Url type="text/html"
template="{{ url }}?s={searchTerms}&amp;stype=shared" />
</OpenSearchDescription>
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from os import listdir
from os.path import isfile, isdir, join
import unittest
from django.conf import settings
from django.template import Template, Context, VariableDoesNotExist
from django.template.loader import find_template_loader
from django.core.urlresolvers import NoReverseMatch
class TemplateSyntaxTestCase(unittest.TestCase):
def test_templates(self):
"""Test all templates for syntax errors."""
for loader_name in settings.TEMPLATE_LOADERS:
print loader_name
loader = find_template_loader(loader_name)
self._test_dir(loader.get_template_sources(''))
def _test_dir(self, dir, path="/"):
for i in dir:
i = join(path, i)
if isfile(i):
self._test_template(join(path, i))
elif isdir(i):
print "%s:" % i
self._test_dir(listdir(i), i)
def _test_template(self, path):
print path
try:
Template(open(path).read()).render(Context({}))
except (NoReverseMatch, VariableDoesNotExist, KeyError, AttributeError,
ValueError, ) as e:
print e
......@@ -50,6 +50,7 @@ from .views import (
VmGraphView, NodeGraphView, NodeListGraphView,
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView,
)
from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops
......@@ -221,6 +222,8 @@ urlpatterns = patterns(
name="dashboard.views.client-check"),
url(r'^token-login/(?P<token>.*)/$', TokenLogin.as_view(),
name="dashboard.views.token-login"),
url(r'^vm/opensearch.xml$', OpenSearchDescriptionView.as_view(),
name="dashboard.views.vm-opensearch"),
)
urlpatterns += patterns(
......
......@@ -19,6 +19,7 @@ from __future__ import unicode_literals, absolute_import
import logging
from django.core.cache import get_cache
from django.core.urlresolvers import reverse
from django.conf import settings
from django.contrib.auth.models import Group
from django.views.generic import TemplateView
......@@ -121,3 +122,15 @@ class HelpView(TemplateView):
ctx.update({"saml": hasattr(settings, "SAML_CONFIG"),
"store": settings.STORE_URL})
return ctx
class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml"
def get_context_data(self, **kwargs):
context = super(OpenSearchDescriptionView, self).get_context_data(
**kwargs)
context['url'] = self.request.build_absolute_uri(
reverse("dashboard.views.vm-list"))
return context
......@@ -115,6 +115,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
'op': {i.op: i for i in ops},
'connect_commands': user.profile.get_connect_commands(instance),
'hide_tutorial': hide_tutorial,
'fav': instance.favourite_set.filter(user=user).exists(),
})
# activity data
......
......@@ -34,6 +34,15 @@ def pip(env, req):
run("pip install -r %s" % req)
def bower(component=None):
"Install bower component"
with cd("~/circle/circle"):
if component:
run("bower install %s" % component)
else:
run("bower install")
@roles('portal')
def migrate():
"Run db migrations"
......@@ -113,11 +122,14 @@ def pull(dir="~/circle/circle"):
@roles('portal')
def update_portal(test=False):
def update_portal(test=False, git=True):
"Update and restart portal+manager"
with _stopped("portal", "manager"):
if git:
pull()
cleanup()
pip("circle", "~/circle/requirements.txt")
bower()
migrate()
compile_things()
if test:
......@@ -125,6 +137,12 @@ def update_portal(test=False):
@roles('portal')
def build_portal():
"Update portal without pulling from git"
return update_portal(False, False)
@roles('portal')
def stop_portal(test=False):
"Stop portal and manager"
_stop_services("portal", "manager")
......@@ -136,10 +154,15 @@ def update_node():
with _stopped("node", "agentdriver", "monitor-client"):
pull("~/vmdriver")
pip("vmdriver", "~/vmdriver/requirements/production.txt")
_cleanup("~/vmdriver")
pull("~/agentdriver")
pip("agentdriver", "~/agentdriver/requirements.txt")
_cleanup("~/agentdriver")
pull("~/monitor-client")
pip("monitor-client", "~/monitor-client/requirements.txt")
_cleanup("~/monitor-client")
@parallel
......@@ -161,6 +184,18 @@ def checkout(vmdriver="master", agent="master"):
run("git checkout %s" % agent)
@roles('portal')
def cleanup():
"Clean pyc files of portal"
_cleanup()
def _cleanup(dir="~/circle/circle"):
"Clean pyc files"
with cd("~/circle/circle"):
run("find -name '*.py[co]' -exec rm -f {} +")
def _stop_services(*services):
"Stop given services (warn only if not running)"
with settings(warn_only=True):
......@@ -189,3 +224,12 @@ def _stopped(*services):
def _workon(name):
return prefix("source ~/.virtualenvs/%s/bin/activate && "
"source ~/.virtualenvs/%s/bin/postactivate" % (name, name))
@roles('portal')
def install_bash_completion_script():
sudo("wget https://raw.githubusercontent.com/marcelor/fabric-bash-"
"autocompletion/48baf5735bafbb2be5be8787d2c2c04a44b6cdb0/fab "
"-O /etc/bash_completion.d/fab")
print("To have bash completion instantly, run\n"
" source /etc/bash_completion.d/fab")
......@@ -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 string import ascii_letters
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
......@@ -22,7 +23,7 @@ from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules
from django import forms
from netaddr import (IPAddress, IPNetwork, AddrFormatError, ZEROFILL,
EUI, mac_unix)
EUI, mac_unix, AddrConversionError)
import re
......@@ -31,7 +32,6 @@ domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$')
domain_wildcard_re = re.compile(r'^(\*\.)?([A-Za-z0-9_-]\.?)+$')
ipv4_re = re.compile('^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
ipv6_template_re = re.compile(r'^(%\([abcd]\)[dxX]|[A-Za-z0-9:-])+$')
class mac_custom(mac_unix):
......@@ -246,15 +246,60 @@ def val_reverse_domain(value):
raise ValidationError(u'%s - invalid reverse domain name' % value)
def is_valid_ipv6_template(value):
"""Check whether the parameter is a valid ipv6 template."""
return ipv6_template_re.match(value) is not None
def val_ipv6_template(value):
"""Validate whether the parameter is a valid ipv6 template."""
if not is_valid_ipv6_template(value):
raise ValidationError(u'%s - invalid reverse ipv6 template' % value)
"""Validate whether the parameter is a valid ipv6 template.
Normal use:
>>> val_ipv6_template("123::%(a)d:%(b)d:%(c)d:%(d)d")
>>> val_ipv6_template("::%(a)x:%(b)x:%(c)d:%(d)d")
Don't have to use all bytes from the left (no a):
>>> val_ipv6_template("::%(b)x:%(c)d:%(d)d")
But have to use all ones to the right (a, but no b):
>>> val_ipv6_template("::%(a)x:%(c)d:%(d)d")
Traceback (most recent call last):
...
ValidationError: [u"template doesn't use parameter b"]
Detects valid templates building invalid ips:
>>> val_ipv6_template("xxx::%(a)d:%(b)d:%(c)d:%(d)d")
Traceback (most recent call last):
...
ValidationError: [u'template renders invalid IPv6 address']
Also IPv4-compatible addresses are invalid:
>>> val_ipv6_template("::%(a)02x%(b)02x:%(c)d:%(d)d")
Traceback (most recent call last):
...
ValidationError: [u'template results in IPv4 address']
"""
tpl = {ascii_letters[i]: 255 for i in range(4)}
try:
v6 = value % tpl
except:
raise ValidationError(_('%s: invalid template') % value)
used = False
for i in ascii_letters[:4]:
try:
value % {k: tpl[k] for k in tpl if k != i}
except KeyError:
used = True # ok, it misses this key
else:
if used:
raise ValidationError(
_("template doesn't use parameter %s") % i)
try:
v6 = IPAddress(v6, 6)
except:
raise ValidationError(_('template renders invalid IPv6 address'))
try:
v6.ipv4()
except (AddrConversionError, AddrFormatError):
pass # can't converted to ipv4 == it's real ipv6
else:
raise ValidationError(_('template results in IPv4 address'))
def is_valid_ipv4_address(value):
......@@ -284,12 +329,3 @@ def val_mx(value):
domain_re.match(mx[1])):
raise ValidationError(_("Bad MX address format. "
"Should be: <priority>:<hostname>"))
def convert_ipv4_to_ipv6(ipv6_template, ipv4):
"""Convert IPv4 address string to IPv6 address string."""
m = ipv4.words
return IPAddress(ipv6_template % {'a': int(m[0]),
'b': int(m[1]),
'c': int(m[2]),
'd': int(m[3])})
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import
from django.core.management.base import BaseCommand
from firewall.tasks.local_tasks import reloadtask
class Command(BaseCommand):
def handle(self, *args, **options):
reloadtask('Vlan')
......@@ -17,9 +17,11 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from itertools import islice, ifilter
from string import ascii_letters
from itertools import islice, ifilter, chain
from math import ceil
import logging
from netaddr import IPSet, EUI, IPNetwork
import random
from django.contrib.auth.models import User
from django.db import models
......@@ -28,15 +30,17 @@ from django.utils.translation import ugettext_lazy as _
from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain,
val_ipv6_template, val_domain, val_ipv4,
val_domain_wildcard,
val_ipv6, val_mx, convert_ipv4_to_ipv6,
val_ipv6, val_mx,
IPNetworkField, IPAddressField)
from django.core.validators import MinValueValidator, MaxValueValidator
import django.conf
from django.db.models.signals import post_save, post_delete
import random
from celery.exceptions import TimeoutError
from netaddr import IPSet, EUI, IPNetwork, IPAddress, ipv6_full
from common.models import method_cache, WorkerNotFound, HumanSortField
from firewall.tasks.local_tasks import reloadtask
from firewall.tasks.remote_tasks import get_dhcp_clients
from .iptables import IptRule
from acl.models import AclBase
......@@ -360,9 +364,19 @@ class Vlan(AclBase, models.Model):
'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'),
default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa")
ipv6_template = models.TextField(
validators=[val_ipv6_template],
verbose_name=_('ipv6 template'),
default="2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0")
blank=True,
help_text=_('Template for translating IPv4 addresses to IPv6. '
'Automatically generated hosts in dual-stack networks '
'will get this address. The template '
'can contain four tokens: "%(a)d", "%(b)d", '
'"%(c)d", and "%(d)d", representing the four bytes '
'of the IPv4 address, respectively, in decimal notation. '
'Moreover you can use any standard printf format '
'specification like %(a)02x to get the first byte as two '
'hexadecimal digits. Usual choices for mapping '
'198.51.100.0/24 to 2001:0DB8:1:1::/64 would be '
'"2001:db8:1:1:%(d)d::" and "2001:db8:1:1:%(d)02x00::".'),
validators=[val_ipv6_template], verbose_name=_('ipv6 template'))
dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'),
help_text=_(
'The address range of the DHCP pool: '
......@@ -377,6 +391,87 @@ class Vlan(AclBase, models.Model):
modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at'))
def clean(self):
super(Vlan, self).clean()
if self.ipv6_template:
if not self.network6:
raise ValidationError(
_("You cannot specify an IPv6 template if there is no "
"IPv6 network set."))
for i in (self.network4[1], self.network4[-1]):
i6 = self.convert_ipv4_to_ipv6(i)
if i6 not in self.network6:
raise ValidationError(
_("%(ip6)s (translated from %(ip4)s) is outside of "
"the IPv6 network.") % {"ip4": i, "ip6": i6})
if self.network6:
tpl, prefixlen = self._magic_ipv6_template(self.network4,
self.network6)
if not self.ipv6_template:
self.ipv6_template = tpl
if not self.host_ipv6_prefixlen:
self.host_ipv6_prefixlen = prefixlen
@staticmethod
def _host_bytes(prefixlen, maxbytes):
return int(ceil((maxbytes - prefixlen / 8.0)))
@staticmethod
def _append_hexa(s, v, lasthalf):
if lasthalf: # can use last half word
assert s[-1] == "0" or s[-1].endswith("00")
if s[-1].endswith("00"):
s[-1] = s[-1][:-2]
s[-1] += "%({})02x".format(v)
s[-1].lstrip("0")
else:
s.append("%({})02x00".format(v))
@classmethod
def _magic_ipv6_template(cls, network4, network6, verbose=None):
"""Offer a sensible ipv6_template value.
Based on prefix lengths the method magically selects verbose (decimal)
format:
>>> Vlan._magic_ipv6_template(IPNetwork("198.51.100.0/24"),
... IPNetwork("2001:0DB8:1:1::/64"))
('2001:db8:1:1:%(d)d::', 80)
However you can explicitly select non-verbose, i.e. hexa format:
>>> Vlan._magic_ipv6_template(IPNetwork("198.51.100.0/24"),
... IPNetwork("2001:0DB8:1:1::/64"), False)
('2001:db8:1:1:%(d)02x00::', 72)
"""
host4_bytes = cls._host_bytes(network4.prefixlen, 4)
host6_bytes = cls._host_bytes(network6.prefixlen, 16)
if host4_bytes > host6_bytes:
raise ValidationError(
_("IPv6 network is too small to map IPv4 addresses to it."))
letters = ascii_letters[4-host4_bytes:4]
remove = host6_bytes // 2
ipstr = network6.network.format(ipv6_full)
s = ipstr.split(":")[0:-remove]
if verbose is None: # use verbose format if net6 much wider
verbose = 2 * (host4_bytes + 1) < host6_bytes
if verbose:
for i in letters:
s.append("%({})d".format(i))
else:
remain = host6_bytes
for i in letters:
cls._append_hexa(s, i, remain % 2 == 1)
remain -= 1
if host6_bytes > host4_bytes:
s.append(":")
tpl = ":".join(s)
# compute prefix length
mask = int(IPAddress(tpl % {"a": 1, "b": 1, "c": 1, "d": 1}))
prefixlen = 128
while mask % 2 == 0:
mask /= 2
prefixlen -= 1
return (tpl, prefixlen)
def __unicode__(self):
return "%s - %s" % ("managed" if self.managed else "unmanaged",
self.name)
......@@ -402,7 +497,7 @@ class Vlan(AclBase, models.Model):
logger.debug("Found unused IPv4 address %s.", ipv4)
ipv6 = None
if self.network6 is not None:
ipv6 = convert_ipv4_to_ipv6(self.ipv6_template, ipv4)
ipv6 = self.convert_ipv4_to_ipv6(ipv4)
if ipv6 in used_v6:
continue
else:
......@@ -411,6 +506,20 @@ class Vlan(AclBase, models.Model):
else:
raise ValidationError(_("All IP addresses are already in use."))
def convert_ipv4_to_ipv6(self, ipv4):
"""Convert IPv4 address string to IPv6 address string."""
if isinstance(ipv4, basestring):
ipv4 = IPAddress(ipv4, 4)
nums = {ascii_letters[i]: int(ipv4.words[i]) for i in range(4)}
return IPAddress(self.ipv6_template % nums)
def get_dhcp_clients(self):
macs = set(i.mac for i in self.host_set.all())
return [{"mac": k, "ip": v["ip"], "hostname": v["hostname"]}
for k, v in chain(*(fw.get_dhcp_clients().iteritems()
for fw in Firewall.objects.all() if fw))
if v["interface"] == self.name and EUI(k) not in macs]
class VlanGroup(models.Model):
"""
......@@ -581,8 +690,7 @@ class Host(models.Model):
def save(self, *args, **kwargs):
if not self.id and self.ipv6 == "auto":
self.ipv6 = convert_ipv4_to_ipv6(self.vlan.ipv6_template,
self.ipv4)
self.ipv6 = self.vlan.convert_ipv4_to_ipv6(self.ipv4)
self.full_clean()
super(Host, self).save(*args, **kwargs)
......@@ -716,9 +824,6 @@ class Host(models.Model):
proto=proto, nat=False, action='accept',
host=self, foreign_network=vg)
if self.behind_nat:
if public < 1024:
raise ValidationError(
_("Only ports above 1024 can be used."))
rule.nat_external_port = public
rule.nat = True
rule.full_clean()
......@@ -838,7 +943,7 @@ class Firewall(models.Model):
return self.name
@method_cache(30)
def get_remote_queue_name(self, queue_id):
def get_remote_queue_name(self, queue_id="firewall"):
"""Returns the name of the remote celery queue for this node.
Throws Exception if there is no worker on the queue.
......@@ -851,6 +956,20 @@ class Firewall(models.Model):
else:
raise WorkerNotFound()
@method_cache(20)
def get_dhcp_clients(self):
try:
return get_dhcp_clients.apply_async(
queue=self.get_remote_queue_name(), expires=60).get(timeout=2)
except TimeoutError:
logger.info("get_dhcp_clients task timed out")
except IOError:
logger.exception("get_dhcp_clients failed. "
"maybe syslog isn't readble by firewall worker")
except:
logger.exception("get_dhcp_clients failed")
return {}
class Domain(models.Model):
name = models.CharField(max_length=40, validators=[val_domain],
......
......@@ -62,5 +62,6 @@ def reload_blacklist(data):
@celery.task(name='firewall.get_dhcp_clients')
def get_dhcp_clients(data):
def get_dhcp_clients():
# {'00:21:5a:73:72:cd': {'interface': 'OFF', 'ip': None, 'hostname': None}}
pass
......@@ -78,6 +78,7 @@ class GetNewAddressTestCase(TestCase):
self.vlan = Vlan(vid=1, name='test', network4='10.0.0.0/29',
network6='2001:738:2001:4031::/80', domain=d,
owner=self.u1)
self.vlan.clean()
self.vlan.save()
self.vlan.host_set.all().delete()
for i in [1] + range(3, 6):
......@@ -85,6 +86,9 @@ class GetNewAddressTestCase(TestCase):
ipv4='10.0.0.%d' % i, vlan=self.vlan,
owner=self.u1).save()
def tearDown(self):
self.vlan.delete()
def test_new_addr_w_empty_vlan(self):
self.vlan.host_set.all().delete()
self.vlan.get_new_address()
......@@ -96,12 +100,6 @@ class GetNewAddressTestCase(TestCase):
owner=self.u1).save()
self.assertRaises(ValidationError, self.vlan.get_new_address)
def test_all_addr_in_use_w_ipv6(self):
Host(hostname='h-x', mac='01:02:03:04:05:06',
ipv4='10.0.0.6', ipv6='2001:738:2001:4031:0:0:2:0',
vlan=self.vlan, owner=self.u1).save()
self.assertRaises(ValidationError, self.vlan.get_new_address)
def test_new_addr(self):
used_v4 = IPSet(self.vlan.host_set.values_list('ipv4', flat=True))
assert self.vlan.get_new_address()['ipv4'] not in used_v4
......@@ -313,11 +311,6 @@ class ReloadTestCase(TestCase):
new_rules = h.rules.count()
self.assertEqual(new_rules, old_rules)
def test_host_add_port_w_validationerror(self):
h = self.h1
self.assertRaises(ValidationError, h.add_port,
'tcp', public=1000, private=22)
def test_periodic_task(self):
# TODO
with patch('firewall.tasks.local_tasks.cache') as cache:
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -15,13 +15,13 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.forms import ModelForm
from django.forms import ModelForm, widgets
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
from crispy_forms.bootstrap import FormActions
from crispy_forms.bootstrap import FormActions, FieldWithButtons, StrictButton
from firewall.models import (Host, Vlan, Domain, Group, Record, BlacklistItem,
Rule, VlanGroup, SwitchPort)
......@@ -122,8 +122,12 @@ class HostForm(ModelForm):
Fieldset(
_('Network'),
'vlan',
'ipv4',
'ipv6',
FieldWithButtons('ipv4', StrictButton(
'<i class="fa fa-magic"></i>', css_id="ipv4-magic",
title=_("Generate random address."))),
FieldWithButtons('ipv6', StrictButton(
'<i class="fa fa-magic"></i>', css_id="ipv6-magic",
title=_("Generate IPv6 pair of IPv4 address."))),
'shared_ip',
'external_ipv4',
),
......@@ -252,7 +256,9 @@ class VlanForm(ModelForm):
Fieldset(
_('IPv6'),
'network6',
'ipv6_template',
FieldWithButtons('ipv6_template', StrictButton(
'<i class="fa fa-magic"></i>', css_id="ipv6-tpl-magic",
title=_("Generate sensible template."))),
'host_ipv6_prefixlen',
),
Fieldset(
......@@ -277,6 +283,9 @@ class VlanForm(ModelForm):
class Meta:
model = Vlan
widgets = {
'ipv6_template': widgets.TextInput,
}
class VlanGroupForm(ModelForm):
......
$('i[class="fa fa-times"]').click(function() {
$('#small_rule_table i[class="fa fa-times"]').click(function() {
href = $(this).parent('a').attr('href');
csrf = getCookie('csrftoken');
var click_this = this;
......
......@@ -28,6 +28,49 @@ function getURLParameter(name) {
);
}
function doBlink(id, count) {
if (count > 0) {
$(id).parent().delay(200).queue(function() {
$(this).delay(200).queue(function() {
$(this).removeClass("has-warning").dequeue();
doBlink(id, count-1);});
$(this).addClass("has-warning").dequeue()
});
}
}
$(function() {
$("[title]").tooltip();
$("#ipv6-magic").click(function() {
$.ajax({url: window.location,
data: {ipv4: $("[name=ipv4]").val(),
vlan: $("[name=vlan]").val()},
success: function(data) {
$("[name=ipv6]").val(data.ipv6);
}});
});
$("#ipv4-magic").click(function() {
$.ajax({url: window.location,
data: {vlan: $("[name=vlan]").val()},
success: function(data) {
$("[name=ipv4]").val(data.ipv4);
if ($("[name=ipv6]").val() != data.ipv6) {
doBlink("[name=ipv6]", 3);
}
$("[name=ipv6]").val(data.ipv6);
}});
});
$("#ipv6-tpl-magic").click(function() {
$.ajax({url: window.location,
data: {network4: $("[name=network4]").val(),
network6: $("[name=network6]").val()},
success: function(data) {
$("[name=ipv6_template]").val(data.ipv6_template);
if ($("[name=host_ipv6_prefixlen]").val() != data.host_ipv6_prefixlen) {
doBlink("[name=host_ipv6_prefixlen]", 3);
}
$("[name=host_ipv6_prefixlen]").val(data.host_ipv6_prefixlen);
}});
});
});
Copyright 2012 Igor Vaynberg
Version: @@ver@@ Timestamp: @@timestamp@@
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
General Public License version 2 (the "GPL License"). You may choose either license to govern your
use of this software only upon the condition that you accept all of the terms of either the Apache
License or the GPL License.
You may obtain a copy of the Apache License and the GPL License at:
http://www.apache.org/licenses/LICENSE-2.0
http://www.gnu.org/licenses/gpl-2.0.html
Unless required by applicable law or agreed to in writing, software distributed under the Apache License
or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the Apache License and the GPL License for the specific language governing
permissions and limitations under the Apache License and the GPL License.
\ No newline at end of file
Select2
=================
Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.
To get started checkout examples and documentation at http://ivaynberg.github.com/select2
What Usecases Does Select2 Support
-------------------------------------------------
* Enhances native selects with search
* Enhances native selects with a better multi-select interface
* Loading data from javascript: easily load items via ajax and have them searchable
* Nested optgroups: native selects only support one level of nested, Select2 does not have this restriction
* Tagging: ability to add new items on the fly
* Working with large remote datesets: ability to partially load a dataset based on the search term
* Paging of large datasets: easy support for loading more pages when the results are scrolled to the end
* Templating: support for custom rendering of results and selections
Browser Compatibility
--------------------
* IE 8+ (7 mostly works except for [issue with z-index](https://github.com/ivaynberg/select2/issues/37))
* Chrome 8+
* Firefox 3.5+
* Safari 3+
* Opera 10.6+
Integrations
------------
* [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org))
* [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails)
* [AngularUI](http://angular-ui.github.com/#directives-select2) ([AngularJS](angularjs.org))
* [Django](https://github.com/applegrew/django-select2)
* [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin)
* [Bootstrap](https://github.com/t0m/select2-bootstrap-css) (CSS skin)
* [Yii](https://github.com/tonybolzan/yii-select2)
Internationalization (i18n)
---------------------------
Select2 supports multiple languages by simply including the right
language JS file (`select2_locale_it.js`, `select2_locale_nl.js` etc.).
Missing a language? Just copy `select2_locale_en.js.template`, translate
it and make a pull request back to Select2 here on Github.
Bug tracker
-----------
Have a bug? Please create an issue here on GitHub!
https://github.com/ivaynberg/select2/issues
Mailing list
------------
Have a question? Ask on our mailing list!
select2@googlegroups.com
https://groups.google.com/d/forum/select2
Copyright and License
---------------------
Copyright 2012 Igor Vaynberg
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
General Public License version 2 (the "GPL License"). You may choose either license to govern your
use of this software only upon the condition that you accept all of the terms of either the Apache
License or the GPL License.
You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
http://www.gnu.org/licenses/gpl-2.0.html
Unless required by applicable law or agreed to in writing, software distributed under the Apache License
or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the Apache License and the GPL License for the specific language governing
permissions and limitations under the Apache License and the GPL License.
{
"name": "select2",
"version": "3.4.0",
"main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"],
"dependencies": {
"jquery": ">= 1.7.1"
}
}
#!/bin/bash
set -e
echo -n "Enter the version for this release: "
read ver
if [ ! $ver ]; then
echo "Invalid version."
exit
fi
name="select2"
js="$name.js"
mini="$name.min.js"
css="$name.css"
release="$name-$ver"
tag="$ver"
branch="build-$ver"
curbranch=`git branch | grep "*" | sed "s/* //"`
timestamp=$(date)
tokens="s/@@ver@@/$ver/g;s/\@@timestamp@@/$timestamp/g"
remote="github"
echo "Updating Version Identifiers"
sed -E -e "s/\"version\": \"([0-9\.]+)\",/\"version\": \"$ver\",/g" -i "" component.json select2.jquery.json
git add component.json
git add select2.jquery.json
git commit -m "modified version identifiers in descriptors for release $ver"
git push
git branch "$branch"
git checkout "$branch"
echo "Tokenizing..."
find . -name "$js" | xargs -I{} sed -e "$tokens" -i "" {}
find . -name "$css" | xargs -I{} sed -e "$tokens" -i "" {}
sed -e "s/latest/$ver/g" -i "" component.json
git add "$js"
git add "$css"
echo "Minifying..."
echo "/*" > "$mini"
cat LICENSE | sed "$tokens" >> "$mini"
echo "*/" >> "$mini"
curl -s \
--data-urlencode "js_code@$js" \
http://marijnhaverbeke.nl/uglifyjs \
>> "$mini"
git add "$mini"
git commit -m "release $ver"
echo "Tagging..."
git tag -a "$tag" -m "tagged version $ver"
git push "$remote" --tags
echo "Cleaning Up..."
git checkout "$curbranch"
git branch -D "$branch"
echo "Done"
{
"name": "select2",
"title": "Select2",
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
"keywords": [
"select",
"autocomplete",
"typeahead",
"dropdown",
"multiselect",
"tag",
"tagging"
],
"version": "3.4.0",
"author": {
"name": "Igor Vaynberg",
"url": "https://github.com/ivaynberg"
},
"licenses": [
{
"type": "Apache",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
},
{
"type": "GPL v2",
"url": "http://www.gnu.org/licenses/gpl-2.0.html"
}
],
"bugs": "https://github.com/ivaynberg/select2/issues",
"homepage": "http://ivaynberg.github.com/select2",
"docs": "http://ivaynberg.github.com/select2/",
"download": "https://github.com/ivaynberg/select2/tags",
"dependencies": {
"jquery": ">=1.7.1"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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