A prog2-höz tartozó friss repo anyagok itt elérhetőek: https://git.iit.bme.hu/prog2

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> ';
}