Merge branch 'master' into db-backup-unstable

This commit is contained in:
Luke Sneeringer 2015-04-28 11:50:52 -05:00
commit d120f29ac3
323 changed files with 51061 additions and 665 deletions

View File

@ -199,10 +199,12 @@ server_noattach:
tmux rename-window 'Tower'
tmux select-window -t tower:0
tmux split-window -v 'exec make celeryd'
tmux split-window -h 'exec make socketservice'
tmux select-pane -U
tmux split-window -v 'exec make receiver'
tmux split-window -h 'exec make taskmanager'
tmux new-window 'exec make receiver'
tmux select-window -t tower:1
tmux rename-window 'Extra Services'
tmux split-window -v 'exec make socketservice'
tmux split-window -h 'exec make factcacher'
server: server_noattach
tmux -2 attach-session -t tower
@ -229,6 +231,9 @@ taskmanager:
socketservice:
$(PYTHON) manage.py run_socketio_service
factcacher:
$(PYTHON) manage.py run_fact_cache_receiver
pep8:
pep8 -r awx/
@ -255,7 +260,7 @@ ui_analysis_report: reports/ui_code node_modules Gruntfile.js
reports/ui_code: node_modules clean-ui Brocfile.js bower.json Gruntfile.js
rm -rf reports/ui_code
$(BROCCOLI) build reports/ui_code -- --no-concat --no-tests --no-styles
$(BROCCOLI) build reports/ui_code -- --no-concat --no-tests --no-styles --no-sourcemaps
# Run UI unit tests
test_ui: node_modules minjs_ci Gruntfile.js
@ -294,7 +299,7 @@ devjs: node_modules clean-ui Brocfile.js bower.json Gruntfile.js
# Build minified JS/CSS.
minjs: node_modules clean-ui Brocfile.js
$(BROCCOLI) build awx/ui/dist -- --silent --no-debug --no-tests --compress --no-docs
$(BROCCOLI) build awx/ui/dist -- --silent --no-debug --no-tests --compress --no-docs --no-sourcemaps
minjs_ci: node_modules clean-ui Brocfile.js
$(BROCCOLI) build awx/ui/dist -- --no-debug --compress --no-docs

View File

@ -365,7 +365,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
data[parent_key] = self.kwargs['pk']
# attempt to deserialize the object
serializer = self.serializer_class(data=data)
serializer = self.get_serializer(data=data)
if not serializer.is_valid():
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
@ -377,7 +377,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
# save the object through the serializer, reload and returned the saved
# object deserialized
obj = serializer.save()
serializer = self.serializer_class(obj)
serializer = self.get_serializer(instance=obj)
headers = {'Location': obj.get_absolute_url()}
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

View File

@ -6,6 +6,7 @@ import json
import re
import logging
from dateutil import rrule
from ast import literal_eval
# PyYAML
import yaml
@ -1294,6 +1295,16 @@ class CredentialSerializer(BaseSerializer):
for field in Credential.PASSWORD_FIELDS:
if unicode(attrs.get(field, '')).startswith('$encrypted$'):
attrs.pop(field, None)
# If creating a credential from a view that automatically sets the
# parent_key (user or team), set the other value to None.
view = self.context.get('view', None)
parent_key = getattr(view, 'parent_key', None)
if parent_key == 'user':
attrs['team'] = None
if parent_key == 'team':
attrs['user'] = None
instance = super(CredentialSerializer, self).restore_object(attrs, instance)
return instance
@ -1493,16 +1504,40 @@ class JobCancelSerializer(JobSerializer):
class JobRelaunchSerializer(JobSerializer):
passwords_needed_to_start = serializers.SerializerMethodField('get_passwords_needed_to_start')
class Meta:
fields = ()
fields = ('passwords_needed_to_start',)
def to_native(self, obj):
if obj:
return dict([(p, u'') for p in obj.passwords_needed_to_start])
else:
return {}
res = super(JobRelaunchSerializer, self).to_native(obj)
view = self.context.get('view', None)
if hasattr(view, '_raw_data_form_marker'):
password_keys = dict([(p, u'') for p in self.get_passwords_needed_to_start(obj)])
res.update(password_keys)
return res
def get_passwords_needed_to_start(self, obj):
if obj:
return obj.passwords_needed_to_start
return ''
def validate_passwords_needed_to_start(self, attrs, source):
obj = self.context.get('obj')
data = self.context.get('data')
# Check for passwords needed
needed = self.get_passwords_needed_to_start(obj)
provided = dict([(field, data.get(field, '')) for field in needed])
if not all(provided.values()):
raise serializers.ValidationError(needed)
return attrs
def validate(self, attrs):
obj = self.context.get('obj')
if not obj.credential or obj.credential.active is False:
raise serializers.ValidationError(dict(credential=["Credential not found or deleted."]))
return attrs
class AdHocCommandSerializer(UnifiedJobSerializer):
@ -1547,6 +1582,9 @@ class AdHocCommandSerializer(UnifiedJobSerializer):
ret['inventory'] = None
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
# For the UI, only module_name is returned for name, instead of the
# longer module name + module_args format.
ret['name'] = obj.module_name
return ret
@ -1690,6 +1728,93 @@ class AdHocCommandEventSerializer(BaseSerializer):
res['host'] = reverse('api:host_detail', args=(obj.host.pk,))
return res
class JobLaunchSerializer(BaseSerializer):
passwords_needed_to_start = serializers.Field(source='passwords_needed_to_start')
can_start_without_user_input = serializers.Field(source='can_start_without_user_input')
variables_needed_to_start = serializers.Field(source='variables_needed_to_start')
credential_needed_to_start = serializers.SerializerMethodField('get_credential_needed_to_start')
survey_enabled = serializers.SerializerMethodField('get_survey_enabled')
class Meta:
model = JobTemplate
fields = ('can_start_without_user_input', 'passwords_needed_to_start', 'extra_vars',
'ask_variables_on_launch', 'survey_enabled', 'variables_needed_to_start',
'credential', 'credential_needed_to_start',)
read_only_fields = ('ask_variables_on_launch',)
write_only_fields = ('credential','extra_vars',)
def to_native(self, obj):
res = super(JobLaunchSerializer, self).to_native(obj)
view = self.context.get('view', None)
if obj and hasattr(view, '_raw_data_form_marker'):
if obj.passwords_needed_to_start:
password_keys = dict([(p, u'') for p in obj.passwords_needed_to_start])
res.update(password_keys)
if self.get_credential_needed_to_start(obj) is True:
res.update(dict(credential=''))
return res
def get_credential_needed_to_start(self, obj):
return not (obj and obj.credential and obj.credential.active)
def get_survey_enabled(self, obj):
if obj:
return obj.survey_enabled and 'spec' in obj.survey_spec
return False
def validate_credential(self, attrs, source):
obj = self.context.get('obj')
credential = attrs.get(source, None) or (obj and obj.credential)
if not credential or not credential.active:
raise serializers.ValidationError('Credential not provided')
attrs[source] = credential
return attrs
def validate_passwords_needed_to_start(self, attrs, source):
obj = self.context.get('obj')
passwords = self.context.get('passwords')
data = self.context.get('data')
# fill passwords dict with request data passwords
if obj.passwords_needed_to_start:
try:
for p in obj.passwords_needed_to_start:
passwords[p] = data.get(p)
except KeyError:
raise serializers.ValidationError(obj.passwords_needed_to_start)
return attrs
def validate(self, attrs):
obj = self.context.get('obj')
extra_vars = attrs.get('extra_vars', {})
try:
extra_vars = literal_eval(extra_vars)
extra_vars = json.dumps(extra_vars)
except Exception:
pass
try:
extra_vars = json.loads(extra_vars)
except (ValueError, TypeError):
try:
extra_vars = yaml.safe_load(extra_vars)
except (yaml.YAMLError, TypeError, AttributeError):
raise serializers.ValidationError(dict(extra_vars=['Must be valid JSON or YAML']))
if not isinstance(extra_vars, dict):
extra_vars = {}
if self.get_survey_enabled(obj):
validation_errors = obj.survey_variable_validation(extra_vars)
if validation_errors:
raise serializers.ValidationError(dict(variables_needed_to_start=validation_errors))
if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None or not obj.project.active):
raise serializers.ValidationError(dict(errors=["Job Template Project is missing or undefined"]))
if obj.inventory is None or not obj.inventory.active:
raise serializers.ValidationError(dict(errors=["Job Template Inventory is missing or undefined"]))
return attrs
class ScheduleSerializer(BaseSerializer):

View File

@ -14,16 +14,16 @@
.ansi4 { text-decoration: underline; }
.ansi9 { text-decoration: line-through; }
.ansi30 { color: #000316; }
.ansi31 { color: #AA0000; }
.ansi32 { color: #00AA00; }
.ansi31 { color: #ff5850; }
.ansi32 { color: #60D66F; }
.ansi33 { color: #AA5500; }
.ansi34 { color: #0000AA; }
.ansi35 { color: #E850A8; }
.ansi36 { color: #00AAAA; }
.ansi37 { color: #F5F1DE; }
.ansi40 { background-color: #000000; }
.ansi41 { background-color: #AA0000; }
.ansi42 { background-color: #00AA00; }
.ansi41 { background-color: #ff5850; }
.ansi42 { background-color: #60D66F; }
.ansi43 { background-color: #AA5500; }
.ansi44 { background-color: #0000AA; }
.ansi45 { background-color: #E850A8; }

View File

@ -812,7 +812,7 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
def update_filter(self, request, *args, **kwargs):
''' make sure non-read-only fields that can only be edited by admins, are only edited by admins '''
obj = User.objects.get(pk=kwargs['pk'])
obj = self.get_object()
can_change = request.user.can_access(User, 'change', obj, request.DATA)
can_admin = request.user.can_access(User, 'admin', obj, request.DATA)
if can_change and not can_admin:
@ -828,7 +828,7 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
raise PermissionDenied('Cannot change %s' % ', '.join(changed.keys()))
def destroy(self, request, *args, **kwargs):
obj = User.objects.get(pk=kwargs['pk'])
obj = self.get_object()
can_delete = request.user.can_access(User, 'delete', obj)
if not can_delete:
raise PermissionDenied('Cannot delete user')
@ -1434,44 +1434,34 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
return super(JobTemplateDetail, self).destroy(request, *args, **kwargs)
class JobTemplateLaunch(GenericAPIView):
class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
model = JobTemplate
# FIXME: Add serializer class to define fields in OPTIONS request!
serializer_class = JobLaunchSerializer
is_job_start = True
def get(self, request, *args, **kwargs):
obj = self.get_object()
data = {}
data['can_start_without_user_input'] = obj.can_start_without_user_input()
data['passwords_needed_to_start'] = obj.passwords_needed_to_start
data['ask_variables_on_launch'] = obj.ask_variables_on_launch
data['variables_needed_to_start'] = obj.variables_needed_to_start
data['credential_needed_to_start'] = obj.credential is None
data['survey_enabled'] = obj.survey_enabled and 'spec' in obj.survey_spec
return Response(data)
def post(self, request, *args, **kwargs):
obj = self.get_object()
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
if obj.survey_enabled and 'spec' in obj.survey_spec:
if request.DATA == "":
request_data = {}
else:
request_data = request.DATA
validation_errors = obj.survey_variable_validation(request_data.get('extra_vars', {}))
if validation_errors:
return Response(dict(variables_needed_to_start=validation_errors),
status=status.HTTP_400_BAD_REQUEST)
if obj.credential is None and ('credential' not in request.DATA and 'credential_id' not in request.DATA):
return Response(dict(errors="Credential not provided"), status=status.HTTP_400_BAD_REQUEST)
if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None or not obj.project.active):
return Response(dict(errors="Job Template Project is missing or undefined"), status=status.HTTP_400_BAD_REQUEST)
if obj.inventory is None or not obj.inventory.active:
return Response(dict(errors="Job Template Inventory is missing or undefined"), status=status.HTTP_400_BAD_REQUEST)
new_job = obj.create_unified_job(**request.DATA)
result = new_job.signal_start(**request.DATA)
if 'credential' not in request.DATA and 'credential_id' in request.DATA:
request.DATA['credential'] = request.DATA['credential_id']
passwords = {}
serializer = self.serializer_class(data=request.DATA, context={'obj': obj, 'data': request.DATA, 'passwords': passwords})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
kv = {
'credential': serializer.object.credential.pk,
}
if 'extra_vars' in request.DATA:
kv['extra_vars'] = request.DATA['extra_vars']
kv.update(passwords)
new_job = obj.create_unified_job(**kv)
result = new_job.signal_start(**kv)
if not result:
data = dict(passwords_needed_to_start=new_job.passwords_needed_to_start)
new_job.delete()
@ -1843,7 +1833,7 @@ class JobCancel(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
class JobRelaunch(GenericAPIView):
class JobRelaunch(RetrieveAPIView, GenericAPIView):
model = Job
serializer_class = JobRelaunchSerializer
@ -1854,23 +1844,16 @@ class JobRelaunch(GenericAPIView):
def dispatch(self, *args, **kwargs):
return super(JobRelaunch, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
obj = self.get_object()
data = {}
data['passwords_needed_to_start'] = obj.passwords_needed_to_start
return Response(data)
def post(self, request, *args, **kwargs):
obj = self.get_object()
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
# Check for passwords needed before copying job.
needed = obj.passwords_needed_to_start
provided = dict([(field, request.DATA.get(field, '')) for field in needed])
if not all(provided.values()):
data = dict(passwords_needed_to_start=needed)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
# Note: is_valid() may modify request.DATA
# It will remove any key/value pair who's key is not in the 'passwords_needed_to_start' list
serializer = self.serializer_class(data=request.DATA, context={'obj': obj, 'data': request.DATA})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
new_job = obj.copy()
result = new_job.signal_start(**request.DATA)

View File

@ -205,6 +205,7 @@ class SocketIOHandler(WSGIHandler):
del self.websocket.environ
del self.websocket
if self.environ:
self.environ.pop('wsgi.websocket', None)
del self.environ
def handle_bad_request(self):

View File

@ -4,12 +4,12 @@
# Python
import re
from dateutil.relativedelta import relativedelta
from datetime import datetime
from optparse import make_option
# Django
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.utils.timezone import now
# AWX
from awx.fact.models.fact import * # noqa
@ -30,20 +30,32 @@ class CleanupFacts(object):
# pivot -= granularity
# group by host
def cleanup(self, older_than_abs, granularity):
flag_delete_all = False
fact_oldest = FactVersion.objects.all().order_by('timestamp').first()
if not fact_oldest:
return 0
# Special case, granularity=0x where x is d, w, or y
# The intent is to delete all facts < older_than_abs
if granularity == relativedelta():
flag_delete_all = True
total = 0
date_pivot = older_than_abs
while date_pivot > fact_oldest.timestamp:
date_pivot_next = date_pivot - granularity
kv = {
'timestamp__lte': date_pivot,
'timestamp__gt': date_pivot_next,
'timestamp__lte': date_pivot
}
if not flag_delete_all:
kv['timestamp__gt'] = date_pivot_next
version_objs = FactVersion.objects.filter(**kv).order_by('-timestamp')
if flag_delete_all:
total = version_objs.delete()
break
# Transform array -> {host_id} = [<fact_version>, <fact_version>, ...]
# TODO: If this set gets large then we can use mongo to transform the data set for us.
host_ids = {}
@ -66,13 +78,14 @@ class CleanupFacts(object):
total += count
date_pivot = date_pivot_next
return total
'''
older_than and granularity are of type relativedelta
'''
def run(self, older_than, granularity):
t = datetime.now()
t = now()
deleted_count = self.cleanup(t - older_than, granularity)
print("Deleted %d facts." % deleted_count)

View File

@ -357,8 +357,9 @@ class ExecutableJsonLoader(BaseLoader):
stdout, stderr = proc.communicate()
if proc.returncode != 0:
raise RuntimeError('%r failed (rc=%d) with output: %s' % (cmd, proc.returncode, stderr))
data = json.loads(stdout)
if not isinstance(data, dict):
try:
data = json.loads(stdout)
except ValueError:
raise TypeError('Returned JSON must be a dictionary, got %s instead' % str(type(data)))
except:
logger.error('Failed to load JSON from: %s', stdout)

View File

@ -29,7 +29,7 @@ valid_sockets = []
class TowerBaseNamespace(BaseNamespace):
def get_allowed_methods(self):
return []
return ['recv_disconnect']
def get_initial_acl(self):
global valid_sockets
@ -99,7 +99,8 @@ class AdHocCommandEventNamespace(TowerBaseNamespace):
class ScheduleNamespace(TowerBaseNamespace):
def get_allowed_methods(self):
return ["schedule_changed"]
parent_allowed = super(ScheduleNamespace, self).get_allowed_methods()
return parent_allowed + ["schedule_changed"]
def recv_connect(self):
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
@ -131,7 +132,11 @@ def notification_handler(server):
}
for session_id, socket in list(server.sockets.iteritems()):
if session_id in valid_sockets:
socket.send_packet(packet)
try:
socket.send_packet(packet)
except Exception, e:
logger.error("Error sending client packet to %s: %s" % (str(session_id), str(packet)))
logger.error("Error was: " + str(e))
class Command(NoArgsCommand):
'''

View File

@ -379,6 +379,23 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
# If update_fields has been specified, add our field names to it,
# if hit hasn't been specified, then we're just doing a normal save.
update_fields = kwargs.get('update_fields', [])
# If updating a credential, make sure that we only allow user OR team
# to be set, and clear out the other field based on which one has
# changed.
if self.pk:
cred_before = Credential.objects.get(pk=self.pk)
if self.user and self.team:
# If the user changed, remove the previously assigned team.
if cred_before.user != self.user:
self.team = None
if 'team' not in update_fields:
update_fields.append('team')
# If the team changed, remove the previously assigned user.
elif cred_before.team != self.team:
self.user = None
if 'user' not in update_fields:
update_fields.append('user')
# Set cloud flag based on credential kind.
cloud = self.kind in CLOUD_PROVIDERS + ('aws',)
if self.cloud != cloud:
self.cloud = cloud

View File

@ -945,6 +945,7 @@ class SystemJobOptions(BaseModel):
('cleanup_jobs', _('Remove jobs older than a certain number of days')),
('cleanup_activitystream', _('Remove activity stream entries older than a certain number of days')),
('cleanup_deleted', _('Purge previously deleted items from the database')),
('cleanup_facts', _('Purge and/or reduce the granularity of system tracking data')),
]
class Meta:

View File

@ -14,7 +14,7 @@ class Socket(object):
Intended to allow alteration of backend details in a single, consistent
way throughout the Tower application.
"""
def __init__(self, bucket, rw, debug=0, logger=None):
def __init__(self, bucket, rw, debug=0, logger=None, nowait=False):
"""Instantiate a Socket object, which uses ZeroMQ to actually perform
passing a message back and forth.
@ -42,6 +42,7 @@ class Socket(object):
self._debug = debug
self._logger = logger
self._nowait = nowait
def __enter__(self):
self.connect()
@ -92,7 +93,10 @@ class Socket(object):
# Okay, create the connection.
if self._context is None:
self._context = zmq.Context()
self._socket = self._context.socket(self._rw)
self._socket = self._context.socket(self._rw)
if self._nowait:
self._socket.setsockopt(zmq.RCVTIMEO, 2000)
self._socket.setsockopt(zmq.LINGER, 1000)
if self._rw == zmq.REQ:
self._socket.connect(port)
else:
@ -134,8 +138,8 @@ class Socket(object):
break
except Exception as ex:
if self._logger:
self._logger.info('Publish Exception: %r; retry=%d',
ex, retry, exc_info=True)
self._logger.error('Publish Exception: %r; retry=%d',
ex, retry, exc_info=True)
if retry >= 3:
raise

View File

@ -230,10 +230,10 @@ class BaseTask(Task):
'''
return os.path.abspath(os.path.join(os.path.dirname(__file__), *args))
def build_private_data(self, instance, **kwargs):
def build_private_data(self, job, **kwargs):
'''
Return any private data that needs to be written to a temporary file
for this task.
Return SSH private key data (only if stored in DB as ssh_key_data).
Return structure is a dict of the form:
'''
def build_private_data_dir(self, instance, **kwargs):
@ -244,20 +244,23 @@ class BaseTask(Task):
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return path
def build_private_data_file(self, instance, **kwargs):
def build_private_data_files(self, instance, **kwargs):
'''
Create a temporary file containing the private data.
Create a temporary files containing the private data.
Returns a dictionary with keys from build_private_data
(i.e. 'credential', 'cloud_credential') and values the file path.
'''
private_data = self.build_private_data(instance, **kwargs)
private_data_files = {}
if private_data is not None:
handle, path = tempfile.mkstemp(dir=kwargs.get('private_data_dir', None))
f = os.fdopen(handle, 'w')
f.write(private_data)
f.close()
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
return path
else:
return ''
for name, data in private_data.iteritems():
handle, path = tempfile.mkstemp(dir=kwargs.get('private_data_dir', None))
f = os.fdopen(handle, 'w')
f.write(data)
f.close()
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
private_data_files[name] = path
return private_data_files
def build_passwords(self, instance, **kwargs):
'''
@ -434,7 +437,8 @@ class BaseTask(Task):
# Fetch ansible version once here to support version-dependent features.
kwargs['ansible_version'] = get_ansible_version()
kwargs['private_data_dir'] = self.build_private_data_dir(instance, **kwargs)
kwargs['private_data_file'] = self.build_private_data_file(instance, **kwargs)
# May have to serialize the value
kwargs['private_data_files'] = self.build_private_data_files(instance, **kwargs)
kwargs['passwords'] = self.build_passwords(instance, **kwargs)
args = self.build_args(instance, **kwargs)
safe_args = self.build_safe_args(instance, **kwargs)
@ -505,25 +509,22 @@ class RunJob(BaseTask):
def build_private_data(self, job, **kwargs):
'''
Return SSH private key data needed for this job (only if stored in DB
as ssh_key_data).
Returns a dict of the form
dict['credential'] = <credential_decrypted_ssh_key_data>
dict['cloud_credential'] = <cloud_credential_decrypted_ssh_key_data>
'''
job_credentials = ['credential', 'cloud_credential']
private_data = {}
# If we were sent SSH credentials, decrypt them and send them
# back (they will be written to a temporary file).
credential = getattr(job, 'credential', None)
if credential:
return decrypt_field(credential, 'ssh_key_data') or None
# We might also have been sent a cloud credential. If so, send it.
#
# This sets up an either/or situation with credential and cloud
# credential when it comes to SSH data. This should be fine, as if
# you're running against cloud instances, you'll be using the cloud
# credentials to do so. I assert that no situation currently exists
# where we need both.
cloud_credential = getattr(job, 'cloud_credential', None)
if cloud_credential:
return decrypt_field(cloud_credential, 'ssh_key_data') or None
for cred_name in job_credentials:
credential = getattr(job, cred_name, None)
if credential:
if credential.ssh_key_data not in (None, ''):
private_data[cred_name] = decrypt_field(credential, 'ssh_key_data') or ''
return private_data
def build_passwords(self, job, **kwargs):
'''
@ -583,10 +584,10 @@ class RunJob(BaseTask):
elif cloud_cred and cloud_cred.kind == 'gce':
env['GCE_EMAIL'] = cloud_cred.username
env['GCE_PROJECT'] = cloud_cred.project
env['GCE_PEM_FILE_PATH'] = kwargs['private_data_file']
env['GCE_PEM_FILE_PATH'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
elif cloud_cred and cloud_cred.kind == 'azure':
env['AZURE_SUBSCRIPTION_ID'] = cloud_cred.username
env['AZURE_CERT_PATH'] = kwargs['private_data_file']
env['AZURE_CERT_PATH'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
elif cloud_cred and cloud_cred.kind == 'vmware':
env['VMWARE_USER'] = cloud_cred.username
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
@ -729,7 +730,7 @@ class RunJob(BaseTask):
'''
If using an SSH key, return the path for use by ssh-agent.
'''
return kwargs.get('private_data_file', '')
return kwargs.get('private_data_files', {}).get('credential', '')
def should_use_proot(self, instance, **kwargs):
'''
@ -760,12 +761,17 @@ class RunProjectUpdate(BaseTask):
name = 'awx.main.tasks.run_project_update'
model = ProjectUpdate
def build_private_data(self, project_update, **kwargs):
'''
Return SSH private key data needed for this project update.
'''
private_data = {}
if project_update.credential:
return decrypt_field(project_update.credential, 'ssh_key_data') or None
credential = project_update.credential
if credential.ssh_key_data not in (None, ''):
private_data['scm_credential'] = decrypt_field(project_update.credential, 'ssh_key_data')
return private_data
def build_passwords(self, project_update, **kwargs):
'''
@ -916,7 +922,7 @@ class RunProjectUpdate(BaseTask):
'''
If using an SSH key, return the path for use by ssh-agent.
'''
return kwargs.get('private_data_file', '')
return kwargs.get('private_data_files', {}).get('scm_credential', '')
class RunInventoryUpdate(BaseTask):
@ -930,7 +936,7 @@ class RunInventoryUpdate(BaseTask):
# If this is Microsoft Azure or GCE, return the RSA key
if inventory_update.source in ('azure', 'gce'):
credential = inventory_update.credential
return decrypt_field(credential, 'ssh_key_data')
return dict(cloud_credential=decrypt_field(credential, 'ssh_key_data'))
if inventory_update.source == 'openstack':
credential = inventory_update.credential
@ -938,8 +944,9 @@ class RunInventoryUpdate(BaseTask):
username=credential.username,
password=decrypt_field(credential, "password"),
project_name=credential.project)
openstack_data = {"clouds": {"devstack": {"auth": openstack_auth}}}
return yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True)
private_state = str(inventory_update.source_vars_dict.get("private", "true"))
openstack_data = {"clouds": {"devstack": {"private": private_state, "auth": openstack_auth}}}
return dict(cloud_credential=yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True))
cp = ConfigParser.ConfigParser()
# Build custom ec2.ini for ec2 inventory script to use.
@ -993,7 +1000,7 @@ class RunInventoryUpdate(BaseTask):
if cp.sections():
f = cStringIO.StringIO()
cp.write(f)
return f.getvalue()
return dict(cloud_credential=f.getvalue())
def build_passwords(self, inventory_update, **kwargs):
"""Build a dictionary of authentication/credential information for
@ -1038,31 +1045,32 @@ class RunInventoryUpdate(BaseTask):
# `awx/plugins/inventory` directory; those files should be kept in
# sync with those in Ansible core at all times.
passwords = kwargs.get('passwords', {})
cloud_credential = kwargs.get('private_data_files', {}).get('cloud_credential', '')
if inventory_update.source == 'ec2':
if passwords.get('source_username', '') and passwords.get('source_password', ''):
env['AWS_ACCESS_KEY_ID'] = passwords['source_username']
env['AWS_SECRET_ACCESS_KEY'] = passwords['source_password']
env['EC2_INI_PATH'] = kwargs.get('private_data_file', '')
env['EC2_INI_PATH'] = cloud_credential
elif inventory_update.source == 'rax':
env['RAX_CREDS_FILE'] = kwargs.get('private_data_file', '')
env['RAX_CREDS_FILE'] = cloud_credential
env['RAX_REGION'] = inventory_update.source_regions or 'all'
# Set this environment variable so the vendored package won't
# complain about not being able to determine its version number.
env['PBR_VERSION'] = '0.5.21'
elif inventory_update.source == 'vmware':
env['VMWARE_INI'] = kwargs.get('private_data_file', '')
env['VMWARE_INI'] = cloud_credential
env['VMWARE_HOST'] = passwords.get('source_host', '')
env['VMWARE_USER'] = passwords.get('source_username', '')
env['VMWARE_PASSWORD'] = passwords.get('source_password', '')
elif inventory_update.source == 'azure':
env['AZURE_SUBSCRIPTION_ID'] = passwords.get('source_username', '')
env['AZURE_CERT_PATH'] = kwargs['private_data_file']
env['AZURE_CERT_PATH'] = cloud_credential
elif inventory_update.source == 'gce':
env['GCE_EMAIL'] = passwords.get('source_username', '')
env['GCE_PROJECT'] = passwords.get('source_project', '')
env['GCE_PEM_FILE_PATH'] = kwargs['private_data_file']
env['GCE_PEM_FILE_PATH'] = cloud_credential
elif inventory_update.source == 'openstack':
env['OPENSTACK_CONFIG_FILE'] = kwargs.get('private_data_file', '')
env['OPENSTACK_CONFIG_FILE'] = cloud_credential
elif inventory_update.source == 'file':
# FIXME: Parse source_env to dict, update env.
pass
@ -1163,7 +1171,6 @@ class RunInventoryUpdate(BaseTask):
def get_idle_timeout(self):
return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None)
class RunAdHocCommand(BaseTask):
'''
Celery task to run an ad hoc command using ansible.
@ -1180,8 +1187,10 @@ class RunAdHocCommand(BaseTask):
# If we were sent SSH credentials, decrypt them and send them
# back (they will be written to a temporary file).
creds = ad_hoc_command.credential
if creds:
return decrypt_field(creds, 'ssh_key_data') or None
private_data = {}
if creds and creds.ssh_key_data not in (None, ''):
private_data['ad_hoc_credential'] = decrypt_field(creds, 'ssh_key_data') or ''
return private_data
def build_passwords(self, ad_hoc_command, **kwargs):
'''
@ -1321,7 +1330,7 @@ class RunAdHocCommand(BaseTask):
'''
If using an SSH key, return the path for use by ssh-agent.
'''
return kwargs.get('private_data_file', '')
return kwargs.get('private_data_files', {}).get('ad_hoc_credential', '')
def should_use_proot(self, instance, **kwargs):
'''
@ -1345,10 +1354,15 @@ class RunSystemJob(BaseTask):
args = ['awx-manage', system_job.job_type]
try:
json_vars = json.loads(system_job.extra_vars)
if 'days' in json_vars:
args.extend(['--days', str(json_vars['days'])])
if 'days' in json_vars and system_job.job_type != 'cleanup_facts':
args.extend(['--days', str(json_vars.get('days', 60))])
if system_job.job_type == 'cleanup_jobs':
args.extend(['--jobs', '--project-updates', '--inventory-updates', '--management-jobs'])
if system_job.job_type == 'cleanup_facts':
if 'older_than' in json_vars:
args.extend(['--older_than', str(json_vars['older_than'])])
if 'granularity' in json_vars:
args.extend(['--granularity', str(json_vars['granularity'])])
# Keeping this around in case we want to break this out
# if 'jobs' in json_vars and json_vars['jobs']:
# args.extend(['--jobs'])

View File

@ -28,6 +28,12 @@ class CleanupFactsCommandFunctionalTest(BaseCommandMixin, BaseTest, MongoDBRequi
result, stdout, stderr = self.run_command('cleanup_facts', granularity='1w',older_than='5d')
self.assertEqual(stdout, 'Deleted 0 facts.\n')
def test_invoke_all_deleted(self):
self.create_hosts_and_facts(datetime(year=2015, day=2, month=1, microsecond=0), 10, 20)
result, stdout, stderr = self.run_command('cleanup_facts', granularity='0d', older_than='0d')
self.assertEqual(stdout, 'Deleted 200 facts.\n')
def test_invoke_params_required(self):
result, stdout, stderr = self.run_command('cleanup_facts')
self.assertIsInstance(result, CommandError)

View File

@ -1898,3 +1898,19 @@ class InventoryUpdatesTest(BaseTransactionTest):
other_inv_src_opts = {'source': 'custom', 'source_script': script_data['id']}
with self.current_user(self.super_django_user):
self.put(other_inv_src, other_inv_src_opts, expect=400)
def test_update_from_openstack(self):
api_url = getattr(settings, 'TEST_OPENSTACK_HOST', '')
api_user = getattr(settings, 'TEST_OPENSTACK_USER', '')
api_password = getattr(settings, 'TEST_OPENSTACK_PASSWORD', '')
api_project = getattr(settings, 'TEST_OPENSTACK_PROJECT', '')
if not all([api_url, api_user, api_password, api_project]):
self.skipTest("No test openstack credentials defined")
self.create_test_license_file()
credential = Credential.objects.create(kind='openstack',
host=api_url,
username=api_user,
password=api_password,
project=api_project)
inventory_source = self.update_inventory_source(self.group, source='openstack', credential=credential)
self.check_inventory_source(inventory_source)

View File

@ -482,19 +482,37 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
# Invalid auth can't trigger the launch endpoint
self.check_invalid_auth(launch_url, {}, methods=('post',))
# Implicit, attached credentials
with self.current_user(self.user_sue):
response = self.post(launch_url, {}, expect=202)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'new')
# Explicit, override credentials
with self.current_user(self.user_sue):
response = self.post(launch_url, {'credential': self.cred_doug.pk}, expect=202)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk)
# Can't launch a job template without a credential defined
# Explicit, override credentials
with self.current_user(self.user_sue):
response = self.post(launch_url, {'credential_id': self.cred_doug.pk}, expect=202)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'new')
self.assertEqual(j.credential.pk, self.cred_doug.pk)
# Can't launch a job template without a credential defined (or if we
# pass an invalid/inactive credential value).
with self.current_user(self.user_sue):
response = self.post(no_launch_url, {}, expect=400)
response = self.post(no_launch_url, {'credential': 0}, expect=400)
response = self.post(no_launch_url, {'credential_id': 0}, expect=400)
response = self.post(no_launch_url, {'credential': 'one'}, expect=400)
response = self.post(no_launch_url, {'credential_id': 'one'}, expect=400)
self.cred_doug.mark_inactive()
response = self.post(no_launch_url, {'credential': self.cred_doug.pk}, expect=400)
response = self.post(no_launch_url, {'credential_id': self.cred_doug.pk}, expect=400)
# Job Templates without projects can not be launched
with self.current_user(self.user_sue):
@ -503,9 +521,9 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
jt = JobTemplate.objects.get(pk=response['id'])
jt.project = None
jt.save()
launch_url = reverse('api:job_template_launch',
args=(response['id'],))
self.post(launch_url, {}, expect=400)
launch_url2 = reverse('api:job_template_launch',
args=(response['id'],))
self.post(launch_url2, {}, expect=400)
# Job Templates without inventory can not be launched
with self.current_user(self.user_sue):
@ -514,9 +532,15 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
jt = JobTemplate.objects.get(pk=response['id'])
jt.inventory = None
jt.save()
launch_url = reverse('api:job_template_launch',
args=(response['id'],))
self.post(launch_url, {}, expect=400)
launch_url3 = reverse('api:job_template_launch',
args=(response['id'],))
self.post(launch_url3, {}, expect=400)
# Job Templates with deleted credentials cannot be launched.
self.cred_sue.mark_inactive()
with self.current_user(self.user_sue):
response = self.post(launch_url, {}, expect=400)
class JobTest(BaseJobTestMixin, django.test.TestCase):
@ -1128,6 +1152,27 @@ class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TestCase):
job_extra = json.loads(job.extra_vars)
self.assertTrue("favorite_color" in job_extra)
# launch job template with required survey without providing survey data
with self.current_user(self.user_sue):
self.post(url, json.loads(TEST_SIMPLE_REQUIRED_SURVEY), expect=200)
response = self.get(launch_url)
self.assertTrue('favorite_color' in response['variables_needed_to_start'])
response = self.post(launch_url, dict(extra_vars=dict()), expect=400)
# Note: The below assertion relies on how survey_variable_validation() crafts
# the error message
self.assertIn("'favorite_color' value missing", response['variables_needed_to_start'])
# launch job template with required survey without providing survey data and without
# even providing extra_vars
with self.current_user(self.user_sue):
self.post(url, json.loads(TEST_SIMPLE_REQUIRED_SURVEY), expect=200)
response = self.get(launch_url)
self.assertTrue('favorite_color' in response['variables_needed_to_start'])
response = self.post(launch_url, {}, expect=400)
# Note: The below assertion relies on how survey_variable_validation() crafts
# the error message
self.assertIn("'favorite_color' value missing", response['variables_needed_to_start'])
with self.current_user(self.user_sue):
response = self.post(url, json.loads(TEST_SIMPLE_NONREQUIRED_SURVEY), expect=200)
response = self.get(launch_url)

View File

@ -486,8 +486,11 @@ class ProjectsTest(BaseTransactionTest):
# can add credentials to a user (if user or org admin or super user)
self.post(other_creds, data=new_credentials, expect=401)
self.post(other_creds, data=new_credentials, expect=401, auth=self.get_invalid_credentials())
new_credentials['team'] = team.pk
result = self.post(other_creds, data=new_credentials, expect=201, auth=self.get_super_credentials())
cred_user = result['id']
self.assertEqual(result['team'], None)
del new_credentials['team']
new_credentials['name'] = 'credential2'
self.post(other_creds, data=new_credentials, expect=201, auth=self.get_normal_credentials())
new_credentials['name'] = 'credential3'
@ -497,9 +500,12 @@ class ProjectsTest(BaseTransactionTest):
# can add credentials to a team
new_credentials['name'] = 'credential'
new_credentials['user'] = other.pk
self.post(team_creds, data=new_credentials, expect=401)
self.post(team_creds, data=new_credentials, expect=401, auth=self.get_invalid_credentials())
self.post(team_creds, data=new_credentials, expect=201, auth=self.get_super_credentials())
result = self.post(team_creds, data=new_credentials, expect=201, auth=self.get_super_credentials())
self.assertEqual(result['user'], None)
del new_credentials['user']
new_credentials['name'] = 'credential2'
result = self.post(team_creds, data=new_credentials, expect=201, auth=self.get_normal_credentials())
new_credentials['name'] = 'credential3'
@ -611,6 +617,25 @@ class ProjectsTest(BaseTransactionTest):
cred_put_t = self.put(edit_creds2, data=d_cred_team, expect=200, auth=self.get_normal_credentials())
self.put(edit_creds2, data=d_cred_team, expect=403, auth=self.get_other_credentials())
# Reassign credential between team and user.
with self.current_user(self.super_django_user):
self.post(team_creds, data=dict(id=cred_user.pk), expect=204)
response = self.get(edit_creds1)
self.assertEqual(response['team'], team.pk)
self.assertEqual(response['user'], None)
self.post(other_creds, data=dict(id=cred_user.pk), expect=204)
response = self.get(edit_creds1)
self.assertEqual(response['team'], None)
self.assertEqual(response['user'], other.pk)
self.post(other_creds, data=dict(id=cred_team.pk), expect=204)
response = self.get(edit_creds2)
self.assertEqual(response['team'], None)
self.assertEqual(response['user'], other.pk)
self.post(team_creds, data=dict(id=cred_team.pk), expect=204)
response = self.get(edit_creds2)
self.assertEqual(response['team'], team.pk)
self.assertEqual(response['user'], None)
cred_put_t['disassociate'] = 1
team_url = reverse('api:team_credentials_list', args=(cred_put_t['team'],))
self.post(team_url, data=cred_put_t, expect=204, auth=self.get_normal_credentials())

View File

@ -322,6 +322,13 @@ class UsersTest(BaseTest):
orig = User.objects.get(pk=self.super_django_user.pk)
self.assertTrue(orig.username != 'change')
def test_user_delete_non_existant_user(self):
user_pk = self.normal_django_user.pk
fake_pk = user_pk + 1000
self.assertFalse(User.objects.filter(pk=fake_pk).exists(), "We made up a fake pk and it happened to exist")
url = reverse('api:user_detail', args=(fake_pk,))
self.delete(url, expect=404, auth=self.get_super_credentials())
def test_password_not_shown_in_get_operations_for_list_or_detail(self):
url = reverse('api:user_detail', args=(self.super_django_user.pk,))
data = self.get(url, expect=200, auth=self.get_super_credentials())

View File

@ -24,6 +24,7 @@ from django.utils.encoding import smart_str
# PyCrypto
from Crypto.Cipher import AES
logger = logging.getLogger('awx.main.utils')
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore',
'get_ansible_version', 'get_awx_version', 'update_scm_url',
@ -380,10 +381,13 @@ def get_system_task_capacity():
def emit_websocket_notification(endpoint, event, payload):
from awx.main.socket import Socket
with Socket('websocket', 'w') as websocket:
payload['event'] = event
payload['endpoint'] = endpoint
websocket.publish(payload)
try:
with Socket('websocket', 'w', nowait=True, logger=logger) as websocket:
payload['event'] = event
payload['endpoint'] = endpoint
websocket.publish(payload)
except Exception:
pass
_inventory_updates = threading.local()

View File

@ -72,6 +72,16 @@ Author: Eric Johnson <erjohnso@google.com>
Version: 0.0.1
'''
__requires__ = ['pycrypto>=2.6']
try:
import pkg_resources
except ImportError:
# Use pkg_resources to find the correct versions of libraries and set
# sys.path appropriately when there are multiversion installs. We don't
# fail here as there is code that better expresses the errors where the
# library is used.
pass
USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
USER_AGENT_VERSION="v1"

View File

@ -56,7 +56,7 @@ class OpenStackInventory(object):
self.openstack_config = os_client_config.config.OpenStackConfig(
config_files)
self.clouds = shade.openstack_clouds(self.openstack_config)
self.refresh = refresh
self.refresh = True
self.cache_max_age = self.openstack_config.get_cache_max_age()
cache_path = self.openstack_config.get_cache_path()
@ -101,9 +101,9 @@ class OpenStackInventory(object):
continue
server_vars = meta['server_vars']
hostvars[server.name][
'ansible_ssh_host'] = server_vars['interface_ip']
hostvars[server.name]['ansible_ssh_host'] = server_vars['interface_ip']
hostvars[server.name]['openstack'] = server_vars
hostvars[server.name]['id'] = server_vars['id']
for group in meta['groups']:
groups[group].append(server.name)

View File

@ -118,7 +118,7 @@ class AzureInventory(object):
the Windows Azure API provides.
"""
if hostname not in self.host_metadata:
return "No host found: %s" % hostname
return "No host found: %s" % json.dumps(self.host_metadata)
if jsonify:
return json.dumps(self.host_metadata[hostname])
return self.host_metadata[hostname]
@ -220,12 +220,19 @@ class AzureInventory(object):
def add_deployment(self, cloud_service, deployment):
"""Adds a deployment to the inventory and index"""
for role in deployment.role_instance_list.role_instances:
for ie in role.instance_endpoints.instance_endpoints:
if ie.name == 'SSH':
self.add_instance(role.instance_name, deployment, ie.public_port, cloud_service)
break
try:
# Default port 22 unless port found with name 'SSH'
port = '22'
for ie in role.instance_endpoints.instance_endpoints:
if ie.name == 'SSH':
port = ie.public_port
break
except AttributeError as e:
pass
finally:
self.add_instance(role.instance_name, deployment, port, cloud_service, role.instance_status)
def add_instance(self, hostname, deployment, ssh_port, cloud_service):
def add_instance(self, hostname, deployment, ssh_port, cloud_service, status):
"""Adds an instance to the inventory and index"""
dest = urlparse(deployment.url).hostname
@ -234,7 +241,9 @@ class AzureInventory(object):
self.index[hostname] = deployment.name
self.host_metadata[hostname] = dict(ansible_ssh_host=dest,
ansible_ssh_port=int(ssh_port))
ansible_ssh_port=int(ssh_port),
instance_status=status,
private_id=deployment.private_id)
# List of all azure deployments
self.push(self.inventory, "azure", hostname)

View File

@ -513,15 +513,15 @@ AZURE_REGIONS_BLACKLIST = []
# Inventory variable name/value for determining whether a host is active
# in Microsoft Azure.
AZURE_ENABLED_VAR = 'status'
AZURE_ENABLED_VALUE = 'created'
AZURE_ENABLED_VAR = 'instance_status'
AZURE_ENABLED_VALUE = 'ReadyRole'
# Filter for allowed group and host names when importing inventory from
# Microsoft Azure.
AZURE_GROUP_FILTER = r'^.+$'
AZURE_HOST_FILTER = r'^.+$'
AZURE_EXCLUDE_EMPTY_GROUPS = True
AZURE_INSTANCE_ID_VAR = None
AZURE_INSTANCE_ID_VAR = 'private_id'
# ---------------------
# ----- OpenStack -----
@ -531,7 +531,7 @@ OPENSTACK_ENABLED_VALUE = 'ACTIVE'
OPENSTACK_GROUP_FILTER = r'^.+$'
OPENSTACK_HOST_FILTER = r'^.+$'
OPENSTACK_EXCLUDE_EMPTY_GROUPS = True
OPENSTACK_INSTANCE_ID_VAR = None
OPENSTACK_INSTANCE_ID_VAR = "id"
# ---------------------
# -- Activity Stream --

View File

@ -30,10 +30,7 @@
// WebKit-style focus
.tab-focus() {
// Default
outline: thin dotted;
// WebKit
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
outline: 0;
}
// Center-align a block level element

View File

@ -180,7 +180,6 @@ var tower = angular.module('Tower', [
.constant('$timezones.definitions.location', urlPrefix + 'lib/angular-tz-extensions/tz/data')
.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/jobs', {
@ -644,6 +643,7 @@ var tower = angular.module('Tower', [
if (!/^\/(login|logout)/.test($location.path())) {
// capture most recent URL, excluding login/logout
$rootScope.lastPath = $location.path();
$rootScope.enteredPath = $location.path();
$cookieStore.put('lastPath', $location.path());
}

View File

@ -36,6 +36,7 @@ export function AdhocCtrl($scope, $rootScope, $location, $routeParams,
$scope.id = id;
$scope.argsPopOver = "<p>These arguments are used with the" +
" specified module.</p>";
// fix arguments help popover based on the module selected
$scope.moduleChange = function () {
// NOTE: for selenium testing link -
@ -64,20 +65,13 @@ export function AdhocCtrl($scope, $rootScope, $location, $routeParams,
$scope.providedHostPatterns = $scope.limit;
delete $rootScope.hostPatterns;
if ($scope.removeLookUpInitialize) {
$scope.removeLookUpInitialize();
}
$scope.removeLookUpInitialize = $scope.$on('lookUpInitialize', function () {
LookUpInit({
scope: $scope,
form: form,
current_item: (!Empty($scope.credential_id)) ? $scope.credential_id : null,
list: CredentialList,
field: 'credential',
input_type: 'radio'
});
Wait('stop'); // END: form population
LookUpInit({
scope: $scope,
form: form,
current_item: (!Empty($scope.credential_id)) ? $scope.credential_id : null,
list: CredentialList,
field: 'credential',
input_type: 'radio'
});
if ($scope.removeChoicesReady) {
@ -87,7 +81,10 @@ export function AdhocCtrl($scope, $rootScope, $location, $routeParams,
choicesReadyCount++;
if (choicesReadyCount === 2) {
$scope.$emit('lookUpInitialize');
// this sets the default options for the selects as specified by the controller.
$scope.verbosity = $scope.adhoc_verbosity_options[$scope.verbosity_field.default];
$("#forks-number").spinner("value", $scope.forks_field.default);
Wait('stop'); // END: form population
}
});
@ -210,23 +207,6 @@ export function AdhocCtrl($scope, $rootScope, $location, $routeParams,
credential: $scope.credential,
callback: 'ContinueCred'
});
// // Launch the adhoc job
// Rest.setUrl(url);
// Rest.post(data)
// .success(function (data) {
// Wait('stop');
// $location.path("/ad_hoc_commands/" + data.id);
// })
// .error(function (data, status) {
// ProcessErrors($scope, data, status, form, { hdr: 'Error!',
// msg: 'Failed to launch adhoc command. POST returned status: ' +
// status });
// // TODO: still need to implement popping up a password prompt
// // if the credential requires it. The way that the current end-
// // point works is that I find out if I need to ask for a
// // password from POST, thus I get an error response.
// });
};
// Remove all data input into the form
@ -237,6 +217,8 @@ export function AdhocCtrl($scope, $rootScope, $location, $routeParams,
}
$scope.limit = $scope.providedHostPatterns;
KindChange({ scope: $scope, form: form, reset: false });
$scope.verbosity = $scope.adhoc_verbosity_options[$scope.verbosity_field.default];
$("#forks-number").spinner("value", $scope.forks_field.default);
};
}

View File

@ -105,6 +105,16 @@ export function Authenticate($log, $cookieStore, $compile, $window, $rootScope,
if ($location.path() === '/logout') {
//if logout request, clear AuthToken and user session data
Authorization.logout();
} else if ($location.path() === '/login') {
if ($rootScope.enteredPath) {
$rootScope.lastPath = $rootScope.enteredPath;
} else if (!$rootScope.lastPath) {
// your last path was home
$cookieStore.remove('lastPath');
$rootScope.lastPath = '/home';
}
} else {
$rootScope.enteredPath = $location.path();
}
e = angular.element(document.getElementById('login-modal-content'));
@ -116,7 +126,7 @@ export function Authenticate($log, $cookieStore, $compile, $window, $rootScope,
"<div class=\"login-alert\" ng-show=\"sessionExpired\">Your session timed out due to inactivity. Please sign in.</div>\n" +
"<form id=\"login-form\" name=\"loginForm\" class=\"form-horizontal\" autocomplete=\"off\" novalidate >\n" +
"<div class=\"form-group\">\n" +
"<label class=\"control-label col-md-offset-1 col-md-2 col-sm-offset-1 col-sm-2 col-xs-3 prepend-asterisk\">Username</label>\n" +
"<label class=\"control-label col-md-offset-1 col-md-2 col-sm-offset-1 col-sm-2 col-xs-3 prepend-asterisk prepend-asterisk--login\">Username</label>\n" +
"<div class=\"col-md-8 col-sm-8 col-xs-9\">\n" +
"<input type=\"text\" name=\"login_username\" class=\"form-control\" ng-model=\"login_username\"" +
"id=\"login-username\" autocomplete=\"off\" required>\n" +
@ -125,7 +135,7 @@ export function Authenticate($log, $cookieStore, $compile, $window, $rootScope,
"</div>\n" +
"</div>\n" +
"<div class=\"form-group\">\n" +
"<label class=\"control-label col-md-offset-1 col-md-2 col-sm-offset-1 col-sm-2 col-xs-3 prepend-asterisk\">Password</label>\n" +
"<label class=\"control-label col-md-offset-1 col-md-2 col-sm-offset-1 col-sm-2 col-xs-3 prepend-asterisk prepend-asterisk--login\">Password</label>\n" +
"<div class=\"col-md-8 col-sm-8 col-xs-9\">\n" +
"<input type=\"password\" name=\"login_password\" id=\"login-password\" class=\"form-control\"" +
"ng-model=\"login_password\" required autocomplete=\"off\">\n" +

View File

@ -249,7 +249,7 @@ JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$route
export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GetBasePath,
InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait, Empty, ToJSON,
CallbackHelpInit, SurveyControllerInit, Prompt) {
CallbackHelpInit, SurveyControllerInit, Prompt, GetChoices) {
ClearScope();
@ -274,18 +274,6 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $
$scope.parseType = 'yaml';
ParseTypeChange({ scope: $scope, field_id: 'job_templates_variables', onChange: callback });
$scope.job_type_options = [
{ value: 'run', label: 'Run' },
{ value: 'check', label: 'Check' },
{ value: 'scan' , label: 'Scan'}
];
$scope.verbosity_options = [
{ value: 0, label: 'Default' },
{ value: 1, label: 'Verbose' },
{ value: 3, label: 'Debug' }
];
$scope.playbook_options = [];
$scope.allow_callbacks = 'false';
@ -315,34 +303,70 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $
CloudCredentialList.name = 'cloudcredentials';
CloudCredentialList.iterator = 'cloudcredential';
LookUpInit({
url: GetBasePath('credentials') + '?cloud=true',
scope: $scope,
form: form,
current_item: null,
list: CloudCredentialList,
field: 'cloud_credential',
hdr: 'Select Cloud Credential',
input_type: 'radio'
});
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: $scope,
form: form,
current_item: null,
list: CredentialList,
field: 'credential',
hdr: 'Select Machine Credential',
input_type: "radio"
});
SurveyControllerInit({
scope: $scope,
parent_scope: $scope
});
if ($scope.removeLookUpInitialize) {
$scope.removeLookUpInitialize();
}
$scope.removeLookUpInitialize = $scope.$on('lookUpInitialize', function () {
LookUpInit({
url: GetBasePath('credentials') + '?cloud=true',
scope: $scope,
form: form,
current_item: null,
list: CloudCredentialList,
field: 'cloud_credential',
hdr: 'Select Cloud Credential',
input_type: 'radio'
});
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: $scope,
form: form,
current_item: null,
list: CredentialList,
field: 'credential',
hdr: 'Select Machine Credential',
input_type: "radio"
});
});
var selectCount = 0;
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () {
selectCount++;
if (selectCount === 2) {
// this sets the default options for the selects as specified by the controller.
$scope.verbosity = $scope.verbosity_options[$scope.verbosity_field.default];
$scope.job_type = $scope.job_type_options[$scope.job_type_field.default];
$scope.$emit('lookUpInitialize');
}
});
// setup verbosity options select
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'verbosity',
variable: 'verbosity_options',
callback: 'choicesReadyVerbosity'
});
// setup job type options select
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'job_type',
variable: 'job_type_options',
callback: 'choicesReadyVerbosity'
});
// Update playbook select whenever project value changes
selectPlaybook = function (oldValue, newValue) {
@ -440,7 +464,7 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $
break;
}
if (msg) {
Alert('Waning', msg, 'alert-info');
Alert('Warning', msg, 'alert-info');
}
})
.error(function (data, status) {
@ -621,7 +645,7 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $
JobTemplatesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope',
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit',
'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON', 'CallbackHelpInit', 'SurveyControllerInit', 'Prompt'
'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON', 'CallbackHelpInit', 'SurveyControllerInit', 'Prompt', 'GetChoices'
];
@ -653,19 +677,6 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log,
$scope.parseType = 'yaml';
$scope.showJobType = false;
// Our job type options
$scope.job_type_options = [
{ value: 'run', label: 'Run' },
{ value: 'check', label: 'Check' },
{ value: 'scan', label: 'Scan'}
];
$scope.verbosity_options = [
{ value: 0, label: 'Default' },
{ value: 1, label: 'Verbose' },
{ value: 3, label: 'Debug' }
];
SurveyControllerInit({
scope: $scope,
parent_scope: $scope,
@ -770,7 +781,7 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log,
}
Wait('stop');
if (msg) {
Alert('Waning', msg, 'alert-info');
Alert('Warning', msg, 'alert-info');
}
})
.error(function (data, status) {
@ -963,7 +974,7 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log,
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
choicesCount++;
if (choicesCount === 2) {
if (choicesCount === 4) {
$scope.$emit('LoadJobs');
}
});
@ -984,6 +995,24 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log,
callback: 'choicesReady'
});
// setup verbosity options lookup
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'verbosity',
variable: 'verbosity_options',
callback: 'choicesReady'
});
// setup job type options lookup
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'job_type',
variable: 'job_type_options',
callback: 'choicesReady'
});
function saveCompleted() {
setTimeout(function() {
$scope.$apply(function() {

View File

@ -104,6 +104,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $routePa
scope: scheduled_scope,
list: ScheduledJobsList,
id: 'scheduled-jobs-tab',
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
url: GetBasePath('schedules') + '?next_run__isnull=false',
pageSize: max_rows
});

View File

@ -136,6 +136,7 @@ export function PermissionsAdd($scope, $rootScope, $compile, $location, $log, $r
master.category = 'Inventory';
master.inventoryrequired = true;
master.projectrequired = false;
$scope.run_ad_hoc_commands = false;
LookUpInit({
scope: $scope,
@ -155,6 +156,22 @@ export function PermissionsAdd($scope, $rootScope, $compile, $location, $log, $r
input_type: 'radio'
});
$scope.changeAdhocCommandCheckbox = function () {
if ($scope.category === 'Deploy') {
$scope.run_ad_hoc_command = false;
} else {
if ($scope.permission_type === 'admin') {
$scope.run_ad_hoc_commands = true;
$("#permission_run_ad_hoc_commands_chbox").attr("disabled", true);
} else {
if (!$scope.run_ad_hoc_commands) {
$scope.run_ad_hoc_commands = false;
}
$("#permission_run_ad_hoc_commands_chbox").attr("disabled", false);
}
}
};
// Save
$scope.formSave = function () {
var fld, url, data = {};
@ -226,12 +243,25 @@ export function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $
defaultUrl = GetBasePath('base') + 'permissions/' + id + '/',
master = {};
$scope.changeAdhocCommandCheckbox = function () {
if ($scope.category === 'Deploy') {
$scope.run_ad_hoc_command = false;
} else {
if ($scope.permission_type === 'admin') {
$scope.run_ad_hoc_commands = true;
$("#permission_run_ad_hoc_commands_chbox").attr("disabled", true);
} else {
if (!$scope.run_ad_hoc_commands) {
$scope.run_ad_hoc_commands = false;
}
$("#permission_run_ad_hoc_commands_chbox").attr("disabled", false);
}
}
};
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset();
$scope.selectCategory = function (resetIn) {
var reset = (resetIn === false) ? false : true;
PermissionCategoryChange({ scope: $scope, reset: reset });
@ -285,6 +315,8 @@ export function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $
input_type: 'radio'
});
$scope.changeAdhocCommandCheckbox();
if (!$scope.PermissionAddAllowed) {
// If not a privileged user, disable access
$('form[name="permission_form"]').find('select, input, button').each(function () {

View File

@ -48,11 +48,11 @@ function HostStatusGraph($compile, $window) {
if(data.hosts.total+data.hosts.failed>0){
data = [
{ "label": "Successful",
"color": "#00aa00",
"color": "#60D66F",
"value" : data.hosts.total
} ,
{ "label": "Failed",
"color" : "#aa0000",
"color" : "#ff5850",
"value" : data.hosts.failed
}
];
@ -68,7 +68,7 @@ function HostStatusGraph($compile, $window) {
return '<b>'+x+'</b>'+ '<p>' + Math.floor(y.replace(',','')) + ' Hosts ' + '</p>';
})
.labelType("percent")
.color(['#00aa00', '#aa0000']);
.color(['#60D66F', '#ff5850']);
d3.select(element.find('svg')[0])
.datum(data)

View File

@ -46,12 +46,12 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG
scope.jobType = jobtype;
var timeFormat, graphData = [
{ "color": "#00aa00",
{ "color": "#60D66F",
"key": "Successful",
"values": data.jobs.successful
},
{ "key" : "Failed" ,
"color" : "#aa0000",
"color" : "#ff5850",
"values": data.jobs.failed
}
];

View File

@ -82,14 +82,15 @@ export default
}
},
become_enabled: {
label: 'Enable Become for Credential',
label: 'Enable Privilege Escalation',
type: 'checkbox',
editRequired: false
// awPopOver: '<p>If checked, user will be become the user ' +
// 'specified by the credential.</p>',
// dataPlacement: 'right',
// dataTitle: 'Enable Become for Credential',
// dataContainer: 'body'
addRequired: false,
editRequird: false,
column: 2,
awPopOver: "<p>If enabled, run this playbook as an administrator. This is the equivalent of passing the<code> --become</code> option to the <code> ansible</code> command. </p>",
dataPlacement: 'right',
dataTitle: 'Become Privilege Escalation',
dataContainer: "body"
},
verbosity: {
label: 'Verbosity',
@ -103,8 +104,28 @@ export default
'out of the command run that are supported.',
dataTitle: 'Module',
dataPlacement: 'right',
dataContainer: 'body'
}
dataContainer: 'body',
"default": 1
},
forks: {
label: 'Forks',
id: 'forks-number',
type: 'number',
integer: true,
min: 0,
spinner: true,
"default": 0,
addRequired: false,
editRequired: false,
'class': "input-small",
column: 1,
awPopOver: '<p>The number of parallel or simultaneous processes to use while executing the command. 0 signifies ' +
'the default value from the <a id="ansible_forks_docs" href=\"http://docs.ansible.com/intro_configuration.html#the-ansible-configuration-file\" ' +
' target=\"_blank\">ansible configuration file</a>.</p>',
dataTitle: 'Forks',
dataPlacement: 'right',
dataContainer: "body"
},
},
buttons: {

View File

@ -157,7 +157,7 @@ export default
labelBind: 'hostLabel',
type: 'text',
ngShow: "kind.value == 'vmware' || kind.value == 'openstack'",
awPopOverWatch: "projectPopOver",
awPopOverWatch: "hostPopOver",
awPopOver: "set in helpers/credentials",
dataTitle: 'Host',
dataPlacement: 'right',

View File

@ -176,7 +176,7 @@ export default
'class': "input-small",
column: 1,
awPopOver: '<p>The number of parallel or simultaneous processes to use while executing the playbook. 0 signifies ' +
'the default value from the <a href=\"http://docs.ansible.com/intro_configuration.html#the-ansible-configuration-file\" ' +
'the default value from the <a id="ansible_forks_docs" href=\"http://docs.ansible.com/intro_configuration.html#the-ansible-configuration-file\" ' +
' target=\"_blank\">ansible configuration file</a>.</p>',
dataTitle: 'Forks',
dataPlacement: 'right',
@ -199,7 +199,7 @@ export default
label: 'Verbosity',
type: 'select',
ngOptions: 'v.label for v in verbosity_options track by v.value',
"default": 0,
"default": 1,
addRequired: true,
editRequired: true,
column: 1,

View File

@ -97,6 +97,7 @@ export default
labelClass: 'prepend-asterisk',
type: 'radio_group',
class: 'squeeze',
ngChange: 'changeAdhocCommandCheckbox()',
options: [{
label: 'Read',
value: 'read',

View File

@ -53,11 +53,12 @@ export default
},
source_regions: {
label: 'Regions',
type: 'text',
type: 'select',
ngOptions: 'source.label for source in source_region_choices track by source.value',
multiSelect: true,
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure')",
addRequired: false,
editRequired: false,
awMultiselect: 'source_region_choices',
dataTitle: 'Source Regions',
dataPlacement: 'right',
awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
@ -87,11 +88,12 @@ export default
},
group_by: {
label: 'Only Group By',
type: 'text',
type: 'select',
ngShow: "source && source.value == 'ec2'",
ngOptions: 'source.label for source in group_by_choices track by source.value',
addRequired: false,
editRequired: false,
awMultiselect: 'group_by_choices',
multiSelect: true,
dataTitle: 'Only Group By',
dataPlacement: 'right',
awPopOver: "<p>Select which groups to create automatically. " +
@ -115,9 +117,10 @@ export default
ngShow: "source && source.value === 'custom'",
sourceModel: 'source_script',
sourceField: 'name',
ngClick: 'lookUpSource_script()' , //'lookUpCustom_inventory()',
addRequired: true,
editRequired: true
ngClick: 'lookUpSource_script()' ,
addRequired: false,
editRequired: false,
ngRequired: "source && source.value === 'custom'",
},
extra_vars: {
label: 'Environment Variables', //"{{vars_label}}" ,
@ -162,7 +165,9 @@ export default
},
inventory_variables: {
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value == 'vmware')",
ngShow: "source && (source.value == 'vmware' || " +
"source.value == 'openstack')",
type: 'textarea',
addRequired: false,
editRequird: false,

View File

@ -1,5 +1,5 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
* Copyright (c) 2015 AnsibleWorks, Inc.
*
* GroupsHelper
*
@ -231,105 +231,113 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
}
])
/**
*
* TODO: Document
*
*/
.factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange', 'CustomInventoryList' ,
function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange, CustomInventoryList) {
return function (params) {
.factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange', 'CustomInventoryList', 'CreateSelect2',
function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange, CustomInventoryList, CreateSelect2) {
return function (params) {
var scope = params.scope,
form = params.form,
kind, url, callback, invUrl;
var scope = params.scope,
form = params.form,
kind, url, callback, invUrl;
if (!Empty(scope.source)) {
if (scope.source.value === 'file') {
scope.sourcePathRequired = true;
} else {
scope.sourcePathRequired = false;
// reset fields
scope.source_path = '';
scope[form.name + '_form'].source_path.$setValidity('required', true);
}
if (scope.source.value === 'rax') {
scope.source_region_choices = scope.rax_regions;
//$('#s2id_group_source_regions').select2('data', []);
$('#s2id_source_source_regions').select2('data', [{
id: 'all',
text: 'All'
}]);
$('#source_form').addClass('squeeze');
} else if (scope.source.value === 'ec2') {
scope.source_region_choices = scope.ec2_regions;
$('#s2id_source_source_regions').select2('data', [{
id: 'all',
text: 'All'
}]);
if (!Empty(scope.source)) {
if (scope.source.value === 'file') {
scope.sourcePathRequired = true;
} else {
scope.sourcePathRequired = false;
// reset fields
scope.source_path = '';
scope[form.name + '_form'].source_path.$setValidity('required', true);
}
if (scope.source.value === 'rax') {
scope.source_region_choices = scope.rax_regions;
$('#source_form').addClass('squeeze');
CreateSelect2({
element: '#source_source_regions'
});
} else if (scope.source.value === 'ec2') {
scope.source_region_choices = scope.ec2_regions;
scope.group_by_choices = scope.ec2_group_by;
$('#s2id_group_by').select2('data', []);
$('#source_form').addClass('squeeze');
} else if (scope.source.value === 'gce') {
scope.source_region_choices = scope.gce_regions;
//$('#s2id_group_source_regions').select2('data', []);
$('#s2id_source_source_regions').select2('data', [{
id: 'all',
text: 'All'
}]);
$('#source_form').addClass('squeeze');
} else if (scope.source.value === 'azure') {
scope.source_region_choices = scope.azure_regions;
//$('#s2id_group_source_regions').select2('data', []);
$('#s2id_source_source_regions').select2('data', [{
id: 'all',
text: 'All'
}]);
$('#source_form').addClass('squeeze');
}
if(scope.source.value==="custom"){
// need to filter the possible custom scripts by the organization defined for the current inventory
invUrl = GetBasePath('inventory_scripts') + '?organization='+scope.$parent.inventory.organization;
LookUpInit({
url: invUrl,
scope: scope,
form: form,
hdr: "Select Custom Inventory",
list: CustomInventoryList,
field: 'source_script',
input_type: 'radio'
CreateSelect2({
element: '#source_source_regions'
});
scope.extra_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'extra_vars', parse_variable: form.fields.extra_vars.parseTypeName,
field_id: 'source_extra_vars', onReady: callback });
}
if(scope.source.value==="vmware"){
scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName,
field_id: 'source_inventory_variables', onReady: callback });
}
if (scope.source.value === 'rax' || scope.source.value === 'ec2'|| scope.source.value==='gce' || scope.source.value === 'azure' || scope.source.value === 'vmware') {
kind = (scope.source.value === 'rax') ? 'rax' : (scope.source.value==='gce') ? 'gce' : (scope.source.value==='azure') ? 'azure' : (scope.source.value === 'vmware') ? 'vmware' : 'aws' ;
url = GetBasePath('credentials') + '?cloud=true&kind=' + kind;
LookUpInit({
url: url,
scope: scope,
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
CreateSelect2({
element: '#source_group_by'
});
if ($('#group_tabs .active a').text() === 'Source' && (scope.source.value === 'ec2' )) {
callback = function(){ Wait('stop'); };
Wait('start');
scope.source_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'source_vars', parse_variable: form.fields.source_vars.parseTypeName,
field_id: 'source_source_vars', onReady: callback });
}
}
}
};
}
} else if (scope.source.value === 'gce') {
scope.source_region_choices = scope.gce_regions;
$('#source_form').addClass('squeeze');
CreateSelect2({
element: '#source_source_regions'
});
} else if (scope.source.value === 'azure') {
scope.source_region_choices = scope.azure_regions;
$('#source_form').addClass('squeeze');
CreateSelect2({
element: '#source_source_regions'
});
}
if(scope.source.value==="custom"){
// need to filter the possible custom scripts by the organization defined for the current inventory
invUrl = GetBasePath('inventory_scripts') + '?organization='+scope.$parent.inventory.organization;
LookUpInit({
url: invUrl,
scope: scope,
form: form,
hdr: "Select Custom Inventory",
list: CustomInventoryList,
field: 'source_script',
input_type: 'radio'
});
scope.extra_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'extra_vars', parse_variable: form.fields.extra_vars.parseTypeName,
field_id: 'source_extra_vars', onReady: callback });
}
if(scope.source.value==="vmware" ||
scope.source.value==="openstack"){
scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName,
field_id: 'source_inventory_variables', onReady: callback });
}
if (scope.source.value === 'rax' ||
scope.source.value === 'ec2' ||
scope.source.value==='gce' ||
scope.source.value === 'azure' ||
scope.source.value === 'vmware' ||
scope.source.value === 'openstack') {
if (scope.source.value === 'ec2') {
kind = 'aws';
} else {
kind = scope.source.value;
}
url = GetBasePath('credentials') + '?cloud=true&kind=' + kind;
LookUpInit({
url: url,
scope: scope,
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
if ($('#group_tabs .active a').text() === 'Source' &&
(scope.source.value === 'ec2' )) {
callback = function(){ Wait('stop'); };
Wait('start');
scope.source_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'source_vars',
parse_variable: form.fields.source_vars.parseTypeName,
field_id: 'source_source_vars', onReady: callback });
}
}
}
};
}
])
/**
@ -700,11 +708,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
.factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', '$compile', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate',
'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find', 'WatchInventoryWindowResize',
'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize',
'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', 'CreateSelect2',
function ($rootScope, $location, $log, $routeParams, $compile, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait,
GetChoices, UpdateGroup, SourceChange, Find, WatchInventoryWindowResize, ParseVariableString, ToJSON, GroupsScheduleListInit,
SourceForm, SetSchedulesInnerDialogSize) {
SourceForm, SetSchedulesInnerDialogSize, CreateSelect2) {
return function (params) {
var parent_scope = params.scope,
@ -905,7 +913,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
Wait('start');
ParseTypeChange({ scope: sources_scope, variable: 'source_vars', parse_variable: SourceForm.fields.source_vars.parseTypeName,
field_id: 'source_source_vars', onReady: waitStop });
} else if (sources_scope.source && (sources_scope.source.value === 'vmware')) {
} else if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
sources_scope.source.value === 'openstack')) {
Wait('start');
ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: SourceForm.fields.inventory_variables.parseTypeName,
field_id: 'source_inventory_variables', onReady: waitStop });
@ -1033,8 +1042,19 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
sources_scope.sourceChange(); //set defaults that rely on source value
if (data.source_regions) {
if (data.source === 'ec2' || data.source === 'rax') {
set = (data.source === 'ec2') ? sources_scope.ec2_regions : sources_scope.rax_regions;
if (data.source === 'ec2' ||
data.source === 'rax' ||
data.source === 'gce' ||
data.source === 'azure') {
if (data.source === 'ec2') {
set = sources_scope.ec2_regions;
} else if (data.source === 'rax') {
set = sources_scope.rax_regions;
} else if (data.source === 'gce') {
set = sources_scope.gce_regions;
} else if (data.source === 'azure') {
set = sources_scope.azure_regions;
}
opts = [];
list = data.source_regions.split(',');
for (i = 0; i < list.length; i++) {
@ -1048,7 +1068,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
}
}
master.source_regions = opts;
$('#s2id_source_source_regions').select2('data', opts);
CreateSelect2({
element: "#source_source_regions",
opts: opts
});
}
} else {
// If empty, default to all
@ -1056,7 +1080,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
id: 'all',
text: 'All'
}];
$('#s2id_source_source_regions').select2('data', master.source_regions);
}
if (data.group_by && data.source === 'ec2') {
set = sources_scope.ec2_group_by;
@ -1073,7 +1096,10 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
}
}
master.group_by = opts;
$('#s2id_source_group_by').select2('data', opts);
CreateSelect2({
element: "#source_group_by",
opts: opts
});
}
sources_scope.group_update_url = data.related.update;
modal_scope.$emit('groupVariablesLoaded'); // JT-- "groupVariablesLoaded" is where the schedule info is loaded, so I make a call after the sources_scope.source has been loaded
@ -1249,17 +1275,19 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
};
// Create a string out of selected list of regions
regions = $('#s2id_source_source_regions').select2("data");
r = [];
for (i = 0; i < regions.length; i++) {
r.push(regions[i].id);
if(sources_scope.source_regions){
regions = $('#source_source_regions').select2("data");
r = [];
for (i = 0; i < regions.length; i++) {
r.push(regions[i].id);
}
data.source_regions = r.join();
}
data.source_regions = r.join();
if (sources_scope.source && (sources_scope.source.value === 'ec2')) {
data.instance_filters = sources_scope.instance_filters;
// Create a string out of selected list of regions
group_by = $('#s2id_source_group_by').select2("data");
group_by = $('#source_group_by').select2("data");
r = [];
for (i = 0; i < group_by.length; i++) {
r.push(group_by[i].id);
@ -1276,7 +1304,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.extra_vars, true);
}
if (sources_scope.source && (sources_scope.source.value === 'vmware')) {
if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
sources_scope.source.value === 'openstack')) {
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true);
}
@ -1376,7 +1405,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
group_created = true;
group_id = data.id;
sources_scope.source_url = data.related.inventory_source;
if (properties_scope.variables) {
if (properties_scope.variables && properties_scope.variables !== "---") {
modal_scope.$emit('updateVariables', json_data, data.related.variable_data);
}
else {

View File

@ -1146,7 +1146,7 @@ export default
graph_data.push({
label: 'OK',
value: (scope.host_summary.ok === scope.host_summary.total) ? 1 : scope.host_summary.ok,
color: '#00aa00'
color: '#60D66F'
});
}
if (scope.host_summary.changed) {
@ -1167,7 +1167,7 @@ export default
graph_data.push({
label: 'Failed',
value: (scope.host_summary.failed === scope.host_summary.total) ? 1 : scope.host_summary.failed,
color: '#aa0000'
color: '#ff5850'
});
}

View File

@ -710,9 +710,8 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
if(data.vault_password === "ASK"){
passwords.push("vault_password");
}
scope.$emit(callback, passwords);
}
scope.$emit(callback, passwords);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',

View File

@ -19,7 +19,7 @@
export default
angular.module('LoadConfigHelper', ['Utilities'])
.factory('LoadConfig', ['$log', '$rootScope', '$http', 'ProcessErrors', 'Store', function($log, $rootScope, $http, ProcessErrors, Store) {
.factory('LoadConfig', ['$log', '$rootScope', '$http', '$location', 'ProcessErrors', 'Store', function($log, $rootScope, $http, $location, ProcessErrors, Store) {
return function() {
if ($rootScope.removeLoadConfig) {
@ -42,6 +42,7 @@ angular.module('LoadConfigHelper', ['Utilities'])
});
});
$rootScope.enteredPath = $location.path();
// Load js/local_config.js
$http({ method:'GET', url: $basePath + 'js/local_config.js' })
.success(function(data) {

View File

@ -27,8 +27,8 @@ import listGenerator from 'tower/shared/list-generator/main';
export default
angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'PaginationHelpers', listGenerator.name, 'ApiLoader', 'ModalDialog'])
.factory('LookUpInit', ['Alert', 'Rest', 'generateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog',
function (Alert, Rest, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) {
.factory('LookUpInit', ['Alert', 'Rest', 'ProcessErrors', 'generateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog',
function (Alert, Rest, ProcessErrors, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) {
return function (params) {
var parent_scope = params.scope,
@ -63,6 +63,31 @@ export default
$('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl);
$('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field);
// Auto populate the field if there is only one result
Rest.setUrl(defaultUrl);
Rest.get()
.success(function (data) {
if (data.count === 1) {
parent_scope[field] = data.results[0].id;
if (parent_scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) {
parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
data.results[0][form.fields[field].sourceField];
if (parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) {
parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]
.$setValidity('awlookup', true);
}
}
if (parent_scope[form.name + '_form']) {
parent_scope[form.name + '_form'].$setDirty();
}
}
})
.error(function (data, status) {
ProcessErrors(parent_scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST returned status: ' +
status });
});
parent_scope['lookUp' + name] = function () {

View File

@ -570,6 +570,7 @@ export default
list = params.list,
id = params.id,
url = params.url,
searchSize = params.searchSize,
pageSize = params.pageSize || 5,
spinner = (params.spinner === undefined) ? true : params.spinner;
@ -579,7 +580,7 @@ export default
id: id,
breadCrumbs: false,
scope: scope,
searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12',
searchSize: (searchSize) ? searchSize : 'col-lg-6 col-md-6 col-sm-6 col-xs-12',
showSearch: true
});

View File

@ -78,6 +78,9 @@ export default
},{
name: "Microsoft Azure",
value: "azure"
},{
name: "Openstack",
value: "openstack"
}],
sourceModel: 'inventory_source',
sourceField: 'source',
@ -86,7 +89,7 @@ export default
has_external_source: {
label: 'Has external source?',
searchType: 'in',
searchValue: 'ec2,rax,vmware,azure,gce',
searchValue: 'ec2,rax,vmware,azure,gce,openstack',
searchOnly: true,
sourceModel: 'inventory_source',
sourceField: 'source'
@ -170,4 +173,4 @@ export default
}
}
});
});

View File

@ -47,6 +47,9 @@ export default
},{
name: "Microsoft Azure",
value: "azure"
},{
name: "Openstack",
value: "openstack"
}],
sourceModel: 'inventory_source',
sourceField: 'source',
@ -55,7 +58,7 @@ export default
has_external_source: {
label: 'Has external source?',
searchType: 'in',
searchValue: 'ec2,rax,vmware,azure,gce',
searchValue: 'ec2,rax,vmware,azure,gce,openstack',
searchOnly: true,
sourceModel: 'inventory_source',
sourceField: 'source'

View File

@ -70,7 +70,12 @@ angular.module('AuthService', ['ngCookies', Utilities.name])
$cookieStore.put( 'lastPath', '/portal');
$rootScope.lastPath = '/portal';
}
else if ($cookieStore.get('lastPath') !== '/home' || $cookieStore.get('lastPath') !== '/'){
// do nothing
$rootScope.lastPath = $cookieStore.get('lastPath');
}
else {
// your last path was home
$cookieStore.remove('lastPath');
$rootScope.lastPath = '/home';
}

View File

@ -659,6 +659,75 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
}
])
/**
* @ngdoc method
* @name shared.function:Utilities#CreateSelect2
* @methodOf shared.function:Utilities
* @description Make a regular select drop down a select2 dropdown
* To make a ``<select>`` field a select2 select 2, create the field in the
* form definition with the multiSelect flag set to true. In the controller
* of the page in question, call the CreateSelect2 factory with the element
* id (be sure to include the appropriate jquery identifier in the parameter)
* or any options that should be pre-selected in the select2 field.
* The array of options should be formatted as
* ```
* [
* {
* id: 'id' ,
* text: 'text'
* },
* {
* id: 'id' ,
* text: 'text'
* }
* ]
* ```
*/
.factory('CreateSelect2', [
function () {
return function (params) {
var element = params.element,
options = params.opts;
$.fn.select2.amd.require([
"select2/utils",
"select2/dropdown",
"select2/dropdown/attachContainer",
"select2/dropdown/search",
], function (Utils, DropdownAdapter, AttachContainer, DropdownSearch) {
var CustomAdapter = Utils.Decorate(
Utils.Decorate(
DropdownAdapter,
DropdownSearch
),
AttachContainer
);
$(element).select2({
dropdownAdapter: CustomAdapter,
multiple: 'true',
theme: "bootstrap",
width: '100%'
});
if(options){
for (var d = 0; d < $(element + " option").length; d++) {
var item = $(element + " option")[d];
for ( var f = 0; f < options.length; f++){
if(item.value === options[f].id){
// Append it to the select
item.setAttribute('selected', 'selected');
}
}
}
$(element).trigger('change');
}
});
};
}])
/**
* @ngdoc method
* @name shared.function:Utilities#GetChoices

View File

@ -620,33 +620,6 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
};
}])
.directive('awMultiSelect', [ function() {
return {
require: 'ngModel',
link: function(scope, elm) {
$(elm).multiselect ({
buttonClass: 'btn-default, btn-mini',
buttonWidth: 'auto',
buttonContainer: '<div class="btn-group" />',
maxHeight: false,
buttonText: function(options) {
if (options.length === 0) {
return 'None selected <b class="caret"></b>';
}
if (options.length > 3) {
return options.length + ' selected <b class="caret"></b>';
}
var selected = '';
options.each(function() {
selected += $(this).text() + ', ';
});
return selected.substr(0, selected.length -2) + ' <b class="caret"></b>';
}
});
}
};
}])
//
// Enable jqueryui spinner widget on a numeric input field
//
@ -728,43 +701,6 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
};
}])
/*
awMultiSelect
Relies on select2.js to create a multi-select with tags.
*/
.directive('awMultiselect', [ function() {
return {
require: '^form', //inject the form into the ctrl parameter
link: function(scope, elm, attrs, ctrl) {
$(elm).select2({
multiple: true,
data: function() {
// dynamically load the possible values
if (scope[attrs.awMultiselect]) {
var set = scope[attrs.awMultiselect],
opts = [], i;
for (i=0; i < set.length; i++) {
opts.push({ id: set[i].value, text: set[i].label });
}
return {results: opts };
}
return {results: { id: '', text: ''} };
}
});
// Make sure the form buttons enable when the value changes
$(elm).on('change', function() {
ctrl.$setDirty();
if (!scope.$$phase) {
scope.$digest();
}
});
}
};
}])
/*
* Make an element draggable. Used on inventory groups tree.
*

View File

@ -671,10 +671,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += Attr(field, 'type');
html += "ng-model=\"" + fld + '" ';
html += "name=\"" + fld + '" ';
if (form.name === "permission") {
html += "ng-disabled='permission_type === \"admin\"'";
html += "ng-checked='permission_type === \"admin\"'";
}
html += (field.ngChange) ? Attr(field, 'ngChange') : "";
html += "id=\"" + form.name + "_" + fld + "_chbox\" ";
html += (idx !== undefined) ? "_" + idx : "";
@ -791,7 +787,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
field.awRequiredWhen.variable + "\" " : "";
html += (field.awValidUrl) ? "aw-valid-url " : "";
html += (field.associated && this.form.fields[field.associated].ask) ? "ng-disabled=\"" + field.associated + "_ask\" " : "";
html += (field.awMultiselect) ? "aw-multiselect=\"" + field.awMultiselect + "\" " : "";
html += ">\n";
}
@ -915,7 +910,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
field.awRequiredWhen.variable + "' " : "";
html += (field.awValidUrl) ? "aw-valid-url " : "";
html += (field.associated && this.form.fields[field.associated].ask) ? "ng-disabled='" + field.associated + "_ask' " : "";
html += (field.awMultiselect) ? "aw-multiselect='" + field.awMultiselect + "' " : "";
html += ">\n";
}
@ -1038,7 +1032,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "class=\"form-control";
html += (field['class']) ? " " + field['class'] : "";
html += "\" ";
html += this.attr(field, 'ngOptions');
html += (field.ngOptions) ? this.attr(field, 'ngOptions') : "" ;
html += (field.ngChange) ? this.attr(field, 'ngChange') : "";
html += (field.ngDisabled) ? this.attr(field, 'ngDisabled'): "";
html += (field.ngRequired) ? this.attr(field, 'ngRequired') : "";

View File

@ -34,7 +34,7 @@ angular.module('PromptDialog', ['Utilities'])
var dialog = angular.element(document.getElementById('prompt-modal')),
scope = dialog.scope(), cls, local_backdrop;
scope.promptHeader = params.hdr;
scope.promptBody = $sce.trustAsHtml(params.body);
scope.promptAction = params.action;

View File

@ -23,8 +23,8 @@ export default [ function() {
barWidth: 7,
barSpacing: 2,
zeroBarColor: 'grey',
posBarColor: '#00aa00',
negBarColor: '#aa0000',
posBarColor: '#60D66F',
negBarColor: '#ff5850',
tooltipFormatter: scope.formatter,
tooltipFormat: '{{value:jobs}}',
tooltipValueLookups: {

View File

@ -45,13 +45,13 @@ angular.module('DashboardCountsWidget', ['RestServices', 'Utilities'])
element.html(html);
$compile(element)(scope);
if(dashboard.hosts.failed>0 ){
$('#failed-hosts').replaceWith("<a style=\"color:#aa0000\" href=\"/#/home/hosts/?has_active_failures=true\" id=\"failed-hosts\">"+dashboard.hosts.failed+"</a>");
$('#failed-hosts').replaceWith("<a style=\"color: #ff5850\" href=\"/#/home/hosts/?has_active_failures=true\" id=\"failed-hosts\">"+dashboard.hosts.failed+"</a>");
}
if(dashboard.inventories.inventory_failed>0 ){
$('#failed-inventories').replaceWith("<a style=\"color:#aa0000\" href=/#/inventories/?inventory_sources_with_failures id=\"failed-inventories\">"+dashboard.inventories.inventory_failed+"</a>");
$('#failed-inventories').replaceWith("<a style=\"color: #ff5850\" href=/#/inventories/?inventory_sources_with_failures id=\"failed-inventories\">"+dashboard.inventories.inventory_failed+"</a>");
}
if(dashboard.projects.failed>0 ){
$('#failed-projects').replaceWith("<a style=\"color:#aa0000\" href=\"/#/projects/?status=failed\" id=\"failed-projects\">"+dashboard.projects.failed+"</a>");
$('#failed-projects').replaceWith("<a style=\"color: #ff5850\" href=\"/#/projects/?status=failed\" id=\"failed-projects\">"+dashboard.projects.failed+"</a>");
}
scope.$emit('WidgetLoaded');

View File

@ -63,12 +63,12 @@ angular.module('HostPieChartWidget', ['RestServices', 'Utilities'])
data = [
{
"label": "Successful",
"color": "#00aa00",
"color": "#60D66F",
"value" : dashboard.hosts.total
} ,
{
"label": "Failed",
"color" : "#aa0000",
"color" : "#ff5850",
"value" : dashboard.hosts.failed
}
];
@ -85,7 +85,7 @@ angular.module('HostPieChartWidget', ['RestServices', 'Utilities'])
.tooltipContent(function(x, y) {
return '<b>'+x+'</b>'+ '<p>' + Math.floor(y.replace(',','')) + ' Hosts ' + '</p>';
})
.color(['#00aa00', '#aa0000']);
.color(['#60D66F', '#ff5850']);
host_pie_chart.pie.pieLabelsOutside(true).labelType("percent");

View File

@ -117,13 +117,13 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities'])
var timeFormat, graphData = [
{
"color": "#00aa00",
"color": "#60D66F",
"key": "Successful",
"values": data.jobs.successful
},
{
"key" : "Failed" ,
"color" : "#aa0000",
"color" : "#ff5850",
"values": data.jobs.failed
}
];
@ -216,4 +216,4 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities'])
};
}
]);
]);

View File

@ -12,14 +12,11 @@
@blue: #1778c3; /* logo blue */
@blue-link: #1778c3;
@blue-dark: #2a6496; /* link hover */
@green: #00aa00; // Ansible OK
@grey: #A9A9A9;
@grey-txt: #707070;
@info: #d9edf7; /* alert info background color */
@info-border: #bce8f1; /* alert info border color */
@info-color: #3a87ad;
@red: #aa0000; // Ansible Failed
@red-hover: #AE3F3A;
@unreachable: #FF0000;
@changed: #FF9900; // Ansible Changed
@skipped: #00aaaa; // Ansible Skipped
@ -30,6 +27,10 @@
@tip-background: #0088CC;
@tip-color: #fff;
@green: #60D66F;
@red: #ff5850;
@red-hover: #FA8C87;
@font-face {
font-family: 'Open Sans';
@ -186,6 +187,18 @@ a:focus {
}
}
// removing all the pesky outlines on buttons/links/etc.
a:focus,
a:active,
button:focus,
button:active,
i:focus,
i:active,
.btn:focus,
.btn:active:focus {
outline: 0;
}
.jqstooltip{
background-color: black !important;
border-radius:4px;
@ -365,6 +378,11 @@ textarea.allowresize {
.prepend-asterisk:before {
content: "\002A\00A0";
color: @red;
margin-right: -5px;
}
.prepend-asterisk--login:before {
margin-right: -2px;
}
.subtitle {
@ -656,10 +674,10 @@ dd {
/* Outline required fields in Red when there is an error */
.form-control.ng-dirty.ng-invalid, .form-control.ng-dirty.ng-invalid:focus {
border-color: rgba(204, 0, 0, 0.8);
border-color: rgba(255, 88, 80, 0.8);
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(204, 0, 0, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(204, 0, 0, 0.6);
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(255, 88, 80, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(255, 88, 80, 0.6);
}
/* For some reason TB 3 RC1 does not provide an input-mini */
@ -1487,8 +1505,6 @@ input[type="checkbox"].checkbox-no-label {
border-right: 1px solid #ddd;
}
#groups_table .actions .cancel { padding-right: 2px; }
.node-toggle, .node-no-toggle {
/* also used on job evetns */
float: none;

View File

@ -1,25 +1,23 @@
{
"name": "select2",
"version": "3.5.1",
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
"main": [
"select2.js",
"select2.css",
"select2.png",
"select2x2.png",
"select2-spinner.gif"
"dist/js/select2.js",
"dist/css/select2.css"
],
"dependencies": {
"jquery": ">= 1.7.1"
"repository": {
"type": "git",
"url": "git@github.com:select2/select2.git"
},
"homepage": "https://github.com/ivaynberg/select2",
"_release": "3.5.1",
"version": "4.0.0-rc.2",
"_release": "4.0.0-rc.2",
"_resolution": {
"type": "version",
"tag": "3.5.1",
"commit": "621a3f9532357148b05efc0602f7e06b44ff9bb4"
"tag": "4.0.0-rc.2",
"commit": "69e2d73c42ae261b924a052e45d9b6f6dbc60fc6"
},
"_source": "git://github.com/ivaynberg/select2.git",
"_target": "~3.5.1",
"_originalSource": "select2",
"_direct": true
"_target": "~4.0.0",
"_originalSource": "select2"
}

View File

@ -0,0 +1,6 @@
[*]
indent_style = space
end_of_line = lf
[*.js]
indent_size = 2

View File

@ -1,2 +1,2 @@
.idea
node_modules
dist/js/i18n/build.txt

View File

@ -0,0 +1,4 @@
src/js/banner.*.js
src/js/wrapper.*.js
tests/vendor/*.js
tests/helpers.js

View File

@ -0,0 +1,25 @@
{
"bitwise": true,
"camelcase": true,
"curly": true,
"es3": true,
"eqnull": true,
"freeze": true,
"globals": {
"console": false,
"define": false,
"document": false,
"expect": false,
"MockContainer": false,
"module": false,
"require": false,
"test": false,
"window": false
},
"indent": 2,
"maxlen": 80,
"noarg": true,
"nonew": true,
"quotmark": "single",
"undef": true
}

View File

@ -0,0 +1,20 @@
language: node_js
node_js:
- 0.10
env:
global:
- secure: XMNK8GVxkwKa6oLl7nJwgg/wmY1YDk5rrMd+UXz26EDCsMDbiy1P7GhN2fEiBSLaQ7YfEuvaDcmzQxTrT0YTHp1PDzb2o9J4tIDdEkqPcv1y8xMaYDfmsN0rBPdBwZEg9H5zUgi7OdUbrGswSYxsKCE3x8EOqK89104HyOo1LN4=
- secure: BU5BPRx6H4O3WJ509YPixjUxg+hDF3z2BVJX6NiGmKWweqvCEYFfiiHLwDEgp/ynRcF9vGVi1V4Ly1jq7f8NIajbDZ5q443XchZFYFg78K/EwD5mK6LYt16zb7+Jn0KbzwHeGRGzc9AvcEYlW6i634cSCm4n3BnqtF5PpogSzdw=
script:
- grunt ci
notifications:
email: false
irc:
channels:
- "chat.freenode.net#select2"
on_success: change
on_failure: always

View File

@ -0,0 +1,110 @@
Contributing to Select2
=======================
Looking to contribute something to Select2? **Here's how you can help.**
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved.
Following these guidelines helps to communicate that you respect the time of
the developers managing and developing this open source project. In return,
they should reciprocate that respect in addressing your issue or assessing
patches and features.
Using the issue tracker
-----------------------
When [reporting bugs][reporting-bugs] or
[requesting features][requesting-features], the
[issue tracker on GitHub][issue-tracker] is the recommended channel to use.
The issue tracker **is not** a place for support requests. The
[mailing list][mailing-list] or [IRC channel][irc-channel] are better places to
get help.
Reporting bugs with Select2
---------------------------
We really appreciate clear bug reports that _consistently_ show an issue
_within Select2_.
The ideal bug report follows these guidelines:
1. **Use the [GitHub issue search][issue-search]** &mdash; Check if the issue
has already been reported.
2. **Check if the issue has been fixed** &mdash; Try to reproduce the problem
using the code in the `master` branch.
3. **Isolate the problem** &mdash; Try to create an
[isolated test case][isolated-case] that consistently reproduces the problem.
Please try to be as detailed as possible in your bug report, especially if an
isolated test case cannot be made. Some useful questions to include the answer
to are:
- What steps can be used to reproduce the issue?
- What is the bug and what is the expected outcome?
- What browser(s) and Operating System have you tested with?
- Does the bug happen consistently across all tested browsers?
- What version of jQuery are you using? And what version of Select2?
- Are you using Select2 with other plugins?
All of these questions will help others fix and identify any potential bugs.
Requesting features in Select2
------------------------------
Select2 is a large library that carries with it a lot of functionality. Because
of this, many feature requests will not be implemented in the core library.
Before starting work on a major feature for Select2, **contact the
[community][community] first** or you may risk spending a considerable amount of
time on something which the project developers are not interested in bringing
into the project.
Triaging issues and pull requests
---------------------------------
Anyone can help the project maintainers triage issues and review pull requests.
### Handling new issues
Select2 regularly receives new issues which need to be tested and organized.
When a new issue that comes in that is similar to another existing issue, it
should be checked to make sure it is not a duplicate. Duplicates issues should
be marked by replying to the issue with "Duplicate of #[issue number]" where
`[issue number]` is the url or issue number for the existing issue. This will
allow the project maintainers to quickly close off additional issues and keep
the discussion focused within a single issue.
If you can test issues that are reported to Select2 that contain test cases and
confirm under what conditions bugs happen, that will allow others to identify
what causes a bug quicker.
### Reviewing pull requests
It is very common for pull requests to be opened for issues that contain a clear
solution to the problem. These pull requests should be rigorously reviewed by
the community before being accepted. If you are not sure about a piece of
submitted code, or know of a better way to do something, do not hesitate to make
a comment on the pull request.
### Reviving old tickets
If you come across tickets which have not been updated for a while, you are
encouraged to revive them. While this can be as simple as saying `:+1:`, it is
best if you can include more information on the issue. Common bugs and feature
requests are more likely to be fixed, whether it is by the community or the
developers, so keeping tickets up to date is encouraged.
Licensing
---------
It should also be made clear that **all code contributed to Select** must be
licensable under the [MIT license][licensing]. Code that cannot be released
under this license **cannot be accepted** into the project.
[community]: https://select2.github.io/community.html
[reporting-bugs]: #reporting-bugs-with-select2
[requesting-features]: #requesting-features-in-select2
[issue-tracker]: https://github.com/select2/select2/issues
[mailing-list]: https://github.com/select2/select2#mailing-list
[irc-channel]: https://github.com/select2/select2#irc-channel
[issue-search]: https://github.com/select2/select2/search?q=&type=Issues
[isolated-case]: http://css-tricks.com/6263-reduced-test-cases/
[licensing]: https://github.com/select2/select2/blob/master/LICENSE.md

363
awx/ui/static/lib/select2/Gruntfile.js vendored Normal file
View File

@ -0,0 +1,363 @@
module.exports = function (grunt) {
// Full list of files that must be included by RequireJS
includes = [
'jquery.select2',
'almond'
];
fullIncludes = [
'jquery',
'jquery.mousewheel',
'select2/compat/matcher',
'select2/compat/initSelection',
'select2/compat/inputData',
'select2/compat/query',
'select2/dropdown/attachContainer',
'select2/dropdown/stopPropagation',
'select2/selection/stopPropagation'
].concat(includes);
var i18nModules = [];
var i18nPaths = {};
var i18nFiles = grunt.file.expand({
cwd: 'src/js'
}, 'select2/i18n/*.js');
var testFiles = grunt.file.expand('tests/**/*.html');
var testUrls = testFiles.map(function (filePath) {
return 'http://localhost:9999/' + filePath;
});
var testBuildNumber = "unknown";
if (process.env.TRAVIS_JOB_ID) {
testBuildNumber = "travis-" + process.env.TRAVIS_JOB_ID;
} else {
var currentTime = new Date();
testBuildNumber = "manual-" + currentTime.getTime();
}
for (var i = 0; i < i18nFiles.length; i++) {
var file = i18nFiles[i];
var name = file.split('.')[0];
i18nModules.push({
name: name
});
i18nPaths[name] = '../../' + name;
}
var minifiedBanner = '/*! Select2 <%= package.version %> | https://github.com/select2/select2/blob/master/LICENSE.md */';
grunt.initConfig({
package: grunt.file.readJSON('package.json'),
clean: {
docs: ['docs/_site']
},
concat: {
'dist': {
options: {
banner: grunt.file.read('src/js/wrapper.start.js'),
},
src: [
'dist/js/select2.js',
'src/js/wrapper.end.js'
],
dest: 'dist/js/select2.js'
},
'dist.full': {
options: {
banner: grunt.file.read('src/js/wrapper.start.js'),
},
src: [
'dist/js/select2.full.js',
'src/js/wrapper.end.js'
],
dest: 'dist/js/select2.full.js'
}
},
connect: {
tests: {
options: {
base: '.',
hostname: '127.0.0.1',
port: 9999
}
}
},
uglify: {
'dist': {
src: 'dist/js/select2.js',
dest: 'dist/js/select2.min.js',
options: {
banner: minifiedBanner
}
},
'dist.full': {
src: 'dist/js/select2.full.js',
dest: 'dist/js/select2.full.min.js',
options: {
banner: minifiedBanner
}
}
},
qunit: {
all: {
options: {
urls: testUrls
}
}
},
'saucelabs-qunit': {
all: {
options: {
build: testBuildNumber,
tags: ['tests', 'qunit'],
urls: testUrls,
testname: 'QUnit test for Select2',
browsers: [
{
browserName: 'internet explorer',
version: '8'
},
{
browserName: 'internet explorer',
version: '9'
},
{
browserName: 'internet explorer',
version: '10'
},
{
browserName: 'internet explorer',
version: '11'
},
{
browserName: 'firefox'
},
{
browserName: 'chrome'
},
{
browserName: 'opera',
version: '12'
}
]
}
}
},
'gh-pages': {
options: {
base: 'docs',
branch: 'master',
clone: 'node_modules/grunt-gh-pages/repo',
message: 'Updated docs with master',
push: true,
repo: 'git@github.com:select2/select2.github.io.git'
},
src: '**'
},
jekyll: {
options: {
src: 'docs',
dest: 'docs/_site'
},
build: {
d: null
},
serve: {
options: {
serve: true,
watch: true
}
}
},
jshint: {
options: {
jshintrc: true
},
code: {
src: ['src/js/**/*.js']
},
tests: {
src: ['tests/**/*.js']
}
},
sass: {
dist: {
options: {
outputStyle: 'compressed'
},
files: {
'dist/css/select2.min.css': [
'src/scss/core.scss',
'src/scss/theme/default/layout.css'
]
}
},
dev: {
options: {
outputStyle: 'nested'
},
files: {
'dist/css/select2.css': [
'src/scss/core.scss',
'src/scss/theme/default/layout.css'
]
}
}
},
symlink: {
docs: {
cwd: 'dist',
expand: true,
overwrite: false,
src: [
'*'
],
dest: 'docs/dist',
filter: 'isDirectory'
}
},
requirejs: {
'dist': {
options: {
baseUrl: 'src/js',
optimize: 'none',
name: 'select2/core',
out: 'dist/js/select2.js',
include: includes,
namespace: 'S2',
paths: {
almond: '../../vendor/almond-0.2.9',
jquery: 'jquery.shim'
},
wrap: {
startFile: 'src/js/banner.start.js',
endFile: 'src/js/banner.end.js'
}
}
},
'dist.full': {
options: {
baseUrl: 'src/js',
optimize: 'none',
name: 'select2/core',
out: 'dist/js/select2.full.js',
include: fullIncludes,
namespace: 'S2',
paths: {
almond: '../../vendor/almond-0.2.9',
jquery: 'jquery.shim',
'jquery.mousewheel': '../../vendor/jquery.mousewheel'
},
wrap: {
startFile: 'src/js/banner.start.js',
endFile: 'src/js/banner.end.js'
}
}
},
'i18n': {
options: {
baseUrl: 'src/js/select2/i18n',
dir: 'dist/js/i18n',
paths: i18nPaths,
modules: i18nModules,
namespace: 'S2',
wrap: {
start: minifiedBanner + grunt.file.read('src/js/banner.start.js'),
end: grunt.file.read('src/js/banner.end.js')
}
}
}
},
watch: {
js: {
files: [
'src/js/select2/**/*.js',
'tests/**/*.js'
],
tasks: [
'compile',
'test',
'minify'
]
},
css: {
files: [
'src/scss/**/*.scss'
],
tasks: [
'compile',
'minify'
]
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-symlink');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-gh-pages');
grunt.loadNpmTasks('grunt-jekyll');
grunt.loadNpmTasks('grunt-saucelabs');
grunt.loadNpmTasks('grunt-sass');
grunt.registerTask('default', ['compile', 'test', 'minify']);
grunt.registerTask('compile', [
'requirejs:dist', 'requirejs:dist.full', 'requirejs:i18n',
'concat:dist', 'concat:dist.full',
'sass:dev'
]);
grunt.registerTask('minify', ['uglify', 'sass:dist']);
grunt.registerTask('test', ['connect:tests', 'qunit', 'jshint']);
var ciTasks = [];
ciTasks.push('compile')
ciTasks.push('connect:tests');
// Can't run Sauce Labs tests in pull requests
if (process.env.TRAVIS_PULL_REQUEST == 'false') {
ciTasks.push('saucelabs-qunit');
}
ciTasks.push('qunit');
ciTasks.push('jshint');
grunt.registerTask('ci', ciTasks);
grunt.registerTask('docs', ['symlink:docs', 'jekyll:serve']);
grunt.registerTask('docs-release', ['default', 'clean:docs', 'gh-pages']);
};

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,20 +1,26 @@
Select2
=======
[![Build Status][travis-ci-image]][travis-ci-status]
Select2 is a jQuery-based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.
Select2 is a jQuery-based replacement for select boxes. It supports searching,
remote data sets, and pagination of results.
To get started, checkout examples and documentation at http://ivaynberg.github.com/select2
To get started, checkout examples and documentation at
https://select2.github.io/
Use cases
---------
* Enhancing native selects with search.
* Enhancing native selects with a better multi-select interface.
* Loading data from JavaScript: easily load items via ajax and have them searchable.
* Nesting optgroups: native selects only support one level of nested. Select2 does not have this restriction.
* Loading data from JavaScript: easily load items via AJAX and have them
searchable.
* Nesting optgroups: native selects only support one level of nesting. Select2
does not have this restriction.
* Tagging: ability to add new items on the fly.
* Working with large, remote datasets: ability to partially load a dataset based on the search term.
* Paging of large datasets: easy support for loading more pages when the results are scrolled to the end.
* Working with large, remote datasets: ability to partially load a dataset based
on the search term.
* Paging of large datasets: easy support for loading more pages when the results
are scrolled to the end.
* Templating: support for custom rendering of results and selections.
Browser compatibility
@ -24,76 +30,76 @@ Browser compatibility
* Firefox 10+
* Safari 3+
* Opera 10.6+
Usage
-----
You can source Select2 directly from a [CDN like JSDliver](http://www.jsdelivr.com/#!select2), [download it from this GitHub repo](https://github.com/ivaynberg/select2/tags), or use one of the integrations below.
You can source Select2 directly from a CDN like [JSDliver][jsdelivr] or
[CDNJS][cdnjs], [download it from this GitHub repo][releases], or use one of
the integrations below.
Integrations
------------
* [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org))
* [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails)
* [AngularUI](http://angular-ui.github.com/#directives-select2) ([AngularJS](angularjs.org))
* [Django](https://github.com/applegrew/django-select2)
* [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin)
* [Symfony2](https://github.com/avocode/FormExtensions)
* [Bootstrap 2](https://github.com/t0m/select2-bootstrap-css) and [Bootstrap 3](https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3) (CSS skins)
* [Meteor](https://github.com/nate-strauser/meteor-select2) (modern reactive JavaScript framework; + [Bootstrap 3 skin](https://github.com/esperadomedia/meteor-select2-bootstrap3-css/))
* [Meteor](https://jquery-select2.meteor.com)
* [Yii 2.x](http://demos.krajee.com/widgets#select2)
* [Yii 1.x](https://github.com/tonybolzan/yii-select2)
* [AtmosphereJS](https://atmospherejs.com/package/jquery-select2)
### Example Integrations
* [Knockout.js](https://github.com/ivaynberg/select2/wiki/Knockout.js-Integration)
* [Socket.IO](https://github.com/ivaynberg/select2/wiki/Socket.IO-Integration)
* [PHP](https://github.com/ivaynberg/select2/wiki/PHP-Example)
* [.Net MVC] (https://github.com/ivaynberg/select2/wiki/.Net-MVC-Example)
* [Wicket-Select2][wicket-select2] (Java / [Apache Wicket][wicket])
* [select2-rails][select2-rails] (Ruby on Rails)
* [AngularUI][angularui-select] ([AngularJS][angularjs])
* [Django][django-select2]
* [Symfony][symfony-select2]
* [Symfony2][symfony2-select2]
* [Bootstrap 2][bootstrap2-select2] and [Bootstrap 3][bootstrap3-select2]
(CSS skins)
* [Meteor][meteor-select2] ([Bootstrap 3 skin][meteor-select2-bootstrap3])
* [Meteor][meteor-select2-alt]
* [Yii 2.x][yii2-select2]
* [Yii 1.x][yii-select2]
* [AtmosphereJS][atmospherejs-select2]
Internationalization (i18n)
---------------------------
Select2 supports multiple languages by simply including the right language JS
file (`select2_locale_it.js`, `select2_locale_nl.js`, etc.) after `select2.js`.
file (`dist/js/i18n/it.js`, `dist/js/i18n/nl.js`, etc.) after
`dist/js/select2.js`.
Missing a language? Just copy `select2_locale_en.js.template`, translate
it, and make a pull request back to Select2 here on GitHub.
Missing a language? Just copy `src/js/select2/i18n/en.js`, translate it, and
make a pull request back to Select2 here on GitHub.
Bug tracker
-----------
Have a bug? Please create an issue here on GitHub!
https://github.com/ivaynberg/select2/issues
Mailing list
------------
Have a question? Ask on our mailing list!
select2@googlegroups.com
https://groups.google.com/d/forum/select2
Documentation
-------------
The documentation for Select2 is available
[through GitHub Pages][documentation] and is located within this repository
in the [`docs` folder][documentation-folder].
Community
---------
You can find out about the different ways to get in touch with the Select2
community at the [Select2 community page][community].
Copyright and license
---------------------
The license is available within the repository in the [LICENSE][license] file.
Copyright 2012 Igor Vaynberg
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
General Public License version 2 (the "GPL License"). You may choose either license to govern your
use of this software only upon the condition that you accept all of the terms of either the Apache
License or the GPL License.
You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
http://www.gnu.org/licenses/gpl-2.0.html
Unless required by applicable law or agreed to in writing, software distributed under the Apache License
or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the Apache License and the GPL License for the specific language governing
permissions and limitations under the Apache License and the GPL License.
[angularjs]: https://angularjs.org/
[angularui-select]: http://angular-ui.github.io/#ui-select
[atmospherejs-select2]: https://atmospherejs.com/package/jquery-select2
[bootstrap2-select2]: https://github.com/t0m/select2-bootstrap-css
[bootstrap3-select2]: https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3
[cdnjs]: http://www.cdnjs.com/libraries/select2
[community]: https://select2.github.io/community.html
[django-select2]: https://github.com/applegrew/django-select2
[documentation]: https://select2.github.io/
[documentation-folder]: https://github.com/select2/select2/tree/master/docs
[freenode]: https://freenode.net/
[jsdelivr]: http://www.jsdelivr.com/#!select2
[license]: LICENSE.md
[meteor-select2]: https://github.com/nate-strauser/meteor-select2
[meteor-select2-alt]: https://jquery-select2.meteor.com
[meteor-select2-bootstrap3]: https://github.com/zimme/meteor-select2-bootstrap3-css/
[releases]: https://github.com/select2/select2/releases
[select2-rails]: https://github.com/argerim/select2-rails
[symfony-select2]: https://github.com/19Gerhard85/sfSelect2WidgetsPlugin
[symfony2-select2]: https://github.com/avocode/FormExtensions
[travis-ci-image]: https://travis-ci.org/select2/select2.svg?branch=master
[travis-ci-status]: https://travis-ci.org/select2/select2
[wicket]: http://wicket.apache.org
[wicket-select2]: https://github.com/ivaynberg/wicket-select2
[yii-select2]: https://github.com/tonybolzan/yii-select2
[yii2-select2]: http://demos.krajee.com/widgets#select2

View File

@ -1,8 +1,12 @@
{
"name": "select2",
"version": "3.5.1",
"main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"],
"dependencies": {
"jquery": ">= 1.7.1"
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
"main": [
"dist/js/select2.js",
"dist/css/select2.css"
],
"repository": {
"type": "git",
"url": "git@github.com:select2/select2.git"
}
}

View File

@ -1,66 +1,19 @@
{
"name": "select2",
"repo": "ivaynberg/select2",
"repo": "select/select2",
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
"version": "3.5.1",
"demo": "http://ivaynberg.github.io/select2/",
"version": "4.0.0-rc.2",
"demo": "https://select2.github.io/",
"keywords": [
"jquery"
],
"main": "select2.js",
"main": "dist/js/select2.js",
"styles": [
"select2.css",
"select2-bootstrap.css"
"dist/css/select2.css"
],
"scripts": [
"select2.js",
"select2_locale_ar.js",
"select2_locale_bg.js",
"select2_locale_ca.js",
"select2_locale_cs.js",
"select2_locale_da.js",
"select2_locale_de.js",
"select2_locale_el.js",
"select2_locale_es.js",
"select2_locale_et.js",
"select2_locale_eu.js",
"select2_locale_fa.js",
"select2_locale_fi.js",
"select2_locale_fr.js",
"select2_locale_gl.js",
"select2_locale_he.js",
"select2_locale_hr.js",
"select2_locale_hu.js",
"select2_locale_id.js",
"select2_locale_is.js",
"select2_locale_it.js",
"select2_locale_ja.js",
"select2_locale_ka.js",
"select2_locale_ko.js",
"select2_locale_lt.js",
"select2_locale_lv.js",
"select2_locale_mk.js",
"select2_locale_ms.js",
"select2_locale_nl.js",
"select2_locale_no.js",
"select2_locale_pl.js",
"select2_locale_pt-BR.js",
"select2_locale_pt-PT.js",
"select2_locale_ro.js",
"select2_locale_ru.js",
"select2_locale_sk.js",
"select2_locale_sv.js",
"select2_locale_th.js",
"select2_locale_tr.js",
"select2_locale_uk.js",
"select2_locale_vi.js",
"select2_locale_zh-CN.js",
"select2_locale_zh-TW.js"
],
"images": [
"select2-spinner.gif",
"select2.png",
"select2x2.png"
"dist/js/select2.js",
"dist/js/i18n/*.js"
],
"license": "MIT"
}

View File

@ -1,28 +1,21 @@
{
"name":
"ivaynberg/select2",
"name": "select2/select2",
"description": "Select2 is a jQuery based replacement for select boxes.",
"version": "3.5.1",
"type": "component",
"homepage": "http://ivaynberg.github.io/select2/",
"license": "Apache-2.0",
"homepage": "https://select2.github.io/",
"license": "MIT",
"require": {
"robloach/component-installer": "*",
"components/jquery": ">=1.7.1"
"robloach/component-installer": "*"
},
"extra": {
"component": {
"scripts": [
"select2.js"
"dist/js/select2.js"
],
"files": [
"select2.js",
"select2_locale_*.js",
"select2.css",
"select2-bootstrap.css",
"select2-spinner.gif",
"select2.png",
"select2x2.png"
"dist/js/select2.js",
"dist/js/i18n/*.js",
"dist/css/select2.css"
]
}
}

View File

@ -0,0 +1,557 @@
/*! Select2 Bootstrap Theme v0.1.0-beta.1 | MIT License | github.com/fk/select2-bootstrap-theme */
.select2-container--bootstrap {
display: block;
/*------------------------------------*\
#COMMON STYLES
\*------------------------------------*/
/**
* Search field in the Select2 dropdown.
*/
/**
* No outline for all search fields - in the dropdown
* and inline in multi Select2s.
*/
/**
* Adjust Select2's choices hover and selected styles to match
* Bootstrap 3's default dropdown styles.
*
* @see http://getbootstrap.com/components/#dropdowns
*/
/**
* Address disabled Select2 styles.
*
* @see https://select2.github.io/examples.html#disabled
* @see http://getbootstrap.com/css/#forms-control-disabled
*/
/*------------------------------------*\
#DROPDOWN
\*------------------------------------*/
/**
* Dropdown border color and box-shadow.
*/
/**
* Limit the dropdown height.
*/
/*------------------------------------*\
#SINGLE SELECT2
\*------------------------------------*/
/*------------------------------------*\
#MULTIPLE SELECT2
\*------------------------------------*/
/**
* Address Bootstrap control sizing classes
*
* 1. Reset Bootstrap defaults.
* 2. Adjust the dropdown arrow button icon position.
*
* @see http://getbootstrap.com/css/#forms-control-sizes
*/
/* 1 */
/*------------------------------------*\
#RTL SUPPORT
\*------------------------------------*/
}
.select2-container--bootstrap .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
color: #555555;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
outline: 0;
}
.select2-container--bootstrap .select2-search--dropdown .select2-search__field {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
color: #555555;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}
.select2-container--bootstrap .select2-search__field {
outline: 0;
/* Firefox 18- */
/**
* Firefox 19+
*
* @see http://stackoverflow.com/questions/24236240/color-for-styled-placeholder-text-is-muted-in-firefox
*/
}
.select2-container--bootstrap .select2-search__field::-webkit-input-placeholder {
color: #999;
}
.select2-container--bootstrap .select2-search__field:-moz-placeholder {
color: #999;
}
.select2-container--bootstrap .select2-search__field::-moz-placeholder {
color: #999;
opacity: 1;
}
.select2-container--bootstrap .select2-search__field:-ms-input-placeholder {
color: #999;
}
.select2-container--bootstrap .select2-results__option {
/**
* Disabled results.
*
* @see https://select2.github.io/examples.html#disabled-results
*/
/**
* Hover state.
*/
/**
* Selected state.
*/
}
.select2-container--bootstrap .select2-results__option[role=group] {
padding: 0;
}
.select2-container--bootstrap .select2-results__option[aria-disabled=true] {
color: #777777;
cursor: not-allowed;
}
.select2-container--bootstrap .select2-results__option[aria-selected=true] {
background-color: #f5f5f5;
color: #262626;
}
.select2-container--bootstrap .select2-results__option--highlighted[aria-selected] {
background-color: #337ab7;
color: #fff;
}
.select2-container--bootstrap .select2-results__option .select2-results__option {
padding: 6px 12px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option {
margin-left: -12px;
padding-left: 24px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -24px;
padding-left: 36px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -36px;
padding-left: 48px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -48px;
padding-left: 60px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -60px;
padding-left: 72px;
}
.select2-container--bootstrap .select2-results__group {
color: #777777;
display: block;
padding: 6px 12px;
font-size: 12px;
line-height: 1.42857143;
white-space: nowrap;
}
.select2-container--bootstrap.select2-container--open {
/**
* Handle border radii of the container when the dropdown is showing.
*/
}
.select2-container--bootstrap.select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
border-color: #66afe9;
/**
* Make the dropdown arrow point up while the dropdown is visible.
*/
}
.select2-container--bootstrap.select2-container--open .select2-selection .select2-selection__arrow b {
border-color: transparent transparent #999 transparent;
border-width: 0 4px 4px 4px;
}
.select2-container--bootstrap.select2-container--open.select2-container--below .select2-selection {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-color: transparent;
}
.select2-container--bootstrap.select2-container--open.select2-container--above .select2-selection {
border-top-right-radius: 0;
border-top-left-radius: 0;
border-top-color: transparent;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection,
.select2-container--bootstrap.select2-container--disabled .select2-search__field {
cursor: not-allowed;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection,
.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice {
background-color: #eeeeee;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection__clear,
.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove {
display: none;
}
.select2-container--bootstrap .select2-dropdown {
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
border-color: #66afe9;
overflow-x: hidden;
margin-top: -1px;
}
.select2-container--bootstrap .select2-dropdown--above {
margin-top: 1px;
}
.select2-container--bootstrap .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
}
.select2-container--bootstrap .select2-selection--single {
height: 34px;
line-height: 1.42857143;
padding: 6px 24px 6px 12px;
/**
* Clear the selection.
*/
/**
* Adjust the single Select2's dropdown arrow button appearance.
*/
}
.select2-container--bootstrap .select2-selection--single .select2-selection__clear {
color: #999;
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__clear:hover {
color: #333;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__arrow {
position: absolute;
bottom: 0;
right: 12px;
top: 0;
width: 4px;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
border-color: #999 transparent transparent transparent;
border-style: solid;
border-width: 4px 4px 0 4px;
height: 0;
left: 0;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__rendered {
color: #555555;
padding: 0;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__placeholder {
color: #999;
}
.select2-container--bootstrap .select2-selection--multiple {
min-height: 34px;
/**
* Make Multi Select2's choices match Bootstrap 3's default button styles.
*/
/**
* Minus 2px borders.
*/
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
display: block;
line-height: 1.42857143;
list-style: none;
margin: 0;
overflow: hidden;
padding: 0;
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__placeholder {
color: #999;
float: left;
margin-top: 5px;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
color: #555555;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
cursor: default;
float: left;
margin: 5px 0 0 6px;
padding: 0 6px;
}
.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
background: transparent;
padding: 0 12px;
height: 32px;
line-height: 1.42857143;
margin-top: 0;
min-width: 5em;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 3px;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333;
}
.select2-container--bootstrap.input-sm, .select2-container--bootstrap.input-lg {
border-radius: 0;
font-size: 12px;
height: auto;
line-height: 1;
padding: 0;
}
.select2-container--bootstrap.input-sm .select2-selection--single, .input-group-sm .select2-container--bootstrap .select2-selection--single, .form-group-sm .select2-container--bootstrap .select2-selection--single {
border-radius: 3px;
font-size: 12px;
height: 30px;
line-height: 1.5;
padding: 5px 22px 5px 10px;
/* 2 */
}
.select2-container--bootstrap.input-sm .select2-selection--single .select2-selection__arrow b, .input-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b, .form-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
margin-left: -5px;
}
.select2-container--bootstrap.input-sm .select2-selection--multiple, .input-group-sm .select2-container--bootstrap .select2-selection--multiple, .form-group-sm .select2-container--bootstrap .select2-selection--multiple {
min-height: 30px;
}
.select2-container--bootstrap.input-sm .select2-selection--multiple .select2-selection__choice, .input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice, .form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
font-size: 12px;
line-height: 1.5;
margin: 4px 0 0 5px;
padding: 0 5px;
}
.select2-container--bootstrap.input-sm .select2-selection--multiple .select2-search--inline .select2-search__field, .input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field, .form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
padding: 0 10px;
font-size: 12px;
height: 28px;
line-height: 1.5;
}
.select2-container--bootstrap.input-lg .select2-selection--single, .input-group-lg .select2-container--bootstrap .select2-selection--single, .form-group-lg .select2-container--bootstrap .select2-selection--single {
border-radius: 6px;
font-size: 18px;
height: 46px;
line-height: 1.3333333;
padding: 10px 31px 10px 16px;
/* 1 */
}
.select2-container--bootstrap.input-lg .select2-selection--single .select2-selection__arrow, .input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow, .form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow {
width: 5px;
}
.select2-container--bootstrap.input-lg .select2-selection--single .select2-selection__arrow b, .input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b, .form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
border-width: 5px 5px 0 5px;
margin-left: -5px;
margin-left: -10px;
margin-top: -2.5px;
}
.select2-container--bootstrap.input-lg .select2-selection--multiple, .input-group-lg .select2-container--bootstrap .select2-selection--multiple, .form-group-lg .select2-container--bootstrap .select2-selection--multiple {
min-height: 46px;
}
.select2-container--bootstrap.input-lg .select2-selection--multiple .select2-selection__choice, .input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice, .form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
font-size: 18px;
line-height: 1.3333333;
border-radius: 4px;
margin: 9px 0 0 8px;
padding: 0 10px;
}
.select2-container--bootstrap.input-lg .select2-selection--multiple .select2-search--inline .select2-search__field, .input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field, .form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
padding: 0 16px;
font-size: 18px;
height: 44px;
line-height: 1.3333333;
}
.select2-container--bootstrap.input-lg.select2-container--open .select2-selection--single {
/**
* Make the dropdown arrow point up while the dropdown is visible.
*/
}
.select2-container--bootstrap.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #999 transparent;
border-width: 0 5px 5px 5px;
}
.input-group-lg .select2-container--bootstrap.select2-container--open .select2-selection--single {
/**
* Make the dropdown arrow point up while the dropdown is visible.
*/
}
.input-group-lg .select2-container--bootstrap.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #999 transparent;
border-width: 0 5px 5px 5px;
}
.select2-container--bootstrap[dir="rtl"] {
/**
* Single Select2
*
* 1. Makes sure that .select2-selection__placeholder is positioned
* correctly.
*/
/**
* Multiple Select2
*/
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single {
padding-left: 24px;
padding-right: 12px;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 0;
padding-left: 0;
text-align: right;
/* 1 */
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 12px;
right: auto;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow b {
margin-left: 0;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice,
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder {
float: right;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 0;
margin-right: 6px;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
/*------------------------------------*\
#ADDITIONAL GOODIES
\*------------------------------------*/
/**
* Address Bootstrap's validation states
*
* If a Select2 widget parent has one of Bootstrap's validation state modifier
* classes, adjust Select2's border colors and focus states accordingly.
* You may apply said classes to the Select2 dropdown (body > .select2-container)
* via JavaScript match Bootstraps' to make its styles match.
*
* @see http://getbootstrap.com/css/#forms-control-validation
*/
.has-warning .select2-dropdown,
.has-warning .select2-selection {
border-color: #8a6d3b;
}
.has-warning .select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
border-color: #66512c;
}
.has-warning.select2-drop-active {
border-color: #66512c;
}
.has-warning.select2-drop-active.select2-drop.select2-drop-above {
border-top-color: #66512c;
}
.has-error .select2-dropdown,
.has-error .select2-selection {
border-color: #a94442;
}
.has-error .select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
border-color: #843534;
}
.has-error.select2-drop-active {
border-color: #843534;
}
.has-error.select2-drop-active.select2-drop.select2-drop-above {
border-top-color: #843534;
}
.has-success .select2-dropdown,
.has-success .select2-selection {
border-color: #3c763d;
}
.has-success .select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
border-color: #2b542c;
}
.has-success.select2-drop-active {
border-color: #2b542c;
}
.has-success.select2-drop-active.select2-drop.select2-drop-above {
border-top-color: #2b542c;
}
/**
* Select2 widgets in Bootstrap Input Groups
*
* When Select2 widgets are combined with other elements using Bootstraps
* "Input Group" component, we don't want specific edges of the Select2
* container to have a border-radius.
*
* Use .select2-bootstrap-prepend and .select2-bootstrap-append on
* a Bootstrap 3 .input-group to let the contained Select2 widget know which
* edges should not be rounded as they are directly followed by another element.
*
* @see http://getbootstrap.com/components/#input-groups
*/
/**
* Mimick Bootstraps .input-group .form-control styles.
*
* @see https://github.com/twbs/bootstrap/blob/master/less/input-groups.less
*/
.input-group .select2-container--bootstrap {
display: table;
table-layout: fixed;
position: relative;
z-index: 2;
float: left;
width: 100%;
margin-bottom: 0;
}
.input-group.select2-bootstrap-prepend .select2-container--bootstrap .select2-selection {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.input-group.select2-bootstrap-append .select2-container--bootstrap .select2-selection {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
/**
* Adjust alignment of Bootstrap buttons in Bootstrap Input Groups to address
* Multi Select2's height which - depending on how many elements have been selected -
* may grown higher than their initial size.
*
* @see http://getbootstrap.com/components/#input-groups
*/
.select2-bootstrap-append .select2-container--bootstrap,
.select2-bootstrap-append .input-group-btn,
.select2-bootstrap-append .input-group-btn .btn,
.select2-bootstrap-prepend .select2-container--bootstrap,
.select2-bootstrap-prepend .input-group-btn,
.select2-bootstrap-prepend .input-group-btn .btn {
vertical-align: top;
}

View File

@ -0,0 +1,421 @@
.select2-container {
box-sizing: border-box;
display: inline-block;
margin: 0;
position: relative;
vertical-align: middle; }
.select2-container .select2-selection--single {
box-sizing: border-box;
cursor: pointer;
display: block;
height: 28px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--single .select2-selection__rendered {
display: block;
padding-left: 8px;
padding-right: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 8px;
padding-left: 20px; }
.select2-container .select2-selection--multiple {
box-sizing: border-box;
cursor: pointer;
display: block;
min-height: 32px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--multiple .select2-selection__rendered {
display: inline-block;
overflow: hidden;
padding-left: 8px;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-search--inline {
float: left; }
.select2-container .select2-search--inline .select2-search__field {
box-sizing: border-box;
border: none;
font-size: 100%;
margin-top: 5px; }
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-dropdown {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
box-sizing: border-box;
display: block;
position: absolute;
left: -100000px;
width: 100%;
z-index: 1051; }
.select2-results {
display: block; }
.select2-results__options {
list-style: none;
margin: 0;
padding: 0; }
.select2-results__option {
padding: 6px;
user-select: none;
-webkit-user-select: none; }
.select2-results__option[aria-selected] {
cursor: pointer; }
.select2-container--open .select2-dropdown {
left: 0; }
.select2-container--open .select2-dropdown--above {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--open .select2-dropdown--below {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-search--dropdown {
display: block;
padding: 4px; }
.select2-search--dropdown .select2-search__field {
padding: 4px;
width: 100%;
box-sizing: border-box; }
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-search--dropdown.select2-search--hide {
display: none; }
.select2-close-mask {
border: 0;
margin: 0;
padding: 0;
display: block;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 99;
background-color: #fff;
filter: alpha(opacity=0); }
.select2-container--default .select2-selection--single {
background-color: #fff;
border: 1px solid #aaa;
border-radius: 4px; }
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--default .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold; }
.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px; }
.select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto; }
.select2-container--default.select2-container--disabled .select2-selection--single {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none; }
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--default .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
width: 100%; }
.select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: #999;
margin-top: 5px;
float: left; }
.select2-container--default .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-top: 5px;
margin-right: 10px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder {
float: right; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--default.select2-container--focus .select2-selection--multiple {
border: solid black 1px;
outline: 0; }
.select2-container--default.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
display: none; }
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--default .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa; }
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent;
border: none;
outline: 0; }
.select2-container--default .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--default .select2-results__option[role=group] {
padding: 0; }
.select2-container--default .select2-results__option[aria-disabled=true] {
color: #999; }
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: #ddd; }
.select2-container--default .select2-results__option .select2-results__option {
padding-left: 1em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em; }
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #5897fb;
color: white; }
.select2-container--default .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic .select2-selection--single {
background-color: #f6f6f6;
border: 1px solid #aaa;
border-radius: 4px;
outline: 0;
background-image: -webkit-linear-gradient(top, #ffffff 50%, #eeeeee 100%);
background-image: -o-linear-gradient(top, #ffffff 50%, #eeeeee 100%);
background-image: linear-gradient(to bottom, #ffffff 50%, #eeeeee 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); }
.select2-container--classic .select2-selection--single:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--classic .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px; }
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--classic .select2-selection--single .select2-selection__arrow {
background-color: #ddd;
border: none;
border-left: 1px solid #aaa;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#cccccc', GradientType=0); }
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
border: none;
border-right: 1px solid #aaa;
border-radius: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
left: 1px;
right: auto; }
.select2-container--classic.select2-container--open .select2-selection--single {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
background: transparent;
border: none; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-image: -webkit-linear-gradient(top, #ffffff 0%, #eeeeee 50%);
background-image: -o-linear-gradient(top, #ffffff 0%, #eeeeee 50%);
background-image: linear-gradient(to bottom, #ffffff 0%, #eeeeee 50%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #ffffff 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, #ffffff 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, #ffffff 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); }
.select2-container--classic .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text;
outline: 0; }
.select2-container--classic .select2-selection--multiple:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
list-style: none;
margin: 0;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
display: none; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
color: #888;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #555; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
float: right; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--classic.select2-container--open .select2-selection--multiple {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--classic .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa;
outline: 0; }
.select2-container--classic .select2-search--inline .select2-search__field {
outline: 0; }
.select2-container--classic .select2-dropdown {
background-color: white;
border: 1px solid transparent; }
.select2-container--classic .select2-dropdown--above {
border-bottom: none; }
.select2-container--classic .select2-dropdown--below {
border-top: none; }
.select2-container--classic .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--classic .select2-results__option[role=group] {
padding: 0; }
.select2-container--classic .select2-results__option[aria-disabled=true] {
color: grey; }
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
background-color: #3875d7;
color: white; }
.select2-container--classic .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic.select2-container--open .select2-dropdown {
border-color: #5897fb; }

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/az",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return t+" simvol silin"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(e){return"Sadəcə "+e.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bg",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Моля въведете с "+t+" по-малко символ";return t>1&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím zadejte o jeden znak méně":n<=4?"Prosím zadejte o "+e(n,!0)+" znaky méně":"Prosím zadejte o "+n+" znaků méně"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím zadejte ještě jeden znak":n<=4?"Prosím zadejte ještě další "+e(n,!0)+" znaky":"Prosím zadejte ještě dalších "+n+" znaků"},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky":"Můžete zvolit maximálně "+n+" položek"},noResults:function(){return"Nenalezeny žádné položky"},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Angiv venligst "+t+" tegn mindre";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Angiv venligst "+t+" tegn mere";return n},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"La carga falló"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها می‌توانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Engada ";return t===1?n+="un carácter":n+=t+" caracteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Elimine ";return t===1?n+="un carácter":n+=t+" caracteres",n},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){var t="Só pode ";return e.maximum===1?t+="un elemento":t+=e.maximum+" elementos",t},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%100>9&&e%100<21||e%10===0?e%10>1?n:r:t}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"ių","ius","į"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"ių","ius","į"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ų","us","ą"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn ";return t>1?n+=" flere tegn":n+=" tegn til",n},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t="Er kunnen maar "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t+=" worden geselecteerd",t},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maxiumum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

View File

@ -0,0 +1,3 @@
/*! Select2 4.0.0-rc.2 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"carácter",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})();

Some files were not shown because too many files have changed in this diff Show More