Commit 2e952ddb by Szeberényi Imre

Initial version, Python 2.7

parents
# Python bytecode:
*.py[co]
# Packaging files:
*.egg*
# Editor temp files:
*.swp
*.swo
*~
.vscode
.idea
# Sphinx docs:
build
_build
# SQLite3 database files:
*.db
# Logs:
*.log
.ropeproject
celerybeat-schedule
.coverage
*,cover
coverage.xml
.noseids
# Gettext object file:
*.mo
# saml
circle/attribute-maps
circle/remote_metadata.xml
circle/*.key
circle/*.pem
# collected static files:
circle/static_collected
circle/bower_components
# jsi18n files
jsi18n
scripts.rc
# less
*.css
Adam Dudas
Bence Danyi
Daniel Bach
Gergo Nagy
Imre Szeberenyi
Mate Ory
Sandor Guba
Viktor Kalman
This diff is collapsed. Click to expand it.
Copyright (c) 2012--2013 Budapest University of Technology
and Economics (BME-IK), and contributors.
[SOME OPEN SOURCE LICENSE HERE]
============
circle-cloud
============
This is the Django based controller and web portal of the CIRCLE Cloud.
License
=======
Copyright 2014 Budapest University of Technology and Economics (BME IK_).
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.
Foobar 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
Foobar. If not, see <http://www.gnu.org/licenses/>.
.. _IK: http://ik.bme.hu/
"""
Creates Levels for all installed apps that have levels.
"""
from django.db.models import signals
from django.apps import apps
from django.db import DEFAULT_DB_ALIAS
from django.core.exceptions import ImproperlyConfigured
from ..models import Level, AclBase
def create_levels(app_config, verbosity=False, using=DEFAULT_DB_ALIAS,
**kwargs):
"""Create and set the weights of the configured Levels.
Based on django.contrib.auth.management.__init__.create_permissions"""
# if not router.allow_migrate(using, auth_app.Permission):
# return
from django.contrib.contenttypes.models import ContentType
app_models = [k for k in apps.get_models(app_config)
if AclBase in k.__bases__]
print "Creating levels for models: %s." % ", ".join(
[m.__name__ for m in app_models])
# This will hold the levels we're looking for as
# (content_type, (codename, name))
searched_levels = list()
level_weights = list()
# The codenames and ctypes that should exist.
ctypes = set()
for klass in app_models:
# Force looking up the content types in the current database
# before creating foreign keys to them.
ctype1 = ContentType.objects.db_manager(using).get_for_model(klass)
ctypes.add(ctype1)
weight = 0
try:
for codename, name in klass.ACL_LEVELS:
searched_levels.append((ctype1, (codename, name)))
level_weights.append((ctype1, codename, weight))
weight += 1
except AttributeError:
raise ImproperlyConfigured(
"Class %s doesn't have ACL_LEVELS attribute." % klass)
# Find all the Levels that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_levels = set(Level.objects.using(using).filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
levels = [
Level(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_levels
if (ctype.pk, codename) not in all_levels
]
Level.objects.using(using).bulk_create(levels)
if verbosity >= 2:
print("Adding levels [%s]." % ", ".join(unicode(l) for l in levels))
print("Searched: [%s]." % ", ".join(
unicode(l) for l in searched_levels))
print("All: [%s]." % ", ".join(unicode(l) for l in all_levels))
# set weights
for ctype, codename, weight in level_weights:
Level.objects.filter(codename=codename,
content_type=ctype).update(weight=weight)
signals.post_migrate.connect(
create_levels, dispatch_uid="circle.acl.management.create_levels")
# 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 django.core.management.base import BaseCommand
from .. import create_levels
class Command(BaseCommand):
args = ''
help = 'Regenerates Levels'
def handle(self, *args, **options):
create_levels(None, None, 3)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Level',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=50, verbose_name=b'name')),
('codename', models.CharField(max_length=100, verbose_name=b'codename')),
('weight', models.IntegerField(null=True, verbose_name=b'weight')),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ObjectLevel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.IntegerField()),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('groups', models.ManyToManyField(to='auth.Group')),
('level', models.ForeignKey(to='acl.Level')),
('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
],
options={
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='objectlevel',
unique_together=set([('content_type', 'object_id', 'level')]),
),
migrations.AlterUniqueTogether(
name='level',
unique_together=set([('content_type', 'codename')]),
),
]
# 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/>.
import logging
from django.contrib.auth.models import User, Group
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation
)
from django.contrib.contenttypes.models import ContentType
from django.db.models import (
ManyToManyField, ForeignKey, CharField, Model, IntegerField, Q
)
logger = logging.getLogger(__name__)
class Level(Model):
"""Definition of a permission level.
Instances are automatically populated based on AclBase."""
name = CharField('name', max_length=50)
content_type = ForeignKey(ContentType)
codename = CharField('codename', max_length=100)
weight = IntegerField('weight', null=True)
def __unicode__(self):
return "<%s/%s>" % (unicode(self.content_type), self.name)
class Meta:
app_label = 'acl'
unique_together = (('content_type', 'codename'),
# ('content_type', 'weight'),
# TODO find a way of temp. disabling this constr.
)
class ObjectLevel(Model):
"""Permission level for a specific object."""
level = ForeignKey(Level)
content_type = ForeignKey(ContentType)
object_id = IntegerField()
content_object = GenericForeignKey()
users = ManyToManyField(User)
groups = ManyToManyField(Group)
def __unicode__(self):
return "<%s: %s>" % (unicode(self.content_object), unicode(self.level))
class Meta:
app_label = 'acl'
unique_together = (('content_type', 'object_id', 'level'),)
class AclBase(Model):
"""Define permission levels for Users/Groups per object."""
object_level_set = GenericRelation(ObjectLevel)
def clone_acl(self, other):
"""Clone full ACL from other object."""
assert self.id != other.id or type(self) != type(other)
self.object_level_set.clear()
for i in other.object_level_set.all():
ol = self.object_level_set.create(level=i.level)
for j in i.users.all():
ol.users.add(j)
for j in i.groups.all():
ol.groups.add(j)
@classmethod
def get_level_object(cls, level):
"""Get Level object for this model by codename."""
ct = ContentType.objects.get_for_model(cls)
return Level.objects.get(codename=level, content_type=ct)
def set_level(self, whom, level):
"""Set level of object for a user or group.
:param whom: user or group the level is set for
:type whom: User or Group
:param level: codename of level to set, or None
:type level: Level or str or unicode or NoneType
"""
if isinstance(whom, User):
self.set_user_level(whom, level)
elif isinstance(whom, Group):
self.set_group_level(whom, level)
else:
raise AttributeError('"whom" must be a User or Group object.')
def set_user_level(self, user, level):
"""Set level of object for a user.
:param whom: user the level is set for
:type whom: User
:param level: codename of level to set, or None
:type level: Level or str or unicode or NoneType
"""
logger.info('%s.set_user_level(%s, %s) called',
*[unicode(p) for p in [self, user, level]])
if level is None:
pk = None
else:
if isinstance(level, basestring):
level = self.get_level_object(level)
if not self.object_level_set.filter(level_id=level.pk).exists():
self.object_level_set.create(level=level)
pk = level.pk
for i in self.object_level_set.all():
if i.level_id != pk:
i.users.remove(user)
else:
i.users.add(user)
i.save()
def set_group_level(self, group, level):
"""Set level of object for a user.
:param whom: user the level is set for
:type whom: User or unicode or str
:param level: codename of level to set
:type level: str or unicode
"""
logger.info('%s.set_group_level(%s, %s) called',
*[unicode(p) for p in [self, group, level]])
if level is None:
pk = None
else:
if isinstance(level, basestring):
level = self.get_level_object(level)
if not self.object_level_set.filter(level_id=level.pk).exists():
self.object_level_set.create(level=level)
pk = level.pk
for i in self.object_level_set.all():
if i.level_id != pk:
i.groups.remove(group)
else:
i.groups.add(group)
i.save()
def has_level(self, user, level, group_also=True):
logger.debug('%s.has_level(%s, %s, %s) called',
*[unicode(p) for p in [self, user, level, group_also]])
if user is None or not user.is_authenticated():
return False
if getattr(user, 'is_superuser', False):
logger.debug('- superuser granted')
return True
if isinstance(level, basestring):
level = self.get_level_object(level)
logger.debug("- level set by str: %s", unicode(level))
object_levels = self.object_level_set.filter(
level__weight__gte=level.weight).all()
groups = user.groups.values_list('id', flat=True) if group_also else []
for i in object_levels:
if i.users.filter(pk=user.pk).exists():
return True
if group_also and i.groups.filter(pk__in=groups).exists():
return True
return False
def get_users_with_level(self, **kwargs):
logger.debug('%s.get_users_with_level() called', unicode(self))
object_levels = (self.object_level_set.filter(**kwargs).select_related(
'level').prefetch_related('users').all())
users = []
for object_level in object_levels:
name = object_level.level.codename
olusers = object_level.users.all()
users.extend([(u, name) for u in olusers])
logger.debug('- %s: %s' % (name, [u.username for u in olusers]))
return users
def get_groups_with_level(self):
logger.debug('%s.get_groups_with_level() called', unicode(self))
object_levels = (self.object_level_set.select_related(
'level').prefetch_related('groups').all())
groups = []
for object_level in object_levels:
name = object_level.level.codename
olgroups = object_level.groups.all()
groups.extend([(g, name) for g in olgroups])
logger.debug('- %s: %s' % (name, [g.name for g in olgroups]))
return groups
@classmethod
def get_objects_with_level(cls, level, user,
group_also=True, owner_also=False,
disregard_superuser=False):
logger.debug('%s.get_objects_with_level(%s,%s) called',
unicode(cls), unicode(level), unicode(user))
if user is None or not user.is_authenticated():
return cls.objects.none()
if getattr(user, 'is_superuser', False) and not disregard_superuser:
logger.debug('- superuser granted')
return cls.objects.all()
if isinstance(level, basestring):
level = cls.get_level_object(level)
logger.debug("- level set by str: %s", unicode(level))
ct = ContentType.objects.get_for_model(cls)
levelfilter = Q(users=user)
if group_also:
levelfilter |= Q(groups__in=user.groups.all())
ols = ObjectLevel.objects.filter(
levelfilter,
content_type=ct, level__weight__gte=level.weight).distinct()
clsfilter = Q(object_level_set__in=ols.all())
if owner_also:
clsfilter |= Q(owner=user)
return cls.objects.filter(clsfilter).distinct()
@classmethod
def get_objects_with_group_level(cls, level, group):
if isinstance(level, basestring):
level = cls.get_level_object(level)
ct = ContentType.objects.get_for_model(cls)
levelfilter = Q(groups=group)
ols = ObjectLevel.objects.filter(
levelfilter,
content_type=ct, level__weight__gte=level.weight).distinct()
clsfilter = Q(object_level_set__in=ols.all())
return cls.objects.filter(clsfilter).distinct()
def save(self, *args, **kwargs):
super(AclBase, self).save(*args, **kwargs)
if 'owner' in dict(self.ACL_LEVELS) and (hasattr(self, 'owner') and
self.owner):
self.set_user_level(self.owner, 'owner')
class Meta:
abstract = True
# 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 django.conf import settings
# https://code.djangoproject.com/ticket/7835
if settings.SETTINGS_MODULE == 'circle.settings.test':
from .test_acl import TestModel, Test2Model # noqa
# 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/>.
# Create your views here.
{
"name": "circle",
"version": "0.0.0",
"license": "GPL",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap": "~3.2.0",
"fontawesome": "~4.3.0",
"jquery": "~2.1.1",
"no-vnc": "0.5.1",
"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",
"favico.js": "~0.3.5",
"datatables": "~1.10.4",
"chart.js": "2.3.0",
"clipboard": "~1.6.1"
}
}
# register a signal do update permissions every migration.
# This is based on app django_extensions update_permissions command
from django.db.models.signals import post_migrate
def update_permissions_after_migration(sender, **kwargs):
"""
Update app permission just after every migration.
This is based on app django_extensions update_permissions
management command.
"""
from django.conf import settings
from django.apps import apps
from django.contrib.auth.management import create_permissions
create_permissions(sender, apps.get_models(), 2 if settings.DEBUG else 0)
post_migrate.connect(update_permissions_after_migration)
# 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/>.
"""Development settings and globals."""
# flake8: noqa
from base import * # noqa
########## DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
########## END DEBUG CONFIGURATION
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
########## EMAIL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
########## END EMAIL CONFIGURATION
########## DATABASE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': normpath(join(BASE_DIR, 'default.db')),
# 'USER': '',
# 'PASSWORD': '',
# 'HOST': '',
# 'PORT': '',
# }
# }
########## END DATABASE CONFIGURATION
########## CACHE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
}
}
########## END CACHE CONFIGURATION
########## ROSETTA CONFIGURATION
INSTALLED_APPS += (
'rosetta',
)
########## END ROSETTA CONFIGURATION
########## TOOLBAR CONFIGURATION
# https://github.com/django-debug-toolbar/django-debug-toolbar#installation
if get_env_variable('DJANGO_TOOLBAR', 'FALSE') == 'TRUE':
INSTALLED_APPS += (
'debug_toolbar',
)
# https://github.com/django-debug-toolbar/django-debug-toolbar#installation
INTERNAL_IPS = (
get_env_variable('SSH_CLIENT', '127.0.0.1').split(' ')[0],
)
# https://github.com/django-debug-toolbar/django-debug-toolbar#installation
MIDDLEWARE_CLASSES += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
# https://github.com/django-debug-toolbar/django-debug-toolbar#installation
DEBUG_TOOLBAR_CONFIG = {
'INTERCEPT_REDIRECTS': False,
'SHOW_TEMPLATE_CONTEXT': True,
}
########## END TOOLBAR CONFIGURATION
LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'], 'level': 'DEBUG'}
LOGGING['handlers']['console'] = {'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'}
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': 'DEBUG'}
CRISPY_FAIL_SILENTLY = not DEBUG
# propagate exceptions from signals
if DEBUG:
from django.dispatch import Signal
Signal.send_robust = Signal.send
PIPELINE["COMPILERS"] = (
'dashboard.compilers.DummyLessCompiler',
)
ADMIN_ENABLED = True
ALLOWED_HOSTS = ['*']
# 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/>.
"""Production settings and globals."""
# flake8: noqa
from os import environ
from sys import argv
from base import * # noqa
if 'runserver' in argv:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/
# #allowed-hosts-required-in-production
ALLOWED_HOSTS = get_env_variable('DJANGO_ALLOWED_HOSTS').split(',')
########## END HOST CONFIGURATION
########## EMAIL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
try:
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = get_env_variable('EMAIL_HOST')
except ImproperlyConfigured:
EMAIL_HOST = 'localhost'
else:
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host-password
EMAIL_HOST_PASSWORD = get_env_variable('EMAIL_HOST_PASSWORD', '')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host-user
EMAIL_HOST_USER = get_env_variable('EMAIL_HOST_USER', 'your_email@example.com')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = get_env_variable('EMAIL_PORT', 587)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-use-tls
EMAIL_USE_TLS = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
DEFAULT_FROM_EMAIL = get_env_variable('DEFAULT_FROM_EMAIL')
SERVER_EMAIL = get_env_variable('SERVER_EMAIL', DEFAULT_FROM_EMAIL)
########## END EMAIL CONFIGURATION
########## CACHE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
from urlparse import urlsplit
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': urlsplit(get_env_variable('CACHE_URI')).netloc,
}
}
########## END CACHE CONFIGURATION
########## SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = get_env_variable('SECRET_KEY')
########## END SECRET CONFIGURATION
level = environ.get('LOGLEVEL', 'INFO')
LOGGING['handlers']['syslog']['level'] = level
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['syslog'], 'level': level}
LOGGING['loggers']['djangosaml2'] = {'handlers': ['syslog'], 'level': level}
LOGGING['loggers']['django'] = {'handlers': ['syslog'], 'level': level}
# 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/>.
import os
from .base import * # flake8:noqa
# fix https://github.com/django-nose/django-nose/issues/197
# AttributeError: 'module' object has no attribute 'commit_unless_managed'
# TypeError: _skip_create_test_db() got an unexpected keyword argument 'keepdb'
from django.db import transaction
from django_nose import runner
def _skip_create_test_db(self, verbosity=1, autoclobber=False, serialize=True,
keepdb=True):
return old_skip_create_test_db(
self, verbosity=verbosity, autoclobber=autoclobber,
serialize=serialize)
setattr(transaction, "commit_unless_managed", lambda using: using)
old_skip_create_test_db = runner._skip_create_test_db
setattr(runner, "_skip_create_test_db", _skip_create_test_db)
os.environ['REUSE_DB'] = "1"
os.environ['DJANGO_TEST_DB_NAME'] = "circle"
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' +
get_env_variable('DJANG_DB_TYPE', 'postgresql_psycopg2'),
'NAME': get_env_variable('DJANGO_DB_NAME', 'circle'),
'TEST_NAME': get_env_variable('DJANGO_TEST_DB_NAME', 'circle'),
'USER': get_env_variable('DJANGO_DB_USER', 'circle'),
'PASSWORD': get_env_variable('DJANGO_DB_PASSWORD'),
'HOST': get_env_variable('DJANGO_DB_HOST', ''),
'PORT': get_env_variable('DJANGO_DB_PORT', ''),
}
}
SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += (
'acl.tests',
'django_nose',
'django_jenkins',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
path_to_selenium_test = os.path.join(SITE_ROOT, "dashboard/tests/selenium")
NOSE_ARGS = ['--stop', '--with-doctest', '--with-selenium-driver',
'--selenium-driver=firefox', '-w%s' % path_to_selenium_test]
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'],
'level': 'CRITICAL'}
level = environ.get('LOGLEVEL', 'CRITICAL')
LOGGING['handlers']['console'] = {'level': level,
'class': 'logging.StreamHandler',
'formatter': 'simple'}
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
# 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 .base import * # noqa
# flake8: noqa
########## IN-MEMORY TEST DATABASE
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
},
}
SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += (
'acl.tests',
'django_nose',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'--with-doctest',
'--exclude-dir=dashboard/tests/selenium',
'--exclude=circle'
]
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'],
'level': 'CRITICAL'}
level = environ.get('LOGLEVEL', 'CRITICAL')
LOGGING['handlers']['console'] = {'level': level,
'class': 'logging.StreamHandler',
'formatter': 'simple'}
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
# don't print SQL queries
LOGGING['handlers']['null'] = {'level': "DEBUG",
'class': "logging.NullHandler"}
LOGGING['loggers']['django.db.backends'] = {
'handlers': ['null'],
'propagate': False,
'level': 'DEBUG',
}
# Forbid store usage
STORE_URL = ""
# buildbot doesn't love pipeline
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
SAML_MAIN_ATTRIBUTE_MAX_LENGTH=0 # doctest on SAML2 backend runs either way
# 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 django.conf.urls import include, url
from django.views.generic import TemplateView
from django.conf import settings
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.contrib.auth.views import (
password_reset_confirm, password_reset
)
from circle.settings.base import get_env_variable
from dashboard.views import (
CircleLoginView, HelpView, ResizeHelpView, TwoFactorLoginView
)
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item
admin.autodiscover()
urlpatterns = [
url(r'^$', lambda x: redirect(reverse("dashboard.index"))),
url(r'^network/', include('network.urls')),
url(r'^blacklist-add/', add_blacklist_item),
url(r'^dashboard/', include('dashboard.urls')),
url(r'^request/', include('request.urls')),
# django/contrib/auth/urls.py (care when new version)
url((r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/'
r'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$'),
password_reset_confirm,
{'set_password_form': CircleSetPasswordForm},
name='accounts.password_reset_confirm'
),
url(r'^accounts/password/reset/$', password_reset,
{'password_reset_form': CirclePasswordResetForm},
name="accounts.password-reset",
),
url(r'^accounts/login/?$', CircleLoginView.as_view(),
name="accounts.login"),
url(r'^accounts/', include('django.contrib.auth.urls')),
url(r'^two-factor-login/$', TwoFactorLoginView.as_view(),
name="two-factor-login"),
url(r'^info/help/$', HelpView.as_view(template_name="info/help.html"),
name="info.help"),
url(r'^info/policy/$',
TemplateView.as_view(template_name="info/policy.html"),
name="info.policy"),
url(r'^info/legal/$',
TemplateView.as_view(template_name="info/legal.html"),
name="info.legal"),
url(r'^info/support/$',
TemplateView.as_view(template_name="info/support.html"),
name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"),
]
if 'rosetta' in settings.INSTALLED_APPS:
urlpatterns += [
url(r'^rosetta/', include('rosetta.urls')),
]
if settings.ADMIN_ENABLED:
urlpatterns += [
url(r'^admin/', include(admin.site.urls)),
]
if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
urlpatterns += [
url(r'^saml2/', include('djangosaml2.urls')),
]
handler500 = 'common.views.handler500'
handler403 = 'common.views.handler403'
# 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/>.
"""
WSGI config for circle project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
from os.path import abspath, dirname
from sys import path
SITE_ROOT = dirname(dirname(abspath(__file__)))
path.append(SITE_ROOT)
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "jajaja.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "circle.settings.production")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application # noqa
_application = get_wsgi_application()
def application(environ, start_response):
# copy DJANGO_* wsgi-env vars to process-env
for i in environ.keys():
if i.startswith('DJANGO_'):
os.environ[i] = environ[i]
return _application(environ, start_response)
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)
# -*- coding: utf-8 -*-
# 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/>.
import re
import logging
import sha
from django.conf import settings
from djangosaml2.backends import Saml2Backend as Saml2BackendBase
logger = logging.getLogger(__name__)
class Saml2Backend(Saml2BackendBase):
u"""
>>> b = Saml2Backend()
>>> b.clean_user_main_attribute(u'Ékezetes Enikő')
u'+00c9kezetes+0020Enik+0151'
>>> b.clean_user_main_attribute(u'Cé++')
u'C+00e9+002b+002b'
>>> b.clean_user_main_attribute(u'test')
u'test'
>>> b.clean_user_main_attribute(u'3+4')
u'3+002b4'
"""
def clean_user_main_attribute(self, main_attribute):
def replace(match):
match = match.group()
return '+%04x' % ord(match)
if isinstance(main_attribute, str):
main_attribute = main_attribute.decode('UTF-8')
assert isinstance(main_attribute, unicode)
attr = re.sub(r'[^\w.@-]', replace, main_attribute)
max_length = settings.SAML_MAIN_ATTRIBUTE_MAX_LENGTH
if max_length > 0 and len(attr) > max_length:
logger.info("Main attribute '%s' is too long." % attr)
hashed = sha.new(attr).hexdigest()
attr = hashed[:max_length]
logger.info("New main attribute: %s" % attr)
return attr
def _set_attribute(self, obj, attr, value):
if attr == 'username':
value = self.clean_user_main_attribute(value)
return super(Saml2Backend, self)._set_attribute(obj, attr, value)
# 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 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()
# 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/>.
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 mock import patch
class MockCeleryMixin(object):
def _pre_setup(self):
self.reloadtask_patcher = patch(
'firewall.tasks.local_tasks.reloadtask.apply_async', spec=True)
self.reloadtask_patcher.start()
self.kombu_patcher = patch('kombu.connection.Connection.ensure',
side_effect=RuntimeError())
self.kombu_patcher.start()
self.check_queue_patcher = patch('vm.tasks.vm_tasks.check_queue',
return_value=True)
self.check_queue_patcher.start()
super(MockCeleryMixin, self)._pre_setup()
def _post_teardown(self):
self.reloadtask_patcher.stop()
self.kombu_patcher.stop()
super(MockCeleryMixin, self)._post_teardown()
# 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 ..models import method_cache
class TestClass(object):
id = None
called = 0
def __init__(self, id):
self.id = id
@method_cache()
def method(self, s):
# print 'Called TestClass(%d).method(%s)' % (self.id, s)
self.called += 1
return self.id + len(s)
# 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 collections import deque
from django.test import TestCase
from mock import MagicMock
from .celery_mock import MockCeleryMixin
from .models import TestClass
from ..models import HumanSortField
from ..models import activitycontextimpl
class MethodCacheTestCase(MockCeleryMixin, TestCase):
def test_cache(self):
t1 = TestClass(1)
t2 = TestClass(2)
val1a = t1.method('a')
val1b = t1.method('a')
val2a = t2.method('a')
val2b = t2.method('a')
val2b = t2.method('a')
self.assertEqual(val1a, val1b)
self.assertEqual(val2a, val2b)
self.assertNotEqual(val1a, val2a)
t1.method('b')
self.assertEqual(t1.called, 2)
self.assertEqual(t2.called, 1)
def test_invalidate(self):
t1 = TestClass(1)
val1a = t1.method('a')
val1b = t1.method('a', invalidate_cache=True)
t1.method('a')
self.assertEqual(val1a, val1b)
self.assertEqual(t1.called, 2)
class TestHumanSortField(TestCase):
def test_partition(self):
values = {(lambda s: s.isdigit(), "1234abc56"): ("1234", "abc", "56"),
(lambda s: s.isalpha(), "abc567"): ("abc", "567", ""),
(lambda s: s == "a", "aaababaa"): ("aaa", "b", "abaa"),
(lambda s: s == "a", u"aaababaa"): ("aaa", "b", "abaa"),
}
for (pred, val), result in values.iteritems():
a, b, c = HumanSortField._partition(deque(val), pred)
assert isinstance(c, deque)
c = ''.join(c)
# print "%s, %s => %s" % (val, str(pred), str((a, b, c)))
self.assertEquals((a, b, c), result)
def test_get_normalized(self):
values = {("1234abc56", 4): "1234abc0056",
("abc567", 2): "abc567",
("aaababaa", 8): "aaababaa",
("aa4ababaa", 2): "aa04ababaa",
("aa4aba24baa4", 4): "aa0004aba0024baa0004",
}
for (val, length), result in values.iteritems():
obj = MagicMock(spec=HumanSortField, maximum_number_length=length,
_partition=HumanSortField._partition)
test_result = HumanSortField.get_normalized_value(obj, val)
self.assertEquals(test_result, result)
class ActivityContextTestCase(TestCase):
class MyException(Exception):
pass
def test_unicode(self):
act = MagicMock()
gen = activitycontextimpl(act)
gen.next()
with self.assertRaises(self.MyException):
gen.throw(self.MyException(u'test\xe1'))
def test_str(self):
act = MagicMock()
gen = activitycontextimpl(act)
gen.next()
with self.assertRaises(self.MyException):
gen.throw(self.MyException('test\xbe'))
# 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 mock import MagicMock, patch
from django.test import TestCase
from ..operations import Operation
class OperationTestCase(TestCase):
def test_activity_created_before_async_job(self):
class AbortEx(Exception):
pass
op = TestOp(MagicMock())
op.async_operation = MagicMock(
apply_async=MagicMock(side_effect=AbortEx))
with patch.object(Operation, 'check_precond'):
with patch.object(Operation, 'create_activity') as create_act:
try:
op.async(system=True)
except AbortEx:
self.assertTrue(create_act.called)
def test_check_precond_called_before_create_activity(self):
class AbortEx(Exception):
pass
op = TestOp(MagicMock())
with patch.object(Operation, 'create_activity', side_effect=AbortEx):
with patch.object(Operation, 'check_precond') as chk_pre:
try:
op.call(system=True)
except AbortEx:
self.assertTrue(chk_pre.called)
def test_auth_check_on_non_system_call(self):
op = TestOp(MagicMock())
user = MagicMock()
with patch.object(Operation, 'check_auth') as check_auth:
with patch.object(Operation, 'check_precond'), \
patch.object(Operation, 'create_activity'), \
patch.object(Operation, '_exec_op'):
op.call(user=user)
check_auth.assert_called_with(user)
def test_no_auth_check_on_system_call(self):
op = TestOp(MagicMock())
with patch.object(Operation, 'check_auth', side_effect=AssertionError):
with patch.object(Operation, 'check_precond'), \
patch.object(Operation, 'create_activity'), \
patch.object(Operation, '_exec_op'):
op.call(system=True)
def test_no_exception_for_more_arguments_when_operation_takes_kwargs(self):
op = TestOp(MagicMock())
with patch.object(TestOp, 'create_activity'), \
patch.object(TestOp, '_exec_op'):
op.call(system=True, foo=42)
def test_exception_for_unexpected_arguments(self):
op = TestOp(MagicMock())
with patch.object(TestOp, 'create_activity'), \
patch.object(TestOp, '_exec_op'):
self.assertRaises(TypeError, op.call, system=True, bar=42)
def test_exception_for_missing_arguments(self):
op = TestOp(MagicMock())
with patch.object(TestOp, 'create_activity'):
self.assertRaises(TypeError, op.call, system=True)
class TestOp(Operation):
id = 'test'
def _operation(self, foo):
pass
# 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 sys import exc_info
import logging
from django.shortcuts import render_to_response
from django.template import RequestContext
from .models import HumanReadableException
logger = logging.getLogger(__name__)
def get_context(request, exception):
ctx = {}
if issubclass(exception.__class__, HumanReadableException):
try:
if request.user.is_superuser:
ctx['error'] = exception.get_admin_text()
else:
ctx['error'] = exception.get_user_text()
except:
pass
return ctx
def handler500(request):
cls, exception, traceback = exc_info()
logger.exception("unhandled exception")
ctx = get_context(request, exception)
try:
resp = render_to_response("500.html", ctx,
RequestContext(request).flatten())
except:
resp = render_to_response("500.html", ctx)
resp.status_code = 500
return resp
def handler403(request):
cls, exception, traceback = exc_info()
ctx = get_context(request, exception)
resp = render_to_response("403.html", ctx)
resp.status_code = 403
return resp
# 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/>.
# -*- coding: utf-8 -*-
from django import contrib
from django.contrib.auth.admin import UserAdmin, GroupAdmin
from django.contrib.auth.models import User, Group
from dashboard.models import Profile, GroupProfile, ConnectCommand, Message
class ProfileInline(contrib.admin.TabularInline):
model = Profile
class CommandInline(contrib.admin.TabularInline):
model = ConnectCommand
class GroupProfileInline(contrib.admin.TabularInline):
model = GroupProfile
UserAdmin.inlines = (ProfileInline, CommandInline, )
GroupAdmin.inlines = (GroupProfileInline, )
contrib.admin.site.unregister(User)
contrib.admin.site.register(User, UserAdmin)
contrib.admin.site.unregister(Group)
contrib.admin.site.register(Group, GroupAdmin)
contrib.admin.site.register(Message)
# 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 pipeline.compilers.less import LessCompiler
class DummyLessCompiler(LessCompiler):
def compile_file(self, *args, **kwargs):
pass
# 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 django.conf import settings
from .models import Message
def notifications(request):
count = (request.user.notification_set.filter(status="new").count()
if request.user.is_authenticated() else None)
return {
'NEW_NOTIFICATIONS_COUNT': count
}
def extract_settings(request):
return {
'COMPANY_NAME': getattr(settings, "COMPANY_NAME", None),
'ADMIN_ENABLED': getattr(settings, "ADMIN_ENABLED", False),
}
def broadcast_messages(request):
return {'broadcast_messages': Message.timeframed.filter(enabled=True)}
[
{
"pk": 1,
"model": "firewall.vlan",
"fields": {
"comment": "",
"ipv6_template": "2001:7:2:4031:%(b)d:%(c)d:%(d)d:0",
"domain": 1,
"dhcp_pool": "",
"managed": true,
"name": "pub",
"vid": 3066,
"created_at": "2014-02-19T17:00:17.358Z",
"modified_at": "2014-02-19T17:00:17.358Z",
"owner": null,
"snat_ip": null,
"snat_to": [],
"network6": null,
"network4": "10.7.0.93/16",
"reverse_domain": "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa",
"network_type": "public",
"description": ""
}
},
{
"pk": 1,
"model": "firewall.host",
"fields": {
"comment": "",
"vlan": 1,
"reverse": "",
"created_at": "2014-02-19T17:03:45.365Z",
"hostname": "devenv",
"modified_at": "2014-02-24T15:55:01.412Z",
"location": "",
"mac": "11:22:33:44:55:66",
"shared_ip": false,
"ipv4": "10.7.0.96",
"groups": [],
"ipv6": null,
"owner": 1,
"description": ""
}
},
{
"pk": 1,
"model": "firewall.domain",
"fields": {
"description": "",
"created_at": "2014-02-19T17:00:08.819Z",
"modified_at": "2014-02-19T17:00:08.819Z",
"ttl": 600,
"owner": 1,
"name": "test.ik.bme.hu"
}
},
{
"pk": 1,
"model": "vm.node",
"fields": {
"name": "devenv",
"created": "2014-02-19T17:03:45.322Z",
"overcommit": 1.0,
"enabled": true,
"modified": "2014-02-19T21:11:34.671Z",
"priority": 1,
"traits": [],
"host": 1,
"ram_weight": 1.0,
"cpu_weight": 1.0,
"time_stamp": "2017-12-13T21:08:08.819Z"
}
}
]
[
{
"pk": 1,
"model": "sites.site",
"fields": {
"domain": "example.com",
"name": "example.com"
}
},
{
"pk": 1,
"model": "vm.lease",
"fields": {
"suspend_interval_seconds": 18000,
"name": "lab",
"delete_interval_seconds": 180000
}
},
{
"pk": 1,
"model": "storage.datastore",
"fields": {
"path": "/disks",
"hostname": "wut",
"name": "diszkek"
}
},
{
"pk": 1,
"model": "storage.disk",
"fields": {
"name": "diszk",
"created": "2013-09-16T09:05:56.926Z",
"modified": "2013-09-19T09:10:25.117Z",
"filename": "disc.img",
"destroyed": null,
"base": null,
"datastore": 1,
"dev_num": "a",
"type": "qcow2-norm",
"size": 8589934592,
"is_ready": true
}
},
{
"pk": 1,
"model": "auth.group",
"fields": {
"name": "csopi",
"permissions": []
}
},
{
"pk": 2,
"model": "auth.group",
"fields": {
"name": "akkorszia",
"permissions": []
}
},
{
"pk": 3,
"model": "auth.group",
"fields": {
"name": "akkountsoherek",
"permissions": []
}
},
{
"pk": 4,
"model": "auth.group",
"fields": {
"name": "helog",
"permissions": []
}
},
{
"pk": -1,
"model": "auth.user",
"fields": {
"username": "AnonymousUser",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2013-09-30T12:37:38.484Z",
"groups": [],
"user_permissions": [],
"password": "",
"email": "",
"date_joined": "2013-09-30T12:37:38.484Z"
}
},
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "test",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"last_login": "2013-11-09T03:40:23.157Z",
"groups": [],
"user_permissions": [
115
],
"password": "md5$qLN4mQMOrsUJ$f07129fd1a289a0afb4e09f7a6816a4f",
"email": "test@example.org",
"date_joined": "2013-09-04T15:29:49.914Z"
}
},
{
"pk": 3,
"model": "auth.user",
"fields": {
"username": "proba",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2013-10-14T06:59:48Z",
"groups": [
1
],
"user_permissions": [],
"password": "",
"email": "",
"date_joined": "2013-10-14T06:59:48Z"
}
},
{
"pk": 4,
"model": "auth.user",
"fields": {
"username": "helo",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2013-11-09T01:11:41.534Z",
"groups": [
4
],
"user_permissions": [],
"password": "",
"email": "",
"date_joined": "2013-11-09T01:11:41.534Z"
}
},
{
"pk": 1,
"model": "vm.instance",
"fields": {
"destroyed_at": null,
"disks": [
1
],
"boot_menu": false,
"owner": 1,
"time_of_delete": null,
"max_ram_size": 200,
"pw": "ads",
"time_of_suspend": null,
"ram_size": 200,
"priority": 10,
"template": null,
"access_method": "nx",
"lease": 1,
"node": null,
"description": "",
"arch": "x86_64",
"name": "vanneve",
"created": "2013-09-16T09:05:59.991Z",
"raw_data": "",
"vnc_port": 1234,
"num_cores": 2,
"status": "RUNNING",
"system": "system pls",
"modified": "2013-10-14T07:27:38.192Z"
}
},
{
"pk": 12,
"model": "vm.instance",
"fields": {
"destroyed_at": null,
"disks": [],
"boot_menu": false,
"owner": 1,
"time_of_delete": null,
"max_ram_size": 200,
"pw": "ads",
"time_of_suspend": null,
"ram_size": 200,
"priority": 10,
"template": null,
"access_method": "nx",
"lease": 1,
"node": null,
"description": "",
"arch": "x86_64",
"name": "vanneve",
"created": "2013-09-16T09:05:59.991Z",
"raw_data": "",
"vnc_port": 1235,
"num_cores": 2,
"modified": "2013-10-14T07:27:38.192Z"
}
},
{
"pk": 1,
"model": "firewall.domain",
"fields": {
"description": "",
"created_at": "2013-09-12T14:53:04.035Z",
"modified_at": "2013-10-02T14:01:00.318Z",
"ttl": 600,
"owner": 1,
"name": "test domain"
}
},
{
"pk": 2,
"model": "firewall.vlan",
"fields": {
"comment": "",
"domain": 1,
"dhcp_pool": "",
"name": "test vlan",
"vid": 1,
"created_at": "2013-09-12T14:54:24.044Z",
"modified_at": "2013-10-21T11:19:44.544Z",
"owner": null,
"snat_ip": null,
"snat_to": [],
"network6": null,
"network4": "192.168.1.0/24",
"reverse_domain": "%(d)d.%(c)d.%(b2)d.%(a)d.in-addr.arpa",
"network_type": "public",
"description": ""
}
},
{
"pk": 1,
"model": "firewall.vlan",
"fields": {
"comment": "",
"domain": 1,
"dhcp_pool": "",
"managed": true,
"name": "pub",
"vid": 1066,
"created_at": "2014-01-23T16:51:58.125Z",
"modified_at": "2014-01-23T16:51:58.125Z",
"owner": null,
"snat_ip": null,
"snat_to": [],
"network6": null,
"network4": "152.66.254.61/30",
"reverse_domain": "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa",
"network_type": "public",
"description": ""
}
},
{
"pk": 1,
"model": "vm.lease",
"fields": {
"suspend_interval_seconds": 36000,
"name": "alap",
"delete_interval_seconds": 360000
}
},
{
"pk": 2,
"model": "vm.interfacetemplate",
"fields": {
"vlan": 1,
"managed": true,
"template": 1
}
},
{
"pk": 1,
"model": "vm.instancetemplate",
"fields": {
"req_traits": [],
"disks": [
1
],
"name": "ubuntu",
"parent": null,
"created": "2014-01-23T18:03:52.319Z",
"num_cores": 2,
"description": "",
"boot_menu": false,
"ram_size": 1024,
"modified": "2014-01-24T00:58:19.654Z",
"system": "bubuntu",
"priority": 10,
"access_method": "ssh",
"raw_data": "",
"arch": "x86_64",
"max_ram_size": 1024,
"lease": 1,
"owner": 1
}
}
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import dashboard.validators
import dashboard.models
import model_utils.fields
import sizefield.models
import jsonfield.fields
import django.utils.timezone
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('vm', '__first__'),
]
operations = [
migrations.CreateModel(
name='ConnectCommand',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('access_method', models.CharField(help_text='Type of the remote access method.', max_length=10, verbose_name='access method', choices=[('nx', 'NX'), ('rdp', 'RDP'), ('ssh', 'SSH')])),
('name', models.CharField(help_text='Name of your custom command.', max_length=b'128', verbose_name='name')),
('template', models.CharField(validators=[dashboard.validators.connect_command_template_validator], max_length=256, blank=True, help_text='Template for connection command string. Available parameters are: username, password, host, port.', null=True, verbose_name='command template')),
('user', models.ForeignKey(related_name='command_set', to=settings.AUTH_USER_MODEL)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Favourite',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('instance', models.ForeignKey(to='vm.Instance')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='FutureMember',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('org_id', models.CharField(help_text='Unique identifier of the person, e.g. a student number.', max_length=64)),
('group', models.ForeignKey(to='auth.Group')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='GroupProfile',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('org_id', models.CharField(help_text='Unique identifier of the group at the organization.', max_length=64, unique=True, null=True, blank=True)),
('description', models.TextField(blank=True)),
('group', models.OneToOneField(to='auth.Group')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Notification',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('status', model_utils.fields.StatusField(default=b'new', max_length=100, no_check_for_status=True, choices=[(b'new', 'new'), (b'delivered', 'delivered'), (b'read', 'read')])),
('subject_data', jsonfield.fields.JSONField(null=True)),
('message_data', jsonfield.fields.JSONField(null=True)),
('valid_until', models.DateTimeField(default=None, null=True)),
('to', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created'],
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('preferred_language', models.CharField(default=b'en', max_length=32, verbose_name='preferred language', choices=[(b'en', 'English'), (b'hu', 'Hungarian')])),
('org_id', models.CharField(help_text='Unique identifier of the person, e.g. a student number.', max_length=64, unique=True, null=True, blank=True)),
('instance_limit', models.IntegerField(default=5)),
('use_gravatar', models.BooleanField(default=True, help_text='Whether to use email address as Gravatar profile image', verbose_name='Use Gravatar')),
('email_notifications', models.BooleanField(default=True, help_text='Whether user wants to get digested email notifications.', verbose_name='Email notifications')),
('smb_password', models.CharField(default=dashboard.models.pwgen, help_text='Generated password for accessing store from virtual machines.', max_length=20, verbose_name='Samba password')),
('disk_quota', sizefield.models.FileSizeField(default=2147483648, help_text='Disk quota in mebibytes.', verbose_name='disk quota')),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
],
options={
'permissions': (('use_autocomplete', 'Can use autocomplete.'),),
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='futuremember',
unique_together=set([('org_id', 'group')]),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='connectcommand',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='futuremember',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='groupprofile',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='profile',
options={'ordering': ('id',), 'permissions': (('use_autocomplete', 'Can use autocomplete.'),)},
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0002_auto_20150318_1317'),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('start', models.DateTimeField(null=True, verbose_name='start', blank=True)),
('end', models.DateTimeField(null=True, verbose_name='end', blank=True)),
('message', models.CharField(max_length=500, verbose_name='message')),
('effect', models.CharField(default=b'info', max_length=10, verbose_name='effect', choices=[(b'success', 'success'), (b'info', 'info'), (b'warning', 'warning'), (b'danger', 'danger')])),
('enabled', models.BooleanField(default=False, verbose_name='enabled')),
],
options={
'ordering': ['id'],
'verbose_name': 'message',
'verbose_name_plural': 'messages',
},
bases=(models.Model,),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0003_message'),
]
operations = [
migrations.AddField(
model_name='profile',
name='desktop_notifications',
field=models.BooleanField(default=False, help_text='Whether user wants to get desktop notification when an activity has finished and the window is not in focus.', verbose_name='Desktop notifications'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0004_profile_desktop_notifications'),
]
operations = [
migrations.AddField(
model_name='profile',
name='two_factor_secret',
field=models.CharField(max_length=32, null=True, verbose_name='two factor secret key', blank=True),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-07-07 19:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0005_profile_two_factor_secret'),
]
operations = [
migrations.AlterField(
model_name='connectcommand',
name='name',
field=models.CharField(help_text='Name of your custom command.', max_length=128, verbose_name='name'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.25 on 2020-04-24 20:00
from __future__ import unicode_literals
from django.db import migrations
import sizefield.models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0006_auto_20170707_1909'),
]
operations = [
migrations.AddField(
model_name='groupprofile',
name='disk_quota',
field=sizefield.models.FileSizeField(default=2147483648, help_text='Disk quota in mebibytes.', verbose_name='disk quota'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.25 on 2020-11-06 13:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0007_groupprofile_disk_quota'),
]
operations = [
migrations.AddField(
model_name='profile',
name='template_instance_limit',
field=models.IntegerField(default=1),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.25 on 2021-02-01 17:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0008_profile_template_instance_limit'),
]
operations = [
migrations.AddField(
model_name='groupprofile',
name='instance_limit',
field=models.IntegerField(default=5),
),
migrations.AddField(
model_name='groupprofile',
name='template_instance_limit',
field=models.IntegerField(default=1),
),
]
// 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";
$(function() {
var in_progress = false;
var activity_hash = 5;
var show_all = false;
var reload_vm_detail = false;
/* do we need to check for new activities */
if(decideActivityRefresh()) {
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
}
$('a[href="#activity"]').click(function(){
$('a[href="#activity"] i').addClass('fa-spin');
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
});
$("#activity-refresh").on("click", "#show-all-activities", function() {
$(this).find("i").addClass("fa-spinner fa-spin");
show_all = !show_all;
$('a[href="#activity"]').trigger("click");
return false;
});
/* operations */
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin');
$.ajax({
type: 'GET',
url: $(this).attr('href'),
success: function(data) {
icon.removeClass("fa-spinner fa-spin");
$('body').append(data);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
$('#vm-migrate-node-list li input:checked').closest('li').addClass('panel-primary');
}
});
e.preventDefault();
});
/* if the operation fails show the modal again */
$("body").on("click", "#confirmation-modal #op-form-send", function() {
var url = $(this).closest("form").prop("action");
$.ajax({
url: url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: $(this).closest('form').serialize(),
success: function(data, textStatus, xhr) {
/* hide the modal we just submitted */
$('#confirmation-modal').modal("hide");
/* if it was successful trigger a click event on activity, this will
* - go to that tab
* - starts refreshing the activity
*/
if(data.success) {
$('a[href="#activity"]').trigger("click");
if(data.with_reload) {
// when the activity check stops the page will reload
reload_vm_detail = true;
}
/* if there are messages display them */
if(data.messages && data.messages.length > 0) {
addMessage(data.messages.join("<br />"), data.success ? "success" : "danger");
}
}
else {
/* if the post was not successful wait for the modal to disappear
* then append the new modal
*/
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('body').append(data);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
});
}
},
error: function(xhr, textStatus, error) {
$('#confirmation-modal').modal("hide");
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
return false;
});
function decideActivityRefresh() {
var check = false;
/* if something is still spinning */
if($('.timeline .activity i').hasClass('fa-spin'))
check = true;
return check;
}
function checkNewActivity(runs) {
$.ajax({
type: 'GET',
url: $('a[href="#activity"]').attr('data-activity-url'),
data: {'show_all': show_all},
success: function(data) {
var new_activity_hash = (data.activities + "").hashCode();
if(new_activity_hash != activity_hash) {
$("#activity-refresh").html(data.activities);
}
activity_hash = new_activity_hash;
$("#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(!icon.hasClass("fa-spin"))
icon.prop("class", "fa fa-spinner fa-spin");
} else {
icon.prop("class", "fa " + data.icon);
}
var vm_state = $("#vm-details-state");
if (vm_state.length) {
vm_state.data("status", data['status']); // jshint ignore:line
$("#vm-details-state span").html(data.human_readable_status.toUpperCase());
}
if(data['status'] == "RUNNING") { // jshint ignore:line
if(data.connect_uri) {
$("#dashboard-vm-details-connect-button").removeClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled");
$("#getScreenshotButton").prop("disabled", false);
} else {
if(data.connect_uri) {
$("#dashboard-vm-details-connect-button").addClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
$("#getScreenshotButton").prop("disabled", true);
}
if(data.status == "STOPPED" || data.status == "PENDING") {
$(".change-resources-button").prop("disabled", false);
$(".change-resources-help").hide();
} else {
$(".change-resources-button").prop("disabled", true);
$(".change-resources-help").show();
}
if(runs > 0 && decideActivityRefresh()) {
setTimeout(
function() {checkNewActivity(runs + 1);},
1000 + Math.exp(runs * 0.05)
);
} else {
in_progress = false;
if(document.hasFocus() === false && userWantNotifications()){
sendNotification(generateMessageFromLastActivity());
}
if(reload_vm_detail) location.reload();
if(runs > 1) addConnectText();
}
$('a[href="#activity"] i').removeClass('fa-spin');
},
error: function() {
in_progress = false;
}
});
}
});
// Notification init
$(function(){
if(userWantNotifications())
Notification.requestPermission();
});
function generateMessageFromLastActivity(){
var ac = $("div.activity").first();
var error = ac.children(".timeline-icon-failed").length;
var sign = (error === 1) ? "❌ " : "✓ ";
var msg = ac.children("strong").text().replace(/\s+/g, " ");
return sign + msg;
}
function sendNotification(message) {
var options = { icon: "/static/dashboard/img/favicon.png"};
if (Notification.permission === "granted") {
var notification = new Notification(message, options);
}
else if (Notification.permission !== "denied") {
Notification.requestPermission(function (permission) {
if (permission === "granted") {
var notification = new Notification(message, options);
}
});
}
}
function userWantNotifications(){
var dn = $("#user-options").data("desktop_notifications");
return dn === "True";
}
function addConnectText() {
var activities = $(".timeline .activity");
if(activities.length > 1) {
if(activities.eq(0).data("activity-code") == "vm.Instance.wake_up" ||
activities.eq(0).data("activity-code") == "vm.Instance.agent") {
$("#vm-detail-successful-boot").slideDown(500);
}
}
}
String.prototype.hashCode = function() {
var hash = 0, i, chr, len;
if (this.length === 0) return hash;
for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
$(function() {
var data = JSON.parse($("#chart-data").data("data"));
var labels = [];
for(var i=0; i<data.labels.length; i++) {
labels.push(data.labels[i] + " (" + data.readable_data[i] + ")");
}
var pieChart = new Chart(document.getElementById("datastore-chart"), {
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data.data,
backgroundColor: [
"#57b257",
"#538ccc",
"#f0df24",
"#ff9a38",
"#7f7f7f",
]
}]
},
options: {
legend: {
display: false,
},
tooltips: {
callbacks: {
label: function(item, chartData) {
return data.labels[item.index] + ": " + data.readable_data[item.index];
}
}
},
}
});
$("#datastore-chart-legend").html(pieChart.generateLegend());
});
$(function() {
/* rename */
$("#group-details-h1-name, .group-details-rename-button").click(function() {
$("#group-details-h1-name span").hide();
$("#group-details-rename-form").show().css('display', 'inline-block');
$("#group-details-rename-name").select();
});
/* rename ajax */
$('#group-details-rename-submit').click(function() {
if(!$("#group-details-rename-name")[0].checkValidity()) {
return true;
}
var name = $('#group-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#group-details-h1-name span").text(data.new_name).show();
$('#group-details-rename-form').hide();
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming.", "danger");
}
});
return false;
});
});
$(function() {
/* rename */
$("#group-list-rename-button, .group-details-rename-button").click(function() {
$(".group-list-column-name", $(this).closest("tr")).hide();
$("#group-list-rename", $(this).closest("tr")).css('display', 'inline');
$("#group-list-rename").find("input").select();
});
/* rename ajax */
$('.group-list-rename-submit').click(function() {
var row = $(this).closest("tr");
var name = $('#group-list-rename-name', row).val();
var url = row.find(".group-list-column-name a").prop("href");
$.ajax({
method: 'POST',
url: url,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$(".group-list-column-name", row).html(
$("<a/>", {
'class': "real-link",
href: "/dashboard/group/" + data.group_pk + "/",
text: data.new_name
})
).show();
$('#group-list-rename', row).hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
}
});
return false;
});
});
$(function() {
$('.crosslink').click(function(e) {
e.preventDefault();
var menu = $(this).data("menu");
$(menu).click();
window.location = this.href;
});
var hash = window.location.hash;
if(hash) {
var pane = $(hash).closest(".tab-pane").prop("class");
if (pane) {
if (pane.indexOf("overview") != -1) {
$("#overview_menu").click();
} else {
$("#faq_menu").click();
}
$("html, body").animate({scrollTop: $(hash).offset().top}, 500);
window.location.hash = hash;
}
}
});
// Stupid jQuery table plugin.
/*
* Source: https://github.com/joequery/Stupid-Table-Plugin
* The Stupid jQuery Plugin is licensed under the MIT license.
* Copyright (c) 2012 Joseph McCullough
*/
// Call on a table
// sortFns: Sort functions for your datatypes.
(function($) {
$.fn.stupidtable = function(sortFns) {
return this.each(function() {
var $table = $(this);
sortFns = sortFns || {};
// Merge sort functions with some default sort functions.
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
// ==================================================== //
// Begin execution! //
// ==================================================== //
// Do sorting when THs are clicked
$table.on("click.stupidtable", "th", function() {
var $this = $(this);
var th_index = 0;
var dir = $.fn.stupidtable.dir;
$table.find("th").slice(0, $this.index()).each(function() {
var cols = $(this).attr("colspan") || 1;
th_index += parseInt(cols,10);
});
// Determine (and/or reverse) sorting direction, default `asc`
var sort_dir = $this.data("sort-default") || dir.ASC;
if ($this.data("sort-dir"))
sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
// Choose appropriate sorting function.
var type = $this.data("sort") || null;
// Prevent sorting if no type defined
if (type === null) {
return;
}
// Trigger `beforetablesort` event that calling scripts can hook into;
// pass parameters for sorted column index and sorting direction
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
// Run sorting asynchronously on a timout to force browser redraw after
// `beforetablesort` callback. Also avoids locking up the browser too much.
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortMethod = sortFns[type];
var trs = $table.children("tbody").children("tr");
// Extract the data for the column that needs to be sorted and pair it up
// with the TR itself into a tuple
trs.each(function(index,tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
column.push([order_by, tr]);
});
// Sort by the data-order-by value
column.sort(function(a, b) { return sortMethod(a[0], b[0]); });
if (sort_dir != dir.ASC)
column.reverse();
// Replace the content of tbody with the sorted rows. Strangely (and
// conveniently!) enough, .append accomplishes this for us.
trs = $.map(column, function(kv) { return kv[1]; });
$table.children("tbody").append(trs);
// Reset siblings
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
// Trigger `aftertablesort` event. Similar to `beforetablesort`
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
}, 10);
});
});
};
// Enum containing sorting directions
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
$.fn.stupidtable.default_sort_fns = {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
if (a < b) return -1;
if (a > b) return +1;
return 0;
},
"string-ins": function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
};
})(jQuery);
$(function() {
/* rename */
$("#node-details-h1-name, .node-details-rename-button").click(function() {
$("#node-details-h1-name").hide();
$("#node-details-rename").css('display', 'inline');
$("#node-details-rename-name").focus();
});
/* rename ajax */
$('#node-details-rename-submit').click(function() {
var name = $('#node-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#node-details-h1-name").text(data.new_name).show();
$('#node-details-rename').hide();
// addMessage(data.message, "success");
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
$(".node-details-help-button").click(function() {
$(".node-details-help").stop().slideToggle();
});
// remove trait
$('.node-details-remove-trait').click(function() {
var to_remove = $(this).data("trait-pk");
var clicked = $(this);
$.ajax({
type: 'POST',
url: location.href,
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re.message.toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re.message, 'danger');
}
});
return false;
});
});
$(function() {
$(document).ready( function() {
// find disabled nodes, set danger (red) on the rows
$('.node-disabled').closest("tr").addClass('danger');
});
$('#reschedule-now').click(function() {
$.get($(this).attr('href'), function(data){
highlight = data.result === 'ok' ? 'success' : 'danger';
addMessage(data.message, highlight);
});
return false;
});
});
$(function() {
// change user avatar
$("#dashboard-profile-use-gravatar").click(function() {
var checked = $(this).prop("checked");
var user = $(this).data("user");
$.ajax({
type: 'POST',
url:"/dashboard/profile/" + user + "/use_gravatar/",
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re) {
if(re.new_avatar_url) {
$("#dashboard-profile-avatar").prop("src", re.new_avatar_url);
}
},
error: function(xhr, textStatus, error) {
if(xhr.status == 403) {
addMessage(gettext("You have no permission to change this profile."), "danger");
} else {
addMessage(gettext("Unknown error."), "danger");
}
}
});
});
});
$(function() {
$("#store-list-container").on("click", ".store-list-item", function() {
if($(this).data("item-type") == "D") {
$("#store-list-up-icon").removeClass("fa-reply").addClass("fa-refresh fa-spin");
var url = $(this).prop("href");
$.get(url, function(result) {
$("#store-list-container").html(result);
noJS();
$("[title]").tooltip();
history.pushState({}, "", url);
});
} else {
$(this).next(".store-list-file-infos").stop().slideToggle();
}
return false;
});
/* how upload works
* - user clicks on a "fake" browse button, this triggers a click event on the file upload
* - if the file input changes it adds the name of the file to form (or number of files if multiple is enabled)
* - and finally when we click on the upload button (this event handler) it firsts ask the store api where to upload
* then changes the form's action attr before sending the form itself
*/
$("#store-list-container").on("click", '#store-upload-form button[type="submit"]', 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").submit();
});
return false;
});
/* "fake" browse button */
$("#store-list-container").on("click", "#store-upload-browse", function() {
$('#store-upload-form input[type="file"]').click();
});
$("#store-list-container").on("change", "#store-upload-file", function() {
var input = $(this);
var numFiles = input.get(0).files ? input.get(0).files.length : 1;
var label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label]);
});
$("#store-list-container").on("fileselect", "#store-upload-file", function(event, numFiles, label) {
var input = $("#store-upload-filename");
var log = numFiles > 1 ? numFiles + ' files selected' : label;
if(input.length) {
input.val(log);
}
if(log) {
$('#store-upload-form button[type="submit"]').prop("disabled", false);
} else {
$('#store-upload-form button[type="submit"]').prop("disabled", true);
}
});
});
$(function() {
/* template table sort */
var ttable = $(".template-list-table").stupidtable();
ttable.on("beforetablesort", function(event, data) {
// pass
});
ttable.on("aftertablesort", function(event, data) {
$(".template-list-table thead th i").remove();
var icon_html = '<i class="fa fa-sort-' + (data.direction == "desc" ? "desc" : "asc") + ' pull-right" style="position: absolute;"></i>';
$(".template-list-table thead th").eq(data.column).append(icon_html);
});
// only if js is enabled
$(".template-list-table thead th").css("cursor", "pointer");
$(".template-list-table th a").on("click", function(event) {
if(!$(this).closest("th").data("sort")) return true;
event.preventDefault();
});
});
$(function() {
"use strict";
// 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"));
rfb.sendKey(0xffe3); // press and release ctrl to kill screensaver
if (typeof(msg) !== 'undefined') {
$('#noVNC_status').html(msg);
}
}
$('a[data-toggle$="pill"][href!="#console"]').click(function() {
if (rfb) {
rfb.disconnect();
rfb = 0;
}
$("#vm-info-pane").fadeIn();
$("#vm-detail-pane").removeClass("col-md-12").addClass("col-md-8");
});
$('#sendCtrlAltButton').click(function() {
var fn = Number($('#chooseCtrlAlt').val());
if (fn === 0) {
rfb.sendCtrlAltDel();
} else {
rfb.sendCtrlAltCombine(fn-1);
}
return false;});
// rfb.sendCtrlAltDel(); return false;});
// $('#sendCtrlAltCombine').change(function() {
// var fn = Number($('#sendCtrlAltCombine').val())-1;
// rfb.sendCtrlAltCombine(fn); return false;});
$('#sendPasswordButton').click(function() {
var pw = $("#vm-details-pw-input").val();
for (var i=0; i < pw.length; i++) {
rfb.sendKey(pw.charCodeAt(i));
} return false;});
$("body").on("click", 'a[href$="console"]', function() {
var host, port, password, path;
$("#vm-info-pane").hide();
$("#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;
}
password = '';
$('#_console .btn-toolbar button').attr('disabled', true);
$('#noVNC_status').html('Retreiving authorization token.');
$.get(VNC_URL, function(data) {
if (data.indexOf('vnc') !== 0) {
$('#noVNC_status').html('No authorization token received.');
}
else {
rfb = new RFB({'target': $D('noVNC_canvas'),
'encrypt': (window.location.protocol === "https:"),
'true_color': true,
'local_cursor': true,
'shared': true,
'view_only': false,
'onUpdateState': updateState});
rfb.connect(host, port, password, data);
}
}).fail(function(){
$('#noVNC_status').html("Can't connect to console.");
});
});
if (window.location.hash === "#console")
$('a[href$="console"]').trigger("click");
});
var vlans = [];
var disks = [];
$(function() {
if($(".vm-create-template-list").length) {
vmCreateLoaded();
} else {
vmCustomizeLoaded();
}
});
function vmCreateLoaded() {
$(".vm-create-template-details").hide();
$(".vm-create-template-summary").unbind("click").click(function() {
$(this).next(".vm-create-template-details").slideToggle();
});
$(".customize-vm").click(function() {
var template = $(this).data("template-pk");
$.get("/dashboard/vm/create/?template=" + template, function(data) {
var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
$("#confirmation-modal").on("shown.bs.modal", function() {
setDefaultSliderValues();
});
});
return false;
});
/* start vm button clicks */
$('.vm-create-start').click(function() {
$(this).prop("disabled", true).find("i").prop("class", "fa fa-spinner fa-spin");
template = $(this).data("template-pk");
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: {'template': template},
success: function(data, textStatus, xhr) {
if(data.redirect) {
window.location.replace(data.redirect + '#activity');
}
else {
var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
}
},
error: function(xhr, textStatus, error) {
var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
return false;
});
$('.progress-bar').each(function() {
var min = $(this).attr('aria-valuemin');
var max = $(this).attr('aria-valuemax');
var now = $(this).attr('aria-valuenow');
var siz = (now-min)*100/(max-min);
$(this).css('width', siz+'%');
});
}
function vmCustomizeLoaded() {
$("[title]").tooltip();
/* network thingies */
/* add network */
$('#vm-create-network-add-button').click(function() {
var vlan_pk = $('#vm-create-network-add-select :selected').val();
var managed = $('#vm-create-network-add-select :selected').data('managed');
var name = $('#vm-create-network-add-select :selected').text();
// remove the hex chars
name = name.substring(name.indexOf(" "), name.length);
if ($('#vm-create-network-list').children('span').length < 1) {
$('#vm-create-network-list').html('');
}
$('#vm-create-network-list').append(
vmCreateNetworkLabel(vlan_pk, name, managed)
);
/* select the network in the hidden network select */
$('#vm-create-network-add-vlan option[value="' + vlan_pk + '"]').prop('selected', true);
$('option:selected', $('#vm-create-network-add-select')).remove();
/* add dummy text if no more networks are available */
if($('#vm-create-network-add-select option').length < 1) {
$('#vm-create-network-add-button').attr('disabled', true);
$('#vm-create-network-add-select').html('<option value="-1">' + gettext("No more networks.") + '</option>');
}
return false;
});
/* 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-', '');
// if it's "blue" then it's managed, kinda not cool
var managed = $(this).parent('span').hasClass('label-primary');
$(this).parent('span').fadeOut(500, function() {
/* if ther are no more vlans disabled the add button */
if($('#vm-create-network-add-select option')[0].value == -1) {
$('#vm-create-network-add-button').attr('disabled', false);
$('#vm-create-network-add-select').html('');
}
/* remove the network label */
$(this).remove();
var vlan_name = $(this).text();
var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + vlan_pk + '">'+
(managed ? "&#xf0ac;": "&#xf0c1;") + vlan_name + '</option>';
$('#vm-create-network-add-select').append(html);
/* remove the selection from the multiple select */
$('#vm-create-network-add-vlan option[value="' + vlan_pk + '"]').prop('selected', false);
if ($('#vm-create-network-list').children('span').length < 1) {
$('#vm-create-network-list').append(gettext("Not added to any network"));
}
});
return false;
});
/* copy networks from hidden select */
$('#vm-create-network-add-vlan option').each(function() {
var managed = $(this).text().indexOf("mana") === 0;
var raw_text = $(this).text();
var pk = $(this).val();
if(managed) {
text = raw_text.replace("managed -", "&#xf0ac;");
} else {
text = raw_text.replace("unmanaged -", "&#xf0c1;");
}
var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + pk + '">' + text + '</option>';
if($('#vm-create-network-list span').length < 1) {
$("#vm-create-network-list").html("");
}
if($(this).is(":selected")) {
$("#vm-create-network-list").append(vmCreateNetworkLabel(pk, raw_text.replace("unmanaged -", "").replace("managed -", ""), managed));
} else {
$('#vm-create-network-add-select').append(html);
}
});
// if all networks are added add a dummy and disable the add button
if($("#vm-create-network-add-select option").length < 1) {
$("#vm-create-network-add-select").html('<option value="-1">' + gettext("No more networks.") + '</option>');
$('#vm-create-network-add-button').attr('disabled', true);
}
/* build up network list */
$('#vm-create-network-add-vlan option').each(function() {
vlans.push({
'name': $(this).text().replace("unmanaged -", "&#xf0c1;").replace("managed -", "&#xf0ac;"),
'pk': parseInt($(this).val()),
'managed': $(this).text().indexOf("mana") === 0,
});
});
/* ----- end of networks thingies ----- */
/* copy disks from hidden select */
$('#vm-create-disk-add-form option').each(function() {
var text = $(this).text();
var pk = $(this).val();
var html = '<option value="' + pk + '">' + text + '</option>';
if($('#vm-create-disk-list span').length < 1) {
$("#vm-create-disk-list").html("");
}
if($(this).is(":selected")) {
$("#vm-create-disk-list").append(vmCreateDiskLabel(pk, text));
} else {
$('#vm-create-disk-add-select').append(html);
}
});
/* build up disk list */
$('#vm-create-disk-add-select option').each(function() {
disks.push({
'name': $(this).text(),
'pk': parseInt($(this).val())
});
});
/* start vm button clicks */
$('#confirmation-modal #vm-create-customized-start').click(function() {
var error = false;
$(".cpu-count-input, .ram-input, #id_name, #id_amount ").each(function() {
if(!$(this)[0].checkValidity()) {
error = true;
}
});
if(error) return true;
$(this).find("i").prop("class", "fa fa-spinner fa-spin");
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: $('form').serialize(),
success: function(data, textStatus, xhr) {
if(data.redirect) {
/* it won't redirect to the same page */
if(window.location.pathname == data.redirect) {
window.location.reload();
}
window.location.href = data.redirect + '#activity';
}
else {
var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
}
},
error: function(xhr, textStatus, error) {
var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
return false;
});
/* for no js stuff */
$('.no-js-hidden').show();
$('.js-hidden').hide();
}
function vmCreateNetworkLabel(pk, name, managed) {
return '<span id="vlan-' + pk + '" class="label label-' + (managed ? 'primary' : 'default') + '"><i class="fa fa-' + (managed ? 'globe' : 'link') + '"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-network"><i class="fa fa-times-circle"></i></a></span> ';
}
function vmCreateDiskLabel(pk, name) {
var style = "float: left; margin: 5px 5px 5px 0;";
return '<span id="disk-' + pk + '" class="label label-primary" style="' + style + '"><i class="fa fa-file"></i> ' + name + '</span> ';
}
var Websock_native; // not sure
$(function() {
/* save resources */
$('#vm-details-resources-save').click(function(e) {
var error = false;
$(".cpu-count-input, .ram-input").each(function() {
if(!$(this)[0].checkValidity()) {
error = true;
}
});
if(error) return true;
$('i.fa-floppy-o', this).removeClass("fa-floppy-o").addClass("fa-refresh fa-spin");
var vm = $(this).data("vm");
$.ajax({
type: 'POST',
url: $(this).parent("form").prop('action'),
data: $('#vm-details-resources-form').serialize(),
success: function(data, textStatus, xhr) {
if(data.success) {
$('a[href="#activity"]').trigger("click");
} else {
addMessage(data.messages.join("<br />"), "danger");
}
$("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o");
},
error: function(xhr, textStatus, error) {
$("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o");
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
e.preventDefault();
});
/* save as (close vnc console) */
$('.operation-save_as_template').click(function(e) {
if ($('li.active > a[href$="console"]').length > 0) {
$('a[data-toggle$="pill"][href$="#activity"]').click();
}
});
/* remove tag */
$('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text());
var clicked = $(this);
$.ajax({
type: 'POST',
url: location.href,
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re.message.toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re.message, 'danger');
}
});
return false;
});
/* for js fallback */
$(".vm-details-show-password").parent("div").children("input").prop("type", "password");
/* show password */
$(".vm-details-show-password").click(function() {
var input = $(this).parent("div").children("input");
var eye = $(this).children(".vm-details-password-eye");
var span = $(this);
span.tooltip("destroy");
if(eye.hasClass("fa-eye")) {
eye.removeClass("fa-eye").addClass("fa-eye-slash");
input.prop("type", "text");
input.select();
span.prop("title", gettext("Hide password"));
} else {
eye.removeClass("fa-eye-slash").addClass("fa-eye");
input.prop("type", "password");
span.prop("title", gettext("Show password"));
}
span.tooltip();
});
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").select();
return false;
});
/* rename in home tab */
$(".vm-details-home-edit-name-click").click(function(e) {
$(".vm-details-home-edit-name-click").hide();
$("#vm-details-home-rename").show();
$("input", $("#vm-details-home-rename")).select();
e.preventDefault();
});
/* rename ajax */
$('.vm-details-rename-submit').click(function() {
var name = $(this).parent("span").prev("input").val();
var url = $("#vm-details-rename-form").attr("action");
$.ajax({
method: 'POST',
url: url,
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").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);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
/* update description click */
$(".vm-details-home-edit-description-click").click(function(e) {
$(".vm-details-home-edit-description-click").hide();
$("#vm-details-home-description").show();
var ta = $("#vm-details-home-description textarea");
var tmp = ta.val();
ta.val("");
ta.focus();
ta.val(tmp);
e.preventDefault();
});
/* description update ajax */
$('.vm-details-description-submit').click(function() {
var description = $(this).prev("textarea").val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_description': description},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
var new_desc = data.new_description;
/* we can't simply use $.text, because we need new lines */
var tagsToReplace = {
'&': "&amp;",
'<': "&lt;",
'>': "&gt;",
};
new_desc = new_desc.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
$(".vm-details-home-edit-description")
.html(new_desc.replace(/\n/g, "<br />"));
$(".vm-details-home-edit-description-click").show();
$("#vm-details-home-description").hide();
// update the textareia
$("vm-details-home-description textarea").text(data.new_description);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
// screenshot
$("#getScreenshotButton").click(function() {
var vm = $(this).data("vm-pk");
var ct = $("#vm-console-screenshot");
$("i", this).addClass("fa-spinner fa-spin");
$(this).prop("disabled", true);
ct.slideDown();
var img = $("img", ct).prop("src", '/dashboard/vm/' + vm + '/screenshot/?rnd=' + Math.random());
});
// if the image is loaded remove the spinning stuff
// note: this should not work if the image is cached, but it's not
// see: http://stackoverflow.com/a/3877079/1112653
// note #2: it actually gets cached, so a random number is appended
$("#vm-console-screenshot img").load(function(e) {
$("#getScreenshotButton").prop("disabled", false)
.find("i").removeClass("fa-spinner fa-spin");
});
// screenshot close
$("#vm-console-screenshot button").click(function() {
$(this).closest("div").slideUp();
});
// select connection string
$(".vm-details-connection-string-copy").click(function() {
$(this).parent("div").find("input").select();
});
$("a.operation-password_reset").click(function() {
if(Boolean($(this).data("disabled"))) return false;
});
$("#dashboard-tutorial-toggle").click(function() {
var box = $("#alert-new-template");
var list = box.find("ol");
list.stop().slideToggle(function() {
var url = box.find("form").prop("action");
var hidden = list.css("display") === "none";
box.find("button i").prop("class", "fa fa-caret-" + (hidden ? "down" : "up"));
$.ajax({
type: 'POST',
url: url,
data: {'hidden': hidden},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {}
});
});
return false;
});
$(document).on("click", "#vm-renew-request-lease-button", function(e) {
$("#vm-renew-request-lease").stop().slideToggle();
e.preventDefault();
});
$("#vm-request-resource").click(function(e) {
$(".cpu-priority-slider, .cpu-count-slider, .ram-slider").simpleSlider("setDisabled", false);
$(".ram-input, .cpu-count-input, .cpu-priority-input").prop("disabled", false);
$("#vm-details-resources-form").prop("action", $(this).prop("href"));
$("#vm-request-resource-form").show();
$("#modify-the-resources").show();
$(this).hide();
$("html, body").animate({
scrollTop: $("#modify-the-resources").offset().top - 60
});
return e.preventDefault();
});
// Clipboard for connection strings
if(Clipboard.isSupported()) {
new Clipboard(".vm-details-connection-string-copy", {
text: function(trigger) {
return $($(trigger).data("clipboard-target")).val();
}
});
}
});
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
var shiftKey = 16;
var selected = [];
$(function() {
$(document).keydown(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = true;
if (e.keyCode == shiftKey) shiftDown = true;
}).keyup(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = false;
if (e.keyCode == shiftKey) shiftDown = false;
});
$('.vm-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if(!$(this).data("vm-pk")) return;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('vm-list-selected')) {
selected.splice(getSelectedIndex($(this).index()), 1);
} else {
selected.push({'index': $(this).index(), 'vm': $(this).data("vm-pk")});
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1].index + 1;
end = $(this).index();
if(start > end) {
var tmp = start - 1; start = end; end = tmp - 1;
}
for(var i = start; i <= end; i++) {
var vm = $(".vm-list-table tbody tr").eq(i).data("vm-pk");
if(!isAlreadySelected(vm)) {
selected.push({'index': i, 'vm': vm});
setRowColor($('.vm-list-table tbody tr').eq(i));
}
}
}
retval = false;
} else {
$('.vm-list-selected').removeClass('vm-list-selected');
$(this).addClass('vm-list-selected');
selected = [{'index': $(this).index(), 'vm': $(this).data("vm-pk")}];
}
// show/hide group controls
if(selected.length > 0) {
$('#vm-mass-ops .mass-operation').attr('disabled', false);
} else {
$('#vm-mass-ops .mass-operation').attr('disabled', true);
}
return retval;
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
/* group actions */
/* select all */
$('#vm-list-group-select-all').click(function() {
$('.vm-list-table tbody tr').each(function() {
var index = $(this).index();
var vm = $(this).data("vm-pk");
if(vm && !isAlreadySelected(vm)) {
selected.push({'index': index, 'vm': vm});
$(this).addClass('vm-list-selected');
}
});
if(selected.length > 0)
$('#vm-mass-ops .mass-operation').attr('disabled', false);
return false;
});
/* mass operations */
$("#vm-mass-ops").on('click', '.mass-operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin');
params = "?" + selected.map(function(a){return "vm=" + a.vm;}).join("&");
$.ajax({
type: 'GET',
url: $(this).attr('href') + params,
success: function(data) {
icon.removeClass("fa-spinner fa-spin");
$('body').append(data);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
$("[title]").tooltip({'placement': "left"});
}
});
return false;
});
$("body").on("click", "#mass-op-form-send", function() {
var url = $(this).closest("form").prop("action");
$(this).find("i").prop("class", "fa fa-fw fa-spinner fa-spin");
$.ajax({
url: url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: $(this).closest('form').serialize(),
success: function(data, textStatus, xhr) {
/* hide the modal we just submitted */
$('#confirmation-modal').modal("hide");
updateStatuses(1);
/* if there are messages display them */
if(data.messages && data.messages.length > 0) {
addMessage(data.messages.join("<br />"), "danger");
}
},
error: function(xhr, textStatus, error) {
$('#confirmation-modal').modal("hide");
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " " + xhr.statusText, "danger");
}
}
});
return false;
});
/* table sort */
var table = $(".vm-list-table").stupidtable();
table.on("beforetablesort", function(event, data) {
$(".table-sorting").show();
});
table.on("aftertablesort", function(event, data) {
// this didn't work ;;
// var th = $("this").find("th");
$(".table-sorting").hide();
$(".vm-list-table thead th i").remove();
var icon_html = '<i class="fa fa-sort-' + (data.direction == "desc" ? "desc" : "asc") + ' pull-right"></i>';
$(".vm-list-table thead th").eq(data.column).append(icon_html);
});
// only if js is enabled
$(".vm-list-table thead th").css("cursor", "pointer");
$(".vm-list-table th a").on("click", function(event) {
event.preventDefault();
});
$(document).on("click", ".mass-migrate-node", function() {
$(this).find('input[type="radio"]').prop("checked", true);
});
if(checkStatusUpdate() || $("#vm-list-table tbody tr").length >= 100) {
updateStatuses(1);
}
});
function checkStatusUpdate() {
icons = $("#vm-list-table tbody td.state i");
if(icons.hasClass("fa-spin") || icons.hasClass("migrating-icon")) {
return true;
}
}
function updateStatuses(runs) {
var include_deleted = getParameterByName("include_deleted");
$.get("/dashboard/vm/list/?compact", function(result) {
$("#vm-list-table tbody tr").each(function() {
vm = $(this).data("vm-pk");
status_td = $(this).find("td.state");
status_icon = status_td.find("i");
status_text = status_td.find("span");
if(vm in result) {
if(result[vm].in_status_change) {
if(!status_icon.hasClass("fa-spin")) {
status_icon.prop("class", "fa fa-fw fa-spinner fa-spin");
}
}
else if(result[vm].status == "MIGRATING") {
if(!status_icon.hasClass("migrating-icon")) {
status_icon.prop("class", "fa fa-fw " + result[vm].icon + " migrating-icon");
}
} else {
status_icon.prop("class", "fa fa-fw " + result[vm].icon);
}
status_text.text(result[vm].status);
if("node" in result[vm]) {
$(this).find(".node").text(result[vm].node);
}
} else {
if(!include_deleted)
$(this).remove();
}
});
if(checkStatusUpdate()) {
setTimeout(
function() {updateStatuses(runs + 1);},
1000 + Math.exp(runs * 0.05)
);
}
});
}
function isAlreadySelected(vm) {
for(var i=0; i<selected.length; i++)
if(selected[i].vm == vm)
return true;
return false;
}
function getSelectedIndex(index) {
for(var i=0; i<selected.length; i++)
if(selected[i].index == index)
return i;
return -1;
}
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
ids.push(rows[i].vm);
}
return ids;
}
function setRowColor(row) {
if(!row.hasClass('vm-list-selected')) {
row.addClass('vm-list-selected');
} else {
row.removeClass('vm-list-selected');
}
}
var intro;
$(function() {
$("#vm-details-start-template-tour").click(function() {
intro = introJs();
intro.setOptions({
'nextLabel': gettext("Next") + ' <i class="fa fa-chevron-right"></i>',
'prevLabel': '<i class="fa fa-chevron-left"></i> ' + gettext("Previous"),
'skipLabel': '<i class="fa fa-times"></i> ' + gettext("End tour"),
'doneLabel': gettext("Done"),
});
intro.setOptions({
'steps': get_steps(),
});
intro.onbeforechange(function(target) {
/* if the tab menu item is highlighted */
if($(target).data("toggle") == "pill") {
$(target).trigger("click");
}
/* if anything in a tab is highlighted change to that tab */
var tab = $(target).closest('.tab-pane:not([id^="ipv"])');
var id = tab.prop("id");
if(id) {
id = id.substring(1, id.length);
$('a[href="#' + id + '"]').trigger("click");
}
});
intro.start();
return false;
});
$(document).on('click', '#vm-details-resources-save, .vm-details-home-edit-name-click, .vm-details-home-edit-description-click, a.operation', function() {
if(intro)
intro.exit();
});
});
function get_steps() {
// if an activity is running the #ops will be refreshed
// and the intro will break
deploy_selector = "#ops";
save_as_selector = "#ops";
if(!$('.timeline .activity i').hasClass('fa-spin')) {
vm_status = $("#vm-details-state").data("status");
if(vm_status === "PENDING")
deploy_selector += ' a[class*="operation-deploy"]';
if(vm_status === "RUNNING" || vm_status === "STOPPED")
save_as_selector += ' a[class*="operation-save_as_template"]';
}
steps = [
{
element: document.querySelector("#vm-details-start-template-tour"),
intro: "<p>" + gettext("Welcome to the template tutorial. In this quick tour, we are going to show you how to do the steps described above.") + "</p>" +
"<p>" + gettext('For the next tour step press the "Next" button or the right arrow (or "Back" button/left arrow for the previous step).') + "</p>"
},
{
element: document.querySelector('a[href="#home"]'),
intro: gettext("In this tab you can extend the expiration date of your virtual machine, add tags and modify the name and description."),
},
{
element: document.querySelector('#home_name_and_description'),
intro: gettext("Please add a meaningful description to the virtual machine. Changing the name is also recommended, however you can choose a new name when saving the template."),
},
{
element: document.querySelector('#home_expiration_and_lease'),
intro: gettext("You can change the lease to extend the expiration date. This will be the lease of the new template."),
},
{
element: document.querySelector('a[href="#resources"]'),
intro: gettext("On the resources tab you can edit the CPU/RAM options and add/remove disks if you have required permissions."),
}
];
if($("#vm-details-resources-save").length) {
steps.push(
{
element: document.querySelector('#vm-details-resources-form'),
intro: '<p><strong>' + gettext("CPU priority") + ":</strong> " +
gettext("higher is better") + "</p>" +
"<p><strong>" + gettext("CPU count") + ":</strong> " +
gettext("number of CPU cores.") + "</p>" +
"<p><strong>" + gettext("RAM amount") + ":</strong> " +
gettext("amount of RAM.") + "</p>",
position: "top",
}
);
}
if($(".operation-create_disk").length || $(".operation-download_disk").length) {
steps.push(
{
element: document.querySelector('#vm-details-resources-disk'),
intro: gettext("You can add empty disks, download new ones and remove existing ones here."),
position: "top",
}
);
}
steps.push(
{
element: document.querySelector('a[href="#network"]'),
intro: gettext('You can add new network interfaces or remove existing ones here.'),
},
{
element: document.querySelector(deploy_selector),
intro: gettext("Deploy the virtual machine."),
},
{
element: document.querySelector("#vm-info-pane"),
intro: gettext("Use the CIRCLE client or the connection string to connect to the virtual machine."),
},
{
element: document.querySelector("#vm-info-pane"),
intro: gettext("After you have connected to the virtual machine do your modifications then log off."),
},
{
element: document.querySelector(save_as_selector),
intro: gettext('Press the "Save as template" button and wait until the activity finishes.'),
},
{
element: document.querySelector(".alert-new-template"),
intro: gettext("This is the last message, if something is not clear you can do the the tour again."),
}
);
return steps;
}
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
padding-bottom: 20px;
margin-bottom: 30px; /* sticky footer */
}
html {
position: relative;
min-height: 100%;
}
/* Set widths on the navbar form inputs since otherwise they're 100% wide */
.navbar-form input[type="text"],
.navbar-form input[type="password"] {
width: 180px;
}
/* Wrapping element */
/* Set some basic padding to keep content from hitting the edges */
.body-content {
padding-left: 15px;
padding-right: 15px;
}
/* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) {
/* Let the jumbotron breathe */
.container > :first-child {
margin-top: 20px;
}
/* Remove padding from wrapping element since we kick in the grid classes here */
.body-content {
padding: 0;
}
.navbar-nav > li > a {
padding-top: 12.5px;
padding-bottom: 12.5px;
}
}
.no-margin {
margin: 0!important;
}
.list-group .list-group-footer {
padding-top: 5px;
padding-bottom: 5px;
}
.big {
font-size: 2em;
}
.bigbig {
font-size: 3em;
}
.big-tag {
font-size: 1.2em;
}
/* small buttons for tags, copied from Bootstraps input-sm, bnt-sm */
.btn-tags, .btn-traits {
padding: 3px 6px;
font-size: 11px;
line-height: 1.5;
border-radius: 3px;
}
.input-tags, .input-tratis {
height: 22px;
padding: 2px 8px;
font-size: 11px;
line-height: 1.5;
border-radius: 3px;
}
/* font awesome font */
.font-awesome-font {
font-family: "FontAwesome";
}
.nojs-dropdown-menu
{
position:absolute;
display:none;
z-index: 1;
}
.nojs-dropdown-toggle:focus + .nojs-dropdown-menu
{
display: block;
}
.nojs-dropdown-toggle:focus
{
outline:none;
}
.nojs-dropdown-menu:hover
{
display: block;
}
/* footer */
footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: 30px;
background-color: #101010;
color: white;
font-size: 13px;
padding: 5px 5px 0 5px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.4);
text-align: center;
}
footer a, footer a:hover, footer a:visited {
color: white;
text-decoration: underline;
}
.table-sorting {
display: none;
}
/* 2px border bottom for all bootstrap tables */
.table thead>tr>th {
border-bottom: 1px;
}
.badge-pulse {
-webkit-animation-name: 'pulse_animation';
-webkit-animation-duration: 1000ms;
-webkit-transform-origin: 70% 70%;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
}
@-webkit-keyframes pulse_animation {
0% { -webkit-transform: scale(1); }
30% { -webkit-transform: scale(1); }
40% { -webkit-transform: scale(1.18); }
50% { -webkit-transform: scale(1); }
60% { -webkit-transform: scale(1); }
70% { -webkit-transform: scale(1.08); }
80% { -webkit-transform: scale(1); }
100% { -webkit-transform: scale(1); }
}
.btn-toolbar {
margin-bottom: 5px;
}
.vm-details-home-edit-description {
font-size: 85%; /* ~ small tag */
}
# 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/>.
import json
import logging
from urlparse import urljoin
import os
from datetime import datetime
from django.conf import settings
from django.http import Http404
from os.path import splitext
from requests import get, post, codes
from sizefield.utils import filesizeformat
from storage.models import Disk
logger = logging.getLogger(__name__)
class StoreApiException(Exception):
pass
class NotOkException(StoreApiException):
def __init__(self, status, *args, **kwargs):
self.status = status
super(NotOkException, self).__init__(*args, **kwargs)
class NoStoreException(StoreApiException):
pass
class Store(object):
def __init__(self, user, default_timeout=5):
self.store_url = settings.STORE_URL
if not self.store_url:
raise NoStoreException
if user.is_superuser and not user.profile.org_id:
self.username = 'u-admin'
elif not user.profile.org_id:
raise NoStoreException
else:
self.username = 'u-%s' % user.profile.org_id
self.request_args = {'verify': settings.STORE_VERIFY_SSL}
if settings.STORE_SSL_AUTH:
self.request_args['cert'] = (settings.STORE_CLIENT_CERT,
settings.STORE_CLIENT_KEY)
if settings.STORE_BASIC_AUTH:
self.request_args['auth'] = (settings.STORE_CLIENT_USER,
settings.STORE_CLIENT_PASSWORD)
self.default_timeout = default_timeout
def _request(self, url, method=get, timeout=None,
raise_status_code=True, **kwargs):
url = urljoin(self.store_url, url)
if timeout is None:
timeout = self.default_timeout
kwargs['USER'] = self.username
payload = json.dumps(kwargs)
try:
headers = {'content-type': 'application/json'}
response = method(url, data=payload, headers=headers,
timeout=timeout, **self.request_args)
except Exception:
logger.exception("Error in store %s loading %s",
unicode(method), url)
raise
else:
if raise_status_code and response.status_code != codes.ok:
if response.status_code == 404:
raise Http404()
else:
raise NotOkException(response.status_code)
return response
def _request_cmd(self, cmd, **kwargs):
return self._request("/user/", post, CMD=cmd, **kwargs)
def list(self, path, process=True):
r = self._request_cmd("LIST", PATH=path)
result = r.json()
if process:
return self._process_list(result)
else:
return result
def toplist(self, process=True):
r = self._request_cmd("TOPLIST")
result = r.json()
if process:
return self._process_list(result)
else:
return result
def get_files_with_exts(self, exts, path='/'):
"""
Get list of files from store with the given file extensions.
"""
matching_files = []
file_list = self.list(path, process=False)
for item in file_list:
if os.path.splitext(item['NAME'])[1].strip('.') in exts:
matching_files.append(os.path.join(path, item['NAME']))
return matching_files
def request_download(self, path):
r = self._request_cmd("DOWNLOAD", PATH=path, timeout=10)
return r.json()['LINK']
def request_upload(self, path):
r = self._request_cmd("UPLOAD", PATH=path)
return r.json()['LINK']
def request_ssh_download(self, path):
r = self._request_cmd("SSH_DOWNLOAD", PATH=path)
return r.json()['LINK'], r.json()['PORT']
def request_ssh_upload(self):
r = self._request_cmd("SSH_UPLOAD")
return r.json()['LINK'], r.json()['PORT']
def ssh_upload_finished(self, uploaded_name, path):
self._request_cmd(
"SSH_UPLOAD_FINISHED",
FILENAME=uploaded_name,
PATH=path,
)
def remove(self, path):
self._request_cmd("REMOVE", PATH=path)
def new_folder(self, path):
self._request_cmd("NEW_FOLDER", PATH=path)
def rename(self, old_path, new_name):
self._request_cmd("RENAME", PATH=old_path, NEW_NAME=new_name)
def get_quota(self): # no CMD? :o
r = self._request("/user/")
quota = r.json()
quota.update({
'readable_used': filesizeformat(float(quota['used'])),
'readable_soft': filesizeformat(float(quota['soft'])),
'readable_hard': filesizeformat(float(quota['hard'])),
})
return quota
def set_quota(self, quota):
self._request("/quota/", post, QUOTA=quota)
def user_exist(self):
try:
self._request("/user/")
return True
except NotOkException:
return False
def create_user(self, password, keys, quota):
self._request("/new/", method=post,
SMBPASSWD=password, KEYS=keys, QUOTA=quota)
@staticmethod
def _process_list(content):
for d in content:
d['human_readable_date'] = datetime.utcfromtimestamp(float(
d['MTIME']))
delta = (datetime.utcnow() -
d['human_readable_date']).total_seconds()
d['is_new'] = 0 < delta < 5
d['human_readable_size'] = (
"directory" if d['TYPE'] == "D" else
filesizeformat(float(d['SIZE'])))
if d['DIR'] == ".":
d['directory'] = "/"
else:
d['directory'] = "/" + d['DIR'] + "/"
d['path'] = d['directory']
d['path'] += d['NAME']
if d['TYPE'] == "D":
d['path'] += "/"
d['ext'] = splitext(d['path'])[1]
d['icon'] = ("folder-open" if not d['TYPE'] == "F"
else file_icons.get(d['ext'], "file-o"))
return sorted(content, key=lambda k: k['TYPE'])
file_icons = {
'.txt': "file-text-o",
'.pdf': "file-pdf-o",
'.jpg': "file-image-o",
'.jpeg': "file-image-o",
'.png': "file-image-o",
'.gif': "file-image-o",
'.avi': "file-video-o",
'.mkv': "file-video-o",
'.mp4': "file-video-o",
'.mov': "file-video-o",
'.mp3': "file-sound-o",
'.flac': "file-sound-o",
'.wma': "file-sound-o",
'.pptx': "file-powerpoint-o",
'.ppt': "file-powerpoint-o",
'.doc': "file-word-o",
'.docx': "file-word-o",
'.xlsx': "file-excel-o",
'.xls': "file-excel-o",
'.rar': "file-archive-o",
'.zip': "file-archive-o",
'.7z': "file-archive-o",
'.tar': "file-archive-o",
'.gz': "file-archive-o",
'.py': "file-code-o",
'.html': "file-code-o",
'.js': "file-code-o",
'.css': "file-code-o",
'.c': "file-code-o",
'.cpp': "file-code-o",
'.h': "file-code-o",
'.sh': "file-code-o",
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
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