From 1a26a1796bf1572942992649526a7a43eec81ccf Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 19 Dec 2016 11:02:40 -0500 Subject: [PATCH 001/154] break WJ relaunch check from start capability implement special custom error message for Workflow Job relaunch ability corner case --- awx/api/views.py | 7 +++ awx/main/access.py | 90 ++++++++++++++++++++++++++++--------- awx/main/models/workflow.py | 3 ++ 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index f19b61e4e9..dc49d55e64 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2953,6 +2953,13 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView): serializer_class = EmptySerializer is_job_start = True + def check_object_permissions(self, request, obj): + if request.method == 'POST' and obj: + relaunch_perm, messages = request.user.can_access_with_errors(self.model, 'start', obj) + if not relaunch_perm: + self.permission_denied(request, message=messages['workflow_job_template']) + return super(WorkflowJobRelaunch, self).check_object_permissions(request, obj) + def get(self, request, *args, **kwargs): return Response({}) diff --git a/awx/main/access.py b/awx/main/access.py index 422ed6be7c..9054ab7b94 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -363,27 +363,30 @@ class BaseAccess(object): continue # Compute permission - data = {} - access_method = getattr(self, "can_%s" % method) - if method in ['change']: # 3 args - user_capabilities[display_method] = access_method(obj, data) - elif method in ['delete', 'run_ad_hoc_commands', 'copy']: - user_capabilities[display_method] = access_method(obj) - elif method in ['start']: - user_capabilities[display_method] = access_method(obj, validate_license=False) - elif method in ['add']: # 2 args with data - user_capabilities[display_method] = access_method(data) - elif method in ['attach', 'unattach']: # parent/sub-object call - if type(parent_obj) == Team: - relationship = 'parents' - parent_obj = parent_obj.member_role - else: - relationship = 'members' - user_capabilities[display_method] = access_method( - obj, parent_obj, relationship, skip_sub_obj_read_check=True, data=data) + user_capabilities[display_method] = self.get_method_capability(method, obj, parent_obj) return user_capabilities + def get_method_capability(self, method, obj, parent_obj): + if method in ['change']: # 3 args + return self.can_change(obj, {}) + elif method in ['delete', 'run_ad_hoc_commands', 'copy']: + access_method = getattr(self, "can_%s" % method) + return access_method(obj) + elif method in ['start']: + return self.can_start(obj, validate_license=False) + elif method in ['add']: # 2 args with data + return self.can_add({}) + elif method in ['attach', 'unattach']: # parent/sub-object call + access_method = getattr(self, "can_%s" % method) + if type(parent_obj) == Team: + relationship = 'parents' + parent_obj = parent_obj.member_role + else: + relationship = 'members' + return access_method(obj, parent_obj, relationship, skip_sub_obj_read_check=True, data={}) + return False + class UserAccess(BaseAccess): ''' @@ -1294,6 +1297,12 @@ class JobAccess(BaseAccess): def can_delete(self, obj): return self.org_access(obj) + def get_method_capability(self, method, obj, parent_obj): + if method == 'start': + # Return simplistic permission, will perform detailed check on POST + return (not obj.job_template) or self.user in obj.job_template.execute_role + return super(JobAccess, self).get_method_capability(method, obj, parent_obj) + def can_start(self, obj, validate_license=True): if validate_license: self.check_license() @@ -1483,8 +1492,14 @@ class WorkflowJobNodeAccess(BaseAccess): qs = qs.prefetch_related('success_nodes', 'failure_nodes', 'always_nodes') return qs + @check_superuser def can_add(self, data): - return False + if data is None: # Hide direct creation in API browser + return False + return ( + self.check_related('unified_job_template', UnifiedJobTemplate, data, role_field='execute_role') and + self.check_related('credential', Credential, data, role_field='use_role') and + self.check_related('inventory', Inventory, data, role_field='use_role')) def can_change(self, obj, data): return False @@ -1622,6 +1637,14 @@ class WorkflowJobAccess(BaseAccess): return self.user.is_superuser return self.user in obj.workflow_job_template.admin_role + def get_method_capability(self, method, obj, parent_obj): + if method == 'start': + # Return simplistic permission, will perform detailed check on POST + if not obj.workflow_job_template: + return self.user.is_superuser + return self.user in obj.workflow_job_template.execute_role + return super(WorkflowJobAccess, self).get_method_capability(method, obj, parent_obj) + def can_start(self, obj, validate_license=True): if validate_license: self.check_license() @@ -1629,7 +1652,34 @@ class WorkflowJobAccess(BaseAccess): if self.user.is_superuser: return True - return (obj.workflow_job_template and self.user in obj.workflow_job_template.execute_role) + wfjt = obj.workflow_job_template + # only superusers can relaunch orphans + if not wfjt: + return False + + # execute permission to WFJT is mandatory for any relaunch + if self.user not in wfjt.execute_role: + return False + + # WFJT is valid base for WJ, launch permitted + last_modified = wfjt.nodes_last_modified() + if last_modified and obj.created > last_modified: + return True + + # user's WFJT access doesn't guarentee permission to launch, introspect nodes + return self.can_readd(obj) + + def can_readd(self, obj): + node_qs = obj.workflow_job_nodes.all().prefetch_related('inventory', 'credential', 'unified_job_template') + node_access = WorkflowJobNodeAccess(user=self.user) + wj_add_perm = True + for node in node_qs: + if not node_access.can_add({'reference_obj': node}): + wj_add_perm = False + if not wj_add_perm and self.save_messages: + self.messages['workflow_job_template'] = ('Template has been modified since job was launched, ' + 'and you do not have permission to its resources.') + return wj_add_perm def can_cancel(self, obj): if not obj.can_cancel: diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 526f0a0300..09870cbc18 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -327,6 +327,9 @@ class WorkflowJobOptions(BaseModel): new_workflow_job.copy_nodes_from_original(original=self) return new_workflow_job + def nodes_last_modified(self): + return self.workflow_nodes.aggregate(models.Max('modified'))['modified__max'] + class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin): class Meta: From 93564987d1ecca6e9aa4c2a0a0180b7558115655 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 20 Dec 2016 08:18:13 -0500 Subject: [PATCH 002/154] hold off on using new capabilities methodology on jobs --- awx/api/views.py | 2 +- awx/main/access.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index dc49d55e64..219c48ce4e 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2956,7 +2956,7 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView): def check_object_permissions(self, request, obj): if request.method == 'POST' and obj: relaunch_perm, messages = request.user.can_access_with_errors(self.model, 'start', obj) - if not relaunch_perm: + if not relaunch_perm and 'workflow_job_template' in messages: self.permission_denied(request, message=messages['workflow_job_template']) return super(WorkflowJobRelaunch, self).check_object_permissions(request, obj) diff --git a/awx/main/access.py b/awx/main/access.py index 9054ab7b94..f294aa6cda 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1297,12 +1297,6 @@ class JobAccess(BaseAccess): def can_delete(self, obj): return self.org_access(obj) - def get_method_capability(self, method, obj, parent_obj): - if method == 'start': - # Return simplistic permission, will perform detailed check on POST - return (not obj.job_template) or self.user in obj.job_template.execute_role - return super(JobAccess, self).get_method_capability(method, obj, parent_obj) - def can_start(self, obj, validate_license=True): if validate_license: self.check_license() From 816053986554e71d09c6d31510a61ceed2f57cf8 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Thu, 22 Dec 2016 14:24:15 -0500 Subject: [PATCH 003/154] Update sosreport config. Run pip freeze from venvs, include nginx & rabbit logs, drop apache & redis logs. --- tools/sosreport/tower.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/sosreport/tower.py b/tools/sosreport/tower.py index bc8be031b9..8b61626d1c 100644 --- a/tools/sosreport/tower.py +++ b/tools/sosreport/tower.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 Ansible, Inc. +# Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. import sos @@ -8,7 +8,8 @@ SOSREPORT_TOWER_COMMANDS = [ "ansible --version", # ansible core version "tower-manage --version", # tower version "supervisorctl status", # tower process status - "pip freeze", # pip package list + "/var/lib/awx/venv/tower/bin/pip freeze", # pip package list + "/var/lib/awx/venv/ansible/bin/pip freeze", # pip package list "tree -d /var/lib/awx", # show me the dirs "ls -ll /var/lib/awx", # check permissions "ls -ll /etc/tower", @@ -19,9 +20,8 @@ SOSREPORT_TOWER_DIRS = [ "/etc/tower/", "/etc/ansible/", "/var/log/tower", - "/var/log/httpd", - "/var/log/apache2", - "/var/log/redis", + "/var/log/nginx", + "/var/log/rabbitmq", "/var/log/supervisor", "/var/log/syslog", "/var/log/udev", From 7acb89ff4a006fa5e15a91b4697d0551c74912aa Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 4 Jan 2017 09:21:50 -0500 Subject: [PATCH 004/154] wrap error message in internationalization marker --- awx/main/access.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index f294aa6cda..70ac423098 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1671,8 +1671,8 @@ class WorkflowJobAccess(BaseAccess): if not node_access.can_add({'reference_obj': node}): wj_add_perm = False if not wj_add_perm and self.save_messages: - self.messages['workflow_job_template'] = ('Template has been modified since job was launched, ' - 'and you do not have permission to its resources.') + self.messages['workflow_job_template'] = _('Template has been modified since job was launched, ' + 'and you do not have permission to its resources.') return wj_add_perm def can_cancel(self, obj): From 2e220beda4ddf6710c6b715e3d237dcc3ba4a491 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 4 Jan 2017 10:51:20 -0500 Subject: [PATCH 005/154] provide __init__ arg for LDAP group type --- awx/sso/fields.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/awx/sso/fields.py b/awx/sso/fields.py index fdff68130f..174f4a6853 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -299,7 +299,10 @@ class LDAPGroupTypeField(fields.ChoiceField): data = super(LDAPGroupTypeField, self).to_internal_value(data) if not data: return None - return getattr(django_auth_ldap.config, data)() + if data.endswith('MemberDNGroupType'): + return getattr(django_auth_ldap.config, data)(member_attr='member') + else: + return getattr(django_auth_ldap.config, data)() class LDAPUserFlagsField(fields.DictField): From cc4aa49556cf493ca30427997f36c2b5b55e4376 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 14 Dec 2016 10:13:39 -0500 Subject: [PATCH 006/154] pool restart with thread-based external log config for CTiT enablement of celery task Tower logs --- Makefile | 2 +- awx/conf/apps.py | 6 ++--- awx/conf/signals.py | 4 +-- awx/main/conf.py | 2 +- awx/main/scheduler/tasks.py | 2 ++ awx/main/tasks.py | 50 ++++++++++++++++++++++++++++++++----- awx/main/utils/handlers.py | 23 ++++++++++++++--- awx/settings/defaults.py | 17 +++---------- 8 files changed, 75 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 0679a11911..c0c8f62d75 100644 --- a/Makefile +++ b/Makefile @@ -428,7 +428,7 @@ celeryd: @if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/tower/bin/activate; \ fi; \ - $(PYTHON) manage.py celeryd -l DEBUG -B --autoreload --autoscale=20,3 --schedule=$(CELERY_SCHEDULE_FILE) -Q projects,jobs,default,scheduler,broadcast_all,$(COMPOSE_HOST) + $(PYTHON) manage.py celeryd -l DEBUG -B --autoreload --autoscale=20,3 --schedule=$(CELERY_SCHEDULE_FILE) -Q projects,jobs,default,scheduler,broadcast_all,$(COMPOSE_HOST) -n celery@$(COMPOSE_HOST) #$(PYTHON) manage.py celery multi show projects jobs default -l DEBUG -Q:projects projects -Q:jobs jobs -Q:default default -c:projects 1 -c:jobs 3 -c:default 3 -Ofair -B --schedule=$(CELERY_SCHEDULE_FILE) # Run to start the zeromq callback receiver diff --git a/awx/conf/apps.py b/awx/conf/apps.py index 62ad0085df..4f76e13d40 100644 --- a/awx/conf/apps.py +++ b/awx/conf/apps.py @@ -16,7 +16,7 @@ class ConfConfig(AppConfig): from .settings import SettingsWrapper SettingsWrapper.initialize() if settings.LOG_AGGREGATOR_ENABLED: - LOGGING = settings.LOGGING - LOGGING['handlers']['http_receiver']['class'] = 'awx.main.utils.handlers.HTTPSHandler' - configure_logging(settings.LOGGING_CONFIG, LOGGING) + LOGGING_DICT = settings.LOGGING + LOGGING_DICT['handlers']['http_receiver']['class'] = 'awx.main.utils.handlers.HTTPSHandler' + configure_logging(settings.LOGGING_CONFIG, LOGGING_DICT) # checks.register(SettingsWrapper._check_settings) diff --git a/awx/conf/signals.py b/awx/conf/signals.py index 78411b1435..9d1813843e 100644 --- a/awx/conf/signals.py +++ b/awx/conf/signals.py @@ -13,7 +13,7 @@ import awx.main.signals from awx.conf import settings_registry from awx.conf.models import Setting from awx.conf.serializers import SettingSerializer -from awx.main.tasks import clear_cache_keys +from awx.main.tasks import process_cache_changes logger = logging.getLogger('awx.conf.signals') @@ -32,7 +32,7 @@ def handle_setting_change(key, for_delete=False): cache_keys = set([Setting.get_cache_key(k) for k in setting_keys]) logger.debug('sending signals to delete cache keys(%r)', cache_keys) cache.delete_many(cache_keys) - clear_cache_keys.delay(list(cache_keys)) + process_cache_changes.delay(list(cache_keys)) # Send setting_changed signal with new value for each setting. for setting_key in setting_keys: diff --git a/awx/main/conf.py b/awx/main/conf.py index c51205eaa7..1af962cd87 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -274,7 +274,7 @@ register( register( 'LOG_AGGREGATOR_LOGGERS', field_class=fields.StringListField, - default=['awx', 'activity_stream', 'job_events', 'system_tracking'], + default=['activity_stream', 'job_events', 'system_tracking'], label=_('Loggers to send data to the log aggregator from'), help_text=_('List of loggers that will send HTTP logs to the collector, these can ' 'include any or all of: \n' diff --git a/awx/main/scheduler/tasks.py b/awx/main/scheduler/tasks.py index 5c4c821606..6e169224b7 100644 --- a/awx/main/scheduler/tasks.py +++ b/awx/main/scheduler/tasks.py @@ -34,11 +34,13 @@ def run_job_complete(job_id): @task def run_task_manager(): + logger.debug("Running Tower task manager.") TaskManager().schedule() @task def run_fail_inconsistent_running_jobs(): + logger.debug("Running task to fail inconsistent running jobs.") with transaction.atomic(): # Lock try: diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 02af9239d9..5b7724ea31 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -32,7 +32,8 @@ import pexpect # Celery from celery import Task, task -from celery.signals import celeryd_init +from celery.signals import celeryd_init, worker_process_init +from celery import current_app # Django from django.conf import settings @@ -75,7 +76,8 @@ logger = logging.getLogger('awx.main.tasks') def celery_startup(conf=None, **kwargs): # Re-init all schedules # NOTE: Rework this during the Rampart work - logger.info("Syncing Tower Schedules") + startup_logger = logging.getLogger('awx.main.tasks') + startup_logger.info("Syncing Tower Schedules") for sch in Schedule.objects.all(): try: sch.update_computed_fields() @@ -84,7 +86,25 @@ def celery_startup(conf=None, **kwargs): logger.error("Failed to rebuild schedule {}: {}".format(sch, e)) -def uwsgi_reload(): +def _setup_tower_logger(): + global logger + from django.utils.log import configure_logging + LOGGING_DICT = settings.LOGGING + if settings.LOG_AGGREGATOR_ENABLED: + LOGGING_DICT['handlers']['http_receiver']['class'] = 'awx.main.utils.handlers.HTTPSHandler' + LOGGING_DICT['handlers']['http_receiver']['async'] = False + configure_logging(settings.LOGGING_CONFIG, LOGGING_DICT) + logger = logging.getLogger('awx.main.tasks') + + +@worker_process_init.connect +def task_set_logger_pre_run(*args, **kwargs): + if settings.LOG_AGGREGATOR_ENABLED: + _setup_tower_logger() + logger.debug('Custom Tower logger configured for worker process.') + + +def _uwsgi_reload(): # http://uwsgi-docs.readthedocs.io/en/latest/MasterFIFO.html#available-commands logger.warn('Initiating uWSGI chain reload of server') TRIGGER_CHAIN_RELOAD = 'c' @@ -92,14 +112,29 @@ def uwsgi_reload(): awxfifo.write(TRIGGER_CHAIN_RELOAD) -@task(queue='broadcast_all') -def clear_cache_keys(cache_keys): +def _reset_celery_logging(): + # Worker logger reloaded, now send signal to restart pool + app = current_app._get_current_object() + app.control.broadcast('pool_restart', arguments={'reload': True}, + destination=['celery@{}'.format(settings.CLUSTER_HOST_ID)], reply=False) + + +def _clear_cache_keys(cache_keys): set_of_keys = set([key for key in cache_keys]) logger.debug('cache delete_many(%r)', set_of_keys) cache.delete_many(set_of_keys) + + +@task(queue='broadcast_all') +def process_cache_changes(cache_keys): + logger.warn('Processing cache changes, task args: {0.args!r} kwargs: {0.kwargs!r}'.format( + _clear_cache_keys.request)) + clear_cache_keys(cache_keys) + set_of_keys = set([key for key in cache_keys]) for setting_key in set_of_keys: if setting_key.startswith('LOG_AGGREGATOR_'): - uwsgi_reload() + _uwsgi_reload() + _reset_celery_logging() break @@ -129,6 +164,7 @@ def send_notifications(notification_list, job_id=None): @task(bind=True, queue='default') def run_administrative_checks(self): + logger.warn("Running administrative checks.") if not settings.TOWER_ADMIN_ALERTS: return validation_info = TaskEnhancer().validate_enhancements() @@ -150,11 +186,13 @@ def run_administrative_checks(self): @task(bind=True, queue='default') def cleanup_authtokens(self): + logger.warn("Cleaning up expired authtokens.") AuthToken.objects.filter(expires__lt=now()).delete() @task(bind=True) def cluster_node_heartbeat(self): + logger.debug("Cluster node heartbeat task.") inst = Instance.objects.filter(hostname=settings.CLUSTER_HOST_ID) if inst.exists(): inst = inst[0] diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index 28b7c26af7..71176cbb1a 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -30,6 +30,7 @@ PARAM_NAMES = { 'password': 'LOG_AGGREGATOR_PASSWORD', 'enabled_loggers': 'LOG_AGGREGATOR_LOGGERS', 'indv_facts': 'LOG_AGGREGATOR_INDIVIDUAL_FACTS', + 'enabled_flag': 'LOG_AGGREGATOR_ENABLED', } @@ -48,6 +49,7 @@ class HTTPSHandler(logging.Handler): def __init__(self, fqdn=False, **kwargs): super(HTTPSHandler, self).__init__() self.fqdn = fqdn + self.async = kwargs.get('async', True) for fd in PARAM_NAMES: # settings values take precedence over the input params settings_name = PARAM_NAMES[fd] @@ -100,11 +102,21 @@ class HTTPSHandler(logging.Handler): payload_str = json.dumps(payload_input) else: payload_str = payload_input - return dict(data=payload_str, background_callback=unused_callback) + if self.async: + return dict(data=payload_str, background_callback=unused_callback) + else: + return dict(data=payload_str) + + def skip_log(self, logger_name): + if self.host == '' or (not self.enabled_flag): + return True + if not logger_name.startswith('awx.analytics'): + # Tower log emission is only turned off by enablement setting + return False + return self.enabled_loggers is None or logger_name.split('.')[-1] not in self.enabled_loggers def emit(self, record): - if (self.host == '' or self.enabled_loggers is None or - record.name.split('.')[-1] not in self.enabled_loggers): + if self.skip_log(record.name): return try: payload = self.format(record) @@ -123,7 +135,10 @@ class HTTPSHandler(logging.Handler): self.session.post(host, **self.get_post_kwargs(fact_payload)) return - self.session.post(host, **self.get_post_kwargs(payload)) + if self.async: + self.session.post(host, **self.get_post_kwargs(payload)) + else: + requests.post(host, auth=requests.auth.HTTPBasicAuth(self.username, self.password), **self.get_post_kwargs(payload)) except (KeyboardInterrupt, SystemExit): raise except: diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 439c23783b..16b1484d54 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -372,6 +372,7 @@ CELERY_ACCEPT_CONTENT = ['json'] CELERY_TRACK_STARTED = True CELERYD_TASK_TIME_LIMIT = None CELERYD_TASK_SOFT_TIME_LIMIT = None +CELERYD_POOL_RESTARTS = True CELERYBEAT_SCHEDULER = 'celery.beat.PersistentScheduler' CELERYBEAT_MAX_LOOP_INTERVAL = 60 CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend' @@ -874,7 +875,7 @@ LOGGING = { }, 'http_receiver': { 'class': 'awx.main.utils.handlers.HTTPSNullHandler', - 'level': 'INFO', + 'level': 'DEBUG', 'formatter': 'json', 'host': '', }, @@ -973,7 +974,7 @@ LOGGING = { 'handlers': ['callback_receiver'], }, 'awx.main.tasks': { - 'handlers': ['task_system'] + 'handlers': ['task_system'], }, 'awx.main.scheduler': { 'handlers': ['task_system'], @@ -1001,18 +1002,6 @@ LOGGING = { 'level': 'INFO', 'propagate': False }, - 'awx.analytics.job_events': { - 'handlers': ['null'], - 'level': 'INFO' - }, - 'awx.analytics.activity_stream': { - 'handlers': ['null'], - 'level': 'INFO' - }, - 'awx.analytics.system_tracking': { - 'handlers': ['null'], - 'level': 'INFO' - }, 'django_auth_ldap': { 'handlers': ['console', 'file', 'tower_warnings'], 'level': 'DEBUG', From b0cf05e9c78fe04f299999ac00bf03c2077c1839 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 4 Jan 2017 16:53:23 -0500 Subject: [PATCH 007/154] Lower scheduling access requirement to execute --- awx/main/access.py | 12 +++-- .../functional/test_rbac_job_templates.py | 49 ++++++++++++------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 1de7049426..117ed853ce 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -353,7 +353,7 @@ class BaseAccess(object): # Shortcuts in certain cases by deferring to earlier property if display_method == 'schedule': - user_capabilities['schedule'] = user_capabilities['edit'] + user_capabilities['schedule'] = user_capabilities['start'] continue elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob)): user_capabilities['delete'] = user_capabilities['edit'] @@ -1912,11 +1912,17 @@ class ScheduleAccess(BaseAccess): @check_superuser def can_add(self, data): - return self.check_related('unified_job_template', UnifiedJobTemplate, data, mandatory=True) + return self.check_related('unified_job_template', UnifiedJobTemplate, data, role_field='execute_role', mandatory=True) @check_superuser def can_change(self, obj, data): - return self.check_related('unified_job_template', UnifiedJobTemplate, data, obj=obj, mandatory=True) + if self.check_related('unified_job_template', UnifiedJobTemplate, data, obj=obj, mandatory=True): + return True + if ('unified_job_template' in data and data['unified_job_template'] != obj.pk) or obj.created_by_id != self.user.id: + return False + # Users with execute role can modify the schedules they created + return self.check_related('unified_job_template', UnifiedJobTemplate, data, obj=obj, role_field='execute_role') + def can_delete(self, obj): return self.can_change(obj, {}) diff --git a/awx/main/tests/functional/test_rbac_job_templates.py b/awx/main/tests/functional/test_rbac_job_templates.py index ac545dafee..13e0da8e8c 100644 --- a/awx/main/tests/functional/test_rbac_job_templates.py +++ b/awx/main/tests/functional/test_rbac_job_templates.py @@ -259,22 +259,37 @@ def test_associate_label(label, user, job_template): @pytest.mark.django_db -def test_move_schedule_to_JT_no_access(job_template, rando): - schedule = Schedule.objects.create( - unified_job_template=job_template, - rrule='DTSTART:20151117T050000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1') - job_template.admin_role.members.add(rando) - jt2 = JobTemplate.objects.create(name="other-jt") - access = ScheduleAccess(rando) - assert not access.can_change(schedule, data=dict(unified_job_template=jt2.pk)) +class TestJobTemplateSchedules: + + rrule = 'DTSTART:20151117T050000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1' + rrule2 = 'DTSTART:20151117T050000Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1' + + @pytest.fixture + def jt2(self): + return JobTemplate.objects.create(name="other-jt") + + def test_move_schedule_to_JT_no_access(self, job_template, rando, jt2): + schedule = Schedule.objects.create(unified_job_template=job_template, rrule=self.rrule) + job_template.admin_role.members.add(rando) + access = ScheduleAccess(rando) + assert not access.can_change(schedule, data=dict(unified_job_template=jt2.pk)) -@pytest.mark.django_db -def test_move_schedule_from_JT_no_access(job_template, rando): - schedule = Schedule.objects.create( - unified_job_template=job_template, - rrule='DTSTART:20151117T050000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1') - jt2 = JobTemplate.objects.create(name="other-jt") - jt2.admin_role.members.add(rando) - access = ScheduleAccess(rando) - assert not access.can_change(schedule, data=dict(unified_job_template=jt2.pk)) + def test_move_schedule_from_JT_no_access(self, job_template, rando, jt2): + schedule = Schedule.objects.create(unified_job_template=job_template, rrule=self.rrule) + jt2.admin_role.members.add(rando) + access = ScheduleAccess(rando) + assert not access.can_change(schedule, data=dict(unified_job_template=jt2.pk)) + + + def test_can_create_schedule_with_execute(self, job_template, rando): + job_template.execute_role.members.add(rando) + access = ScheduleAccess(rando) + assert access.can_add({'unified_job_template': job_template}) + + + def test_can_modify_ones_own_schedule(self, job_template, rando): + job_template.execute_role.members.add(rando) + schedule = Schedule.objects.create(unified_job_template=job_template, rrule=self.rrule, created_by=rando) + access = ScheduleAccess(rando) + assert access.can_change(schedule, {'rrule': self.rrule2}) From f76680b625e6e4f34a5832df0a1f551ced191302 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 5 Jan 2017 10:28:23 -0500 Subject: [PATCH 008/154] add back in the Tower logger, fix bug --- awx/conf/apps.py | 3 +++ awx/main/conf.py | 2 +- awx/main/tasks.py | 7 +++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/awx/conf/apps.py b/awx/conf/apps.py index 4f76e13d40..6e09545236 100644 --- a/awx/conf/apps.py +++ b/awx/conf/apps.py @@ -18,5 +18,8 @@ class ConfConfig(AppConfig): if settings.LOG_AGGREGATOR_ENABLED: LOGGING_DICT = settings.LOGGING LOGGING_DICT['handlers']['http_receiver']['class'] = 'awx.main.utils.handlers.HTTPSHandler' + if 'awx' in settings.LOG_AGGREGATOR_LOGGERS: + if 'http_receiver' not in LOGGING_DICT['loggers']['awx']['handlers']: + LOGGING_DICT['loggers']['awx']['handlers'] += ['http_receiver'] configure_logging(settings.LOGGING_CONFIG, LOGGING_DICT) # checks.register(SettingsWrapper._check_settings) diff --git a/awx/main/conf.py b/awx/main/conf.py index 1af962cd87..c51205eaa7 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -274,7 +274,7 @@ register( register( 'LOG_AGGREGATOR_LOGGERS', field_class=fields.StringListField, - default=['activity_stream', 'job_events', 'system_tracking'], + default=['awx', 'activity_stream', 'job_events', 'system_tracking'], label=_('Loggers to send data to the log aggregator from'), help_text=_('List of loggers that will send HTTP logs to the collector, these can ' 'include any or all of: \n' diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 5b7724ea31..f991037a3e 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -93,6 +93,9 @@ def _setup_tower_logger(): if settings.LOG_AGGREGATOR_ENABLED: LOGGING_DICT['handlers']['http_receiver']['class'] = 'awx.main.utils.handlers.HTTPSHandler' LOGGING_DICT['handlers']['http_receiver']['async'] = False + if 'awx' in settings.LOG_AGGREGATOR_LOGGERS: + if 'http_receiver' not in LOGGING_DICT['loggers']['awx']['handlers']: + LOGGING_DICT['loggers']['awx']['handlers'] += ['http_receiver'] configure_logging(settings.LOGGING_CONFIG, LOGGING_DICT) logger = logging.getLogger('awx.main.tasks') @@ -128,8 +131,8 @@ def _clear_cache_keys(cache_keys): @task(queue='broadcast_all') def process_cache_changes(cache_keys): logger.warn('Processing cache changes, task args: {0.args!r} kwargs: {0.kwargs!r}'.format( - _clear_cache_keys.request)) - clear_cache_keys(cache_keys) + process_cache_changes.request)) + _clear_cache_keys(cache_keys) set_of_keys = set([key for key in cache_keys]) for setting_key in set_of_keys: if setting_key.startswith('LOG_AGGREGATOR_'): From 03734015c50ae36eba87bfe1891b8ef378adc976 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 9 Dec 2016 11:15:20 -0500 Subject: [PATCH 009/154] set_stats support --- awx/lib/tower_display_callback/events.py | 5 +++++ awx/lib/tower_display_callback/module.py | 7 ++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/awx/lib/tower_display_callback/events.py b/awx/lib/tower_display_callback/events.py index c17cf2c7f1..002c753c2e 100644 --- a/awx/lib/tower_display_callback/events.py +++ b/awx/lib/tower_display_callback/events.py @@ -180,6 +180,11 @@ class EventContext(object): for key in event_data.keys(): if key in ('job_id', 'ad_hoc_command_id', 'uuid', 'parent_uuid', 'created', 'artifact_data'): event_dict[key] = event_data.pop(key) + if key == 'artifact_data': + if '_run' in event_dict[key]: + event_dict[key] = event_dict[key]['_run'] + else: + event_dict[key] = {} elif key in ('verbosity', 'pid'): event_dict[key] = event_data[key] return event_dict diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/tower_display_callback/module.py index 59faa7ac79..cf266694f4 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/tower_display_callback/module.py @@ -110,11 +110,7 @@ class BaseCallbackModule(CallbackBase): event_data.setdefault('uuid', str(uuid.uuid4())) if 'res' in event_data: - event_data['res'] = self.censor_result(copy.copy(event_data['res'])) - res = event_data.get('res', None) - if res and isinstance(res, dict): - if 'artifact_data' in res: - event_data['artifact_data'] = res['artifact_data'] + event_data['res'] = self.censor_result(copy.deepcopy(event_data['res'])) if event not in self.EVENTS_WITHOUT_TASK: task = event_data.pop('task', None) @@ -329,6 +325,7 @@ class BaseCallbackModule(CallbackBase): ok=stats.ok, processed=stats.processed, skipped=stats.skipped, + artifact_data=stats.custom, ) with self.capture_event_data('playbook_on_stats', **event_data): super(BaseCallbackModule, self).v2_playbook_on_stats(stats) From df903285318e3359d7dd9ca18526d60774da4a21 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 5 Jan 2017 15:08:52 -0500 Subject: [PATCH 010/154] remove duplicate conversion of keys into set --- awx/main/tasks.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index f991037a3e..1ff6b3216e 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -122,8 +122,7 @@ def _reset_celery_logging(): destination=['celery@{}'.format(settings.CLUSTER_HOST_ID)], reply=False) -def _clear_cache_keys(cache_keys): - set_of_keys = set([key for key in cache_keys]) +def _clear_cache_keys(set_of_keys): logger.debug('cache delete_many(%r)', set_of_keys) cache.delete_many(set_of_keys) @@ -132,8 +131,8 @@ def _clear_cache_keys(cache_keys): def process_cache_changes(cache_keys): logger.warn('Processing cache changes, task args: {0.args!r} kwargs: {0.kwargs!r}'.format( process_cache_changes.request)) - _clear_cache_keys(cache_keys) set_of_keys = set([key for key in cache_keys]) + _clear_cache_keys(set_of_keys) for setting_key in set_of_keys: if setting_key.startswith('LOG_AGGREGATOR_'): _uwsgi_reload() From dca70f4bd1f42dd01daf04a3feaf13ed519b3bbe Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 5 Jan 2017 15:29:16 -0500 Subject: [PATCH 011/154] protect against unhandled PermissionDenied error --- awx/api/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/awx/api/views.py b/awx/api/views.py index 0a1fa8cc53..f5579bb54a 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2316,7 +2316,10 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView): always_allow_superuser = False def update_raw_data(self, data): - obj = self.get_object() + try: + obj = self.get_object() + except PermissionDenied: + return data extra_vars = data.pop('extra_vars', None) or {} if obj: for p in obj.passwords_needed_to_start: From 9422e87f00595adf4a16d25ce738d90ca2cebbc3 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 5 Jan 2017 15:42:08 -0500 Subject: [PATCH 012/154] simplify schedule execute_role editing rule --- awx/main/access.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 117ed853ce..2026e8589d 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1918,10 +1918,10 @@ class ScheduleAccess(BaseAccess): def can_change(self, obj, data): if self.check_related('unified_job_template', UnifiedJobTemplate, data, obj=obj, mandatory=True): return True - if ('unified_job_template' in data and data['unified_job_template'] != obj.pk) or obj.created_by_id != self.user.id: - return False # Users with execute role can modify the schedules they created - return self.check_related('unified_job_template', UnifiedJobTemplate, data, obj=obj, role_field='execute_role') + return ( + obj.created_by == self.user and + self.check_related('unified_job_template', UnifiedJobTemplate, data, obj=obj, role_field='execute_role', mandatory=True)) def can_delete(self, obj): From 29ce7b368449ee148b9d6edad756d2ace8788aa2 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 6 Jan 2017 07:55:59 -0500 Subject: [PATCH 013/154] silence Django19 warnings in ui views --- awx/ui/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/views.py b/awx/ui/views.py index 933b4808e7..0a47154615 100644 --- a/awx/ui/views.py +++ b/awx/ui/views.py @@ -16,6 +16,7 @@ index = IndexView.as_view() class PortalRedirectView(RedirectView): + permanent = True url = '/#/portal' portal_redirect = PortalRedirectView.as_view() From 60cbc3cbbc202b7cea5efe34660b92bee63ab5a3 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 6 Jan 2017 08:24:40 -0500 Subject: [PATCH 014/154] do not allow YAML strings that are OrderedDicts --- awx/main/validators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/main/validators.py b/awx/main/validators.py index 1c92d9a645..c045e936cb 100644 --- a/awx/main/validators.py +++ b/awx/main/validators.py @@ -185,8 +185,9 @@ def vars_validate_or_raise(vars_str): except ValueError: pass try: - yaml.safe_load(vars_str) - return vars_str + r = yaml.safe_load(vars_str) + if not (isinstance(r, basestring) and r.startswith('OrderedDict(')): + return vars_str except yaml.YAMLError: pass raise RestValidationError(_('Must be valid JSON or YAML.')) From a1267a3dee324411560515adb1b5d4b6612a7c24 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 6 Jan 2017 10:04:57 -0500 Subject: [PATCH 015/154] update artifacts to work with ansible set_stats --- awx/lib/tower_display_callback/events.py | 7 +------ awx/lib/tower_display_callback/module.py | 6 +++++- awx/main/models/jobs.py | 25 ++++++++++++++++++------ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/awx/lib/tower_display_callback/events.py b/awx/lib/tower_display_callback/events.py index 002c753c2e..f46d5ae96f 100644 --- a/awx/lib/tower_display_callback/events.py +++ b/awx/lib/tower_display_callback/events.py @@ -178,13 +178,8 @@ class EventContext(object): event_data['res'] = {} event_dict = dict(event=event, event_data=event_data) for key in event_data.keys(): - if key in ('job_id', 'ad_hoc_command_id', 'uuid', 'parent_uuid', 'created', 'artifact_data'): + if key in ('job_id', 'ad_hoc_command_id', 'uuid', 'parent_uuid', 'created',): event_dict[key] = event_data.pop(key) - if key == 'artifact_data': - if '_run' in event_dict[key]: - event_dict[key] = event_dict[key]['_run'] - else: - event_dict[key] = {} elif key in ('verbosity', 'pid'): event_dict[key] = event_data[key] return event_dict diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/tower_display_callback/module.py index cf266694f4..45a2781e0b 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/tower_display_callback/module.py @@ -315,6 +315,9 @@ class BaseCallbackModule(CallbackBase): with self.capture_event_data('playbook_on_notify', **event_data): super(BaseCallbackModule, self).v2_playbook_on_notify(result, handler) + ''' + ansible_stats is, retoractively, added in 2.2 + ''' def v2_playbook_on_stats(self, stats): self.clear_play() # FIXME: Add count of plays/tasks. @@ -325,8 +328,9 @@ class BaseCallbackModule(CallbackBase): ok=stats.ok, processed=stats.processed, skipped=stats.skipped, - artifact_data=stats.custom, + artifact_data=stats.custom.get('_run', {}) ) + with self.capture_event_data('playbook_on_stats', **event_data): super(BaseCallbackModule, self).v2_playbook_on_stats(stats) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 8a6b9fc91d..417a4579b8 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -1175,7 +1175,6 @@ class JobEvent(CreatedModifiedModel): # Save UUID and parent UUID for determining parent-child relationship. job_event_uuid = kwargs.get('uuid', None) parent_event_uuid = kwargs.get('parent_uuid', None) - artifact_dict = kwargs.get('artifact_data', None) # Sanity check: Don't honor keys that we don't recognize. valid_keys = {'job_id', 'event', 'event_data', 'playbook', 'play', @@ -1185,6 +1184,11 @@ class JobEvent(CreatedModifiedModel): if key not in valid_keys: kwargs.pop(key) + event_data = kwargs.get('event_data', None) + artifact_dict = None + if event_data: + artifact_dict = event_data.pop('artifact_data', None) + # Try to find a parent event based on UUID. if parent_event_uuid: cache_key = '{}_{}'.format(kwargs['job_id'], parent_event_uuid) @@ -1208,12 +1212,21 @@ class JobEvent(CreatedModifiedModel): # Save artifact data to parent job (if provided). if artifact_dict: - event_data = kwargs.get('event_data', None) if event_data and isinstance(event_data, dict): - res = event_data.get('res', None) - if res and isinstance(res, dict): - if res.get('_ansible_no_log', False): - artifact_dict['_ansible_no_log'] = True + # Note: Core has not added support for marking artifacts as + # sensitive yet. Going forward, core will not use + # _ansible_no_log to denote sensitive set_stats calls. + # Instead, they plan to add a flag outside of the traditional + # no_log mechanism. no_log will not work for this feature, + # in core, because sensitive data is scrubbed before sending + # data to the callback. The playbook_on_stats is the callback + # in which the set_stats data is used. + + # Again, the sensitive artifact feature has not yet landed in + # core. The below is how we mark artifacts payload as + # senstive + # artifact_dict['_ansible_no_log'] = True + # parent_job = Job.objects.filter(pk=kwargs['job_id']).first() if parent_job and parent_job.artifacts != artifact_dict: parent_job.artifacts = artifact_dict From f07cd69012b50342bc09980b5e55a0453690b006 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 6 Jan 2017 09:53:11 -0500 Subject: [PATCH 016/154] corresponding test for dict extra_vars bug --- awx/main/tests/functional/api/test_job_template.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index 4983aaac69..ec4286176e 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -65,6 +65,17 @@ def test_edit_sensitive_fields(patch, job_template_factory, alice, grant_project }, alice, expect=expect) +@pytest.mark.django_db +def test_reject_dict_extra_vars_patch(patch, job_template_factory, admin_user): + # Expect a string for extra_vars, raise 400 in this case that would + # otherwise have been saved incorrectly + jt = job_template_factory( + 'jt', organization='org1', project='prj', inventory='inv', credential='cred' + ).job_template + patch(reverse('api:job_template_detail', args=(jt.id,)), + {'extra_vars': {'foo': 5}}, admin_user, expect=400) + + @pytest.mark.django_db def test_edit_playbook(patch, job_template_factory, alice): objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred') From 86a9a921ebc735de0eb4534153e26d2e1802441e Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 6 Jan 2017 11:47:45 -0500 Subject: [PATCH 017/154] remove deepcopy, not sure how that slipped in --- awx/lib/tower_display_callback/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/tower_display_callback/module.py index 45a2781e0b..480527a651 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/tower_display_callback/module.py @@ -110,7 +110,7 @@ class BaseCallbackModule(CallbackBase): event_data.setdefault('uuid', str(uuid.uuid4())) if 'res' in event_data: - event_data['res'] = self.censor_result(copy.deepcopy(event_data['res'])) + event_data['res'] = self.censor_result(copy.copy(event_data['res'])) if event not in self.EVENTS_WITHOUT_TASK: task = event_data.pop('task', None) From 5fcd467fe6ac8b48e49c675da10d004255e789d9 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 6 Jan 2017 14:44:29 -0500 Subject: [PATCH 018/154] rename helper function for WJ relaunch --- awx/main/access.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 70ac423098..89d04586c2 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1661,9 +1661,9 @@ class WorkflowJobAccess(BaseAccess): return True # user's WFJT access doesn't guarentee permission to launch, introspect nodes - return self.can_readd(obj) + return self.can_recreate(obj) - def can_readd(self, obj): + def can_recreate(self, obj): node_qs = obj.workflow_job_nodes.all().prefetch_related('inventory', 'credential', 'unified_job_template') node_access = WorkflowJobNodeAccess(user=self.user) wj_add_perm = True From 8bc3befd03e39e03360e99f539f62d9dd92703e8 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 6 Jan 2017 16:53:56 -0500 Subject: [PATCH 019/154] only accept extra_vars launchtime param for system jobs --- awx/api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 239f02e746..5b72b0261d 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -3164,8 +3164,8 @@ class SystemJobTemplateLaunch(GenericAPIView): def post(self, request, *args, **kwargs): obj = self.get_object() - new_job = obj.create_unified_job(**request.data) - new_job.signal_start(**request.data) + new_job = obj.create_unified_job(extra_vars=request.data.get('extra_vars', {})) + new_job.signal_start() data = dict(system_job=new_job.id) return Response(data, status=status.HTTP_201_CREATED) From f557d9cb717fb7aa370e4ec4594222f95e897231 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 6 Jan 2017 17:08:49 -0500 Subject: [PATCH 020/154] Fixed bug that I introduced that was preventing permissions from being added --- awx/ui/client/src/bread-crumb/bread-crumb.service.js | 2 +- .../shared/multi-select-list/select-list-item.directive.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.service.js b/awx/ui/client/src/bread-crumb/bread-crumb.service.js index c51383ef19..b3d510047d 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.service.js +++ b/awx/ui/client/src/bread-crumb/bread-crumb.service.js @@ -21,7 +21,7 @@ export default }); }); // Remove the clone from the dom - $breadcrumbClone.remove();console.log(availableWidth); + $breadcrumbClone.remove(); if(expandedBreadcrumbWidth > availableWidth) { let widthToTrim = expandedBreadcrumbWidth - availableWidth; // Sort the crumbs from biggest to smallest diff --git a/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js b/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js index 4867c5e07c..a9eeff647e 100644 --- a/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js +++ b/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js @@ -30,13 +30,12 @@ export default item: '=item' }, require: '^multiSelectList', - template: '', + template: '', link: function(scope, element, attrs, multiSelectList) { scope.decoratedItem = multiSelectList.registerItem(scope.item); - scope.isSelected = scope.decoratedItem.isSelected ? true : false; - scope.$watch('isSelected', function(value) { + scope.$watch('item.isSelected', function(value) { if (value === true) { multiSelectList.selectItem(scope.decoratedItem); } else if (value === false) { From 43a04e6ada74385b249c482893c725a8ce47eea9 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Thu, 5 Jan 2017 16:39:00 -0800 Subject: [PATCH 021/154] fixing the lookup modal for groups-> add credential lookup and fixing the ec2 lookup to query for "aws" --- awx/ui/client/src/forms/Groups.js | 5 +++++ .../src/inventories/manage/groups/groups-add.controller.js | 3 ++- .../src/inventories/manage/groups/groups-edit.controller.js | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/forms/Groups.js b/awx/ui/client/src/forms/Groups.js index b5c876c3b9..944fddd2c5 100644 --- a/awx/ui/client/src/forms/Groups.js +++ b/awx/ui/client/src/forms/Groups.js @@ -69,6 +69,11 @@ export default ngModel: 'source' }, credential: { + // initializes a default value for this search param + // search params with default values set will not generate user-interactable search tags + search: { + kind: null + }, label: 'Cloud Credential', type: 'lookup', list: 'CredentialList', diff --git a/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js index 031cf03f8a..9229595d96 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js @@ -31,9 +31,10 @@ export default ['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList } $scope.lookupCredential = function(){ + let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value; $state.go('.credential', { credential_search: { - kind: $scope.source.value, + kind: kind, page_size: '5', page: '1' } diff --git a/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js index 796dee5066..1b54bc92f1 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js @@ -58,9 +58,10 @@ export default ['$state', '$stateParams', '$scope', 'ToggleNotification', 'Parse }; $scope.lookupCredential = function(){ + let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value; $state.go('.credential', { credential_search: { - kind: $scope.source.value, + kind: kind, page_size: '5', page: '1' } From 8847110c23775887d1b313493f73e835e457d91b Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Fri, 6 Jan 2017 15:36:22 -0800 Subject: [PATCH 022/154] removing unnecessary console.log statement --- awx/ui/client/src/bread-crumb/bread-crumb.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.service.js b/awx/ui/client/src/bread-crumb/bread-crumb.service.js index c51383ef19..b3d510047d 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.service.js +++ b/awx/ui/client/src/bread-crumb/bread-crumb.service.js @@ -21,7 +21,7 @@ export default }); }); // Remove the clone from the dom - $breadcrumbClone.remove();console.log(availableWidth); + $breadcrumbClone.remove(); if(expandedBreadcrumbWidth > availableWidth) { let widthToTrim = expandedBreadcrumbWidth - availableWidth; // Sort the crumbs from biggest to smallest From e4d661f6594491042590b91344723399c9ee3d5b Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 6 Jan 2017 21:13:14 -0500 Subject: [PATCH 023/154] Fixed the column widths on the permissions modals --- .../add-rbac-resource/rbac-resource.partial.html | 2 +- .../rbac-selected-list.directive.js | 12 ++++++++++-- .../add-rbac-user-team/rbac-user-team.partial.html | 2 +- .../rbac-multiselect/permissionsUsers.list.js | 2 +- .../rbac-multiselect-list.directive.js | 13 +++++++++++++ .../shared/list-generator/list-generator.factory.js | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html index 75eaeb8302..f138e108d0 100644 --- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html +++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html @@ -6,7 +6,7 @@
- {{ object.name }} + {{ object.name || object.username }}
Add Permissions
diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js index f65d0324ad..7a66606e5f 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js @@ -42,8 +42,6 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', list.listTitleBadge = false; - // @issue - fix field.columnClass values for this view - switch(scope.resourceType){ case 'projects': @@ -51,6 +49,8 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', name: list.fields.name, scm_type: list.fields.scm_type }; + list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.scm_type.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'inventories': @@ -58,6 +58,8 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', name: list.fields.name, organization: list.fields.organization }; + list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.organization.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'job_templates': @@ -67,6 +69,8 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', name: list.fields.name, description: list.fields.description }; + list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.description.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'workflow_templates': @@ -77,12 +81,16 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', name: list.fields.name, description: list.fields.description }; + list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.description.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'credentials': list.fields = { name: list.fields.name, description: list.fields.description }; + list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.description.columnClass = 'col-md-5 col-sm-5 hidden-xs'; } list.fields = _.each(list.fields, (field) => field.nosort = true); diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html index 975870944c..a89621eda8 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html @@ -7,7 +7,7 @@
- {{ owner.name }} + {{ owner.name || owner.username }}
Add Permissions
diff --git a/awx/ui/client/src/access/rbac-multiselect/permissionsUsers.list.js b/awx/ui/client/src/access/rbac-multiselect/permissionsUsers.list.js index 8955d30aa0..58a5605281 100644 --- a/awx/ui/client/src/access/rbac-multiselect/permissionsUsers.list.js +++ b/awx/ui/client/src/access/rbac-multiselect/permissionsUsers.list.js @@ -34,7 +34,7 @@ username: { key: true, label: 'Username', - columnClass: 'col-md-3 col-sm-3 col-xs-9' + columnClass: 'col-md-5 col-sm-5 col-xs-11' }, }, diff --git a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js index da80fa4a58..0e277c3f5f 100644 --- a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js +++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js @@ -43,6 +43,8 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL name: list.fields.name, scm_type: list.fields.scm_type }; + list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; + list.fields.scm_type.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'Inventories': @@ -50,6 +52,8 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL name: list.fields.name, organization: list.fields.organization }; + list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; + list.fields.organization.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'JobTemplates': @@ -59,6 +63,8 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL name: list.fields.name, description: list.fields.description }; + list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; + list.fields.description.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'WorkflowTemplates': @@ -69,6 +75,8 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL name: list.fields.name, description: list.fields.description }; + list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; + list.fields.description.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; case 'Users': list.fields = { @@ -76,12 +84,17 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL first_name: list.fields.first_name, last_name: list.fields.last_name }; + list.fields.username.columnClass = 'col-md-5 col-sm-5 col-xs-11'; + list.fields.first_name.columnClass = 'col-md-3 col-sm-3 hidden-xs'; + list.fields.last_name.columnClass = 'col-md-3 col-sm-3 hidden-xs'; break; default: list.fields = { name: list.fields.name, description: list.fields.description }; + list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; + list.fields.description.columnClass = 'col-md-5 col-sm-5 hidden-xs'; } list_html = generateList.build({ diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index fd664dc167..9efb118a2c 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -311,7 +311,7 @@ export default ['$location', '$compile', '$rootScope', 'Attr', 'Icon', } if (list.multiSelect) { - innerTable += ''; + innerTable += ''; } // Change layout if a lookup list, place radio buttons before labels From eeebf2ddf846e254e100b57e4da0cfd63626dee8 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 9 Jan 2017 09:03:20 -0500 Subject: [PATCH 024/154] Fixing various audit bugs --- .../streamDetailModal.block.less | 1 + .../streamDetailModal.partial.html | 2 +- .../dashboard/lists/dashboard-list.block.less | 19 +++---------------- awx/ui/client/src/forms/Groups.js | 2 +- awx/ui/client/src/forms/Inventories.js | 2 +- awx/ui/client/src/helpers/Jobs.js | 2 +- .../add/inventory-add.controller.js | 10 ++++++++-- .../edit/inventory-edit.controller.js | 2 +- awx/ui/client/src/lists/AllJobs.js | 2 ++ awx/ui/client/src/lists/Projects.js | 4 ++-- awx/ui/client/src/partials/jobs.html | 2 +- .../list-generator/list-generator.factory.js | 2 +- 12 files changed, 23 insertions(+), 27 deletions(-) diff --git a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less index 6b00dc76f5..9e0cc73720 100644 --- a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less +++ b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less @@ -35,5 +35,6 @@ margin-bottom: 0; max-height: 200px; overflow: scroll; + overflow-x: auto; color: @as-detail-changes-txt; } diff --git a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html index f5d5acf553..67e4452ebc 100644 --- a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html +++ b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html @@ -22,7 +22,7 @@
diff --git a/awx/ui/client/src/dashboard/lists/dashboard-list.block.less b/awx/ui/client/src/dashboard/lists/dashboard-list.block.less index 102188034d..0903b01c74 100644 --- a/awx/ui/client/src/dashboard/lists/dashboard-list.block.less +++ b/awx/ui/client/src/dashboard/lists/dashboard-list.block.less @@ -32,34 +32,21 @@ } .DashboardList-viewAll { - color: @btn-txt; - background-color: @btn-bg; - font-size: 12px; - border: 1px solid @default-icon-hov; - border-radius: 5px; + font-size: 11px; margin-right: 15px; - margin-top: 10px; + margin-top: 13px; margin-bottom: 10px; padding-left: 10px; padding-right: 10px; padding-bottom: 5px; padding-top: 5px; - transition: background-color 0.2s; -} - -.DashboardList-viewAll:hover { - color: @btn-txt; - background-color: @btn-bg-hov; -} - -.DashboardList-viewAll:focus { - color: @btn-txt; } .DashboardList-container { flex: 1; width: 100%; padding: 20px; + padding-top: 0; } .DashboardList-tableHeader--name { diff --git a/awx/ui/client/src/forms/Groups.js b/awx/ui/client/src/forms/Groups.js index b5c876c3b9..22c33094f1 100644 --- a/awx/ui/client/src/forms/Groups.js +++ b/awx/ui/client/src/forms/Groups.js @@ -43,7 +43,7 @@ export default label: 'Variables', type: 'textarea', class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 12, + rows: 6, 'default': '---', dataTitle: 'Group Variables', dataPlacement: 'right', diff --git a/awx/ui/client/src/forms/Inventories.js b/awx/ui/client/src/forms/Inventories.js index e7106b8277..b78e16064d 100644 --- a/awx/ui/client/src/forms/Inventories.js +++ b/awx/ui/client/src/forms/Inventories.js @@ -78,7 +78,7 @@ angular.module('InventoryFormDefinition', ['ScanJobsListDefinition']) }, close: { ngClick: 'formCancel()', - ngHide: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' }, save: { ngClick: 'formSave()', diff --git a/awx/ui/client/src/helpers/Jobs.js b/awx/ui/client/src/helpers/Jobs.js index 9f64ac8906..d7649d1c33 100644 --- a/awx/ui/client/src/helpers/Jobs.js +++ b/awx/ui/client/src/helpers/Jobs.js @@ -233,7 +233,7 @@ export default hdr: hdr, body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody, action: action, - actionText: (action_label === 'cancel' || job.status === 'new') ? "YES" : "DELETE" + actionText: (action_label === 'cancel' || job.status === 'new') ? "OK" : "DELETE" }); }); diff --git a/awx/ui/client/src/inventories/add/inventory-add.controller.js b/awx/ui/client/src/inventories/add/inventory-add.controller.js index 7f08e716a6..e6f9b7e3a3 100644 --- a/awx/ui/client/src/inventories/add/inventory-add.controller.js +++ b/awx/ui/client/src/inventories/add/inventory-add.controller.js @@ -11,10 +11,16 @@ */ function InventoriesAdd($scope, $rootScope, $compile, $location, $log, - $stateParams, GenerateForm, InventoryForm, Rest, Alert, ProcessErrors, + $stateParams, GenerateForm, InventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors, ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON, $state) { + $scope.canAdd = false; + rbacUiControlService.canAdd(GetBasePath('inventory')) + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + Rest.setUrl(GetBasePath('inventory')); Rest.options() .success(function(data) { @@ -91,7 +97,7 @@ function InventoriesAdd($scope, $rootScope, $compile, $location, $log, } export default ['$scope', '$rootScope', '$compile', '$location', - '$log', '$stateParams', 'GenerateForm', 'InventoryForm', 'Rest', 'Alert', + '$log', '$stateParams', 'GenerateForm', 'InventoryForm', 'rbacUiControlService', 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd ]; diff --git a/awx/ui/client/src/inventories/edit/inventory-edit.controller.js b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js index ba6de0f183..3e26dec8dc 100644 --- a/awx/ui/client/src/inventories/edit/inventory-edit.controller.js +++ b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js @@ -32,7 +32,7 @@ function InventoriesEdit($scope, $rootScope, $compile, $location, form.formFieldSize = null; $scope.inventory_id = inventory_id; - $scope.$watch('invnentory_obj.summary_fields.user_capabilities.edit', function(val) { + $scope.$watch('inventory_obj.summary_fields.user_capabilities.edit', function(val) { if (val === false) { $scope.canAdd = false; } diff --git a/awx/ui/client/src/lists/AllJobs.js b/awx/ui/client/src/lists/AllJobs.js index 16a37e5c04..051375f157 100644 --- a/awx/ui/client/src/lists/AllJobs.js +++ b/awx/ui/client/src/lists/AllJobs.js @@ -16,6 +16,8 @@ export default index: false, hover: true, well: false, + title: false, + fields: { status: { label: '', diff --git a/awx/ui/client/src/lists/Projects.js b/awx/ui/client/src/lists/Projects.js index bf8fde2d03..0a03058242 100644 --- a/awx/ui/client/src/lists/Projects.js +++ b/awx/ui/client/src/lists/Projects.js @@ -42,14 +42,14 @@ export default scm_revision: { label: i18n._('Revision'), excludeModal: true, - columnClass: 'col-lg-3 col-md-2 col-sm-3 hidden-xs', + columnClass: 'col-lg-4 col-md-2 col-sm-3 hidden-xs', class: 'List-staticColumnAdjacent--monospace' }, scm_type: { label: i18n._('Type'), ngBind: 'project.type_label', excludeModal: true, - columnClass: 'col-lg-3 col-md-2 col-sm-3 hidden-xs' + columnClass: 'col-lg-2 col-md-2 col-sm-3 hidden-xs' }, last_updated: { label: i18n._('Last Updated'), diff --git a/awx/ui/client/src/partials/jobs.html b/awx/ui/client/src/partials/jobs.html index 00de9af821..46f7b09761 100644 --- a/awx/ui/client/src/partials/jobs.html +++ b/awx/ui/client/src/partials/jobs.html @@ -16,7 +16,7 @@ -
+
diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index fd664dc167..100ab608ba 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -134,7 +134,7 @@ export default ['$location', '$compile', '$rootScope', 'Attr', 'Icon', base, action, fld, cnt, field_action, fAction, itm; if (options.mode !== 'lookup') { - if (options.title !== false) { + if (options.title !== false && list.title !== false) { html += "
"; html += "
"; From 638cb98631a25a1514b73e0c5f77c7b51bdf28e7 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 9 Jan 2017 09:09:11 -0500 Subject: [PATCH 025/154] omit project can_cancel check from access logic --- awx/main/access.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index f2f1f72367..08a2c3e377 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -985,8 +985,6 @@ class ProjectUpdateAccess(BaseAccess): @check_superuser def can_cancel(self, obj): - if not obj.can_cancel: - return False if self.user == obj.created_by: return True # Project updates cascade delete with project, admin role descends from org admin From ad472c168fcfac3317f1bec404f4ec00ddce7f47 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 9 Jan 2017 12:25:04 -0500 Subject: [PATCH 026/154] systematic update of new_in_xx flags --- awx/api/views.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/awx/api/views.py b/awx/api/views.py index 239f02e746..486b83fae2 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -851,6 +851,7 @@ class OrganizationNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView) serializer_class = NotificationTemplateSerializer parent_model = Organization relationship = 'notification_templates_any' + new_in_300 = True class OrganizationNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView): @@ -859,6 +860,7 @@ class OrganizationNotificationTemplatesErrorList(SubListCreateAttachDetachAPIVie serializer_class = NotificationTemplateSerializer parent_model = Organization relationship = 'notification_templates_error' + new_in_300 = True class OrganizationNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView): @@ -867,6 +869,7 @@ class OrganizationNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIV serializer_class = NotificationTemplateSerializer parent_model = Organization relationship = 'notification_templates_success' + new_in_300 = True class OrganizationAccessList(ResourceAccessList): @@ -921,6 +924,7 @@ class TeamRolesList(SubListCreateAttachDetachAPIView): metadata_class = RoleMetadata parent_model = Team relationship='member_role.children' + new_in_300 = True def get_queryset(self): team = get_object_or_404(Team, pk=self.kwargs['pk']) @@ -1103,6 +1107,7 @@ class ProjectNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): serializer_class = NotificationTemplateSerializer parent_model = Project relationship = 'notification_templates_any' + new_in_300 = True class ProjectNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView): @@ -1111,6 +1116,7 @@ class ProjectNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView): serializer_class = NotificationTemplateSerializer parent_model = Project relationship = 'notification_templates_error' + new_in_300 = True class ProjectNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView): @@ -1119,6 +1125,7 @@ class ProjectNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView): serializer_class = NotificationTemplateSerializer parent_model = Project relationship = 'notification_templates_success' + new_in_300 = True class ProjectUpdatesList(SubListAPIView): @@ -1156,6 +1163,7 @@ class ProjectUpdateList(ListAPIView): model = ProjectUpdate serializer_class = ProjectUpdateListSerializer + new_in_13 = True class ProjectUpdateDetail(RetrieveDestroyAPIView): @@ -1196,6 +1204,7 @@ class ProjectUpdateNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = ProjectUpdate relationship = 'notifications' + new_in_300 = True class ProjectAccessList(ResourceAccessList): @@ -1271,6 +1280,7 @@ class UserRolesList(SubListCreateAttachDetachAPIView): parent_model = User relationship='roles' permission_classes = (IsAuthenticated,) + new_in_300 = True def get_queryset(self): u = get_object_or_404(User, pk=self.kwargs['pk']) @@ -2170,6 +2180,7 @@ class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIVi serializer_class = NotificationTemplateSerializer parent_model = InventorySource relationship = 'notification_templates_any' + new_in_300 = True def post(self, request, *args, **kwargs): parent = self.get_parent_object() @@ -2281,6 +2292,7 @@ class InventoryUpdateNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = InventoryUpdate relationship = 'notifications' + new_in_300 = True class JobTemplateList(ListCreateAPIView): @@ -2399,6 +2411,7 @@ class JobTemplateSurveySpec(GenericAPIView): model = JobTemplate parent_model = JobTemplate serializer_class = EmptySerializer + new_in_210 = True def get(self, request, *args, **kwargs): obj = self.get_object() @@ -2465,6 +2478,7 @@ class WorkflowJobTemplateSurveySpec(WorkflowsEnforcementMixin, JobTemplateSurvey model = WorkflowJobTemplate parent_model = WorkflowJobTemplate + new_in_310 = True class JobTemplateActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): @@ -2482,6 +2496,7 @@ class JobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): serializer_class = NotificationTemplateSerializer parent_model = JobTemplate relationship = 'notification_templates_any' + new_in_300 = True class JobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView): @@ -2490,6 +2505,7 @@ class JobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView serializer_class = NotificationTemplateSerializer parent_model = JobTemplate relationship = 'notification_templates_error' + new_in_300 = True class JobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView): @@ -2498,6 +2514,7 @@ class JobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIVi serializer_class = NotificationTemplateSerializer parent_model = JobTemplate relationship = 'notification_templates_success' + new_in_300 = True class JobTemplateLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView): @@ -2956,6 +2973,7 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView): model = WorkflowJob serializer_class = EmptySerializer is_job_start = True + new_in_310 = True def check_object_permissions(self, request, obj): if request.method == 'POST' and obj: @@ -3139,6 +3157,7 @@ class SystemJobTemplateList(ListAPIView): model = SystemJobTemplate serializer_class = SystemJobTemplateSerializer + new_in_210 = True def get(self, request, *args, **kwargs): if not request.user.is_superuser and not request.user.is_system_auditor: @@ -3150,6 +3169,7 @@ class SystemJobTemplateDetail(RetrieveAPIView): model = SystemJobTemplate serializer_class = SystemJobTemplateSerializer + new_in_210 = True class SystemJobTemplateLaunch(GenericAPIView): @@ -3157,6 +3177,7 @@ class SystemJobTemplateLaunch(GenericAPIView): model = SystemJobTemplate serializer_class = EmptySerializer is_job_start = True + new_in_210 = True def get(self, request, *args, **kwargs): return Response({}) @@ -3179,6 +3200,7 @@ class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView): parent_model = SystemJobTemplate relationship = 'schedules' parent_key = 'unified_job_template' + new_in_210 = True class SystemJobTemplateJobsList(SubListAPIView): @@ -3188,6 +3210,7 @@ class SystemJobTemplateJobsList(SubListAPIView): parent_model = SystemJobTemplate relationship = 'jobs' parent_key = 'system_job_template' + new_in_210 = True class SystemJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): @@ -3196,6 +3219,7 @@ class SystemJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPI serializer_class = NotificationTemplateSerializer parent_model = SystemJobTemplate relationship = 'notification_templates_any' + new_in_300 = True class SystemJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView): @@ -3204,6 +3228,7 @@ class SystemJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachA serializer_class = NotificationTemplateSerializer parent_model = SystemJobTemplate relationship = 'notification_templates_error' + new_in_300 = True class SystemJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView): @@ -3212,6 +3237,7 @@ class SystemJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetac serializer_class = NotificationTemplateSerializer parent_model = SystemJobTemplate relationship = 'notification_templates_success' + new_in_300 = True class JobList(ListCreateAPIView): @@ -3249,10 +3275,12 @@ class JobLabelList(SubListAPIView): parent_model = Job relationship = 'labels' parent_key = 'job' + new_in_300 = True class WorkflowJobLabelList(WorkflowsEnforcementMixin, JobLabelList): parent_model = WorkflowJob + new_in_310 = True class JobActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): @@ -3347,6 +3375,7 @@ class JobNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = Job relationship = 'notifications' + new_in_300 = True class BaseJobHostSummariesList(SubListAPIView): @@ -3857,12 +3886,14 @@ class AdHocCommandNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = AdHocCommand relationship = 'notifications' + new_in_300 = True class SystemJobList(ListCreateAPIView): model = SystemJob serializer_class = SystemJobListSerializer + new_in_210 = True def get(self, request, *args, **kwargs): if not request.user.is_superuser and not request.user.is_system_auditor: @@ -3874,6 +3905,7 @@ class SystemJobDetail(RetrieveDestroyAPIView): model = SystemJob serializer_class = SystemJobSerializer + new_in_210 = True class SystemJobCancel(RetrieveAPIView): @@ -3881,6 +3913,7 @@ class SystemJobCancel(RetrieveAPIView): model = SystemJob serializer_class = SystemJobCancelSerializer is_job_cancel = True + new_in_210 = True def post(self, request, *args, **kwargs): obj = self.get_object() @@ -3897,6 +3930,7 @@ class SystemJobNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = SystemJob relationship = 'notifications' + new_in_300 = True class UnifiedJobTemplateList(ListAPIView): @@ -4019,6 +4053,7 @@ class UnifiedJobStdout(RetrieveAPIView): class ProjectUpdateStdout(UnifiedJobStdout): model = ProjectUpdate + new_in_13 = True class InventoryUpdateStdout(UnifiedJobStdout): @@ -4089,6 +4124,7 @@ class NotificationTemplateNotificationList(SubListAPIView): parent_model = NotificationTemplate relationship = 'notifications' parent_key = 'notification_template' + new_in_300 = True class NotificationList(ListAPIView): From 90be8644ec2a70f2281e967785bfa42782941794 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 9 Jan 2017 12:53:45 -0500 Subject: [PATCH 027/154] move error divs down a line --- .../scheduler/schedulerForm.partial.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html b/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html index b619ead47a..d68ebfe3b8 100644 --- a/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html +++ b/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html @@ -507,11 +507,15 @@
+ +
+ + +
Please enter the number of days you would like to keep this data.
Please enter a valid number.
Please enter a non-negative number.
Please enter a number smaller than 9999.
-
@@ -520,11 +524,15 @@
+ +
+ + +
Please enter the number of days you would like to keep this data.
Please enter a valid number.
Please enter a non-negative number.
Please enter a number smaller than 9999.
-
From 7816d8e39e2d832b28140eeae9e42ec572b84400 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 9 Jan 2017 13:39:11 -0500 Subject: [PATCH 028/154] Fixing audit bugs --- awx/ui/client/legacy-styles/forms.less | 2 -- awx/ui/client/src/access/add-rbac.block.less | 4 ++++ .../auth-form/sub-forms/auth-azure.form.js | 4 ++-- .../auth-form/sub-forms/auth-github-org.form.js | 4 ++-- .../auth-form/sub-forms/auth-github-team.form.js | 4 ++-- .../auth-form/sub-forms/auth-github.form.js | 4 ++-- .../sub-forms/auth-google-oauth2.form.js | 4 ++-- .../auth-form/sub-forms/auth-ldap.form.js | 4 ++-- .../auth-form/sub-forms/auth-radius.form.js | 4 ++-- .../auth-form/sub-forms/auth-saml.form.js | 4 ++-- .../src/configuration/configuration.block.less | 13 +++++++++++++ .../jobs-form/configuration-jobs.form.js | 4 ++-- .../sub-forms/system-activity-stream.form.js | 4 ++-- .../system-form/sub-forms/system-logging.form.js | 4 ++-- .../system-form/sub-forms/system-misc.form.js | 4 ++-- .../ui-form/configuration-ui.form.js | 4 ++-- awx/ui/client/src/lists/Projects.js | 16 ++++++++-------- awx/ui/client/src/shared/modal/modal.less | 1 - awx/ui/templates/ui/index.html | 2 +- 19 files changed, 52 insertions(+), 38 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 89ff33dd03..8d44ec661a 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -44,8 +44,6 @@ color: @list-header-txt; font-size: 14px; font-weight: bold; - padding-bottom: 25px; - min-height: 45px; word-break: break-all; max-width: 90%; word-wrap: break-word; diff --git a/awx/ui/client/src/access/add-rbac.block.less b/awx/ui/client/src/access/add-rbac.block.less index 88f5b1211f..0bc6617ba4 100644 --- a/awx/ui/client/src/access/add-rbac.block.less +++ b/awx/ui/client/src/access/add-rbac.block.less @@ -51,6 +51,10 @@ padding-top: 20px; } +.AddPermissions-list { + margin-bottom: 20px; +} + .AddPermissions-list .List-searchRow { height: 0px; } diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js index 17b36e67fb..c780814f87 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js @@ -38,8 +38,8 @@ buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js index bd547cf8b1..81eeec8329 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js @@ -28,8 +28,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js index d43d8c01be..72af97b899 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js @@ -28,8 +28,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js index 03af137a7c..e95fef41b0 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js @@ -24,8 +24,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js index ac1c23545e..1aa8cbab30 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js @@ -36,8 +36,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js index 8d38fea688..824692ea9d 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js @@ -84,8 +84,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js index b16fd649dc..cec19eb122 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js @@ -29,8 +29,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js index ca2bb50dcb..7ef0735247 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js @@ -56,8 +56,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/configuration.block.less b/awx/ui/client/src/configuration/configuration.block.less index 0b2a1ea0ee..b25d36e400 100644 --- a/awx/ui/client/src/configuration/configuration.block.less +++ b/awx/ui/client/src/configuration/configuration.block.less @@ -12,6 +12,19 @@ float: right } +.Form-resetAll { + border: none; + padding: 0; + background-color: @white; + margin-right: auto; + color: @default-link; + font-size: 12px; + + &:hover { + color: @default-link-hov; + } +} + .Form-tab { min-width: 77px; } diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js index caf0392c24..99e52498c2 100644 --- a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js +++ b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js @@ -64,8 +64,8 @@ buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-activity-stream.form.js b/awx/ui/client/src/configuration/system-form/sub-forms/system-activity-stream.form.js index 09cf80eccd..3dc7fd89f7 100644 --- a/awx/ui/client/src/configuration/system-form/sub-forms/system-activity-stream.form.js +++ b/awx/ui/client/src/configuration/system-form/sub-forms/system-activity-stream.form.js @@ -22,8 +22,8 @@ buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js b/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js index ee99024b45..6fcfa0dda5 100644 --- a/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js +++ b/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js @@ -48,8 +48,8 @@ buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-misc.form.js b/awx/ui/client/src/configuration/system-form/sub-forms/system-misc.form.js index 690418f323..892dfd0bc0 100644 --- a/awx/ui/client/src/configuration/system-form/sub-forms/system-misc.form.js +++ b/awx/ui/client/src/configuration/system-form/sub-forms/system-misc.form.js @@ -26,8 +26,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js b/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js index 0ef2cb44ff..e566c076d1 100644 --- a/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js +++ b/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js @@ -32,8 +32,8 @@ export default ['i18n', function(i18n) { buttons: { reset: { ngClick: 'vm.resetAllConfirm()', - label: i18n._('Reset All'), - class: 'Form-button--left Form-cancelButton' + label: i18n._('Revert all to default'), + class: 'Form-resetAll' }, cancel: { ngClick: 'vm.formCancel()', diff --git a/awx/ui/client/src/lists/Projects.js b/awx/ui/client/src/lists/Projects.js index bf8fde2d03..e64310b458 100644 --- a/awx/ui/client/src/lists/Projects.js +++ b/awx/ui/client/src/lists/Projects.js @@ -61,6 +61,14 @@ export default }, actions: { + refresh: { + mode: 'all', + awToolTip: i18n._("Refresh the page"), + ngClick: "refresh()", + ngShow: "socketStatus === 'error'", + actionClass: 'btn List-buttonDefault', + buttonContent: i18n._('REFRESH') + }, add: { mode: 'all', // One of: edit, select, all ngClick: 'addProject()', @@ -68,14 +76,6 @@ export default actionClass: 'btn List-buttonSubmit', buttonContent: '+ ' + i18n._('ADD'), ngShow: "canAdd" - }, - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refresh()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') } }, diff --git a/awx/ui/client/src/shared/modal/modal.less b/awx/ui/client/src/shared/modal/modal.less index da254e07f6..2b90315afa 100644 --- a/awx/ui/client/src/shared/modal/modal.less +++ b/awx/ui/client/src/shared/modal/modal.less @@ -13,7 +13,6 @@ .Modal-header { display: flex; width: 100%; - margin-bottom: 25px; } .Modal-title { diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 9c8d91918f..3b29937bc0 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -81,7 +81,7 @@
From 69527fdb084e49b4bb55b22eea259e1785735c2d Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 9 Jan 2017 15:10:02 -0500 Subject: [PATCH 029/154] unicode conversion of paths in inventory import --- awx/main/management/commands/inventory_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 5c8c9df9c4..c1399e1a11 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -64,7 +64,7 @@ class MemObject(object): all_vars = {} files_found = 0 for suffix in ('', '.yml', '.yaml', '.json'): - path = ''.join([base_path, suffix]) + path = ''.join([base_path, suffix]).encode("utf-8") if not os.path.exists(path): continue if not os.path.isfile(path): @@ -462,7 +462,7 @@ class ExecutableJsonLoader(BaseLoader): # to set their variables for k,v in self.all_group.all_hosts.iteritems(): if 'hostvars' not in _meta: - data = self.command_to_json([self.source, '--host', k]) + data = self.command_to_json([self.source, '--host', k.encode("utf-8")]) else: data = _meta['hostvars'].get(k, {}) if isinstance(data, dict): From 1e5b3fbe31f21dbf529be4d4dd6f769bb22ec4e1 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 9 Jan 2017 16:43:02 -0500 Subject: [PATCH 030/154] handle undefined (empty) ec2 variable textarea * Adding a check for undefined, null check was not enough. applyDefaults() is only called in 'add' mode. This method is responsible for settings the fields value to the passed in 'default'. Since applyDefaults() isn't called in 'edit' mode, the field has a value of undefined. --- .../src/inventories/manage/groups/groups-edit.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js index 1b54bc92f1..ae90cae8bf 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js @@ -123,7 +123,7 @@ export default ['$state', '$stateParams', '$scope', 'ToggleNotification', 'Parse $scope.source = source; if (source.value === 'ec2' || source.value === 'custom' || source.value === 'vmware' || source.value === 'openstack') { - $scope[source.value + '_variables'] = $scope[source.value + '_variables'] === null ? '---' : $scope[source.value + '_variables']; + $scope[source.value + '_variables'] = $scope[source.value + '_variables'] === (null || undefined) ? '---' : $scope[source.value + '_variables']; ParseTypeChange({ scope: $scope, field_id: source.value + '_variables', From a3d8cbde253b3ba473f872322899677542675b56 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 08:51:33 -0500 Subject: [PATCH 031/154] add in defaults for cleanup facts management job --- awx/main/management/commands/cleanup_facts.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/main/management/commands/cleanup_facts.py b/awx/main/management/commands/cleanup_facts.py index a709f81c1a..f6b3c76b26 100644 --- a/awx/main/management/commands/cleanup_facts.py +++ b/awx/main/management/commands/cleanup_facts.py @@ -96,12 +96,12 @@ class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--older_than', dest='older_than', - default=None, - help='Specify the relative time to consider facts older than (w)eek (d)ay or (y)ear (i.e. 5d, 2w, 1y).'), + default='30d', + help='Specify the relative time to consider facts older than (w)eek (d)ay or (y)ear (i.e. 5d, 2w, 1y). Defaults to 30d.'), make_option('--granularity', dest='granularity', - default=None, - help='Window duration to group same hosts by for deletion (w)eek (d)ay or (y)ear (i.e. 5d, 2w, 1y).'), + default='1w', + help='Window duration to group same hosts by for deletion (w)eek (d)ay or (y)ear (i.e. 5d, 2w, 1y). Defaults to 1w.'), make_option('--module', dest='module', default=None, From 4990a59fa747d307d66a23721f4346baa430adc2 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 09:38:52 -0500 Subject: [PATCH 032/154] mark inventory scripts as added in 2.1.0 --- awx/api/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awx/api/views.py b/awx/api/views.py index 48892e7f00..7d0586b296 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1553,12 +1553,14 @@ class InventoryScriptList(ListCreateAPIView): model = CustomInventoryScript serializer_class = CustomInventoryScriptSerializer + new_in_210 = True class InventoryScriptDetail(RetrieveUpdateDestroyAPIView): model = CustomInventoryScript serializer_class = CustomInventoryScriptSerializer + new_in_210 = True def destroy(self, request, *args, **kwargs): instance = self.get_object() From 8caf355857e2339db6f75c9bf44be26448478c44 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 09:47:54 -0500 Subject: [PATCH 033/154] handle reload when deleting org card under edit --- .../list/organizations-list.controller.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/organizations/list/organizations-list.controller.js b/awx/ui/client/src/organizations/list/organizations-list.controller.js index 9bae54afc8..769a844354 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.controller.js +++ b/awx/ui/client/src/organizations/list/organizations-list.controller.js @@ -127,6 +127,16 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', }); }; + function isDeletedOrganizationBeingEdited(deleted_organization_id, editing_organization_id) { + if (editing_organization_id === undefined) { + return false; + } + if (deleted_organization_id == editing_organization_id) { + return true; + } + return false; + } + $scope.deleteOrganization = function(id, name) { var action = function() { @@ -137,7 +147,11 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', Rest.destroy() .success(function() { Wait('stop'); - $state.reload('organizations'); + if (isDeletedOrganizationBeingEdited(id, $stateParams.organization_id) === true) { + $state.go('^', null, { reload: true }); + } else { + $state.reload('organizations'); + } }) .error(function(data, status) { ProcessErrors($scope, data, status, null, { From 78d8d4139a50baa8d89915e2cb17a9e4ea785631 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 10:09:29 -0500 Subject: [PATCH 034/154] workflow docs revisions after label copy change --- docs/workflow.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/workflow.md b/docs/workflow.md index fcabb9891f..43cc6104f9 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -47,11 +47,11 @@ Workflow job summary: ``` ### Workflow Copy and Relaunch -Other than the normal way of creating workflow job templates, it is also possible to copy existing workflow job templates. The resulting new workflow job template will be identical to which it copies from, except for `name` field which will be appended a text to indicate it's a copy. +Other than the normal way of creating workflow job templates, it is also possible to copy existing workflow job templates. The resulting new workflow job template will be mostly identical to which it copies from, except for `name` field which will be appended a text to indicate it's a copy. -Workflow job templates can be copied by POSTing to endpoint `/workflow_job_templates/\d+/copy/`. After copy finished, the resulting new workflow job template will have identical fields including description, extra_vars, labels, 'launch_type' and survey-related fields (survey_passwords, survey_spec and survey_enabled). More importantly, workflow job template node of the original workflow job template, as well as the topology they bear, will be copied. Note there are RBAC restrictions on determining which workflow job template node is copied. In specific, a workflow job template is allowed to be copied if the user has at least read permission on all related resources like credential and job template. On the other hand, schedules and notification templates of the original workflow job template will not be copied nor shared, and the name of the created workflow job template is the original name plus a special-formatted suffix to indicate its copy origin as well as the copy time, such as 'copy_from_name@10:30:00 am'. +Workflow job templates can be copied by POSTing to endpoint `/workflow_job_templates/\d+/copy/`. After copy finished, the resulting new workflow job template will have identical fields including description, extra_vars, and survey-related fields (survey_spec and survey_enabled). More importantly, workflow job template node of the original workflow job template, as well as the topology they bear, will be copied. Note there are RBAC restrictions on determining which workflow job template node is copied. In specific, a workflow job template is allowed to be copied if the user has at least read permission on all related resources like credential and job template. On the other hand, schedules and notification templates of the original workflow job template will not be copied nor shared, and the name of the created workflow job template is the original name plus a special-formatted suffix to indicate its copy origin as well as the copy time, such as 'copy_from_name@10:30:00 am'. -Worflow jobs cannot be copied directly, instead a workflow job is implicitly copied when it needs to relaunch. Relaunching an existing workflow job is done by POSTing to endpoint `/workflow_jobs/\d+/relaunch/`. What happens next is the original workflow job is copied to create a new workflow job. The new workflow job then gets a copy of all nodes of the original as well as the topology they bear. Finally the full-fledged new workflow job is triggered to run, thus fulfilling the purpose of relaunch. +Workflow jobs cannot be copied directly, instead a workflow job is implicitly copied when it needs to relaunch. Relaunching an existing workflow job is done by POSTing to endpoint `/workflow_jobs/\d+/relaunch/`. What happens next is the original workflow job is copied to create a new workflow job. The new workflow job then gets a copy of all nodes of the original as well as the topology they bear. Finally the full-fledged new workflow job is triggered to run, thus fulfilling the purpose of relaunch. Survey password-type answers should also be redacted in the relaunched version of the workflow job. ## Test Coverage ### CRUD-related From 7f6551ec20f128a639494a27127d29ac4b03f616 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 10:12:09 -0500 Subject: [PATCH 035/154] consider org id in url an int --- .../src/organizations/list/organizations-list.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/organizations/list/organizations-list.controller.js b/awx/ui/client/src/organizations/list/organizations-list.controller.js index 769a844354..3849c123a4 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.controller.js +++ b/awx/ui/client/src/organizations/list/organizations-list.controller.js @@ -131,7 +131,7 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', if (editing_organization_id === undefined) { return false; } - if (deleted_organization_id == editing_organization_id) { + if (deleted_organization_id === editing_organization_id) { return true; } return false; @@ -147,7 +147,7 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', Rest.destroy() .success(function() { Wait('stop'); - if (isDeletedOrganizationBeingEdited(id, $stateParams.organization_id) === true) { + if (isDeletedOrganizationBeingEdited(id, parseInt($stateParams.organization_id)) === true) { $state.go('^', null, { reload: true }); } else { $state.reload('organizations'); From 52c861503bf0242aee61d14393adde34f7b5305c Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Tue, 10 Jan 2017 10:15:00 -0500 Subject: [PATCH 036/154] Updated fix for form titles --- awx/ui/client/legacy-styles/forms.less | 1 + awx/ui/client/src/shared/modal/modal.less | 1 + 2 files changed, 2 insertions(+) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 8d44ec661a..570a096c7e 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -47,6 +47,7 @@ word-break: break-all; max-width: 90%; word-wrap: break-word; + margin-bottom: 20px; } .Form-secondaryTitle{ diff --git a/awx/ui/client/src/shared/modal/modal.less b/awx/ui/client/src/shared/modal/modal.less index 2b90315afa..da254e07f6 100644 --- a/awx/ui/client/src/shared/modal/modal.less +++ b/awx/ui/client/src/shared/modal/modal.less @@ -13,6 +13,7 @@ .Modal-header { display: flex; width: 100%; + margin-bottom: 25px; } .Modal-title { From efe0f867050d94edad62ba655d26708ff2cdc6ce Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Tue, 10 Jan 2017 10:51:23 -0500 Subject: [PATCH 037/154] Set TIME_ZONE to None. This causes Tower to use the system timezone. --- awx/settings/defaults.py | 2 +- awx/settings/local_settings.py.docker_compose | 2 +- awx/settings/local_settings.py.example | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 8e67e9a025..8c296bbe76 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -73,7 +73,7 @@ DATABASES = { # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'America/New_York' +TIME_ZONE = None # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html diff --git a/awx/settings/local_settings.py.docker_compose b/awx/settings/local_settings.py.docker_compose index a439d17989..1202b1cbe1 100644 --- a/awx/settings/local_settings.py.docker_compose +++ b/awx/settings/local_settings.py.docker_compose @@ -114,7 +114,7 @@ SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'America/New_York' +TIME_ZONE = None # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html diff --git a/awx/settings/local_settings.py.example b/awx/settings/local_settings.py.example index e731acc8b1..2996a8a28e 100644 --- a/awx/settings/local_settings.py.example +++ b/awx/settings/local_settings.py.example @@ -71,7 +71,7 @@ SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'America/New_York' +TIME_ZONE = None # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html From 30fb6fcdd375ae929f8438dc1faf9da862c5ee4e Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 11:14:44 -0500 Subject: [PATCH 038/154] update clarification on what WFJT copy does --- docs/workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workflow.md b/docs/workflow.md index 43cc6104f9..bb2f49e5c4 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -49,7 +49,7 @@ Workflow job summary: ### Workflow Copy and Relaunch Other than the normal way of creating workflow job templates, it is also possible to copy existing workflow job templates. The resulting new workflow job template will be mostly identical to which it copies from, except for `name` field which will be appended a text to indicate it's a copy. -Workflow job templates can be copied by POSTing to endpoint `/workflow_job_templates/\d+/copy/`. After copy finished, the resulting new workflow job template will have identical fields including description, extra_vars, and survey-related fields (survey_spec and survey_enabled). More importantly, workflow job template node of the original workflow job template, as well as the topology they bear, will be copied. Note there are RBAC restrictions on determining which workflow job template node is copied. In specific, a workflow job template is allowed to be copied if the user has at least read permission on all related resources like credential and job template. On the other hand, schedules and notification templates of the original workflow job template will not be copied nor shared, and the name of the created workflow job template is the original name plus a special-formatted suffix to indicate its copy origin as well as the copy time, such as 'copy_from_name@10:30:00 am'. +Workflow job templates can be copied by POSTing to endpoint `/workflow_job_templates/\d+/copy/`. After copy finished, the resulting new workflow job template will have identical fields including description, extra_vars, and survey-related fields (survey_spec and survey_enabled). More importantly, workflow job template node of the original workflow job template, as well as the topology they bear, will be copied. Note there are RBAC restrictions copying workflow job template nodes. A workflow job template is allowed to be copied if the user has permission to add an equivalent workflow job template. If the user performing the copy does not have access to a node's related resources (job template, inventory, or credential), those related fields will be null in the copy's version of the node. Schedules and notification templates of the original workflow job template will not be copied nor shared, and the name of the created workflow job template is the original name plus a special-formatted suffix to indicate its copy origin as well as the copy time, such as 'copy_from_name@10:30:00 am'. Workflow jobs cannot be copied directly, instead a workflow job is implicitly copied when it needs to relaunch. Relaunching an existing workflow job is done by POSTing to endpoint `/workflow_jobs/\d+/relaunch/`. What happens next is the original workflow job is copied to create a new workflow job. The new workflow job then gets a copy of all nodes of the original as well as the topology they bear. Finally the full-fledged new workflow job is triggered to run, thus fulfilling the purpose of relaunch. Survey password-type answers should also be redacted in the relaunched version of the workflow job. From d46de8f20d7715bc2a215b630efd0eda815779e7 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 12:25:45 -0500 Subject: [PATCH 039/154] fix job details memory leaks --- .../host-status-bar.directive.js | 6 ++- .../job-results-stdout.directive.js | 44 ++++++++++++++----- .../src/job-results/job-results.controller.js | 36 +++++++++++---- .../src/job-results/parse-stdout.service.js | 1 - 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js index f7fbd2e8f6..778d238e47 100644 --- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js +++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js @@ -15,7 +15,7 @@ export default [ 'templateUrl', link: function(scope) { // as count is changed by event data coming in, // update the host status bar - scope.$watch('count', function(val) { + var toDestroy = scope.$watch('count', function(val) { if (val) { Object.keys(val).forEach(key => { // reposition the hosts status bar by setting @@ -38,6 +38,10 @@ export default [ 'templateUrl', .filter(key => (val[key] > 0)).length > 0); } }); + + scope.$on('$destroy', function(){ + toDestroy(); + }); } }; }]; diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js index 77c87c74da..d6f0ee8e04 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js @@ -12,6 +12,18 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', templateUrl: templateUrl('job-results/job-results-stdout/job-results-stdout'), restrict: 'E', link: function(scope, element) { + var toDestroy = [], + resizer, + scrollWatcher; + + scope.$on('$destroy', function(){ + $(window).off("resize", resizer); + $(window).off("scroll", scrollWatcher); + $(".JobResultsStdOut-stdoutContainer").off('scroll', + scrollWatcher); + toDestroy.forEach(v => v()); + }); + scope.stdoutContainerAvailable.resolve("container available"); // utility function used to find the top visible line and // parent header in the pane @@ -115,9 +127,15 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', // stop iterating over the standard out // lines once the first one has been // found + + $this = null; return false; - } - }); + } + + $this = null; + }); + + $container = null; return { visLine: visItem, @@ -131,22 +149,24 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', } else { scope.isMobile = false; } - // watch changes to the window size - $(window).resize(function() { + + resizer = function() { // and update the isMobile var accordingly if (window.innerWidth <= 1200 && !scope.isMobile) { scope.isMobile = true; } else if (window.innerWidth > 1200 & scope.isMobile) { scope.isMobile = false; } - }); + }; + // watch changes to the window size + $(window).resize(resizer); var lastScrollTop; var initScrollTop = function() { lastScrollTop = 0; }; - var scrollWatcher = function() { + scrollWatcher = function() { var st = $(this).scrollTop(); var netScroll = st + $(this).innerHeight(); var fullHeight; @@ -178,11 +198,15 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', } lastScrollTop = st; + + st = null; + netScroll = null; + fullHeight = null; }; // update scroll watchers when isMobile changes based on // window resize - scope.$watch('isMobile', function(val) { + toDestroy.push(scope.$watch('isMobile', function(val) { if (val === true) { // make sure ^ TOP always shown for mobile scope.stdoutOverflowed = true; @@ -204,7 +228,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', $(".JobResultsStdOut-stdoutContainer").on('scroll', scrollWatcher); } - }); + })); // called to scroll to follow anchor scope.followScroll = function() { @@ -237,7 +261,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', // if following becomes active, go ahead and get to the bottom // of the standard out pane - scope.$watch('followEngaged', function(val) { + toDestroy.push(scope.$watch('followEngaged', function(val) { // scroll to follow point if followEngaged is true if (val) { scope.followScroll(); @@ -251,7 +275,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', scope.followTooltip = "Click to follow standard out as it comes in."; } } - }); + })); // follow button ng-click function scope.followToggleClicked = function() { diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 56aed6ce5b..c2c1f99df3 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -1,5 +1,7 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', 'Rest', '$state', 'QuerySet', '$rootScope', 'moment', function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, Rest, $state, QuerySet, $rootScope, moment) { + var toDestroy = []; + var cancelRequests = false; // used for tag search $scope.job_event_dataset = Dataset.data; @@ -66,14 +68,14 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // update label in left pane and tooltip in right pane when the job_status // changes - $scope.$watch('job_status', function(status) { + toDestroy.push($scope.$watch('job_status', function(status) { if (status) { $scope.status_label = $scope.jobOptions.status.choices .filter(val => val[0] === status) .map(val => val[1])[0]; $scope.status_tooltip = "Job " + $scope.status_label; } - }); + })); // update the job_status value. Use the cached rootScope value if there // is one. This is a workaround when the rest call for the jobData is @@ -278,6 +280,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy .stdout)($scope.events[mungedEvent .counter])); } + + classList = null; + putIn = null; } else { // this is a header or recap line, so just // append to the bottom @@ -368,7 +373,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy delete event.event; processEvent(event); }); - if (events.next) { + if (events.next && !cancelRequests) { getEvents(events.next); } else { // put those paused events into the pane @@ -378,7 +383,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy }; // grab non-header recap lines - $scope.$watch('job_event_dataset', function(val) { + toDestroy.push($scope.$watch('job_event_dataset', function(val) { // pause websocket events from coming in to the pane $scope.gotPreviouslyRanEvents = $q.defer(); @@ -391,19 +396,19 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy delete event.event; processEvent(event); }); - if (val.next) { + if (val.next && !cancelRequests) { getEvents(val.next); } else { // put those paused events into the pane $scope.gotPreviouslyRanEvents.resolve(""); } }); - }); + })); // Processing of job_events messages from the websocket - $scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { + toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { var url = Dataset @@ -446,10 +451,10 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy } }); - }); + })); // Processing of job-status messages from the websocket - $scope.$on(`ws-jobs`, function(e, data) { + toDestroy.push($scope.$on(`ws-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt($scope.job.id,10)) { // controller is defined, so set the job_status @@ -477,5 +482,18 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // for this job. cache the socket status on root scope $rootScope['lastSocketStatus' + data.unified_job_id] = data.status; } + })); + + $scope.$on('$destroy', function(){ + cancelRequests = true; + eventQueue.initialize(); + Object.keys($scope.events) + .forEach(v => { + $scope.events[v].$destroy(); + $scope.events[v] = null; + }); + $scope.events = {}; + clearInterval(elapsedInterval); + toDestroy.forEach(v => v()); }); }]; diff --git a/awx/ui/client/src/job-results/parse-stdout.service.js b/awx/ui/client/src/job-results/parse-stdout.service.js index 8d6564f3f0..858d86efe2 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -185,7 +185,6 @@ export default ['$log', 'moment', function($log, moment){ data-uuid="${clickClass}"> `; - // console.log(expandDom); return expandDom; } else { // non-header lines don't get an expander From a1c0bd5dcdfd6144cf13edf21f2f307456dd3e34 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 12:26:13 -0500 Subject: [PATCH 040/154] increase page size to improve ux of loading large amounts of standard out --- awx/ui/client/src/job-results/job-results.route.js | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js index 88154d3114..ec47cc7757 100644 --- a/awx/ui/client/src/job-results/job-results.route.js +++ b/awx/ui/client/src/job-results/job-results.route.js @@ -25,6 +25,7 @@ export default { params: { job_event_search: { value: { + page_size: 100, order_by: 'id', not__event__in: 'playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats' }, From dda8f14673d9b37548f2c3a2e76c775df1c9f00d Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 16:53:00 -0500 Subject: [PATCH 041/154] establish filter context checking on job details page in other words, stop making requests and doing stuff for a stale filter state also currently removing the behavior that kept live events controlled by the filter --- .../src/job-results/job-results.controller.js | 135 +++++++++--------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index c2c1f99df3..ac9ff7993a 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -3,6 +3,10 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy var toDestroy = []; var cancelRequests = false; + // this allows you to manage the timing of rest-call based events as + // filters are updated. see processPage for more info + var currentContext = 1; + // used for tag search $scope.job_event_dataset = Dataset.data; @@ -187,7 +191,12 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // This is where the async updates to the UI actually happen. // Flow is event queue munging in the service -> $scope setting in here - var processEvent = function(event) { + var processEvent = function(event, context) { + // only care about filter context checking when the event comes + // as a rest call + if (context && context !== currentContext) { + return; + } // put the event in the queue var mungedEvent = eventQueue.populate(event); @@ -362,46 +371,69 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy getSkeleton(jobData.related.job_events + "?order_by=id&or__event__in=playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats"); }); + var getEvents; + + var processPage = function(events, context) { + // currentContext is the context of the filter when this request + // to processPage was made + // + // currentContext is the context of the filter currently + // + // if they are not the same, make sure to stop process events/ + // making rest calls for next pages/etc. (you can see context is + // also passed into getEvents and processEvent and similar checks + // exist in these functions) + if (context !== currentContext) { + return; + } + + events.results.forEach(event => { + // get the name in the same format as the data + // coming over the websocket + event.event_name = event.event; + delete event.event; + + processEvent(event, context); + }); + if (events.next && !cancelRequests) { + getEvents(events.next, context); + } else { + // put those paused events into the pane + $scope.gotPreviouslyRanEvents.resolve(""); + } + }; + // grab non-header recap lines - var getEvents = function(url) { + getEvents = function(url, context) { + if (context !== currentContext) { + return; + } + jobResultsService.getEvents(url) .then(events => { - events.results.forEach(event => { - // get the name in the same format as the data - // coming over the websocket - event.event_name = event.event; - delete event.event; - processEvent(event); - }); - if (events.next && !cancelRequests) { - getEvents(events.next); - } else { - // put those paused events into the pane - $scope.gotPreviouslyRanEvents.resolve(""); - } + processPage(events, context); }); }; // grab non-header recap lines toDestroy.push($scope.$watch('job_event_dataset', function(val) { + eventQueue.initialize(); + Object.keys($scope.events) + .forEach(v => { + $scope.events[v].$destroy(); + $scope.events[v] = null; + }); + $scope.events = {}; + // pause websocket events from coming in to the pane $scope.gotPreviouslyRanEvents = $q.defer(); + currentContext += 1; + + let context = currentContext; $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); $scope.hasSkeleton.promise.then(() => { - val.results.forEach(event => { - // get the name in the same format as the data - // coming over the websocket - event.event_name = event.event; - delete event.event; - processEvent(event); - }); - if (val.next && !cancelRequests) { - getEvents(val.next); - } else { - // put those paused events into the pane - $scope.gotPreviouslyRanEvents.resolve(""); - } + processPage(val, context); }); })); @@ -411,45 +443,16 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { - var url = Dataset - .config.url.split("?")[0] + - QuerySet.encodeQueryset($state.params.job_event_search); - var noFilter = (url.split("&") - .filter(v => v.indexOf("page=") !== 0 && - v.indexOf("/api/v1") !== 0 && - v.indexOf("order_by=id") !== 0 && - v.indexOf("not__event__in=playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats") !== 0).length === 0); - - if(data.event_name === "playbook_on_start" || - data.event_name === "playbook_on_play_start" || - data.event_name === "playbook_on_task_start" || - data.event_name === "playbook_on_stats" || - noFilter) { - // for header and recap lines, as well as if no filters - // were added by the user, just put the line in the - // standard out pane (and increment play and task - // count) - if (data.event_name === "playbook_on_play_start") { - $scope.playCount++; - } else if (data.event_name === "playbook_on_task_start") { - $scope.taskCount++; - } - processEvent(data); - } else { - // to make sure host event/verbose lines go through a - // user defined filter, appent the id to the url, and - // make a request to the job_events endpoint with the - // id of the incoming event appended. If the event, - // is returned, put the line in the standard out pane - Rest.setUrl(`${url}&id=${data.id}`); - Rest.get() - .success(function(isHere) { - if (isHere.count) { - processEvent(data); - } - }); + // for header and recap lines, as well as if no filters + // were added by the user, just put the line in the + // standard out pane (and increment play and task + // count) + if (data.event_name === "playbook_on_play_start") { + $scope.playCount++; + } else if (data.event_name === "playbook_on_task_start") { + $scope.taskCount++; } - + processEvent(data); }); })); From d65e1a5d8230034e7d49833a524a413c92c25a37 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 16:54:05 -0500 Subject: [PATCH 042/154] fix parse standard out issue --- awx/ui/client/src/job-results/parse-stdout.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/job-results/parse-stdout.service.js b/awx/ui/client/src/job-results/parse-stdout.service.js index 858d86efe2..178fd88f4b 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -195,7 +195,7 @@ export default ['$log', 'moment', function($log, moment){ return _ .zip(_.range(event.start_line + 1, event.end_line + 1), - event.stdout.replace("\t", " ").split("\r\n").slice(0, -1)); + event.stdout.replace("\t", " ").split("\r\n")).slice(0, -1); }, // public function that provides the parsed stdout line, given a // job_event From d98e4a6cc51d1ec8d7e2cc0688a9381a0217725e Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 11:26:36 -0500 Subject: [PATCH 043/154] dont delete skeleton line scopes as it causes toggle to not work --- .../src/job-results/job-results.controller.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index ac9ff7993a..aef8b2ae86 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -418,12 +418,20 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // grab non-header recap lines toDestroy.push($scope.$watch('job_event_dataset', function(val) { eventQueue.initialize(); + Object.keys($scope.events) .forEach(v => { - $scope.events[v].$destroy(); - $scope.events[v] = null; + // dont destroy scope events for skeleton lines + let name = $scope.events[v].event.name; + + if (!(name === "playbook_on_play_start" || + name === "playbook_on_task_start" || + name === "playbook_on_stats")) { + $scope.events[v].$destroy(); + $scope.events[v] = null; + delete $scope.events[v]; + } }); - $scope.events = {}; // pause websocket events from coming in to the pane $scope.gotPreviouslyRanEvents = $q.defer(); @@ -443,10 +451,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { - // for header and recap lines, as well as if no filters - // were added by the user, just put the line in the + // put the line in the // standard out pane (and increment play and task - // count) + // count if applicable) if (data.event_name === "playbook_on_play_start") { $scope.playCount++; } else if (data.event_name === "playbook_on_task_start") { From b8ce36022700f38141a062515d34dda5a4fef8f6 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 11:30:56 -0500 Subject: [PATCH 044/154] update api max events header based on performance findings --- awx/settings/defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 8e67e9a025..2ec150bf35 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -154,7 +154,7 @@ STDOUT_MAX_BYTES_DISPLAY = 1048576 # Returned in the header on event api lists as a recommendation to the UI # on how many events to display before truncating/hiding -RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER = 10000 +RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER = 4000 # The maximum size of the ansible callback event's res data structure # beyond this limit and the value will be removed From e52e6ae19239f401c5ed6794ec5c5477b6050b1a Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 11:25:54 -0500 Subject: [PATCH 045/154] handle case where the UJT related resource is null --- docs/workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workflow.md b/docs/workflow.md index bb2f49e5c4..e0d195d18c 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -32,7 +32,7 @@ As stated, workflow job templates can be created with populated `extra_vars`. Th Job resources spawned by workflow jobs are needed by workflow to run correctly. Therefore deletion of spawned job resources is blocked while the underlying workflow job is executing. -Other than success and failure, a workflow spawned job resource can also end with status 'error' and 'canceled'. When a workflow spawned job resource errors, all branches starting from that job will stop executing while the rest keep their own paces. Canceling a workflow spawned job resource follows the same rules. +Other than success and failure, a workflow spawned job resource can also end with status 'error' and 'canceled'. When a workflow spawned job resource errors, all branches starting from that job will stop executing while the rest keep their own paces. Canceling a workflow spawned job resource follows the same rules. If the unified job template of the node is null (which could be a result of deleting the unified job template or copying a its workflow when the user lacks necessary permissions to use it), then the branch should stop executing in this case as well. A workflow job itself can also be canceled. In this case all its spawned job resources will be canceled if cancelable and following paths stop executing. From 0a975736d96301a7dc0c3cf128361878dc6bcd8c Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 11:50:58 -0500 Subject: [PATCH 046/154] further workflow doc copy edit with QE --- docs/workflow.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/workflow.md b/docs/workflow.md index e0d195d18c..d7336bce08 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -32,7 +32,7 @@ As stated, workflow job templates can be created with populated `extra_vars`. Th Job resources spawned by workflow jobs are needed by workflow to run correctly. Therefore deletion of spawned job resources is blocked while the underlying workflow job is executing. -Other than success and failure, a workflow spawned job resource can also end with status 'error' and 'canceled'. When a workflow spawned job resource errors, all branches starting from that job will stop executing while the rest keep their own paces. Canceling a workflow spawned job resource follows the same rules. If the unified job template of the node is null (which could be a result of deleting the unified job template or copying a its workflow when the user lacks necessary permissions to use it), then the branch should stop executing in this case as well. +Other than success and failure, a workflow spawned job resource can also end with status 'error' and 'canceled'. When a workflow spawned job resource errors, all branches starting from that job will stop executing while the rest continue executing. Canceling a workflow spawned job resource follows the same rules. If the unified job template of the node is null (which could be a result of deleting the unified job template or copying a workflow when the user lacks necessary permissions to use the resource), then the branch should stop executing in this case as well. A workflow job itself can also be canceled. In this case all its spawned job resources will be canceled if cancelable and following paths stop executing. @@ -47,9 +47,9 @@ Workflow job summary: ``` ### Workflow Copy and Relaunch -Other than the normal way of creating workflow job templates, it is also possible to copy existing workflow job templates. The resulting new workflow job template will be mostly identical to which it copies from, except for `name` field which will be appended a text to indicate it's a copy. +Other than the normal way of creating workflow job templates, it is also possible to copy existing workflow job templates. The resulting new workflow job template will be mostly identical to the original, except for `name` field which will be appended a text to indicate it's a copy. -Workflow job templates can be copied by POSTing to endpoint `/workflow_job_templates/\d+/copy/`. After copy finished, the resulting new workflow job template will have identical fields including description, extra_vars, and survey-related fields (survey_spec and survey_enabled). More importantly, workflow job template node of the original workflow job template, as well as the topology they bear, will be copied. Note there are RBAC restrictions copying workflow job template nodes. A workflow job template is allowed to be copied if the user has permission to add an equivalent workflow job template. If the user performing the copy does not have access to a node's related resources (job template, inventory, or credential), those related fields will be null in the copy's version of the node. Schedules and notification templates of the original workflow job template will not be copied nor shared, and the name of the created workflow job template is the original name plus a special-formatted suffix to indicate its copy origin as well as the copy time, such as 'copy_from_name@10:30:00 am'. +Workflow job templates can be copied by POSTing to endpoint `/workflow_job_templates/\d+/copy/`. After copy finished, the resulting new workflow job template will have identical fields including description, extra_vars, and survey-related fields (survey_spec and survey_enabled). More importantly, workflow job template node of the original workflow job template, as well as the topology they bear, will be copied. Note there are RBAC restrictions on copying workflow job template nodes. A workflow job template is allowed to be copied if the user has permission to add an equivalent workflow job template. If the user performing the copy does not have access to a node's related resources (job template, inventory, or credential), those related fields will be null in the copy's version of the node. Schedules and notification templates of the original workflow job template will not be copied nor shared, and the name of the created workflow job template is the original name plus a special-formatted suffix to indicate its copy origin as well as the copy time, such as 'copy_from_name@10:30:00 am'. Workflow jobs cannot be copied directly, instead a workflow job is implicitly copied when it needs to relaunch. Relaunching an existing workflow job is done by POSTing to endpoint `/workflow_jobs/\d+/relaunch/`. What happens next is the original workflow job is copied to create a new workflow job. The new workflow job then gets a copy of all nodes of the original as well as the topology they bear. Finally the full-fledged new workflow job is triggered to run, thus fulfilling the purpose of relaunch. Survey password-type answers should also be redacted in the relaunched version of the workflow job. From bc4bc2e8cefeafd0d864b32808bfe21cd6b4622f Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Tue, 10 Jan 2017 12:15:38 -0500 Subject: [PATCH 047/154] Enable show/hide on password and secret fields --- .../src/configuration/auth-form/sub-forms/auth-azure.form.js | 3 ++- .../configuration/auth-form/sub-forms/auth-github-org.form.js | 3 ++- .../configuration/auth-form/sub-forms/auth-github-team.form.js | 3 ++- .../src/configuration/auth-form/sub-forms/auth-github.form.js | 3 ++- .../auth-form/sub-forms/auth-google-oauth2.form.js | 3 ++- .../src/configuration/auth-form/sub-forms/auth-ldap.form.js | 3 ++- .../src/configuration/auth-form/sub-forms/auth-radius.form.js | 3 ++- .../src/configuration/auth-form/sub-forms/auth-saml.form.js | 3 ++- .../configuration/system-form/sub-forms/system-logging.form.js | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js index 17b36e67fb..83ee570a13 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js @@ -16,7 +16,8 @@ reset: 'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY' }, SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET' }, SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: { diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js index bd547cf8b1..88b3d273f6 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js @@ -16,7 +16,8 @@ export default ['i18n', function(i18n) { reset: 'SOCIAL_AUTH_GITHUB_ORG_KEY' }, SOCIAL_AUTH_GITHUB_ORG_SECRET: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'SOCIAL_AUTH_GITHUB_ORG_SECRET' }, SOCIAL_AUTH_GITHUB_ORG_NAME: { diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js index d43d8c01be..42cdd3fcaf 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js @@ -16,7 +16,8 @@ export default ['i18n', function(i18n) { reset: 'SOCIAL_AUTH_GITHUB_TEAM_KEY' }, SOCIAL_AUTH_GITHUB_TEAM_SECRET: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'SOCIAL_AUTH_GITHUB_TEAM_SECRET' }, SOCIAL_AUTH_GITHUB_TEAM_ID: { diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js index 03af137a7c..2fb983eadc 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js @@ -16,7 +16,8 @@ export default ['i18n', function(i18n) { reset: 'SOCIAL_AUTH_GITHUB_KEY' }, SOCIAL_AUTH_GITHUB_SECRET: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'SOCIAL_AUTH_GITHUB_SECRET' } }, diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js index ac1c23545e..2715c73f9a 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js @@ -16,7 +16,8 @@ export default ['i18n', function(i18n) { reset: 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY' }, SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET' }, SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: { diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js index 8d38fea688..25fb2ce934 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js @@ -21,7 +21,8 @@ export default ['i18n', function(i18n) { reset: 'AUTH_LDAP_BIND_DN' }, AUTH_LDAP_BIND_PASSWORD: { - type: 'password' + type: 'sensitive', + hasShowInputButton: true, }, AUTH_LDAP_USER_SEARCH: { type: 'textarea', diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js index b16fd649dc..70d68015e0 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js @@ -21,7 +21,8 @@ export default ['i18n', function(i18n) { reset: 'RADIUS_PORT' }, RADIUS_SECRET: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'RADIUS_SECRET' } }, diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js index ca2bb50dcb..0d10e6e8b1 100644 --- a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js +++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js @@ -20,7 +20,8 @@ export default ['i18n', function(i18n) { reset: 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT' }, SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY' }, SOCIAL_AUTH_SAML_ORG_INFO: { diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js b/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js index ee99024b45..a14844bf26 100644 --- a/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js +++ b/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js @@ -30,7 +30,8 @@ reset: 'LOG_AGGREGATOR_USERNAME' }, LOG_AGGREGATOR_PASSWORD: { - type: 'text', + type: 'sensitive', + hasShowInputButton: true, reset: 'LOG_AGGREGATOR_PASSWORD' }, LOG_AGGREGATOR_LOGGERS: { From fe896dbda5e8ce63e39e331b7e49a8e0cd5b8feb Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 12:57:40 -0500 Subject: [PATCH 048/154] permission tab disable for private credentials --- awx/ui/client/src/controllers/Credentials.js | 19 ++++++++++--------- awx/ui/client/src/forms/Credentials.js | 4 +++- awx/ui/client/src/shared/directives.js | 8 +++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/awx/ui/client/src/controllers/Credentials.js b/awx/ui/client/src/controllers/Credentials.js index a64056ccd4..2bddc26f0d 100644 --- a/awx/ui/client/src/controllers/Credentials.js +++ b/awx/ui/client/src/controllers/Credentials.js @@ -283,7 +283,7 @@ CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, $stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt, GetBasePath, GetChoices, KindChange, Empty, OwnerChange, FormSave, Wait, - $state, CreateSelect2, Authorization) { + $state, CreateSelect2, Authorization, i18n) { ClearScope(); @@ -336,13 +336,14 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, }); } - // if the credential is assigned to an organization, allow permission delegation - // do NOT use $scope.organization in a view directive to determine if a credential is associated with an org - // @todo why not? ^ and what is this type check for a number doing - should this be a type check for undefined? - $scope.disablePermissionAssignment = typeof($scope.organization) === 'number' ? false : true; - if ($scope.disablePermissionAssignment) { - $scope.permissionsTooltip = 'Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.'; - } + $scope.$watch('organization', function(val) { + if (val === undefined) { + $scope.permissionsTooltip = i18n._('Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.'); + } else { + $scope.permissionsTooltip = ''; + } + }); + setAskCheckboxes(); KindChange({ scope: $scope, @@ -613,5 +614,5 @@ CredentialsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices', 'KindChange', 'Empty', 'OwnerChange', - 'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization' + 'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n', ]; diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js index 9d816c9ce4..727172c94c 100644 --- a/awx/ui/client/src/forms/Credentials.js +++ b/awx/ui/client/src/forms/Credentials.js @@ -420,7 +420,9 @@ export default related: { permissions: { - disabled: 'disablePermissionAssignment', + disabled: '(organization === undefined ? true : false)', + // Do not transition the state if organization is undefined + ngClick: `(organization === undefined ? true : false)||$state.go('credentials.edit.permissions')`, awToolTip: '{{permissionsTooltip}}', dataTipWatch: 'permissionsTooltip', dataPlacement: 'top', diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 1da55f80f8..a66ca71e9f 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -621,11 +621,9 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) if (attrs.tipWatch) { // Add dataTipWatch: 'variable_name' scope.$watch(attrs.tipWatch, function(newVal, oldVal) { - if (newVal !== oldVal) { - // Where did fixTitle come from?: - // http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click - $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle'); - } + // Where did fixTitle come from?: + // http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click + $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle'); }); } } From f3ade65ac6e8b883c61a31acc34aba4464b7b5eb Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Tue, 10 Jan 2017 14:27:55 -0500 Subject: [PATCH 049/154] Vendor pywinrm and deps for Ansible. --- requirements/requirements_ansible.in | 1 + requirements/requirements_ansible.txt | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/requirements/requirements_ansible.in b/requirements/requirements_ansible.in index 2d381c955b..5497b2a7b1 100644 --- a/requirements/requirements_ansible.in +++ b/requirements/requirements_ansible.in @@ -7,5 +7,6 @@ boto==2.45.0 psphere==0.5.2 psutil==5.0.0 pyvmomi==6.5 +pywinrm[kerberos]==0.2.2 secretstorage==2.3.1 shade==1.13.1 diff --git a/requirements/requirements_ansible.txt b/requirements/requirements_ansible.txt index 59a7fe9544..fef057d56b 100644 --- a/requirements/requirements_ansible.txt +++ b/requirements/requirements_ansible.txt @@ -64,8 +64,10 @@ msrestazure==0.4.6 # via azure-common munch==2.0.4 # via shade netaddr==0.7.18 # via oslo.config, oslo.utils, python-neutronclient netifaces==0.10.5 # via oslo.utils, shade +ntlm-auth==1.0.2 # via requests-ntlm oauthlib==2.0.1 # via requests-oauthlib openstacksdk==0.9.11 # via python-openstackclient +ordereddict==1.1 # via ntlm-auth os-client-config==1.24.0 # via openstacksdk, osc-lib, python-magnumclient, python-neutronclient, shade os-diskconfig-python-novaclient-ext==0.1.3 # via rackspace-novaclient os-networksv2-python-novaclient-ext==0.26 # via rackspace-novaclient @@ -83,6 +85,7 @@ psutil==5.0.0 pyasn1==0.1.9 # via cryptography pycparser==2.17 # via cffi PyJWT==1.4.2 # via adal +pykerberos==1.1.13 # via requests-kerberos pyparsing==2.1.10 # via cliff, cmd2, oslo.utils python-cinderclient==1.9.0 # via python-openstackclient, shade python-dateutil==2.6.0 # via adal, azure-storage @@ -100,24 +103,28 @@ python-swiftclient==3.2.0 # via python-heatclient, python-troveclient, shade python-troveclient==2.7.0 # via shade pytz==2016.10 # via babel, oslo.serialization, oslo.utils pyvmomi==6.5 +pywinrm[kerberos]==0.2.2 PyYAML==3.12 # via cliff, os-client-config, psphere, python-heatclient, python-ironicclient, python-mistralclient rackspace-auth-openstack==1.3 # via rackspace-novaclient rackspace-novaclient==2.1 rax-default-network-flags-python-novaclient-ext==0.4.0 # via rackspace-novaclient rax-scheduled-images-python-novaclient-ext==0.3.1 # via rackspace-novaclient +requests-kerberos==0.11.0 # via pywinrm +requests-ntlm==1.0.0 # via pywinrm requests-oauthlib==0.7.0 # via msrest -requests==2.11.1 # via adal, azure-servicebus, azure-servicemanagement-legacy, azure-storage, keystoneauth1, msrest, python-cinderclient, python-designateclient, python-glanceclient, python-heatclient, python-ironicclient, python-keystoneclient, python-magnumclient, python-mistralclient, python-neutronclient, python-novaclient, python-swiftclient, python-troveclient, pyvmomi, requests-oauthlib +requests==2.11.1 # via adal, azure-servicebus, azure-servicemanagement-legacy, azure-storage, keystoneauth1, msrest, python-cinderclient, python-designateclient, python-glanceclient, python-heatclient, python-ironicclient, python-keystoneclient, python-magnumclient, python-mistralclient, python-neutronclient, python-novaclient, python-swiftclient, python-troveclient, pyvmomi, pywinrm, requests-kerberos, requests-ntlm, requests-oauthlib requestsexceptions==1.1.3 # via os-client-config, shade rfc3986==0.4.1 # via oslo.config secretstorage==2.3.1 shade==1.13.1 simplejson==3.10.0 # via osc-lib, python-cinderclient, python-neutronclient, python-novaclient, python-troveclient -six==1.10.0 # via cliff, cryptography, debtcollector, keystoneauth1, mock, openstacksdk, osc-lib, oslo.config, oslo.i18n, oslo.serialization, oslo.utils, python-cinderclient, python-dateutil, python-designateclient, python-glanceclient, python-heatclient, python-ironicclient, python-keystoneclient, python-magnumclient, python-mistralclient, python-neutronclient, python-novaclient, python-openstackclient, python-swiftclient, python-troveclient, pyvmomi, shade, stevedore, warlock +six==1.10.0 # via cliff, cryptography, debtcollector, keystoneauth1, mock, ntlm-auth, openstacksdk, osc-lib, oslo.config, oslo.i18n, oslo.serialization, oslo.utils, python-cinderclient, python-dateutil, python-designateclient, python-glanceclient, python-heatclient, python-ironicclient, python-keystoneclient, python-magnumclient, python-mistralclient, python-neutronclient, python-novaclient, python-openstackclient, python-swiftclient, python-troveclient, pyvmomi, pywinrm, shade, stevedore, warlock stevedore==1.19.1 # via cliff, keystoneauth1, openstacksdk, osc-lib, oslo.config, python-designateclient, python-keystoneclient, python-magnumclient suds==0.4 # via psphere unicodecsv==0.14.1 # via cliff warlock==1.2.0 # via python-glanceclient wrapt==1.10.8 # via debtcollector, positional +xmltodict==0.10.2 # via pywinrm # The following packages are considered to be unsafe in a requirements file: # setuptools # via cryptography From 0f8f53b9d0b91ac31138f2ddb99f750c67d38822 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Tue, 10 Jan 2017 14:35:44 -0500 Subject: [PATCH 050/154] Fixed resolve in route --- awx/ui/client/src/inventories/main.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/inventories/main.js b/awx/ui/client/src/inventories/main.js index 8505c9b80f..ac031283cd 100644 --- a/awx/ui/client/src/inventories/main.js +++ b/awx/ui/client/src/inventories/main.js @@ -66,7 +66,19 @@ angular.module('inventory', [ ], ParentObject: ['groupData', function(groupData) { return groupData; - }] + }], + UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', + function(Rest, GetBasePath, $stateParams, $q) { + Rest.setUrl(GetBasePath('unified_jobs')); + var val = $q.defer(); + Rest.options() + .then(function(data) { + val.resolve(data.data); + }, function(data) { + val.reject(data); + }); + return val.promise; + }] }, views: { // clear form template when views render in this substate From c0bbc4a9dd775cb5c81b65ad1f0928e50fe7a305 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 14:48:48 -0500 Subject: [PATCH 051/154] show message when too many events come back for a job detail view --- .../job-results-stdout.block.less | 6 +++++ .../job-results-stdout.partial.html | 7 +++++ .../src/job-results/job-results.controller.js | 17 ++++++++++-- .../shared/smart-search/queryset.service.js | 27 ++++++++++++++----- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index 408e4cbb32..0f01da5b2e 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -162,6 +162,7 @@ .JobResultsStdOut-stdoutColumn { padding-left: 20px; + padding-right: 20px; padding-top: 2px; padding-bottom: 2px; color: @default-interface-txt; @@ -171,6 +172,11 @@ width:100%; } +.JobResultsStdOut-stdoutColumn--tooMany { + font-weight: bold; + text-transform: uppercase; +} + .JobResultsStdOut-stdoutColumn { cursor: pointer; } diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 0ba992b146..87e65f54b4 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -31,6 +31,13 @@
+
+
+ +
+
The standard out based on the current filter is too large to display. Please use additional filters to view results.
+
diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index aef8b2ae86..81ce5894bf 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -383,7 +383,11 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // making rest calls for next pages/etc. (you can see context is // also passed into getEvents and processEvent and similar checks // exist in these functions) - if (context !== currentContext) { + // + // also, if the page doesn't contain results (i.e.: the response + // returns an error), don't process the page + if (context !== currentContext || events === undefined || + events.results === undefined) { return; } @@ -441,7 +445,16 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); $scope.hasSkeleton.promise.then(() => { - processPage(val, context); + if (val.count > parseInt(val.maxEvents)) { + $(".header_task").hide(); + $(".header_play").hide(); + $scope.tooManyEvents = true; + } else { + $(".header_task").show(); + $(".header_play").show(); + $scope.tooManyEvents = false; + processPage(val, context); + } }); })); diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index 359245bed4..0ffeb521ce 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -147,20 +147,33 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear Wait('start'); this.url = `${endpoint}${this.encodeQueryset(params)}`; Rest.setUrl(this.url); + return Rest.get() - .success(this.success.bind(this)) - .error(this.error.bind(this)) - .finally(Wait('stop')); + .then(function(response) { + Wait('stop'); + + if (response + .headers('X-UI-Max-Events') !== null) { + response.data.maxEvents = response. + headers('X-UI-Max-Events'); + } + + return response; + }) + .catch(function(response) { + Wait('stop'); + + this.error(response.data, response.status); + + return response; + }.bind(this)); }, error(data, status) { ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + this.url + '. GET returned: ' + status }); - }, - success(data) { - return data; - }, + } }; } ]; From 2b0598f0929ed83df9da7d4dabb4946577a58577 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Tue, 10 Jan 2017 14:49:58 -0500 Subject: [PATCH 052/154] Started to refactor some smart search functionality --- awx/ui/client/src/shared/smart-search/main.js | 3 +- .../shared/smart-search/queryset.service.js | 102 ++++++++++++++++-- .../smart-search/smart-search.controller.js | 76 +++++++++---- .../smart-search/smart-search.service.js | 19 ++++ .../smart-search/smart-search.service-test.js | 42 ++++++++ 5 files changed, 215 insertions(+), 27 deletions(-) create mode 100644 awx/ui/client/src/shared/smart-search/smart-search.service.js create mode 100644 awx/ui/tests/spec/smart-search/smart-search.service-test.js diff --git a/awx/ui/client/src/shared/smart-search/main.js b/awx/ui/client/src/shared/smart-search/main.js index 7653df7bd7..e7aaf825a7 100644 --- a/awx/ui/client/src/shared/smart-search/main.js +++ b/awx/ui/client/src/shared/smart-search/main.js @@ -2,11 +2,12 @@ import directive from './smart-search.directive'; import controller from './smart-search.controller'; import service from './queryset.service'; import DjangoSearchModel from './django-search-model.class'; - +import smartSearchService from './smart-search.service'; export default angular.module('SmartSearchModule', []) .directive('smartSearch', directive) .controller('SmartSearchController', controller) .service('QuerySet', service) + .service('SmartSearchService', smartSearchService) .constant('DjangoSearchModel', DjangoSearchModel); diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index 359245bed4..ec18f1b0d1 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -1,5 +1,5 @@ -export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSearchModel', '$cacheFactory', - function($q, Rest, ProcessErrors, $rootScope, Wait, DjangoSearchModel, $cacheFactory) { +export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSearchModel', '$cacheFactory', 'SmartSearchService', + function($q, Rest, ProcessErrors, $rootScope, Wait, DjangoSearchModel, $cacheFactory, SmartSearchService) { return { // kick off building a model for a specific endpoint // this is usually a list's basePath @@ -67,29 +67,114 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear return angular.isObject(params) ? `?${queryset}` : ''; function encodeTerm(value, key){ + if (Array.isArray(value)){ - return _.map(value, (item) => `${key}=${item}`).join('&') + '&'; + return _.map(value, function(item){ + item = item.replace(/"|'/g, ""); + return `${key}=${item}`.join('&') + '&'; + }); } else { + value = value.replace(/"|'/g, ""); return `${key}=${value}&`; } } }, // encodes a ui smart-search param to a django-friendly param // operand:key:comparator:value => {operand__key__comparator: value} - encodeParam(param){ - let split = param.split(':'); - return {[split.slice(0,split.length -1).join('__')] : split[split.length-1]}; + encodeParam(params){ + // Assumption here is that we have a key and a value so the length + // of the paramParts array will be 2. [0] is the key and [1] the value + let paramParts = SmartSearchService.splitTermIntoParts(params.term); + let keySplit = paramParts[0].split('.'); + let exclude = false; + let lessThanGreaterThan = paramParts[1].match(/^(>|<).*$/) ? true : false; + if(keySplit[0].startsWith("-")) { + exclude = true; + keySplit[0] = keySplit[0].replace(/^-/, ''); + } + let paramString = exclude ? "not__" : ""; + let valueString = paramParts[1]; + if(keySplit.length === 1) { + if(params.searchTerm && !lessThanGreaterThan) { + paramString += keySplit[0] + '__icontains'; + } + else if(params.relatedSearchTerm) { + paramString += keySplit[0] + '__search'; + } + else { + paramString += keySplit[0]; + } + } + else { + paramString += keySplit.join('__'); + } + + if(lessThanGreaterThan) { + if(paramParts[1].match(/^>=.*$/)) { + paramString += '__gte'; + valueString = valueString.replace(/^(>=)/,""); + } + else if(paramParts[1].match(/^<=.*$/)) { + paramString += '__lte'; + valueString = valueString.replace(/^(<=)/,""); + } + else if(paramParts[1].match(/^<.*$/)) { + paramString += '__lt'; + valueString = valueString.replace(/^(<)/,""); + } + else if(paramParts[1].match(/^>.*$/)) { + paramString += '__gt'; + valueString = valueString.replace(/^(>)/,""); + } + } + + return {[paramString] : valueString}; }, // decodes a django queryset param into a ui smart-search tag or set of tags decodeParam(value, key){ + + let decodeParamString = function(searchString) { + if(key === 'search') { + // Don't include 'search:' in the search tag + return decodeURIComponent(`${searchString}`); + } + else { + key = key.replace(/__icontains/g, ""); + let split = key.split('__'); + let decodedParam = searchString; + let exclude = false; + if(key.startsWith('not__')) { + exclude = true; + split = split.splice(1, split.length); + } + if(key.endsWith('__gt')) { + decodedParam = '>' + decodedParam; + split = split.splice(0, split.length-1); + } + else if(key.endsWith('__lt')) { + decodedParam = '<' + decodedParam; + split = split.splice(0, split.length-1); + } + else if(key.endsWith('__gte')) { + decodedParam = '>=' + decodedParam; + split = split.splice(0, split.length-1); + } + else if(key.endsWith('__lte')) { + decodedParam = '<=' + decodedParam; + split = split.splice(0, split.length-1); + } + return exclude ? `-${split.join('.')}:${decodedParam}` : `${split.join('.')}:${decodedParam}`; + } + }; + if (Array.isArray(value)){ return _.map(value, (item) => { - return `${key.split('__').join(':')}:${item}`; + return decodeParamString(item); }); } else { - return `${key.split('__').join(':')}:${value}`; + return decodeParamString(value); } }, @@ -161,6 +246,7 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear success(data) { return data; }, + }; } ]; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 95f3be3adb..7b09def616 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -1,5 +1,5 @@ -export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', 'QuerySet', - function($stateParams, $scope, $state, QuerySet, GetBasePath, qs) { +export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', 'QuerySet', 'SmartSearchService', + function($stateParams, $scope, $state, QuerySet, GetBasePath, qs, SmartSearchService) { let path, relations, // steps through the current tree of $state configurations, grabs default search params @@ -17,6 +17,7 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' $scope.searchTags = stripDefaultParams($state.params[`${$scope.iterator}_search`]); qs.initFieldset(path, $scope.djangoModel, relations).then((data) => { $scope.models = data.models; + $scope.options = data.options.data; $scope.$emit(`${$scope.list.iterator}_options`, data.options); }); } @@ -38,6 +39,16 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' return flat; } + function setDefaults(term) { + if ($scope.list.defaultSearchParams) { + return $scope.list.defaultSearchParams(term); + } else { + return { + search: encodeURIComponent(term) + }; + } + } + $scope.toggleKeyPane = function() { $scope.showKeyPane = !$scope.showKeyPane; }; @@ -56,7 +67,27 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' // remove tag, merge new queryset, $state.go $scope.remove = function(index) { - let removed = qs.encodeParam($scope.searchTags.splice(index, 1)[0]); + let tagToRemove = $scope.searchTags.splice(index, 1)[0]; + let termParts = SmartSearchService.splitTermIntoParts(tagToRemove); + let removed; + if (termParts.length === 1) { + removed = setDefaults(tagToRemove); + } + else { + let root = termParts[0].split(".")[0].replace(/^-/, ''); + let encodeParams = { + term: tagToRemove + }; + if(_.has($scope.options.actions.GET, root)) { + if($scope.options.actions.GET[root].type && $scope.options.actions.GET[root].type === 'field') { + encodeParams.relatedSearchTerm = true; + } + else { + encodeParams.searchTerm = true; + } + } + removed = qs.encodeParam(encodeParams); + } _.each(removed, (value, key) => { if (Array.isArray(queryset[key])){ _.remove(queryset[key], (item) => item === value); @@ -79,26 +110,35 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' let params = {}, origQueryset = _.clone(queryset); - function setDefaults(term) { - // "name" and "description" are sane defaults for MOST models, but not ALL! - // defaults may be configured in ListDefinition.defaultSearchParams - if ($scope.list.defaultSearchParams) { - return $scope.list.defaultSearchParams(term); - } else { - return { - or__name__icontains: term, - or__description__icontains: term - }; - } - } + // Remove leading/trailing whitespace if there is any + terms = terms.trim(); if(terms && terms !== '') { - _.forEach(terms.split(' '), (term) => { + // Split the terms up + let splitTerms = SmartSearchService.splitSearchIntoTerms(terms); + _.forEach(splitTerms, (term) => { + + let termParts = SmartSearchService.splitTermIntoParts(term); + // if only a value is provided, search using default keys - if (term.split(':').length === 1) { + if (termParts.length === 1) { params = _.merge(params, setDefaults(term)); } else { - params = _.merge(params, qs.encodeParam(term)); + // Figure out if this is a search term + let root = termParts[0].split(".")[0].replace(/^-/, ''); + if(_.has($scope.options.actions.GET, root)) { + if($scope.options.actions.GET[root].type && $scope.options.actions.GET[root].type === 'field') { + params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true})); + } + else { + params = _.merge(params, qs.encodeParam({term: term, searchTerm: true})); + } + } + // Its not a search term or a related search term + else { + params = _.merge(params, qs.encodeParam({term: term})); + } + } }); diff --git a/awx/ui/client/src/shared/smart-search/smart-search.service.js b/awx/ui/client/src/shared/smart-search/smart-search.service.js new file mode 100644 index 0000000000..fe683c08d7 --- /dev/null +++ b/awx/ui/client/src/shared/smart-search/smart-search.service.js @@ -0,0 +1,19 @@ +export default [function() { + return { + splitSearchIntoTerms(searchString) { + return searchString.match(/(?:[^\s("')]+|"[^"]*"|'[^']*')+/g); + }, + splitTermIntoParts(searchTerm) { + let breakOnColon = searchTerm.match(/(?:[^:"]+|"[^"]*")+/g); + + if(breakOnColon.length > 2) { + // concat all the strings after the first one together + let stringsToJoin = breakOnColon.slice(1,breakOnColon.length); + return [breakOnColon[0], stringsToJoin.join(':')]; + } + else { + return breakOnColon; + } + } + }; +}]; diff --git a/awx/ui/tests/spec/smart-search/smart-search.service-test.js b/awx/ui/tests/spec/smart-search/smart-search.service-test.js new file mode 100644 index 0000000000..679a5656b4 --- /dev/null +++ b/awx/ui/tests/spec/smart-search/smart-search.service-test.js @@ -0,0 +1,42 @@ +'use strict'; + +describe('Service: SmartSearch', () => { + let SmartSearchService; + + beforeEach(angular.mock.module('Tower')); + + beforeEach(angular.mock.module('SmartSearchModule')); + + beforeEach(angular.mock.inject((_SmartSearchService_) => { + SmartSearchService = _SmartSearchService_; + })); + + describe('fn splitSearchIntoTerms', () => { + it('should convert the search string to an array tag strings', () =>{ + expect(SmartSearchService.splitSearchIntoTerms('foo')).toEqual(["foo"]); + expect(SmartSearchService.splitSearchIntoTerms('foo bar')).toEqual(["foo", "bar"]); + expect(SmartSearchService.splitSearchIntoTerms('name:foo bar')).toEqual(["name:foo", "bar"]); + expect(SmartSearchService.splitSearchIntoTerms('name:foo description:bar')).toEqual(["name:foo", "description:bar"]); + expect(SmartSearchService.splitSearchIntoTerms('name:"foo bar"')).toEqual(["name:\"foo bar\""]); + expect(SmartSearchService.splitSearchIntoTerms('name:"foo bar" description:"bar foo"')).toEqual(["name:\"foo bar\"", "description:\"bar foo\""]); + expect(SmartSearchService.splitSearchIntoTerms('name:"foo bar" description:"bar foo"')).toEqual(["name:\"foo bar\"", "description:\"bar foo\""]); + expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\'')).toEqual(["name:\'foo bar\'"]); + expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\' description:\'bar foo\'')).toEqual(["name:\'foo bar\'", "description:\'bar foo\'"]); + expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\' description:\'bar foo\'')).toEqual(["name:\'foo bar\'", "description:\'bar foo\'"]); + expect(SmartSearchService.splitSearchIntoTerms('name:\"foo bar\" description:\'bar foo\'')).toEqual(["name:\"foo bar\"", "description:\'bar foo\'"]); + }); + }); + + describe('fn splitTermIntoParts', () => { + it('should convert the search term to a key and value', () =>{ + expect(SmartSearchService.splitTermIntoParts('foo')).toEqual(["foo"]); + expect(SmartSearchService.splitTermIntoParts('foo:bar')).toEqual(["foo", "bar"]); + expect(SmartSearchService.splitTermIntoParts('foo:bar:foobar')).toEqual(["foo", "bar:foobar"]); + expect(SmartSearchService.splitTermIntoParts('name:\"foo bar\"')).toEqual(["name", "\"foo bar\""]); + expect(SmartSearchService.splitTermIntoParts('name:\"foo:bar\"')).toEqual(["name", "\"foo:bar\""]); + expect(SmartSearchService.splitTermIntoParts('name:\'foo bar\'')).toEqual(["name", "\'foo bar\'"]); + expect(SmartSearchService.splitTermIntoParts('name:\'foo:bar\'')).toEqual(["name", "\'foo:bar\'"]); + }); + }); + +}); From ff419921944928793178318c5ed9ec9dec3ac705 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 15:52:08 -0500 Subject: [PATCH 053/154] don't include var if not going to use it --- awx/ui/client/src/shared/directives.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index a66ca71e9f..55278c5ab9 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -620,7 +620,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) if (attrs.tipWatch) { // Add dataTipWatch: 'variable_name' - scope.$watch(attrs.tipWatch, function(newVal, oldVal) { + scope.$watch(attrs.tipWatch, function(newVal) { // Where did fixTitle come from?: // http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle'); From d590432590dfe8a7a3eb146d30085ed9c2355ee7 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 15:54:06 -0500 Subject: [PATCH 054/154] gracefully handle 409 on delete related to #3245 --- awx/ui/client/src/shared/Utilities.js | 2 ++ .../client/src/templates/list/templates-list.controller.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index 9bc18dfd13..3bb9c02de0 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -198,6 +198,8 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) msg += 'Please contact your system administrator.'; } Alert(defaultMsg.hdr, msg); + } else if (status === 409) { + Alert('Conflict', data.conflict || "Resource currently in use."); } else if (status === 410) { Alert('Deleted Object', 'The requested object was previously deleted and can no longer be accessed.'); } else if ((status === 'Token is expired') || (status === 401 && data.detail && data.detail === 'Token is expired') || diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index 5bcd659a38..820f843852 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -131,7 +131,7 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', handleSuccessfulDelete(); }, function (data) { Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', + ProcessErrors($scope, data, data.status, null, { hdr: 'Error!', msg: 'Call to delete workflow job template failed. DELETE returned status: ' + status }); }); } @@ -141,8 +141,8 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', handleSuccessfulDelete(); }, function (data) { Wait('stop'); - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to delete job template failed. DELETE returned status: ' + status }); + ProcessErrors($scope, data, data.status, null, { hdr: 'Error!', + msg: 'Call to delete job template failed. DELETE returned status: ' + data.status }); }); } else { From d589dceaa2e3509899c2ccbc9518d04ad33e4d22 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Tue, 10 Jan 2017 16:00:59 -0500 Subject: [PATCH 055/154] Fixed pagination by stringifying the page number before trying to update the query param --- awx/ui/client/src/shared/paginate/paginate.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/paginate/paginate.controller.js b/awx/ui/client/src/shared/paginate/paginate.controller.js index fdd80000c7..407f9e10f6 100644 --- a/awx/ui/client/src/shared/paginate/paginate.controller.js +++ b/awx/ui/client/src/shared/paginate/paginate.controller.js @@ -18,7 +18,7 @@ export default ['$scope', '$stateParams', '$state', '$filter', 'GetBasePath', 'Q return; } path = GetBasePath($scope.basePath) || $scope.basePath; - queryset = _.merge($stateParams[`${$scope.iterator}_search`], { page: page }); + queryset = _.merge($stateParams[`${$scope.iterator}_search`], { page: page.toString() }); $state.go('.', { [$scope.iterator + '_search']: queryset }); From 0befcced8754a602d2387bbdd2f1969eea1f7b3d Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 16:11:13 -0500 Subject: [PATCH 056/154] update too large message for standard out --- .../job-results-stdout/job-results-stdout.block.less | 1 + .../job-results-stdout/job-results-stdout.partial.html | 8 ++++---- awx/ui/client/src/job-results/job-results.block.less | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index 0f01da5b2e..2df922e2c4 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -175,6 +175,7 @@ .JobResultsStdOut-stdoutColumn--tooMany { font-weight: bold; text-transform: uppercase; + color: @default-err; } .JobResultsStdOut-stdoutColumn { diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 87e65f54b4..63e41b8fa8 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -31,16 +31,16 @@
+
+
+
-
The standard out based on the current filter is too large to display. Please use additional filters to view results.
+
The standard output is too large to display. Please specify additional filters to narrow the standard out.
-
-
-
Date: Tue, 10 Jan 2017 16:42:30 -0500 Subject: [PATCH 057/154] better job list empty message related to #4250 --- awx/ui/client/src/lists/AllJobs.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/lists/AllJobs.js b/awx/ui/client/src/lists/AllJobs.js index 16a37e5c04..12a94a5f1b 100644 --- a/awx/ui/client/src/lists/AllJobs.js +++ b/awx/ui/client/src/lists/AllJobs.js @@ -7,7 +7,8 @@ export default angular.module('AllJobsDefinition', ['sanitizeFilter', 'capitalizeFilter']) - .value( 'AllJobsList', { + .factory('AllJobsList', ['i18n', function(i18n) { + return { name: 'jobs', basePath: 'unified_jobs', @@ -16,6 +17,8 @@ export default index: false, hover: true, well: false, + emptyListText: i18n._('No jobs have yet run.'), + fields: { status: { label: '', @@ -113,4 +116,5 @@ export default ngShow: "(job.status !== 'running' && job.status !== 'waiting' && job.status !== 'pending') && job.summary_fields.user_capabilities.delete" } } - }); + }; +}]); From 806e6d143fd0aba88716bcd25eed0d4783400f23 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 09:15:46 -0500 Subject: [PATCH 058/154] word wrap large callback key in alert modal --- .../add-job-template/job-template-add.controller.js | 2 +- .../edit-job-template/job-template-edit.controller.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index bbb97b3684..858cfc3452 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -295,7 +295,7 @@ ${data.related.callback}

-

The host configuration key is: +

The host configuration key is: ${$filter('sanitize')(data.host_config_key)} diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index 83df997d81..a42c089273 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -432,14 +432,14 @@ export default ${data.related.callback}

-

The host configuration key is: +

The host configuration key is: ${$filter('sanitize')(data.host_config_key)}

`, - 'alert-info', saveCompleted, null, null, + 'alert-danger', saveCompleted, null, null, null, true); } var orgDefer = $q.defer(); From a36981dcb98705fc9a8db21567b5224caba0a08a Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Tue, 10 Jan 2017 16:53:35 -0500 Subject: [PATCH 059/154] Now handling multiple instances of the same query param with different values --- .../shared/smart-search/queryset.service.js | 6 ++++-- .../smart-search/smart-search.controller.js | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index ec18f1b0d1..89ad2f14d1 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -69,10 +69,12 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear function encodeTerm(value, key){ if (Array.isArray(value)){ - return _.map(value, function(item){ + let concated = ''; + angular.forEach(value, function(item){ item = item.replace(/"|'/g, ""); - return `${key}=${item}`.join('&') + '&'; + concated += `${key}=${item}&`; }); + return concated; } else { value = value.replace(/"|'/g, ""); diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 7b09def616..2da9648ca1 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -120,23 +120,34 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' let termParts = SmartSearchService.splitTermIntoParts(term); + function combineSameSearches(a,b){ + if (_.isArray(a)) { + return a.concat(b); + } + else { + if(a) { + return [a,b]; + } + } + } + // if only a value is provided, search using default keys if (termParts.length === 1) { - params = _.merge(params, setDefaults(term)); + params = _.merge(params, setDefaults(term), combineSameSearches); } else { // Figure out if this is a search term let root = termParts[0].split(".")[0].replace(/^-/, ''); if(_.has($scope.options.actions.GET, root)) { if($scope.options.actions.GET[root].type && $scope.options.actions.GET[root].type === 'field') { - params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true})); + params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches); } else { - params = _.merge(params, qs.encodeParam({term: term, searchTerm: true})); + params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches); } } // Its not a search term or a related search term else { - params = _.merge(params, qs.encodeParam({term: term})); + params = _.merge(params, qs.encodeParam({term: term}), combineSameSearches); } } From f2d2409c16f0f2eb4a805435277ebf68b7fbdb9a Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 17:03:42 -0500 Subject: [PATCH 060/154] mispellings and reduce verticle space usage --- .../job-template-add.controller.js | 26 +++++++++--------- .../job-template-edit.controller.js | 27 +++++++++---------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index 858cfc3452..d6f26092f7 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -287,20 +287,18 @@ if (data.related && data.related.callback) { Alert('Callback URL', -`
-

Host callbacks are enabled for this template. The callback URL is:

-

- - ${$scope.callback_server_path} - ${data.related.callback} - -

-

The host configuration key is: - - ${$filter('sanitize')(data.host_config_key)} - -

-
`, +`Host callbacks are enabled for this template. The callback URL is: +

+ + ${$scope.callback_server_path} + ${data.related.callback} + +

+

The host configuration key is: + + ${$filter('sanitize')(data.host_config_key)} + +

`, 'alert-danger', saveCompleted, null, null, null, true); } diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index a42c089273..f4df25796b 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -423,21 +423,18 @@ export default if (data.related && data.related.callback) { Alert('Callback URL', -` -
-

Host callbacks are enabled for this template. The callback URL is:

-

- - ${$scope.callback_server_path} - ${data.related.callback} - -

-

The host configuration key is: - - ${$filter('sanitize')(data.host_config_key)} - -

-
+`Host callbacks are enabled for this template. The callback URL is: +

+ + ${$scope.callback_server_path} + ${data.related.callback} + +

+

The host configuration key is: + + ${$filter('sanitize')(data.host_config_key)} + +

`, 'alert-danger', saveCompleted, null, null, null, true); From 3e8c7127712fa630d74766cc1315f80ae9b0ece4 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Tue, 10 Jan 2017 17:04:48 -0500 Subject: [PATCH 061/154] Licenses + LGPL code for winrm vendoring. --- docs/licenses/ntlm-auth-1.0.2.zip | Bin 0 -> 33770 bytes docs/licenses/ntlm-auth.txt | 163 ++++++++++++++++++++++ docs/licenses/pykerberos.txt | 202 ++++++++++++++++++++++++++++ docs/licenses/pywinrm.txt | 22 ++- docs/licenses/requests-kerberos.txt | 15 +++ docs/licenses/requests-ntlm.txt | 15 +++ 6 files changed, 411 insertions(+), 6 deletions(-) create mode 100644 docs/licenses/ntlm-auth-1.0.2.zip create mode 100644 docs/licenses/ntlm-auth.txt create mode 100644 docs/licenses/pykerberos.txt create mode 100644 docs/licenses/requests-kerberos.txt create mode 100644 docs/licenses/requests-ntlm.txt diff --git a/docs/licenses/ntlm-auth-1.0.2.zip b/docs/licenses/ntlm-auth-1.0.2.zip new file mode 100644 index 0000000000000000000000000000000000000000..d847ae6be669bb0708836f521f93bd5ecf0a2313 GIT binary patch literal 33770 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 literal 0 HcmV?d00001 diff --git a/docs/licenses/ntlm-auth.txt b/docs/licenses/ntlm-auth.txt new file mode 100644 index 0000000000..21429cb1fa --- /dev/null +++ b/docs/licenses/ntlm-auth.txt @@ -0,0 +1,163 @@ +GNU Lesser General Public License +================================= + +_Version 3, 29 June 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +### 0. Additional Definitions + +As used herein, “this License” refers to version 3 of the GNU Lesser +General Public License, and the “GNU GPL” refers to version 3 of the GNU +General Public License. + +“The Library” refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A “Combined Work” is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the “Linked +Version”. + +The “Minimal Corresponding Source” for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +### 1. Exception to Section 3 of the GNU GPL + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +### 2. Conveying Modified Versions + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +* **a)** under this License, provided that you make a good faith effort to +ensure that, in the event an Application does not supply the +function or data, the facility still operates, and performs +whatever part of its purpose remains meaningful, or + +* **b)** under the GNU GPL, with none of the additional permissions of +this License applicable to that copy. + +### 3. Object Code Incorporating Material from Library Header Files + +The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +* **a)** Give prominent notice with each copy of the object code that the +Library is used in it and that the Library and its use are +covered by this License. +* **b)** Accompany the object code with a copy of the GNU GPL and this license +document. + +### 4. Combined Works + +You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + +* **a)** Give prominent notice with each copy of the Combined Work that +the Library is used in it and that the Library and its use are +covered by this License. + +* **b)** Accompany the Combined Work with a copy of the GNU GPL and this license +document. + +* **c)** For a Combined Work that displays copyright notices during +execution, include the copyright notice for the Library among +these notices, as well as a reference directing the user to the +copies of the GNU GPL and this license document. + +* **d)** Do one of the following: + - **0)** Convey the Minimal Corresponding Source under the terms of this +License, and the Corresponding Application Code in a form +suitable for, and under terms that permit, the user to +recombine or relink the Application with a modified version of +the Linked Version to produce a modified Combined Work, in the +manner specified by section 6 of the GNU GPL for conveying +Corresponding Source. + - **1)** Use a suitable shared library mechanism for linking with the +Library. A suitable mechanism is one that **(a)** uses at run time +a copy of the Library already present on the user's computer +system, and **(b)** will operate properly with a modified version +of the Library that is interface-compatible with the Linked +Version. + +* **e)** Provide Installation Information, but only if you would otherwise +be required to provide such information under section 6 of the +GNU GPL, and only to the extent that such information is +necessary to install and execute a modified version of the +Combined Work produced by recombining or relinking the +Application with a modified version of the Linked Version. (If +you use option **4d0**, the Installation Information must accompany +the Minimal Corresponding Source and Corresponding Application +Code. If you use option **4d1**, you must provide the Installation +Information in the manner specified by section 6 of the GNU GPL +for conveying Corresponding Source.) + +### 5. Combined Libraries + +You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +* **a)** Accompany the combined library with a copy of the same work based +on the Library, uncombined with any other library facilities, +conveyed under the terms of this License. +* **b)** Give prominent notice with the combined library that part of it +is a work based on the Library, and explaining where to find the +accompanying uncombined form of the same work. + +### 6. Revised Versions of the GNU Lesser General Public License + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License “or any later version” +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/docs/licenses/pykerberos.txt b/docs/licenses/pykerberos.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/docs/licenses/pykerberos.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/licenses/pywinrm.txt b/docs/licenses/pywinrm.txt index 3f28a5f534..3e21facc07 100644 --- a/docs/licenses/pywinrm.txt +++ b/docs/licenses/pywinrm.txt @@ -1,9 +1,19 @@ -MIT License +Copyright (c) 2013 Alexey Diyan -Copyright (c) +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: -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 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. \ No newline at end of file +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. \ No newline at end of file diff --git a/docs/licenses/requests-kerberos.txt b/docs/licenses/requests-kerberos.txt new file mode 100644 index 0000000000..581f115e78 --- /dev/null +++ b/docs/licenses/requests-kerberos.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012 Kenneth Reitz + +Permission to use, copy, modify and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/docs/licenses/requests-ntlm.txt b/docs/licenses/requests-ntlm.txt new file mode 100644 index 0000000000..5f3e18de12 --- /dev/null +++ b/docs/licenses/requests-ntlm.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2013 Ben Toews + +Permission to use, copy, modify and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. From a41fd5bdf89e31c16cf98a9158eede2b71010d21 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 16:18:30 -0500 Subject: [PATCH 062/154] display workflow template instead of wfjt related to #3404 --- .../templates/list/templates-list.controller.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index 5bcd659a38..2734232ae2 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -45,12 +45,14 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', $scope.$on(`${list.iterator}_options`, function(event, data){ $scope.options = data.data.actions.GET; optionsRequestDataProcessing(); + renameTypeLabelFromWorkflowJobTemplateToWorkflowTemplate(); }); $scope.$watchCollection('templates', function() { - optionsRequestDataProcessing(); - } - ); + optionsRequestDataProcessing(); + renameTypeLabelFromWorkflowJobTemplateToWorkflowTemplate(); + }); + // iterate over the list and add fields like type label, after the // OPTIONS request returns, or the list is sorted/paginated/searched function optionsRequestDataProcessing(){ @@ -70,6 +72,13 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', }); } + function renameTypeLabelFromWorkflowJobTemplateToWorkflowTemplate() { + $scope[list.name].forEach(function(item) { + if (item.type_label === "Workflow Job Template") { + item.type_label = "Workflow Template"; + } + }); + } $scope.$on(`ws-jobs`, function () { // @issue - this is no longer quite as ham-fisted but I'd like for someone else to take a peek From 36acc5c6bc9ce6dd86fe0f14d2614324796d271f Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 17:19:24 -0500 Subject: [PATCH 063/154] better empty list error message related to #4047 --- awx/ui/client/src/forms/Users.js | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js index 0ef16a4aff..672dd2beea 100644 --- a/awx/ui/client/src/forms/Users.js +++ b/awx/ui/client/src/forms/Users.js @@ -121,6 +121,7 @@ export default organizations: { awToolTip: i18n._('Please save before assigning to organizations'), basePath: 'api/v1/users/{{$stateParams.user_id}}/organizations', + emptyListText: i18n._('Please add user to an Organization.'), search: { page_size: '10' }, From 5a5fcb161486c2fec1f6479fb6678165edb9c002 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 17:44:37 -0500 Subject: [PATCH 064/154] remove activity stream link on scheduled jobs list related to #3833 --- awx/ui/client/src/scheduler/main.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/awx/ui/client/src/scheduler/main.js b/awx/ui/client/src/scheduler/main.js index f8620c880e..9e04feae66 100644 --- a/awx/ui/client/src/scheduler/main.js +++ b/awx/ui/client/src/scheduler/main.js @@ -285,9 +285,7 @@ export default } }, data: { - activityStream: true, - activityStreamTarget: 'job', - activityStreamId: 'id' + activityStream: false, }, ncyBreadcrumb: { parent: 'jobs', From 3a118f911d2c38dc7ad3565001faf337a53d40ad Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 18:02:44 -0500 Subject: [PATCH 065/154] change projects list field ordering related to #4372 --- awx/ui/client/src/lists/Projects.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/lists/Projects.js b/awx/ui/client/src/lists/Projects.js index 34cf545035..26eb532f0e 100644 --- a/awx/ui/client/src/lists/Projects.js +++ b/awx/ui/client/src/lists/Projects.js @@ -39,18 +39,18 @@ export default columnClass: "col-lg-4 col-md-4 col-sm-5 col-xs-7 List-staticColumnAdjacent", modalColumnClass: 'col-md-8' }, - scm_revision: { - label: i18n._('Revision'), - excludeModal: true, - columnClass: 'col-lg-4 col-md-2 col-sm-3 hidden-xs', - class: 'List-staticColumnAdjacent--monospace' - }, scm_type: { label: i18n._('Type'), ngBind: 'project.type_label', excludeModal: true, columnClass: 'col-lg-2 col-md-2 col-sm-3 hidden-xs' }, + scm_revision: { + label: i18n._('Revision'), + excludeModal: true, + columnClass: 'col-lg-4 col-md-2 col-sm-3 hidden-xs', + class: 'List-staticColumnAdjacent--monospace' + }, last_updated: { label: i18n._('Last Updated'), filter: "longDate", From b903ad069e5d152a2eae25d3a99aac43e1601a8b Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 18:23:26 -0500 Subject: [PATCH 066/154] Revert "display workflow template instead of wfjt" This reverts commit 459b03e2e90b2aeaaf071a7438323890f238bf3f. --- .../templates/list/templates-list.controller.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index 2734232ae2..5bcd659a38 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -45,14 +45,12 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', $scope.$on(`${list.iterator}_options`, function(event, data){ $scope.options = data.data.actions.GET; optionsRequestDataProcessing(); - renameTypeLabelFromWorkflowJobTemplateToWorkflowTemplate(); }); $scope.$watchCollection('templates', function() { - optionsRequestDataProcessing(); - renameTypeLabelFromWorkflowJobTemplateToWorkflowTemplate(); - }); - + optionsRequestDataProcessing(); + } + ); // iterate over the list and add fields like type label, after the // OPTIONS request returns, or the list is sorted/paginated/searched function optionsRequestDataProcessing(){ @@ -72,13 +70,6 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', }); } - function renameTypeLabelFromWorkflowJobTemplateToWorkflowTemplate() { - $scope[list.name].forEach(function(item) { - if (item.type_label === "Workflow Job Template") { - item.type_label = "Workflow Template"; - } - }); - } $scope.$on(`ws-jobs`, function () { // @issue - this is no longer quite as ham-fisted but I'd like for someone else to take a peek From e5a2671d21eb43e86e47bd23fe1f73c230405eca Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 10 Jan 2017 18:23:52 -0500 Subject: [PATCH 067/154] rename wfjt to Workflow Template in OPTIONS --- awx/api/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 05dca74cb6..66ca32764a 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -251,6 +251,7 @@ class BaseSerializer(serializers.ModelSerializer): 'inventory_update': _('Inventory Sync'), 'system_job': _('Management Job'), 'workflow_job': _('Workflow Job'), + 'workflow_job_template': _('Workflow Template'), } choices = [] for t in self.get_types(): From 8f259f2bb797d2a3fb9c440bee027bd00e6aa4bb Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 18:50:52 -0500 Subject: [PATCH 068/154] handle case of node with null UJT --- awx/main/scheduler/__init__.py | 2 ++ awx/main/scheduler/dag_workflow.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index e92eb429e1..acff2cf8a3 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -114,6 +114,8 @@ class TaskManager(): dag = WorkflowDAG(workflow_job) spawn_nodes = dag.bfs_nodes_to_run() for spawn_node in spawn_nodes: + if spawn_node.unified_job_template is None: + continue kv = spawn_node.get_job_kwargs() job = spawn_node.unified_job_template.create_unified_job(**kv) spawn_node.job = job diff --git a/awx/main/scheduler/dag_workflow.py b/awx/main/scheduler/dag_workflow.py index c765b48678..5fc716584a 100644 --- a/awx/main/scheduler/dag_workflow.py +++ b/awx/main/scheduler/dag_workflow.py @@ -67,6 +67,8 @@ class WorkflowDAG(SimpleDAG): obj = n['node_object'] job = obj.job + if obj.unified_job_template is None: + continue if not job: return False # Job is about to run or is running. Hold our horses and wait for From 98260124992e50c1bbccf1b9e40d734d4e505fd3 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 11 Jan 2017 09:25:00 -0500 Subject: [PATCH 069/154] do not display tab tooltip in edit mode by default related to #4387 --- awx/ui/client/src/forms/Credentials.js | 1 + awx/ui/client/src/shared/form-generator.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js index 727172c94c..1ec2628cfd 100644 --- a/awx/ui/client/src/forms/Credentials.js +++ b/awx/ui/client/src/forms/Credentials.js @@ -425,6 +425,7 @@ export default ngClick: `(organization === undefined ? true : false)||$state.go('credentials.edit.permissions')`, awToolTip: '{{permissionsTooltip}}', dataTipWatch: 'permissionsTooltip', + awToolTipTabEnabledInEditMode: true, dataPlacement: 'top', basePath: 'api/v1/credentials/{{$stateParams.credential_id}}/access_list/', search: { diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 63146ce890..02d94665cd 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1479,7 +1479,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += `
Date: Wed, 11 Jan 2017 09:49:52 -0500 Subject: [PATCH 070/154] update workflow DAG tests so nodes not in sideways state --- awx/main/tests/unit/scheduler/test_dag.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/main/tests/unit/scheduler/test_dag.py b/awx/main/tests/unit/scheduler/test_dag.py index 54e7de4fa8..932f4436ec 100644 --- a/awx/main/tests/unit/scheduler/test_dag.py +++ b/awx/main/tests/unit/scheduler/test_dag.py @@ -5,7 +5,7 @@ import pytest # AWX from awx.main.scheduler.dag_simple import SimpleDAG from awx.main.scheduler.dag_workflow import WorkflowDAG -from awx.main.models import Job +from awx.main.models import Job, JobTemplate from awx.main.models.workflow import WorkflowJobNode @@ -72,6 +72,7 @@ def factory_node(): if status: j = Job(status=status) wfn.job = j + wfn.unified_job_template = JobTemplate(name='JT{}'.format(id)) return wfn return fn From 974fe1d2445752b67b3ddd087abd481ba98e58f4 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 11 Jan 2017 10:23:47 -0500 Subject: [PATCH 071/154] Fix an issue with set_stats for unsupported Ansible versions --- awx/lib/tower_display_callback/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/tower_display_callback/module.py index 480527a651..7336da2f08 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/tower_display_callback/module.py @@ -328,7 +328,7 @@ class BaseCallbackModule(CallbackBase): ok=stats.ok, processed=stats.processed, skipped=stats.skipped, - artifact_data=stats.custom.get('_run', {}) + artifact_data=stats.custom.get('_run', {}) if hasattr(stats, 'custom') else {} ) with self.capture_event_data('playbook_on_stats', **event_data): From 8796af7e28e6cc5704de61bc3fc87be8b192c0f5 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 11 Jan 2017 10:39:24 -0500 Subject: [PATCH 072/154] Make session and csrf cookies secure by default --- awx/settings/defaults.py | 6 ++++++ awx/settings/development.py | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 8e67e9a025..6a47ba628f 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -163,6 +163,12 @@ MAX_EVENT_RES_DATA = 700000 # Note: This setting may be overridden by database settings. EVENT_STDOUT_MAX_BYTES_DISPLAY = 1024 +# Disallow sending session cookies over insecure connections +SESSION_COOKIE_SECURE = True + +# Disallow sending csrf cookies over insecure connections +CSRF_COOKIE_SECURE = True + TEMPLATE_CONTEXT_PROCESSORS = ( # NOQA 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', diff --git a/awx/settings/development.py b/awx/settings/development.py index ebe81260d1..1326c12814 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -24,11 +24,11 @@ ALLOWED_HOSTS = ['*'] mimetypes.add_type("image/svg+xml", ".svg", True) mimetypes.add_type("image/svg+xml", ".svgz", True) -MONGO_HOST = '127.0.0.1' -MONGO_PORT = 27017 -MONGO_USERNAME = None -MONGO_PASSWORD = None -MONGO_DB = 'system_tracking_dev' +# Disallow sending session cookies over insecure connections +SESSION_COOKIE_SECURE = False + +# Disallow sending csrf cookies over insecure connections +CSRF_COOKIE_SECURE = False # Override django.template.loaders.cached.Loader in defaults.py TEMPLATE_LOADERS = ( From 644e4647981f3d0c7a56fa6b6fabadb296ac9ea3 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 10:47:06 -0500 Subject: [PATCH 073/154] Handle the injected __search and __icontains so that they don't show up in the search tags. --- .../client/src/shared/smart-search/queryset.service.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index 89ad2f14d1..edb1301b9c 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -68,6 +68,9 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear function encodeTerm(value, key){ + key = key.replace(/__icontains_DEFAULT/g, "__icontains"); + key = key.replace(/__search_DEFAULT/g, "__search"); + if (Array.isArray(value)){ let concated = ''; angular.forEach(value, function(item){ @@ -99,10 +102,10 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear let valueString = paramParts[1]; if(keySplit.length === 1) { if(params.searchTerm && !lessThanGreaterThan) { - paramString += keySplit[0] + '__icontains'; + paramString += keySplit[0] + '__icontains_DEFAULT'; } else if(params.relatedSearchTerm) { - paramString += keySplit[0] + '__search'; + paramString += keySplit[0] + '__search_DEFAULT'; } else { paramString += keySplit[0]; @@ -142,7 +145,8 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear return decodeURIComponent(`${searchString}`); } else { - key = key.replace(/__icontains/g, ""); + key = key.replace(/__icontains_DEFAULT/g, ""); + key = key.replace(/__search_DEFAULT/g, ""); let split = key.split('__'); let decodedParam = searchString; let exclude = false; From e1ce3c3199c9b30a4181a869d39f907a4d4720bd Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 11 Jan 2017 10:58:40 -0500 Subject: [PATCH 074/154] add title parameter to rbac modal directives related to #4048 --- .../src/access/add-rbac-resource/rbac-resource.directive.js | 3 ++- .../src/access/add-rbac-resource/rbac-resource.partial.html | 2 +- .../access/add-rbac-user-team/rbac-user-team.directive.js | 3 ++- .../access/add-rbac-user-team/rbac-user-team.partial.html | 2 +- awx/ui/client/src/shared/stateDefinitions.factory.js | 6 +++--- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js index 3bcdece9f6..e84b78face 100644 --- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js +++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js @@ -15,7 +15,8 @@ export default ['templateUrl', '$state', usersDataset: '=', teamsDataset: '=', resourceData: '=', - withoutTeamPermissions: '@' + withoutTeamPermissions: '@', + title: '@' }, controller: controller, templateUrl: templateUrl('access/add-rbac-resource/rbac-resource'), diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html index f138e108d0..5cd8e19b1e 100644 --- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html +++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html @@ -8,7 +8,7 @@
{{ object.name || object.username }}
- Add Permissions + {{ title }}
diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js index ced1ceb744..ea4aafd2f3 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js @@ -11,7 +11,8 @@ export default ['templateUrl', return { restrict: 'E', scope: { - resolve: "=" + resolve: "=", + title: "@", }, controller: controller, templateUrl: templateUrl('access/add-rbac-user-team/rbac-user-team'), diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html index a89621eda8..bc30eff1b7 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html @@ -9,7 +9,7 @@
{{ owner.name || owner.username }}
- Add Permissions + {{ title }}
diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js index 65cd07d475..96e7493598 100644 --- a/awx/ui/client/src/shared/stateDefinitions.factory.js +++ b/awx/ui/client/src/shared/stateDefinitions.factory.js @@ -267,7 +267,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat }, views: { [`modal@${formStateDefinition.name}`]: { - template: `` + template: `` } }, resolve: { @@ -332,7 +332,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat }, views: { [`modal@${formStateDefinition.name}`]: { - template: `` + template: `` } }, resolve: { @@ -501,7 +501,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat }, views: { [`modal@${formStateDefinition.name}`]: { - template: `` + template: `` } }, resolve: { From e69dc0f36eb2f87da911ee7c140b3ae841a9df99 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 10 Jan 2017 18:17:12 -0500 Subject: [PATCH 075/154] Clarification of copy RBAC messages --- awx/api/views.py | 2 ++ awx/main/access.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 7d0586b296..b3d877b67a 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2910,6 +2910,8 @@ class WorkflowJobTemplateCopy(WorkflowsEnforcementMixin, GenericAPIView): copy_TF, messages = request.user.can_access_with_errors(self.model, 'copy', obj) data['can_copy'] = copy_TF data['warnings'] = messages + if not copy_TF: + data['warnings'] = _('You do not have permission to make a copy.') return Response(data) def post(self, request, *args, **kwargs): diff --git a/awx/main/access.py b/awx/main/access.py index f2f1f72367..0696d10970 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1043,7 +1043,7 @@ class JobTemplateAccess(BaseAccess): Project.accessible_objects(self.user, 'use_role').exists() or Inventory.accessible_objects(self.user, 'use_role').exists()) - # if reference_obj is provided, determine if it can be coppied + # if reference_obj is provided, determine if it can be copied reference_obj = data.get('reference_obj', None) if 'job_type' in data and data['job_type'] == PERM_INVENTORY_SCAN: @@ -1543,13 +1543,13 @@ class WorkflowJobTemplateAccess(BaseAccess): for node in qs.all(): node_errors = {} if node.inventory and self.user not in node.inventory.use_role: - node_errors['inventory'] = 'Prompted inventory %s can not be coppied.' % node.inventory.name + node_errors['inventory'] = 'Prompted inventory %s can not be copied.' % node.inventory.name if node.credential and self.user not in node.credential.use_role: - node_errors['credential'] = 'Prompted credential %s can not be coppied.' % node.credential.name + node_errors['credential'] = 'Prompted credential %s can not be copied.' % node.credential.name ujt = node.unified_job_template if ujt and not self.user.can_access(UnifiedJobTemplate, 'start', ujt, validate_license=False): node_errors['unified_job_template'] = ( - 'Prompted %s %s can not be coppied.' % (ujt._meta.verbose_name_raw, ujt.name)) + 'Prompted %s %s can not be copied.' % (ujt._meta.verbose_name_raw, ujt.name)) if node_errors: wfjt_errors[node.id] = node_errors self.messages.update(wfjt_errors) From 23f4898eea9eadf817346d2b1cbf1da1fc840962 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 11 Jan 2017 11:25:08 -0500 Subject: [PATCH 076/154] Re-wirte ad-hoc notification templates to organization to match docs --- awx/main/models/ad_hoc_commands.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/awx/main/models/ad_hoc_commands.py b/awx/main/models/ad_hoc_commands.py index 27d8754aa6..057924eda7 100644 --- a/awx/main/models/ad_hoc_commands.py +++ b/awx/main/models/ad_hoc_commands.py @@ -20,7 +20,7 @@ from django.core.urlresolvers import reverse # AWX from awx.main.models.base import * # noqa from awx.main.models.unified_jobs import * # noqa -from awx.main.models.notifications import JobNotificationMixin +from awx.main.models.notifications import JobNotificationMixin, NotificationTemplate from awx.main.fields import JSONField logger = logging.getLogger('awx.main.models.ad_hoc_commands') @@ -157,18 +157,20 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin): @property def notification_templates(self): - all_inventory_sources = set() + all_orgs = set() for h in self.hosts.all(): - for invsrc in h.inventory_sources.all(): - all_inventory_sources.add(invsrc) + all_orgs.add(h.inventory.organization) active_templates = dict(error=set(), success=set(), any=set()) - for invsrc in all_inventory_sources: - notifications_dict = invsrc.notification_templates - for notification_type in active_templates.keys(): - for templ in notifications_dict[notification_type]: - active_templates[notification_type].add(templ) + base_notification_templates = NotificationTemplate.objects + for org in all_orgs: + for templ in base_notification_templates.filter(organization_notification_templates_for_errors=org): + active_templates['error'].add(templ) + for templ in base_notification_templates.filter(organization_notification_templates_for_success=org): + active_templates['success'].add(templ) + for templ in base_notification_templates.filter(organization_notification_templates_for_any=org): + active_templates['any'].add(templ) active_templates['error'] = list(active_templates['error']) active_templates['any'] = list(active_templates['any']) active_templates['success'] = list(active_templates['success']) From 2c97425291f32c355f48339dbb053c1b3ef3ef94 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 11 Jan 2017 12:23:57 -0500 Subject: [PATCH 077/154] prototype for new copy GET details --- awx/api/views.py | 16 ++++++++++------ awx/main/access.py | 18 ++++++++++++------ .../list/templates-list.controller.js | 14 +++++++------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index b3d877b67a..7e8ea65540 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2906,12 +2906,16 @@ class WorkflowJobTemplateCopy(WorkflowsEnforcementMixin, GenericAPIView): def get(self, request, *args, **kwargs): obj = self.get_object() - data = {} - copy_TF, messages = request.user.can_access_with_errors(self.model, 'copy', obj) - data['can_copy'] = copy_TF - data['warnings'] = messages - if not copy_TF: - data['warnings'] = _('You do not have permission to make a copy.') + can_copy, messages = request.user.can_access_with_errors(self.model, 'copy', obj) + data = { + 'can_copy': can_copy, 'can_copy_without_user_input': can_copy, + 'templates_unable_to_copy': [] if can_copy else ['all'], + 'credentials_unable_to_copy': [] if can_copy else ['all'], + 'inventories_unable_to_copy': [] if can_copy else ['all'] + } + if messages and can_copy: + data['can_copy_without_user_input'] = False + data.update(messages) return Response(data) def post(self, request, *args, **kwargs): diff --git a/awx/main/access.py b/awx/main/access.py index 0696d10970..75b53d2527 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1537,22 +1537,28 @@ class WorkflowJobTemplateAccess(BaseAccess): def can_copy(self, obj): if self.save_messages: - wfjt_errors = {} + missing_ujt = [] + missing_credentials = [] + missing_inventories = [] qs = obj.workflow_job_template_nodes qs.select_related('unified_job_template', 'inventory', 'credential') for node in qs.all(): node_errors = {} if node.inventory and self.user not in node.inventory.use_role: - node_errors['inventory'] = 'Prompted inventory %s can not be copied.' % node.inventory.name + missing_inventories.append(node.inventory.name) if node.credential and self.user not in node.credential.use_role: - node_errors['credential'] = 'Prompted credential %s can not be copied.' % node.credential.name + missing_credentials.append(node.credential.name) ujt = node.unified_job_template if ujt and not self.user.can_access(UnifiedJobTemplate, 'start', ujt, validate_license=False): - node_errors['unified_job_template'] = ( - 'Prompted %s %s can not be copied.' % (ujt._meta.verbose_name_raw, ujt.name)) + missing_ujt.append(ujt.name) if node_errors: wfjt_errors[node.id] = node_errors - self.messages.update(wfjt_errors) + if missing_ujt: + self.messages['templates_unable_to_copy'] = missing_ujt + if missing_credentials: + self.messages['credentials_unable_to_copy'] = missing_credentials + if missing_inventories: + self.messages['inventories_unable_to_copy'] = missing_inventories return self.check_related('organization', Organization, {'reference_obj': obj}, mandatory=True) diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index 820f843852..a71f9573c3 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -220,7 +220,7 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', .then(function(result) { if(result.data.can_copy) { - if(!result.data.warnings || _.isEmpty(result.data.warnings)) { + if(result.data.can_copy_without_user_input) { // Go ahead and copy the workflow - the user has full priveleges on all the resources TemplateCopyService.copyWorkflow(template.id) .then(function(result) { @@ -235,16 +235,16 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', let bodyHtml = `
- You may not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and may result in an incomplete workflow. + You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.
`; // Go and grab all of the warning strings - _.forOwn(result.data.warnings, function(warning) { - if(warning) { - _.forOwn(warning, function(warningString) { - bodyHtml += '
' + warningString + '
'; - }); + _.forOwn(result.data.templates_unable_to_copy, function(ujt) { + if(ujt) { + // _.forOwn(ujts, function(warningString) { + bodyHtml += '
' + ujt + '
'; + // }); } } ); From 5ddd91897704e4acf29648415da7e4f65823a902 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 11 Jan 2017 13:07:19 -0500 Subject: [PATCH 078/154] updates to job results based on feedback from pr --- .../job-results-stdout/job-results-stdout.directive.js | 2 +- awx/ui/client/src/job-results/job-results.controller.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js index d6f0ee8e04..14a34a607a 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js @@ -21,7 +21,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', $(window).off("scroll", scrollWatcher); $(".JobResultsStdOut-stdoutContainer").off('scroll', scrollWatcher); - toDestroy.forEach(v => v()); + toDestroy.forEach(closureFunc => closureFunc()); }); scope.stdoutContainerAvailable.resolve("container available"); diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 81ce5894bf..30da9d4ea9 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -508,6 +508,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy })); $scope.$on('$destroy', function(){ + $( ".JobResultsStdOut-aLineOfStdOut" ).remove(); cancelRequests = true; eventQueue.initialize(); Object.keys($scope.events) @@ -517,6 +518,6 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy }); $scope.events = {}; clearInterval(elapsedInterval); - toDestroy.forEach(v => v()); + toDestroy.forEach(closureFunc => closureFunc()); }); }]; From c452f5fd804db4b2105a33f3234d6c54ec7fe9a9 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 13:21:19 -0500 Subject: [PATCH 079/154] Added more testing --- .../smart-search/queryset.service-test.js | 43 +++++++++++-------- .../smart-search/smart-search.service-test.js | 1 + 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/awx/ui/tests/spec/smart-search/queryset.service-test.js b/awx/ui/tests/spec/smart-search/queryset.service-test.js index b932925171..3d49440920 100644 --- a/awx/ui/tests/spec/smart-search/queryset.service-test.js +++ b/awx/ui/tests/spec/smart-search/queryset.service-test.js @@ -3,7 +3,8 @@ describe('Service: QuerySet', () => { let $httpBackend, QuerySet, - Authorization; + Authorization, + SmartSearchService; beforeEach(angular.mock.module('Tower', ($provide) =>{ // @todo: improve app source / write testing utilities for interim @@ -17,9 +18,10 @@ describe('Service: QuerySet', () => { })); beforeEach(angular.mock.module('RestServices')); - beforeEach(angular.mock.inject((_$httpBackend_, _QuerySet_) => { + beforeEach(angular.mock.inject((_$httpBackend_, _QuerySet_, _SmartSearchService_) => { $httpBackend = _$httpBackend_; QuerySet = _QuerySet_; + SmartSearchService = _SmartSearchService_; // @todo: improve app source // config.js / local_settings emit $http requests in the app's run block @@ -33,24 +35,27 @@ describe('Service: QuerySet', () => { .respond(200, ''); })); - describe('fn encodeQuery', () => { - xit('null/undefined params should return an empty string', () => { - expect(QuerySet.encodeQuery(null)).toEqual(''); - expect(QuerySet.encodeQuery(undefined)).toEqual(''); + describe('fn encodeParam', () => { + it('should encode parameters properly', () =>{ + expect(QuerySet.encodeParam({term: "name:foo", searchTerm: true})).toEqual({"name__icontains_DEFAULT" : "foo"}); + expect(QuerySet.encodeParam({term: "-name:foo", searchTerm: true})).toEqual({"not__name__icontains_DEFAULT" : "foo"}); + expect(QuerySet.encodeParam({term: "name:'foo bar'", searchTerm: true})).toEqual({"name__icontains_DEFAULT" : "'foo bar'"}); + expect(QuerySet.encodeParam({term: "-name:'foo bar'", searchTerm: true})).toEqual({"not__name__icontains_DEFAULT" : "'foo bar'"}); + expect(QuerySet.encodeParam({term: "organization:foo", relatedSearchTerm: true})).toEqual({"organization__search_DEFAULT" : "foo"}); + expect(QuerySet.encodeParam({term: "-organization:foo", relatedSearchTerm: true})).toEqual({"not__organization__search_DEFAULT" : "foo"}); + expect(QuerySet.encodeParam({term: "organization.name:foo", relatedSearchTerm: true})).toEqual({"organization__name" : "foo"}); + expect(QuerySet.encodeParam({term: "-organization.name:foo", relatedSearchTerm: true})).toEqual({"not__organization__name" : "foo"}); + expect(QuerySet.encodeParam({term: "id:11", searchTerm: true})).toEqual({"id__icontains_DEFAULT" : "11"}); + expect(QuerySet.encodeParam({term: "-id:11", searchTerm: true})).toEqual({"not__id__icontains_DEFAULT" : "11"}); + expect(QuerySet.encodeParam({term: "id:>11", searchTerm: true})).toEqual({"id__gt" : "11"}); + expect(QuerySet.encodeParam({term: "-id:>11", searchTerm: true})).toEqual({"not__id__gt" : "11"}); + expect(QuerySet.encodeParam({term: "id:>=11", searchTerm: true})).toEqual({"id__gte" : "11"}); + expect(QuerySet.encodeParam({term: "-id:>=11", searchTerm: true})).toEqual({"not__id__gte" : "11"}); + expect(QuerySet.encodeParam({term: "id:<11", searchTerm: true})).toEqual({"id__lt" : "11"}); + expect(QuerySet.encodeParam({term: "-id:<11", searchTerm: true})).toEqual({"not__id__lt" : "11"}); + expect(QuerySet.encodeParam({term: "id:<=11", searchTerm: true})).toEqual({"id__lte" : "11"}); + expect(QuerySet.encodeParam({term: "-id:<=11", searchTerm: true})).toEqual({"not__id__lte" : "11"}); }); - xit('should encode params to a string', () => { - let params = { - or__created_by: 'Jenkins', - or__modified_by: 'Jenkins', - and__not__status: 'success', - }, - result = '?or__created_by=Jenkins&or__modified_by=Jenkins&and__not__status=success'; - expect(QuerySet.encodeQuery(params)).toEqual(result); - }); - }); - - xdescribe('fn decodeQuery', () => { - }); diff --git a/awx/ui/tests/spec/smart-search/smart-search.service-test.js b/awx/ui/tests/spec/smart-search/smart-search.service-test.js index 679a5656b4..d5c35a08c2 100644 --- a/awx/ui/tests/spec/smart-search/smart-search.service-test.js +++ b/awx/ui/tests/spec/smart-search/smart-search.service-test.js @@ -24,6 +24,7 @@ describe('Service: SmartSearch', () => { expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\' description:\'bar foo\'')).toEqual(["name:\'foo bar\'", "description:\'bar foo\'"]); expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\' description:\'bar foo\'')).toEqual(["name:\'foo bar\'", "description:\'bar foo\'"]); expect(SmartSearchService.splitSearchIntoTerms('name:\"foo bar\" description:\'bar foo\'')).toEqual(["name:\"foo bar\"", "description:\'bar foo\'"]); + expect(SmartSearchService.splitSearchIntoTerms('name:\"foo bar\" foo')).toEqual(["name:\"foo bar\"", "foo"]); }); }); From eaf883f34992d20c10ab6fd13ed7ada6b51635b0 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 11 Jan 2017 14:02:17 -0500 Subject: [PATCH 080/154] dem controllers just needed to know who headers is related to #4663 --- awx/ui/client/src/notifications/notificationTemplates.form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/client/src/notifications/notificationTemplates.form.js b/awx/ui/client/src/notifications/notificationTemplates.form.js index 48fcbad9d7..51fd4125d0 100644 --- a/awx/ui/client/src/notifications/notificationTemplates.form.js +++ b/awx/ui/client/src/notifications/notificationTemplates.form.js @@ -323,6 +323,7 @@ export default ['i18n', function(i18n) { headers: { label: i18n._('HTTP Headers'), type: 'textarea', + name: 'headers', rows: 5, 'class': 'Form-formGroup--fullWidth', awRequiredWhen: { From cd61f58b11987e7f44d2ddeacc45ad57816bea4a Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 10 Jan 2017 21:12:29 -0800 Subject: [PATCH 081/154] hiding default search param as a search tag in scenario where the user types a search that conflicts w/ the default search params. Ex: searching for kind:gce when kind:ssh is a default search param shouldn't create a search tag for kind:ssh, as someone shouldn't be able to remove that tag...it's a default! --- .../smart-search/smart-search.controller.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 95f3be3adb..fc9cca9e77 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -23,11 +23,24 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' // Removes state definition defaults and pagination terms function stripDefaultParams(params) { - let stripped =_.pick(params, (value, key) => { + let strippedCopy, stripped =_.pick(params, (value, key) => { // setting the default value of a term to null in a state definition is a very explicit way to ensure it will NEVER generate a search tag, even with a non-default value return defaults[key] !== value && key !== 'order_by' && key !== 'page' && key !== 'page_size' && defaults[key] !== null; }); - return _(stripped).map(qs.decodeParam).flatten().value(); + strippedCopy = _.cloneDeep(stripped); + if(_.keys(_.pick(defaults, _.keys(strippedCopy))).length > 0){ + for (var key in strippedCopy) { + if (strippedCopy.hasOwnProperty(key)) { + let value = strippedCopy[key]; + if(_.isArray(value)){ + let index = _.indexOf(value, defaults[key]); + value = value.splice(index, 1)[0]; + } + } + } + stripped = strippedCopy; + } + return _(strippedCopy).map(qs.decodeParam).flatten().value(); } // searchable relationships From a693aad95da401712efc519fd3b92979aaa46a21 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 11 Jan 2017 14:22:28 -0500 Subject: [PATCH 082/154] a workable version of the new copy GET schema implemented --- awx/api/views.py | 12 +++--- .../tests/functional/test_rbac_workflow.py | 6 +-- .../list/templates-list.controller.js | 38 +++++++++++++++---- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 7e8ea65540..7c22bdf57f 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2907,12 +2907,12 @@ class WorkflowJobTemplateCopy(WorkflowsEnforcementMixin, GenericAPIView): def get(self, request, *args, **kwargs): obj = self.get_object() can_copy, messages = request.user.can_access_with_errors(self.model, 'copy', obj) - data = { - 'can_copy': can_copy, 'can_copy_without_user_input': can_copy, - 'templates_unable_to_copy': [] if can_copy else ['all'], - 'credentials_unable_to_copy': [] if can_copy else ['all'], - 'inventories_unable_to_copy': [] if can_copy else ['all'] - } + data = OrderedDict([ + ('can_copy', can_copy), ('can_copy_without_user_input', can_copy), + ('templates_unable_to_copy', [] if can_copy else ['all']), + ('credentials_unable_to_copy', [] if can_copy else ['all']), + ('inventories_unable_to_copy', [] if can_copy else ['all']) + ]) if messages and can_copy: data['can_copy_without_user_input'] = False data.update(messages) diff --git a/awx/main/tests/functional/test_rbac_workflow.py b/awx/main/tests/functional/test_rbac_workflow.py index a0f0348e38..f2ce04404f 100644 --- a/awx/main/tests/functional/test_rbac_workflow.py +++ b/awx/main/tests/functional/test_rbac_workflow.py @@ -120,13 +120,11 @@ class TestWorkflowJobAccess: access = WorkflowJobTemplateAccess(rando, save_messages=True) assert not access.can_copy(wfjt) warnings = access.messages - assert 1 in warnings - assert 'inventory' in warnings[1] + assert 'inventories_unable_to_copy' in warnings def test_workflow_copy_warnings_jt(self, wfjt, rando, job_template): wfjt.workflow_job_template_nodes.create(unified_job_template=job_template) access = WorkflowJobTemplateAccess(rando, save_messages=True) assert not access.can_copy(wfjt) warnings = access.messages - assert 1 in warnings - assert 'unified_job_template' in warnings[1] + assert 'templates_unable_to_copy' in warnings diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index a71f9573c3..d0b910e6b9 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -239,14 +239,36 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest',
`; - // Go and grab all of the warning strings - _.forOwn(result.data.templates_unable_to_copy, function(ujt) { - if(ujt) { - // _.forOwn(ujts, function(warningString) { - bodyHtml += '
' + ujt + '
'; - // }); - } - } ); + // List the unified job templates user can not access + if (result.data.templates_unable_to_copy.length > 0) { + bodyHtml += '
Unified Job Templates that can not be copied
    '; + _.forOwn(result.data.templates_unable_to_copy, function(ujt) { + if(ujt) { + bodyHtml += '
  • ' + ujt + '
  • '; + } + }); + bodyHtml += '
'; + } + // List the prompted inventories user can not access + if (result.data.inventories_unable_to_copy.length > 0) { + bodyHtml += '
Node prompted inventories that can not be copied
    '; + _.forOwn(result.data.inventories_unable_to_copy, function(inv) { + if(inv) { + bodyHtml += '
  • ' + inv + '
  • '; + } + }); + bodyHtml += '
'; + } + // List the prompted credentials user can not access + if (result.data.credentials_unable_to_copy.length > 0) { + bodyHtml += '
Node prompted credentials that can not be copied
    '; + _.forOwn(result.data.credentials_unable_to_copy, function(cred) { + if(cred) { + bodyHtml += '
  • ' + cred + '
  • '; + } + }); + bodyHtml += '
'; + } bodyHtml += '
'; From 33aabfeb4c7416048e52be9aa0688c966272313d Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 14:53:08 -0500 Subject: [PATCH 083/154] Fixed bug where clicking add permission when editing an org would do nothing --- awx/ui/client/src/forms/Inventories.js | 2 +- awx/ui/client/src/forms/Organizations.js | 2 +- awx/ui/client/src/forms/Workflows.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/forms/Inventories.js b/awx/ui/client/src/forms/Inventories.js index b78e16064d..5d96ebfaa5 100644 --- a/awx/ui/client/src/forms/Inventories.js +++ b/awx/ui/client/src/forms/Inventories.js @@ -103,7 +103,7 @@ angular.module('InventoryFormDefinition', ['ScanJobsListDefinition']) add: { label: i18n._('Add'), ngClick: "$state.go('.add')", - awToolTip: 'Add a permission', + awToolTip: i18n._('Add a permission'), actionClass: 'btn List-buttonSubmit', buttonContent: '+ ADD', ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' diff --git a/awx/ui/client/src/forms/Organizations.js b/awx/ui/client/src/forms/Organizations.js index 011ad90907..b50d7eeb3a 100644 --- a/awx/ui/client/src/forms/Organizations.js +++ b/awx/ui/client/src/forms/Organizations.js @@ -68,7 +68,7 @@ export default searchType: 'select', actions: { add: { - ngClick: "addPermission", + ngClick: "$state.go('.add')", label: i18n._('Add'), awToolTip: i18n._('Add a permission'), actionClass: 'btn List-buttonSubmit', diff --git a/awx/ui/client/src/forms/Workflows.js b/awx/ui/client/src/forms/Workflows.js index e938d42d55..a5a76d0bbd 100644 --- a/awx/ui/client/src/forms/Workflows.js +++ b/awx/ui/client/src/forms/Workflows.js @@ -122,7 +122,7 @@ export default add: { ngClick: "$state.go('.add')", label: i18n._('Add'), - awToolTip: 'Add a permission', + awToolTip: i18n._('Add a permission'), actionClass: 'btn List-buttonSubmit', buttonContent: '+ '+ i18n._('ADD'), ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' From 50fd3d38cb92dcf8b71de9700b3f0c1ad88e8ad8 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 13:57:13 -0500 Subject: [PATCH 084/154] Treat any search string that doesn't start with a matching attribute as a string --- .../client/src/shared/smart-search/smart-search.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 2da9648ca1..1ec198c18d 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -145,9 +145,9 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches); } } - // Its not a search term or a related search term + // Its not a search term or a related search term - treat it as a string else { - params = _.merge(params, qs.encodeParam({term: term}), combineSameSearches); + params = _.merge(params, setDefaults(term), combineSameSearches); } } From 46491a59e617c7fb9992a620da4e8b02c6d92cf8 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 15:46:52 -0500 Subject: [PATCH 085/154] Removed the use of startsWith from encodeParam. PhantomJS didn't recognize that when the unit tests ran in jenkins. Rather than try to figure out why that might be the case I just changed the line. --- awx/ui/client/src/shared/smart-search/queryset.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index edb1301b9c..86a2bc2b20 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -94,7 +94,7 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear let keySplit = paramParts[0].split('.'); let exclude = false; let lessThanGreaterThan = paramParts[1].match(/^(>|<).*$/) ? true : false; - if(keySplit[0].startsWith("-")) { + if(keySplit[0].match(/^-/g)) { exclude = true; keySplit[0] = keySplit[0].replace(/^-/, ''); } From a9f78af3d2491c1d376209331653689aaaa166c2 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 11 Jan 2017 16:29:25 -0500 Subject: [PATCH 086/154] wire up teams into the rbac list directive related to #4675 --- .../rbac-multiselect/rbac-multiselect-list.directive.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js index 0e277c3f5f..2f0d790317 100644 --- a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js +++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js @@ -88,6 +88,14 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL list.fields.first_name.columnClass = 'col-md-3 col-sm-3 hidden-xs'; list.fields.last_name.columnClass = 'col-md-3 col-sm-3 hidden-xs'; break; + case 'Teams': + list.fields = { + name: list.fields.name, + organization: list.fields.organization, + }; + list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; + list.fields.organization.columnClass = 'col-md-5 col-sm-5 hidden-xs'; + break; default: list.fields = { name: list.fields.name, From 82f16f0551ad70461d12c2f18fbef05bd4ee7747 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Wed, 11 Jan 2017 14:25:08 -0800 Subject: [PATCH 087/154] small change based on PR feedback --- .../client/src/shared/smart-search/smart-search.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index fc9cca9e77..580a56c6f3 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -23,11 +23,11 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' // Removes state definition defaults and pagination terms function stripDefaultParams(params) { - let strippedCopy, stripped =_.pick(params, (value, key) => { + let stripped =_.pick(params, (value, key) => { // setting the default value of a term to null in a state definition is a very explicit way to ensure it will NEVER generate a search tag, even with a non-default value return defaults[key] !== value && key !== 'order_by' && key !== 'page' && key !== 'page_size' && defaults[key] !== null; }); - strippedCopy = _.cloneDeep(stripped); + let strippedCopy = _.cloneDeep(stripped); if(_.keys(_.pick(defaults, _.keys(strippedCopy))).length > 0){ for (var key in strippedCopy) { if (strippedCopy.hasOwnProperty(key)) { From 1cf3a259eeeab0c0754e099cb9009db199d20a6e Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 11 Jan 2017 18:04:47 -0500 Subject: [PATCH 088/154] add view more/less logic to credential owners related to #3313 --- .../src/credentials/ownerList.partial.html | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/awx/ui/client/src/credentials/ownerList.partial.html b/awx/ui/client/src/credentials/ownerList.partial.html index 1ed46c081b..8fde4f3c0a 100644 --- a/awx/ui/client/src/credentials/ownerList.partial.html +++ b/awx/ui/client/src/credentials/ownerList.partial.html @@ -1,5 +1,12 @@ - + \ No newline at end of file From dc47bacc61630ed309e584e86a255c256a5954d5 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 11 Jan 2017 18:51:37 -0500 Subject: [PATCH 089/154] deal with capped lines --- .../src/job-results/parse-stdout.service.js | 22 +++++++++++++++---- .../job-results/parse-stdout.service-test.js | 13 +++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/job-results/parse-stdout.service.js b/awx/ui/client/src/job-results/parse-stdout.service.js index 178fd88f4b..bf4bac3799 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -27,6 +27,7 @@ export default ['$log', 'moment', function($log, moment){ line = line.replace(/u001b/g, ''); // ansi classes + line = line.replace(/\[1;im/g, ''); line = line.replace(/\[1;31m/g, ''); line = line.replace(/\[0;31m/g, ''); line = line.replace(/\[0;32m/g, ''); @@ -39,6 +40,7 @@ export default ['$log', 'moment', function($log, moment){ line = line.replace(/()\s/g, '$1'); //end span + line = line.replace(/\[0im/g, ''); line = line.replace(/\[0m/g, ''); } else { // For the host event modal in the standard out tab, @@ -192,10 +194,22 @@ export default ['$log', 'moment', function($log, moment){ } }, getLineArr: function(event) { - return _ - .zip(_.range(event.start_line + 1, - event.end_line + 1), - event.stdout.replace("\t", " ").split("\r\n")).slice(0, -1); + let lineNums = _.range(event.start_line + 1, + event.end_line + 1); + + let lines = event.stdout + .replace("\t", " ") + .split("\r\n"); + + if (lineNums.length > lines.length) { + let padBy = lineNums.length - lines.length; + + for (let i = 0; i <= padBy; i++) { + lines.push("[1;imline capped.[0im"); + } + } + + return _.zip(lineNums, lines).slice(0, -1); }, // public function that provides the parsed stdout line, given a // job_event diff --git a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js index 5dd6788b02..e34ad4d6c2 100644 --- a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js +++ b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js @@ -115,6 +115,19 @@ describe('parseStdoutService', () => { expect(returnedEvent).toEqual(expectedReturn); }); + + it('deals correctly with capped lines', () => { + let mockEvent = { + start_line: 7, + end_line: 11, + stdout: "a\r\nb\r\nc..." + }; + let expectedReturn = [[8, "a"],[9, "b"], [10,"c..."], [11, "[1;imline capped.[0im"]]; + + let returnedEvent = parseStdoutService.getLineArr(mockEvent); + + expect(returnedEvent).toEqual(expectedReturn); + }); }); describe('parseStdout()', () => { From f44670576703c5c0812d42fea15c2e2f0e2bcc8f Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 11 Jan 2017 18:52:06 -0500 Subject: [PATCH 090/154] update escaped parse standard out unit test --- .../spec/job-results/parse-stdout.service-test.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js index e34ad4d6c2..9dada797b0 100644 --- a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js +++ b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js @@ -31,10 +31,14 @@ describe('parseStdoutService', () => { unstyledLine = 'ok: [host-00]'; expect(parseStdoutService.prettify(line, unstyled)).toBe(unstyledLine); }); + + it('can return empty strings', () => { + expect(parseStdoutService.prettify("")).toBe(""); + }); }); describe('getLineClasses()', () => { - xit('creates a string that is used as a class', () => { + it('creates a string that is used as a class', () => { let headerEvent = { event_name: 'playbook_on_task_start', event_data: { @@ -44,12 +48,15 @@ describe('parseStdoutService', () => { }; let lineNum = 3; let line = "TASK [setup] *******************************************************************"; - let styledLine = " header_task header_task_80dd087c-268b-45e8-9aab-1083bcfd9364 play_0f667a23-d9ab-4128-a735-80566bcdbca0 line_num_3"; + let styledLine = " header_task header_task_80dd087c-268b-45e8-9aab-1083bcfd9364 actual_header play_0f667a23-d9ab-4128-a735-80566bcdbca0 line_num_3"; expect(parseStdoutService.getLineClasses(headerEvent, line, lineNum)).toBe(styledLine); }); }); describe('getStartTime()', () => { + // TODO: the problem is that the date here calls moment, and thus + // the date will be timezone'd in the string (this could be + // different based on where you are) xit('creates returns a badge with the start time of the event', () => { let headerEvent = { event_name: 'playbook_on_play_start', From 83a6b8ba539509f4c03da6d512a5f470b2778558 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 20:43:43 -0500 Subject: [PATCH 091/154] Fixed bug where relaunching a workflow job from the details view was throwing a 404. Also fixed bug where relaunching a workflow from the details view was not redirecting the user to the new details view. --- .../job-submission-factories/launchjob.factory.js | 5 +++-- .../client/src/workflow-results/workflow-results.service.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js index 3c9dbb1c60..64c9344b19 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js @@ -120,7 +120,7 @@ export default Rest.post(job_launch_data) .success(function(data) { Wait('stop'); - var job = data.job || data.system_job || data.project_update || data.inventory_update || data.ad_hoc_command || data.workflow_job; + var job = data.job || data.system_job || data.project_update || data.inventory_update || data.ad_hoc_command; if($rootScope.portalMode===false && Empty(data.system_job) || (base === 'home')){ // use $state.go with reload: true option to re-instantiate sockets in @@ -131,7 +131,8 @@ export default if(_.has(data, 'job')) { goToJobDetails('jobDetail'); } - else if(_.has(data, 'workflow_job')) { + else if(data.type && data.type === 'workflow_job') { + job = data.id; goToJobDetails('workflowResults'); } else if(_.has(data, 'ad_hoc_command')) { diff --git a/awx/ui/client/src/workflow-results/workflow-results.service.js b/awx/ui/client/src/workflow-results/workflow-results.service.js index 2d3fddf2f4..601c845eaa 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.service.js +++ b/awx/ui/client/src/workflow-results/workflow-results.service.js @@ -126,7 +126,7 @@ export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErr }, relaunchJob: function(scope) { InitiatePlaybookRun({ scope: scope, id: scope.workflow.id, - relaunch: true, job_type: 'workflow_job_template' }); + relaunch: true, job_type: 'workflow_job' }); } }; return val; From 531bf153300a507bd0122929c8d372db79ad214f Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 11 Jan 2017 21:12:31 -0500 Subject: [PATCH 092/154] Workflow node border tweaks and a small change to the workflow root in the details view. --- .../workflow-chart.directive.js | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index 01b935127a..5a2f0c80cc 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -174,7 +174,20 @@ export default [ '$state','moment', nodeEnter.each(function(d) { let thisNode = d3.select(this); - if(d.isStartNode) { + if(d.isStartNode && scope.mode === 'details') { + // Overwrite the default root height and width and replace it with a small blue square + rootW = 25; + rootH = 25; + thisNode.append("rect") + .attr("width", rootW) + .attr("height", rootH) + .attr("y", 10) + .attr("rx", 5) + .attr("ry", 5) + .attr("fill", "#337ab7") + .attr("class", "WorkflowChart-rootNode"); + } + else if(d.isStartNode && scope.mode !== 'details') { thisNode.append("rect") .attr("width", rootW) .attr("height", rootH) @@ -190,7 +203,6 @@ export default [ '$state','moment', .attr("dy", ".35em") .attr("class", "WorkflowChart-startText") .text(function () { return "START"; }) - .attr("display", function() { return scope.mode === 'details' ? 'none' : null;}) .call(add_node); } else { @@ -200,15 +212,15 @@ export default [ '$state','moment', .attr("rx", 5) .attr("ry", 5) .attr('stroke', function(d) { - if(d.edgeType) { - if(d.edgeType === "failure") { - return "#d9534f"; - } - else if(d.edgeType === "success") { + if(d.job && d.job.status) { + if(d.job.status === "successful"){ return "#5cb85c"; } - else if(d.edgeType === "always"){ - return "#337ab7"; + else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") { + return "#d9534f"; + } + else { + return "#D7D7D7"; } } else { @@ -593,15 +605,15 @@ export default [ '$state','moment', t.selectAll(".rect") .attr('stroke', function(d) { - if(d.edgeType) { - if(d.edgeType === "failure") { - return "#d9534f"; - } - else if(d.edgeType === "success") { + if(d.job && d.job.status) { + if(d.job.status === "successful"){ return "#5cb85c"; } - else if(d.edgeType === "always"){ - return "#337ab7"; + else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") { + return "#d9534f"; + } + else { + return "#D7D7D7"; } } else { From 07011b2fc3a8c7383e0aee11ed909673688f6901 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Thu, 12 Jan 2017 10:25:15 -0500 Subject: [PATCH 093/154] Updated spacing in modal elements --- awx/ui/client/legacy-styles/ansible-ui.less | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index e925bff5d4..2b9d4dfc67 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -729,18 +729,6 @@ legend { .navigation { margin: 15px 0 15px 0; } - .modal-body { - .alert { - padding: 0; - border: none; - margin: 0; - } - .alert-danger { - background-color: @default-bg; - border: none; - color: @default-interface-txt; - } - } .footer-navigation { margin: 10px 0 10px 0; @@ -1638,17 +1626,20 @@ tr td button i { } /* overrides to TB modal */ +.modal-content { + padding: 20px; +} .modal-header { color: @default-interface-txt; - margin: .1em 0; + // margin: .1em 0; white-space: nowrap; width: 90%; overflow: hidden; text-overflow: ellipsis; width: 100%; border: none; - padding: 12px 14px 0 12px; + padding: 0; } .modal { @@ -1677,8 +1668,19 @@ tr td button i { } .modal-body { - padding: 20px 14px 7px 14px; + // padding: 20px 14px 7px 14px; min-height: 120px; + padding: 20px 0; + + .alert { + padding: 0; + margin: 0; + } + .alert-danger { + background-color: @default-bg; + border: none; + color: @default-interface-txt; + } } #prompt-modal .modal-body { @@ -1690,15 +1692,17 @@ tr td button i { } .modal-footer { - padding: .3em 1em .5em .4em; + // padding: .3em 1em .5em .4em; + padding: 0; border: none; + margin-top: 0; .btn.btn-primary { text-transform: uppercase; background-color: @default-succ; border-color: @default-succ; padding: 5px 15px; - margin: .5em .4em .5em 0; + // margin: .5em .4em .5em 0; cursor: pointer; &:hover { @@ -2219,10 +2223,6 @@ a:hover { font-family: 'Open Sans'; } -.modal-body .alert { - padding: 10px; -} - .WorkflowBadge{ background-color: @b7grey; border-radius: 10px; From 18fcfee96de8c9fa873a6f65ca2000e61013cbaf Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Thu, 12 Jan 2017 10:38:40 -0500 Subject: [PATCH 094/154] Fixed a few search related bugs based on PR review feedback --- awx/ui/client/src/shared/form-generator.js | 6 +++--- .../src/shared/smart-search/smart-search.controller.js | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 63146ce890..e9d6597001 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1830,7 +1830,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat // smart-search directive html += `
+ ng-hide="${itm}.length === 0 && (searchTags | isEmpty)"> + ng-show="${itm}.length === 0 && !(searchTags | isEmpty)">
No records matched your search.
@@ -1865,7 +1865,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat // Show the "no items" box when loading is done and the user isn't actively searching and there are no results var emptyListText = (collection.emptyListText) ? collection.emptyListText : i18n._("PLEASE ADD ITEMS TO THIS LIST"); html += `
`; - html += `
${emptyListText}
`; + html += `
${emptyListText}
`; html += '
'; html += ` diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 1ec198c18d..b601813a72 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -85,12 +85,19 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', ' else { encodeParams.searchTerm = true; } + removed = qs.encodeParam(encodeParams); + } + else { + removed = setDefaults(tagToRemove); } - removed = qs.encodeParam(encodeParams); } _.each(removed, (value, key) => { if (Array.isArray(queryset[key])){ _.remove(queryset[key], (item) => item === value); + // If the array is now empty, remove that key + if(queryset[key].length === 0) { + delete queryset[key]; + } } else { delete queryset[key]; From ee0295a37836fa8a14490bc9e12b023433114479 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Thu, 12 Jan 2017 10:55:54 -0500 Subject: [PATCH 095/154] css mods --- .../src/credentials/ownerList.block.less | 37 +++++++++++++++++++ .../src/credentials/ownerList.partial.html | 8 ++-- 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 awx/ui/client/src/credentials/ownerList.block.less diff --git a/awx/ui/client/src/credentials/ownerList.block.less b/awx/ui/client/src/credentials/ownerList.block.less new file mode 100644 index 0000000000..7f6272a302 --- /dev/null +++ b/awx/ui/client/src/credentials/ownerList.block.less @@ -0,0 +1,37 @@ +/** @define OwnerList */ +@import "./client/src/shared/branding/colors.default.less"; + +.OwnerList { + display: flex; + flex-wrap: wrap; + align-items: flex-start; +} + +.OwnerList-seeBase { + display: flex; + max-width: 100%; + + color: @default-link; + margin: 5px 0px; + text-transform: uppercase; + padding: 2px 0px; + cursor: pointer; + border-radius: 5px; + font-size: 11px; +} + +.OwnerList-seeBase:hover { + color: @default-link-hov; +} + +.OwnerList-seeLess { + .OwnerList-seeBase; +} + +.OwnerList-seeMore { + .OwnerList-seeBase; +} + +.OwnerList-Container { + margin-right: 5px; +} diff --git a/awx/ui/client/src/credentials/ownerList.partial.html b/awx/ui/client/src/credentials/ownerList.partial.html index 8fde4f3c0a..5c73ac4d15 100644 --- a/awx/ui/client/src/credentials/ownerList.partial.html +++ b/awx/ui/client/src/credentials/ownerList.partial.html @@ -1,12 +1,12 @@ -
-
+ \ No newline at end of file From c67989bc90de9f3d296dbb247ce399d1e419fa3b Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Thu, 12 Jan 2017 11:32:56 -0500 Subject: [PATCH 096/154] correctly cancel/delete on job details related to #4620 * Changed cancel prompt buttons from "Cancel" "Cancel" to "Cancel" "Proceed", talk about some confusing shit. * Removed copy and pasted code that would call a delete upon canceling a job * Don't expect job_status to be an object in the view, just `job_status`, not `job_status.status` --- .../src/job-results/job-results.partial.html | 10 +++++----- .../src/job-results/job-results.service.js | 16 +--------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html index 6fe760b55e..4788a036c4 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -36,9 +36,9 @@
-
-
-
-
- {{ key }} -
-
-
Type: {{ value.type }}
-
Description: {{value.help_text}}
-
- Enumerated: {{ choice[0] }} -
+
+
+ EXAMPLES:
+ + +
-
- Searchable relationships: {{ relation }}, +
+ FIELDS: {{ key }}, +
+
+ RELATED FIELDS: {{ relation }}, +
+
+ ADDITIONAL INFORMATION: For additional information on advanced search search syntax please see the Ansible Tower documentation.
From d5ce044e8d688e44555591d190a604671a9e0189 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Thu, 12 Jan 2017 19:40:28 -0500 Subject: [PATCH 109/154] add optional template-type attribute to smart status icon directive for linking to workflow jobs (#4695) The recent_jobs summary fields of the endpoints driving the status icon controller do not provide the necessary information to build a url for the correct job details endpoint in all cases. This adds an optional template-type attribute to the smart status directive to override the default url building behavior. --- .../src/partials/job-template-smart-status.html | 2 +- .../src/smart-status/smart-status.controller.js | 13 +++++++++++++ .../src/smart-status/smart-status.directive.js | 3 ++- .../src/smart-status/smart-status.partial.html | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/partials/job-template-smart-status.html b/awx/ui/client/src/partials/job-template-smart-status.html index 1c45c5fe36..d4f8d860b3 100644 --- a/awx/ui/client/src/partials/job-template-smart-status.html +++ b/awx/ui/client/src/partials/job-template-smart-status.html @@ -1 +1 @@ - + diff --git a/awx/ui/client/src/smart-status/smart-status.controller.js b/awx/ui/client/src/smart-status/smart-status.controller.js index 10762de15d..1283552a55 100644 --- a/awx/ui/client/src/smart-status/smart-status.controller.js +++ b/awx/ui/client/src/smart-status/smart-status.controller.js @@ -15,9 +15,21 @@ export default ['$scope', '$filter', var singleJobStatus = true; var firstJobStatus; var recentJobs = $scope.jobs; + var detailsBaseUrl; + if(!recentJobs){ return; } + + // unless we explicitly define a value for the template-type attribute when invoking the + // directive, assume the status icons are for a regular (non-workflow) job when building + // the details url path + if (typeof $scope.templateType !== 'undefined' && $scope.templateType === 'workflow_job_template') { + detailsBaseUrl = '/#/workflows/'; + } else { + detailsBaseUrl = '/#/jobs/'; + } + var sparkData = _.sortBy(recentJobs.map(function(job) { @@ -38,6 +50,7 @@ export default ['$scope', '$filter', data.sortDate = job.finished || "running" + data.jobId; data.finished = $filter('longDate')(job.finished) || job.status+""; data.status_tip = "JOB ID: " + data.jobId + "
STATUS: " + data.smartStatus + "
FINISHED: " + data.finished; + data.detailsUrl = detailsBaseUrl + data.jobId; // If we've already determined that there are both failed and successful jobs OR if the current job in the loop is // pending/waiting/running then we don't worry about checking for a single job status diff --git a/awx/ui/client/src/smart-status/smart-status.directive.js b/awx/ui/client/src/smart-status/smart-status.directive.js index 1ed0ecdcc5..a4486e125e 100644 --- a/awx/ui/client/src/smart-status/smart-status.directive.js +++ b/awx/ui/client/src/smart-status/smart-status.directive.js @@ -9,7 +9,8 @@ export default [ 'templateUrl', function(templateUrl) { return { scope: { - jobs: '=' + jobs: '=', + templateType: '=?', }, templateUrl: templateUrl('smart-status/smart-status'), restrict: 'E', diff --git a/awx/ui/client/src/smart-status/smart-status.partial.html b/awx/ui/client/src/smart-status/smart-status.partial.html index a0545ec814..ec1a81fac3 100644 --- a/awx/ui/client/src/smart-status/smart-status.partial.html +++ b/awx/ui/client/src/smart-status/smart-status.partial.html @@ -1,6 +1,6 @@
-
-
- EXAMPLES: +
+
+
+ EXAMPLES: +
+ + +
- - -
FIELDS: {{ key }}, From 894577c89a8c8d3db9a0533783f741138de9bb78 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 12 Jan 2017 20:51:03 -0500 Subject: [PATCH 111/154] Add Japanese and French po files for the UI. --- awx/ui/po/fr.po | 2963 +++++++++++++++++++++++++++++++++++++++++++++++ awx/ui/po/ja.po | 2803 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 5766 insertions(+) create mode 100644 awx/ui/po/fr.po create mode 100644 awx/ui/po/ja.po diff --git a/awx/ui/po/fr.po b/awx/ui/po/fr.po new file mode 100644 index 0000000000..b37f3871b7 --- /dev/null +++ b/awx/ui/po/fr.po @@ -0,0 +1,2963 @@ +# Corina Roe , 2017. #zanata +# Sam Friedmann , 2017. #zanata +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Project-Id-Version: \n" +"MIME-Version: 1.0\n" +"PO-Revision-Date: 2017-01-11 12:31+0000\n" +"Last-Translator: Corina Roe \n" +"Language-Team: French\n" +"Language: fr\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" + +#: client/src/notifications/notificationTemplates.form.js:371 +msgid "%s or %s" +msgstr "%s ou %s" + +#: client/src/controllers/Projects.js:397 +#: client/src/controllers/Projects.js:679 +msgid "" +"%sNote:%s Mercurial does not support password authentication for SSH. Do not " +"put the username and key in the URL. If using Bitbucket and SSH, do not " +"supply your Bitbucket username." +msgstr "" +"%Remarque :%s Mercurial ne prend pas en charge l'authentification par mot de " +"passe pour SSH. N'entrez ni le nom d'utilisateur, ni la clé dans l'URL. Si " +"vous utilisez Bitbucket et SSH, ne saisissez pas votre nom d'utilisateur " +"Bitbucket." + +#: client/src/controllers/Projects.js:384 +#: client/src/controllers/Projects.js:666 +msgid "" +"%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key " +"only, do not enter a username (other than git). Additionally, GitHub and " +"Bitbucket do not support password authentication when using SSH. GIT read " +"only protocol (git://) does not use username or password information." +msgstr "" +"%Remarque :%s Si vous utilisez le protocole SSH pour GitHub ou Bitbucket, " +"entrez uniquement une clé SSH sans nom d'utilisateur (autre que git). De " +"plus, GitHub et Bitbucket ne prennent pas en charge l'authentification par " +"mot de passe lorsque SSH est utilisé. Le protocole GIT en lecture seule (git:" +"//) n'utilise pas les informations de nom d'utilisateur ou de mot de passe." + +#: client/src/forms/Credentials.js:287 +msgid "(defaults to %s)" +msgstr "(défini par défaut sur %s)" + +#: client/src/organizations/list/organizations-list.partial.html:15 +msgid "+ ADD" +msgstr "+ AJOUTER" + +#: client/src/controllers/Users.js:185 +msgid "A value is required" +msgstr "Entrez une valeur" + +#: client/src/forms/Credentials.js:442 +#: client/src/forms/Inventories.js:153 +#: client/src/forms/JobTemplates.js:414 +#: client/src/forms/Organizations.js:75 +#: client/src/forms/Projects.js:237 +#: client/src/forms/Teams.js:86 +#: client/src/forms/Workflows.js:127 +#: client/src/inventory-scripts/inventory-scripts.list.js:45 +#: client/src/lists/Credentials.js:59 +#: client/src/lists/Inventories.js:68 +#: client/src/lists/Projects.js:67 +#: client/src/lists/Teams.js:50 +#: client/src/lists/Templates.js:62 +#: client/src/lists/Users.js:58 +#: client/src/notifications/notificationTemplates.list.js:52 +msgid "ADD" +msgstr "AJOUTER" + +#: client/src/notifications/notifications.list.js:68 +msgid "ADD NOTIFICATION TEMPLATE" +msgstr "AJOUTER UN MODÈLE DE NOTIFICATION" + +#: client/src/forms/Credentials.js:199 +msgid "API Key" +msgstr "Clé API" + +#: client/src/notifications/notificationTemplates.form.js:248 +msgid "API Service/Integration Key" +msgstr "Service API/Clé d'intégration" + +#: client/src/notifications/shared/type-change.service.js:52 +msgid "API Token" +msgstr "Token API" + +#: client/src/setup-menu/setup-menu.partial.html:59 +msgid "About Tower" +msgstr "Tower" + +#: client/src/forms/Credentials.js:92 +msgid "Access Key" +msgstr "Clé d'accès" + +#: client/src/notifications/notificationTemplates.form.js:226 +msgid "Account SID" +msgstr "SID de compte" + +#: client/src/notifications/notificationTemplates.form.js:184 +msgid "Account Token" +msgstr "Token de compte" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:20 +#: client/src/shared/list-generator/list-generator.factory.js:538 +msgid "Actions" +msgstr "Actions" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:17 +#: client/src/lists/Templates.js:40 +msgid "Activity" +msgstr "Activité" + +#: client/src/configuration/system-form/configuration-system.controller.js:81 +msgid "Activity Stream" +msgstr "Flux d'activité" + +#: client/src/forms/Inventories.js:104 +#: client/src/forms/Inventories.js:150 +#: client/src/forms/Organizations.js:72 +#: client/src/forms/Teams.js:83 +#: client/src/forms/Workflows.js:124 +msgid "Add" +msgstr "Ajouter" + +#: client/src/lists/Credentials.js:17 +msgid "Add Credentials" +msgstr "Ajouter des informations d'identification" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:12 +msgid "Add Existing Hosts" +msgstr "Ajouter des hôtes existants" + +#: client/src/lists/Inventories.js:15 +msgid "Add Inventories" +msgstr "Ajouter des inventaires" + +#: client/src/notifications/notifications.list.js:63 +msgid "Add Notification" +msgstr "Ajouter une notification" + +#: client/src/lists/Projects.js:15 +msgid "Add Project" +msgstr "Ajouter un projet" + +#: client/src/forms/JobTemplates.js:459 +#: client/src/forms/Workflows.js:172 +#: client/src/shared/form-generator.js:1707 +msgid "Add Survey" +msgstr "Ajouter un questionnaire" + +#: client/src/lists/Teams.js:15 +msgid "Add Team" +msgstr "Ajouter une équipe" + +#: client/src/lists/Users.js:25 +msgid "Add Users" +msgstr "Ajouter des utilisateurs" + +#: client/src/forms/Credentials.js:440 +#: client/src/forms/Inventories.js:151 +#: client/src/forms/JobTemplates.js:412 +#: client/src/forms/Organizations.js:73 +#: client/src/forms/Projects.js:235 +msgid "Add a permission" +msgstr "Ajouter une permission" + +#: client/src/setup-menu/setup-menu.partial.html:23 +msgid "" +"Add passwords, SSH keys, etc. for Tower to use when launching jobs against " +"machines, or when syncing inventories or projects." +msgstr "" +"Ajouter des mots de passe, des clés SSH, etc. pour Tower afin de les " +"utiliser lors du lancement de tâches sur des machines ou durant la " +"synchronisation d'inventaires ou de projets." + +#: client/src/forms/Teams.js:84 +msgid "Add user to team" +msgstr "Ajouter un utilisateur à l'équipe" + +#: client/src/shared/form-generator.js:1450 +msgid "Admin" +msgstr "Administrateur" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:37 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:43 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:65 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:74 +msgid "All" +msgstr "Tous" + +#: client/src/portal-mode/portal-mode-jobs.partial.html:7 +msgid "All Jobs" +msgstr "Toutes les tâches" + +#: client/src/forms/JobTemplates.js:299 +#: client/src/forms/JobTemplates.js:306 +msgid "Allow Provisioning Callbacks" +msgstr "Autoriser les rappels d'exécution de Tower job_template" + +#: client/src/setup-menu/setup-menu.partial.html:11 +msgid "Allow others to sign into Tower and own the content they create." +msgstr "" +"Autoriser les autres à se connecter à Tower et à devenir propriétaire du " +"contenu qu'ils créent." + +#: client/src/forms/WorkflowMaker.js:50 +msgid "Always" +msgstr "Toujours" + +#: client/src/controllers/Projects.js:220 +msgid "" +"An SCM update does not appear to be running for project: %s. Click the " +"%sRefresh%s button to view the latest status." +msgstr "" +"Une mise à jour SCM ne semble pas s'exécuter pour le projet : %s. Cliquez " +"sur le bouton %sActualiser%s pour voir l'état le plus récent." + +#: client/src/controllers/Projects.js:162 +msgid "Are you sure you want to delete the project below?" +msgstr "Voulez-vous vraiment supprimer le projet ci-dessous ?" + +#: client/src/controllers/Users.js:102 +msgid "Are you sure you want to delete the user below?" +msgstr "Voulez-vous vraiment supprimer l'utilisateur ci-dessous ?" + +#: client/src/controllers/Projects.js:647 +msgid "Are you sure you want to remove the %s below from %s?" +msgstr "Voulez-vous vraimment supprimer le %s ci-dessous de %s ?" + +#: client/src/forms/Credentials.js:233 +#: client/src/forms/Credentials.js:271 +#: client/src/forms/Credentials.js:310 +#: client/src/forms/Credentials.js:395 +msgid "Ask at runtime?" +msgstr "Demander durant l'éxecution ?" + +#: client/src/shared/form-generator.js:1452 +msgid "Auditor" +msgstr "Auditeur" + +#: client/src/forms/Credentials.js:73 +msgid "" +"Authentication for network device access. This can include SSH keys, " +"usernames, passwords, and authorize information. Network credentials are " +"used when submitting jobs to run playbooks against network devices." +msgstr "" +"Authentification pour l'accès aux périphériques réseau. Il peut s'agir de " +"clés SSH, de noms d'utilisateur, de mots de passe et d'informations " +"d'autorisation. Les informations d'identification réseau sont utilisées au " +"cours de l'envoi de tâches afin d'exécuter des playbooks sur des " +"périphériques réseau." + +#: client/src/forms/Credentials.js:69 +msgid "" +"Authentication for remote machine access. This can include SSH keys, " +"usernames, passwords, and sudo information. Machine credentials are used " +"when submitting jobs to run playbooks against remote hosts." +msgstr "" +"Authentification pour l'accès aux machines distantes. Il peut s'agir de clés " +"SSH, de noms d'utilisateur, de mots de passe et d'informations sudo. Les " +"informations d'identification de machine sont utilisées au cours de l'envoi " +"de tâches afin d'exécuter des playbooks sur des hôtes distants." + +#: client/src/forms/Credentials.js:341 +msgid "Authorize" +msgstr "Autoriser" + +#: client/src/forms/Credentials.js:349 +msgid "Authorize Password" +msgstr "Mot de passe d'autorisation" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:101 +msgid "Azure AD" +msgstr "Azure AD" + +#: client/src/forms/Projects.js:80 +msgid "" +"Base path used for locating playbooks. Directories found inside this path " +"will be listed in the playbook directory drop-down. Together the base path " +"and selected playbook directory provide the full path used to locate " +"playbooks." +msgstr "" +"Chemin de base utilisé pour localiser les playbooks. Les répertoires " +"localisés dans ce chemin sont répertoriés dans la liste déroulante des " +"répertoires de playbooks. Le chemin de base et le répertoire de playbook " +"sélectionnés fournissent ensemble le chemin complet servant à localiser les " +"playbooks." + +#: client/src/forms/JobTemplates.js:293 +msgid "Become Privilege Escalation" +msgstr "Activer l'élévation des privilèges" + +#: client/src/license/license.partial.html:104 +msgid "Browse" +msgstr "Parcourir" + +#: client/src/app.js:317 +msgid "CREDENTIALS" +msgstr "INFORMATIONS D'IDENTIFICATION" + +#: client/src/forms/Projects.js:194 +msgid "Cache Timeout" +msgstr "Expiration du délai d'attente du cache" + +#: client/src/forms/Projects.js:183 +msgid "Cache Timeout%s (seconds)%s" +msgstr "Expiration du délai d'attente du cache%s (secondes)%s" + +#: client/src/controllers/Projects.js:156 +#: client/src/controllers/Users.js:95 +msgid "Call to %s failed. DELETE returned status:" +msgstr "Échec de l'appel de %s. État DELETE renvoyé :" + +#: client/src/controllers/Projects.js:201 +#: client/src/controllers/Projects.js:217 +msgid "Call to %s failed. GET status:" +msgstr "Échec de l'appel de %s. État GET :" + +#: client/src/controllers/Projects.js:641 +msgid "Call to %s failed. POST returned status:" +msgstr "Échec de l'appel de %s. État POST renvoyé :" + +#: client/src/controllers/Projects.js:180 +msgid "Call to %s failed. POST status:" +msgstr "Échec de l'appel de %s. État POST :" + +#: client/src/controllers/Projects.js:226 +msgid "Call to get project failed. GET status:" +msgstr "Échec de l'appel du projet en GET. État GET :" + +#: client/src/configuration/configuration.controller.js:434 +#: client/src/shared/form-generator.js:1695 +msgid "Cancel" +msgstr "Annuler" + +#: client/src/controllers/Projects.js:196 +msgid "Cancel Not Allowed" +msgstr "Annulation non autorisée" + +#: client/src/lists/Projects.js:121 +msgid "Cancel the SCM update" +msgstr "Annuler la mise à jour SCM" + +#: client/src/controllers/Projects.js:53 +msgid "Canceled. Click for details" +msgstr "Annulé. Cliquez pour connaître les détails." + +#: client/src/forms/Projects.js:82 +msgid "Change %s under \"Configure Tower\" to change this location." +msgstr "Modifiez %s sous \"Configurer Tower\" pour changer d'emplacement." + +#: client/src/shared/form-generator.js:1084 +msgid "Choose a %s" +msgstr "Choisir un %s" + +#: client/src/license/license.partial.html:97 +msgid "" +"Choose your license file, agree to the End User License Agreement, and click " +"submit." +msgstr "" +"Choisissez votre fichier de licence, acceptez le Contrat de licence de " +"l'utilisateur final et validez." + +#: client/src/forms/Projects.js:151 +msgid "Clean" +msgstr "Nettoyer" + +#: client/src/lists/Inventories.js:18 +msgid "" +"Click on a row to select it, and click Finished when done. Click the %s " +"button to create a new inventory." +msgstr "" +"Cliquez sur une ligne pour la sélectionner, puis sur Terminé lorsque vous " +"avez fini. Cliquez sur le bouton %s pour créer un inventaire." + +#: client/src/lists/Teams.js:18 +msgid "" +"Click on a row to select it, and click Finished when done. Click the %s " +"button to create a new team." +msgstr "" +"Cliquez sur une ligne pour la sélectionner, puis sur Terminé lorsque vous " +"avez fini. Cliquez sur le bouton %s pour créer une équipe." + +#: client/src/lists/Templates.js:19 +msgid "" +"Click on a row to select it, and click Finished when done. Use the %s button " +"to create a new job template." +msgstr "" +"Cliquez sur une ligne pour la sélectionner, puis sur Terminé lorsque vous " +"avez fini. Cliquez sur le bouton %s pour créer un modèle de tâche." + +#: client/src/forms/Credentials.js:319 +msgid "Client ID" +msgstr "ID du client" + +#: client/src/notifications/notificationTemplates.form.js:259 +msgid "Client Identifier" +msgstr "Identifiant client" + +#: client/src/forms/Credentials.js:328 +msgid "Client Secret" +msgstr "Question secrète du client" + +#: client/src/shared/form-generator.js:1699 +msgid "Close" +msgstr "Fermer" + +#: client/src/forms/JobTemplates.js:164 +#: client/src/forms/JobTemplates.js:176 +msgid "Cloud Credential" +msgstr "Informations d'identification cloud" + +#: client/src/helpers/Credentials.js:158 +msgid "CloudForms Host" +msgstr "Hôte CloudForms" + +#: client/src/notifications/notificationTemplates.form.js:295 +msgid "Color can be one of %s." +msgstr "La couleur peut être l'une des %s." + +#: client/src/lists/CompletedJobs.js:18 +msgid "Completed Jobs" +msgstr "Tâches terminées" + +#: client/src/management-jobs/card/card.partial.html:32 +msgid "Configure Notifications" +msgstr "Configurer les notifications" + +#: client/src/forms/Users.js:82 +msgid "Confirm Password" +msgstr "Confirmer le mot de passe" + +#: client/src/configuration/configuration.controller.js:441 +msgid "Confirm Reset" +msgstr "Confirmer la réinitialisation" + +#: client/src/configuration/configuration.controller.js:450 +msgid "Confirm factory reset" +msgstr "Confirmer la réinitialisation usine" + +#: client/src/forms/JobTemplates.js:255 +#: client/src/forms/JobTemplates.js:273 +#: client/src/forms/WorkflowMaker.js:141 +#: client/src/forms/WorkflowMaker.js:156 +msgid "" +"Consult the Ansible documentation for further details on the usage of tags." +msgstr "" +"Consultez la documentation d'Ansible pour en savoir plus sur l'utilisation " +"des balises." + +#: client/src/forms/JobTemplates.js:241 +msgid "" +"Control the level of output ansible will produce as the playbook executes." +msgstr "" +"Contrôlez le niveau de sortie qu'Ansible génère lors de l'exécution du " +"playbook." + +#: client/src/lists/Templates.js:100 +msgid "Copy" +msgstr "Copier" + +#: client/src/lists/Templates.js:103 +msgid "Copy template" +msgstr "Copier le modèle" + +#: client/src/forms/Credentials.js:18 +msgid "Create Credential" +msgstr "Créer des informations d'identification" + +#: client/src/lists/Users.js:52 +msgid "Create New" +msgstr "Créer" + +#: client/src/lists/Credentials.js:57 +msgid "Create a new credential" +msgstr "Créer de nouvelles informations d'identification" + +#: client/src/inventory-scripts/inventory-scripts.list.js:43 +msgid "Create a new custom inventory" +msgstr "Créer un inventaire personnalisé" + +#: client/src/lists/Inventories.js:66 +msgid "Create a new inventory" +msgstr "Créer un inventaire" + +#: client/src/notifications/notificationTemplates.list.js:50 +#: client/src/notifications/notifications.list.js:66 +msgid "Create a new notification template" +msgstr "Créer un modèle de notification" + +#: client/src/organizations/list/organizations-list.partial.html:16 +msgid "Create a new organization" +msgstr "Créer une organisation" + +#: client/src/lists/Projects.js:65 +msgid "Create a new project" +msgstr "Créer un projet" + +#: client/src/lists/Teams.js:48 +msgid "Create a new team" +msgstr "Créer une équipe" + +#: client/src/lists/Templates.js:60 +msgid "Create a new template" +msgstr "Créer un modèle" + +#: client/src/lists/Users.js:56 +msgid "Create a new user" +msgstr "Créer un utilisateur" + +#: client/src/setup-menu/setup-menu.partial.html:35 +msgid "Create and edit scripts to dynamically load hosts from any source." +msgstr "" +"Créez et modifiez des scripts pour charger dynamiquement des hôtes à partir " +"de n'importe quelle source." + +#: client/src/setup-menu/setup-menu.partial.html:42 +msgid "" +"Create templates for sending notifications with Email, HipChat, Slack, and " +"SMS." +msgstr "" +"Créer des modèles pour envoyer des notifications via email, HipChat, Slack, " +"et SMS." + +#: client/src/forms/JobTemplates.js:154 +#: client/src/forms/WorkflowMaker.js:60 +#: client/src/forms/WorkflowMaker.js:69 +msgid "Credential" +msgstr "Information d'identification" + +#: client/src/lists/Credentials.js:18 +#: client/src/lists/Credentials.js:19 +#: client/src/setup-menu/setup-menu.partial.html:22 +msgid "Credentials" +msgstr "Informations d'identification" + +#: client/src/inventory-scripts/inventory-scripts.form.js:50 +#: client/src/inventory-scripts/inventory-scripts.form.js:60 +msgid "Custom Script" +msgstr "Script personnalisé" + +#: client/src/app.js:409 +msgid "DASHBOARD" +msgstr "TABLEAU DE BORD" + +#: client/src/controllers/Projects.js:649 +#: client/src/controllers/Users.js:104 +msgid "DELETE" +msgstr "SUPPRIMER" + +#: client/src/controllers/Projects.js:161 +#: client/src/controllers/Projects.js:646 +#: client/src/controllers/Users.js:101 +#: client/src/inventory-scripts/inventory-scripts.list.js:74 +#: client/src/lists/Credentials.js:90 +#: client/src/lists/Inventories.js:92 +#: client/src/lists/Teams.js:77 +#: client/src/lists/Templates.js:125 +#: client/src/lists/Users.js:87 +#: client/src/notifications/notificationTemplates.list.js:89 +msgid "Delete" +msgstr "Supprimer" + +#: client/src/lists/Credentials.js:92 +msgid "Delete credential" +msgstr "Supprimer les informations d'identification" + +#: client/src/lists/Inventories.js:94 +msgid "Delete inventory" +msgstr "Supprimer l'inventaire" + +#: client/src/inventory-scripts/inventory-scripts.list.js:76 +msgid "Delete inventory script" +msgstr "Supprimer le script d'inventaire" + +#: client/src/notifications/notificationTemplates.list.js:91 +msgid "Delete notification" +msgstr "Supprimer la notification" + +#: client/src/forms/Projects.js:161 +msgid "Delete on Update" +msgstr "Supprimer lors de la mise à jour" + +#: client/src/lists/Teams.js:81 +msgid "Delete team" +msgstr "Supprimer l'équipe" + +#: client/src/lists/Templates.js:128 +msgid "Delete template" +msgstr "Supprimer le modèle" + +#: client/src/lists/CompletedJobs.js:82 +msgid "Delete the job" +msgstr "Supprimer la tâche" + +#: client/src/forms/Projects.js:163 +msgid "" +"Delete the local repository in its entirety prior to performing an update." +msgstr "" +"Supprimer le référentiel local dans son intégralité avant de lancer la mise " +"à jour." + +#: client/src/lists/Projects.js:115 +msgid "Delete the project" +msgstr "Supprimer le projet" + +#: client/src/lists/ScheduledJobs.js:80 +msgid "Delete the schedule" +msgstr "Supprimer la planification" + +#: client/src/lists/Users.js:91 +msgid "Delete user" +msgstr "Supprimer l'utilisateur" + +#: client/src/forms/Projects.js:163 +msgid "" +"Depending on the size of the repository this may significantly increase the " +"amount of time required to complete an update." +msgstr "" +"Selon la taille du référentiel, cette opération risque d'augmenter " +"considérablement le délai d'exécution de la mise à jour." + +#: client/src/forms/Credentials.js:41 +#: client/src/forms/Inventories.js:37 +#: client/src/forms/JobTemplates.js:42 +#: client/src/forms/Organizations.js:33 +#: client/src/forms/Projects.js:38 +#: client/src/forms/Teams.js:34 +#: client/src/forms/Users.js:142 +#: client/src/forms/Users.js:167 +#: client/src/forms/Workflows.js:41 +#: client/src/inventory-scripts/inventory-scripts.form.js:32 +#: client/src/inventory-scripts/inventory-scripts.list.js:25 +#: client/src/lists/Credentials.js:34 +#: client/src/lists/PortalJobTemplates.js:29 +#: client/src/lists/Teams.js:30 +#: client/src/lists/Templates.js:36 +#: client/src/notifications/notificationTemplates.form.js:36 +msgid "Description" +msgstr "Description" + +#: client/src/notifications/notificationTemplates.form.js:138 +#: client/src/notifications/notificationTemplates.form.js:143 +#: client/src/notifications/notificationTemplates.form.js:155 +#: client/src/notifications/notificationTemplates.form.js:160 +#: client/src/notifications/notificationTemplates.form.js:372 +msgid "Destination Channels" +msgstr "Canaux de destination" + +#: client/src/notifications/notificationTemplates.form.js:367 +msgid "Destination Channels or Users" +msgstr "Canaux de destination pour les utilisateurs" + +#: client/src/notifications/notificationTemplates.form.js:209 +#: client/src/notifications/notificationTemplates.form.js:214 +msgid "Destination SMS Number" +msgstr "Numéro SMS de destination" + +#: client/src/license/license.partial.html:5 +#: client/src/shared/form-generator.js:1481 +msgid "Details" +msgstr "Détails" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:70 +#: client/src/configuration/configuration.controller.js:170 +#: client/src/configuration/configuration.controller.js:232 +#: client/src/configuration/system-form/configuration-system.controller.js:49 +msgid "Discard changes" +msgstr "Ignorer les modifications" + +#: client/src/forms/Teams.js:148 +msgid "Dissasociate permission from team" +msgstr "Dissocier la permission de l'équipe" + +#: client/src/forms/Users.js:217 +msgid "Dissasociate permission from user" +msgstr "Dissocier la permission de l'utilisateur" + +#: client/src/forms/Credentials.js:382 +#: client/src/helpers/Credentials.js:133 +msgid "Domain Name" +msgstr "Nom de domaine" + +#: client/src/inventory-scripts/inventory-scripts.form.js:58 +msgid "" +"Drag and drop your custom inventory script file here or create one in the " +"field to import your custom inventory." +msgstr "" +"Faites glisser votre script d'inventaire personnalisé et déposez-le ici ou " +"créez-en un dans le champ pour importer votre inventaire personnalisé." + +#: client/src/forms/Projects.js:174 +msgid "" +"Each time a job runs using this project, perform an update to the local " +"repository prior to starting the job." +msgstr "" +"Chaque fois qu'une tâche s'exécute avec ce projet, réalisez une mise à jour " +"dans le référentiel local avant de lancer la tâche." + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:63 +#: client/src/inventory-scripts/inventory-scripts.list.js:57 +#: client/src/lists/Credentials.js:71 +#: client/src/lists/Inventories.js:78 +#: client/src/lists/Teams.js:60 +#: client/src/lists/Templates.js:108 +#: client/src/lists/Users.js:68 +#: client/src/notifications/notificationTemplates.list.js:63 +#: client/src/notifications/notificationTemplates.list.js:72 +msgid "Edit" +msgstr "Modifier" + +#: client/src/forms/JobTemplates.js:466 +#: client/src/forms/Workflows.js:179 +#: client/src/shared/form-generator.js:1711 +msgid "Edit Survey" +msgstr "Modifier le questionnaire" + +#: client/src/lists/Credentials.js:73 +msgid "Edit credential" +msgstr "Modifier les informations d'identification" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:66 +msgid "Edit host" +msgstr "Modifier l'hôte" + +#: client/src/lists/Inventories.js:80 +msgid "Edit inventory" +msgstr "Modifier l'inventaire" + +#: client/src/inventory-scripts/inventory-scripts.list.js:59 +msgid "Edit inventory script" +msgstr "Modifier le script d'inventaire" + +#: client/src/notifications/notificationTemplates.list.js:74 +msgid "Edit notification" +msgstr "Modifier la notification" + +#: client/src/lists/Teams.js:64 +msgid "Edit team" +msgstr "Modifier l'équipe" + +#: client/src/lists/Templates.js:110 +msgid "Edit template" +msgstr "Modifier le modèle" + +#: client/src/lists/Projects.js:102 +msgid "Edit the project" +msgstr "Modifier le projet" + +#: client/src/lists/ScheduledJobs.js:66 +msgid "Edit the schedule" +msgstr "Modifier la planification" + +#: client/src/lists/Users.js:72 +msgid "Edit user" +msgstr "Modifier l'utilisateur" + +#: client/src/controllers/Projects.js:196 +msgid "" +"Either you do not have access or the SCM update process completed. Click the " +"%sRefresh%s button to view the latest status." +msgstr "" +"Vous n'avez pas accès, ou la mise à jour SCM est terminée. Cliquez sur le " +"bouton %sActualiser%s pour voir l'état le plus récent." + +#: client/src/forms/Credentials.js:192 +#: client/src/forms/Users.js:42 +msgid "Email" +msgstr "Email" + +#: client/src/forms/JobTemplates.js:288 +msgid "Enable Privilege Escalation" +msgstr "Activer l'élévation des privilèges" + +#: client/src/forms/JobTemplates.js:303 +msgid "" +"Enables creation of a provisioning callback URL. Using the URL a host can " +"contact Tower and request a configuration update using this job template." +msgstr "" +"Active la création d'une URL de rappels d'exécution de Tower job_template. " +"Avec cette URL, un hôte peut contacter Tower et demander une mise à jour de " +"la configuration à l'aide de ce modèle de tâche." + +#: client/src/helpers/Credentials.js:306 +msgid "Encrypted credentials are not supported." +msgstr "" +"Les informations d'identification chiffrées ne sont pas prises en charge." + +#: client/src/license/license.partial.html:108 +msgid "End User License Agreement" +msgstr "Contrat de licence de l'utilisateur final" + +#: client/src/forms/Inventories.js:60 +msgid "" +"Enter inventory variables using either JSON or YAML syntax. Use the radio " +"button to toggle between the two." +msgstr "" +"Entrez les variables d'inventaire avec la syntaxe JSON ou YAML. Utilisez le " +"bouton radio pour basculer entre les deux." + +#: client/src/helpers/Credentials.js:159 +msgid "" +"Enter the hostname or IP address for the virtual %s machine which is hosting " +"the CloudForm appliance." +msgstr "" +"Entrez le nom d'hôte ou l'adresse IP de la machine %s virtuelle qui héberge " +"l'appliance CloudForm." + +#: client/src/helpers/Credentials.js:150 +msgid "" +"Enter the hostname or IP address name which %scorresponds to your Red Hat " +"Satellite 6 server." +msgstr "" +"Entrez le nom d'hôte ou l'adresse IP qui %scorrespond à votre serveur Red " +"Hat Satellite 6." + +#: client/src/helpers/Credentials.js:128 +msgid "" +"Enter the hostname or IP address which corresponds to your VMware vCenter." +msgstr "" +"Entrez le nom d'hôte ou l'adresse IP qui correspond à votre VMware vCenter." + +#: client/src/configuration/configuration.controller.js:292 +#: client/src/configuration/configuration.controller.js:370 +#: client/src/configuration/configuration.controller.js:404 +#: client/src/configuration/configuration.controller.js:423 +#: client/src/controllers/Projects.js:133 +#: client/src/controllers/Projects.js:155 +#: client/src/controllers/Projects.js:180 +#: client/src/controllers/Projects.js:201 +#: client/src/controllers/Projects.js:216 +#: client/src/controllers/Projects.js:225 +#: client/src/controllers/Projects.js:363 +#: client/src/controllers/Projects.js:557 +#: client/src/controllers/Projects.js:623 +#: client/src/controllers/Projects.js:641 +#: client/src/controllers/Users.js:182 +#: client/src/controllers/Users.js:267 +#: client/src/controllers/Users.js:321 +#: client/src/controllers/Users.js:94 +#: client/src/helpers/Credentials.js:310 +#: client/src/helpers/Credentials.js:326 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:119 +msgid "Error!" +msgstr "Erreur !" + +#: client/src/controllers/Projects.js:381 +#: client/src/controllers/Projects.js:664 +msgid "Example URLs for GIT SCM include:" +msgstr "Exemples d'URL pour le SCM GIT :" + +#: client/src/controllers/Projects.js:394 +#: client/src/controllers/Projects.js:676 +msgid "Example URLs for Mercurial SCM include:" +msgstr "Exemples d'URL pour le SCM Mercurial :" + +#: client/src/controllers/Projects.js:389 +#: client/src/controllers/Projects.js:671 +msgid "Example URLs for Subversion SCM include:" +msgstr "Exemples d'URL pour le SCM Subversion :" + +#: client/src/license/license.partial.html:39 +msgid "Expires On" +msgstr "Arrive à expiration le" + +#: client/src/forms/JobTemplates.js:352 +#: client/src/forms/JobTemplates.js:364 +#: client/src/forms/Workflows.js:72 +#: client/src/forms/Workflows.js:84 +msgid "Extra Variables" +msgstr "Variables supplémentaires" + +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:67 +msgid "FAILED" +msgstr "ÉCHEC" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:80 +msgid "Failed" +msgstr "Échec" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:44 +msgid "Failed Hosts" +msgstr "Échec des hôtes" + +#: client/src/controllers/Users.js:182 +msgid "Failed to add new user. POST returned status:" +msgstr "L'ajout de l'utilisateur a échoué. État POST renvoyé :" + +#: client/src/helpers/Credentials.js:311 +msgid "Failed to create new Credential. POST status:" +msgstr "La création des informations d'identification a échoué. État POST :" + +#: client/src/controllers/Projects.js:364 +msgid "Failed to create new project. POST returned status:" +msgstr "La création du projet a échoué. État POST renvoyé :" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:120 +msgid "Failed to get third-party login types. Returned status:" +msgstr "L'obtention des types de connexion tiers a échoué. État renvoyé :" + +#: client/src/controllers/Projects.js:558 +msgid "Failed to retrieve project: %s. GET status:" +msgstr "La récupération du projet a échoué : %s. État GET :" + +#: client/src/controllers/Users.js:268 +#: client/src/controllers/Users.js:322 +msgid "Failed to retrieve user: %s. GET status:" +msgstr "La récupération de l'utilisateur a échoué : %s. État GET :" + +#: client/src/configuration/configuration.controller.js:371 +msgid "Failed to save settings. Returned status:" +msgstr "L'enregistrement des paramètres a échoué. État renvoyé :" + +#: client/src/configuration/configuration.controller.js:405 +msgid "Failed to save toggle settings. Returned status:" +msgstr "" +"L'enregistrement des paramètres d'activation/désactivation a échoué. État " +"renvoyé :" + +#: client/src/helpers/Credentials.js:327 +msgid "Failed to update Credential. PUT status:" +msgstr "La mise à jour des informations d'identification a échoué. État PUT :" + +#: client/src/controllers/Projects.js:623 +msgid "Failed to update project: %s. PUT status:" +msgstr "La mise à jour du projet a échoué : %s. État PUT :" + +#: client/src/notifications/notifications.list.js:49 +msgid "Failure" +msgstr "Défaillance" + +#: client/src/lists/CompletedJobs.js:56 +#: client/src/lists/PortalJobs.js:37 +msgid "Finished" +msgstr "Terminé" + +#: client/src/forms/Users.js:28 +#: client/src/lists/Users.js:41 +msgid "First Name" +msgstr "Prénom" + +#: client/src/helpers/Credentials.js:142 +msgid "For example, %s" +msgstr "Par exemple, %s" + +#: client/src/notifications/notificationTemplates.form.js:142 +#: client/src/notifications/notificationTemplates.form.js:159 +#: client/src/notifications/notificationTemplates.form.js:213 +#: client/src/notifications/notificationTemplates.form.js:333 +#: client/src/notifications/notificationTemplates.form.js:371 +#: client/src/notifications/notificationTemplates.form.js:98 +msgid "For example:" +msgstr "Par exemple :" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:54 +msgid "" +"For hosts that are part of an external inventory, this flag cannot be " +"changed. It will be set by the inventory sync process." +msgstr "" +"Pour les hôtes qui font partie d'un inventaire externe, ce marqueur ne peut " +"pas être modifié. Il sera défini par le processus de synchronisation des " +"inventaires." + +#: client/src/forms/JobTemplates.js:223 +#: client/src/forms/WorkflowMaker.js:125 +msgid "" +"For more information and examples see %sthe Patterns topic at docs.ansible." +"com%s." +msgstr "" +"Pour obtenir plus d'informations et voir des exemples, reportez-vous à la " +"rubrique %Patterns sur docs.ansible.com%s." + +#: client/src/forms/JobTemplates.js:199 +#: client/src/forms/JobTemplates.js:212 +msgid "Forks" +msgstr "Forks" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:102 +msgid "GitHub" +msgstr "GitHub" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:103 +msgid "GitHub Org" +msgstr "GitHub Org" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:104 +msgid "GithHub Team" +msgstr "Équipe GithHub" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:105 +msgid "Google OAuth2" +msgstr "Google OAuth2" + +#: client/src/forms/Teams.js:118 +msgid "Granted Permissions" +msgstr "Permissions accordées" + +#: client/src/forms/Users.js:183 +msgid "Granted permissions" +msgstr "Permissions accordées" + +#: client/src/setup-menu/setup-menu.partial.html:5 +msgid "" +"Group all of your content to manage permissions across departments in your " +"company." +msgstr "" +"Regroupez l'ensemble du contenu pour gérer les permissions entre les " +"différents services de votre entreprise." + +#: client/src/notifications/notificationTemplates.form.js:324 +msgid "HTTP Headers" +msgstr "En-têtes HTTP" + +#: client/src/forms/Credentials.js:140 +#: client/src/notifications/notificationTemplates.form.js:72 +msgid "Host" +msgstr "Hôte" + +#: client/src/helpers/Credentials.js:131 +msgid "Host (Authentication URL)" +msgstr "Hôte (URL d'authentification)" + +#: client/src/forms/JobTemplates.js:326 +#: client/src/forms/JobTemplates.js:335 +msgid "Host Config Key" +msgstr "Clé de configuration de l'hôte" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:55 +msgid "Host Enabled" +msgstr "Hôte activé" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:39 +msgid "Hosts" +msgstr "Hôtes" + +#: client/src/license/license.partial.html:52 +msgid "Hosts Available" +msgstr "Hôtes disponibles" + +#: client/src/license/license.partial.html:64 +msgid "Hosts Remaining" +msgstr "Hôtes restants" + +#: client/src/license/license.partial.html:58 +msgid "Hosts Used" +msgstr "Hôtes utilisés" + +#: client/src/license/license.partial.html:116 +msgid "I agree to the End User License Agreement" +msgstr "J'accepte le Contrat de licence de l'utilisateur final" + +#: client/src/main-menu/main-menu.partial.html:104 +#: client/src/main-menu/main-menu.partial.html:27 +msgid "INVENTORIES" +msgstr "INVENTAIRES" + +#: client/src/notifications/notificationTemplates.form.js:356 +msgid "IRC Nick" +msgstr "Surnom IRC" + +#: client/src/notifications/notificationTemplates.form.js:345 +msgid "IRC Server Address" +msgstr "Adresse du serveur IRC" + +#: client/src/notifications/shared/type-change.service.js:58 +msgid "IRC Server Password" +msgstr "Mot de passe du serveur IRC" + +#: client/src/notifications/shared/type-change.service.js:57 +msgid "IRC Server Port" +msgstr "Port du serveur IRC" + +#: client/src/forms/JobTemplates.js:291 +msgid "" +"If enabled, run this playbook as an administrator. This is the equivalent of " +"passing the %s option to the %s command." +msgstr "" +"Si cette option est activée, exécutez ce playbook en tant qu'administrateur. " +"Cette opération revient à transmettre l'option %s à la commande %s." + +#: client/src/forms/Credentials.js:54 +msgid "" +"If no organization is given, the credential can only be used by the user " +"that creates the credential. Organization admins and system administrators " +"can assign an organization so that roles for the credential can be assigned " +"to users and teams in that organization." +msgstr "" +"Si aucune organisation n'est renseignée, les informations d'identification " +"ne peuvent être utilisées que par l'utilisateur qui les crée. Les " +"administrateurs d'organisation et les administrateurs système peuvent " +"assigner une organisation pour que les rôles liés aux informations " +"d'identification puissent être associés aux utilisateurs et aux équipes de " +"cette organisation." + +#: client/src/license/license.partial.html:70 +msgid "" +"If you are ready to upgrade, please contact us by clicking the button below" +msgstr "" +"Si vous êtes prêt à effectuer une mise à niveau, contactez-nous en cliquant " +"sur le bouton ci-dessous" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:54 +msgid "" +"Indicates if a host is available and should be included in running jobs." +msgstr "" +"Indique si un hôte est disponible et doit être ajouté aux tâches en cours " +"d'exécution." + +#: client/src/forms/JobTemplates.js:58 +msgid "" +"Instead, %s will check playbook syntax, test environment setup and report " +"problems." +msgstr "" +"À la place, %s vérifie la syntaxe du playbook, teste la configuration de " +"l'environnement et signale les problèmes." + +#: client/src/license/license.partial.html:11 +msgid "Invalid License" +msgstr "Licence non valide" + +#: client/src/license/license.controller.js:69 +#: client/src/license/license.controller.js:76 +msgid "Invalid file format. Please upload valid JSON." +msgstr "Format de fichier non valide. Chargez un fichier JSON valide." + +#: client/src/login/loginModal/loginModal.partial.html:34 +msgid "Invalid username and/or password. Please try again." +msgstr "Nom d'utilisateur et/ou mot de passe non valide. Veuillez réessayer." + +#: client/src/dashboard/counts/dashboard-counts.directive.js:50 +#: client/src/lists/Inventories.js:16 +#: client/src/lists/Inventories.js:17 +msgid "Inventories" +msgstr "Inventaires" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:41 +#: client/src/forms/JobTemplates.js:73 +#: client/src/forms/JobTemplates.js:86 +#: client/src/forms/WorkflowMaker.js:79 +#: client/src/forms/WorkflowMaker.js:89 +msgid "Inventory" +msgstr "Inventaire" + +#: client/src/inventory-scripts/inventory-scripts.list.js:12 +#: client/src/setup-menu/setup-menu.partial.html:34 +msgid "Inventory Scripts" +msgstr "Scripts d'inventaire" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:46 +msgid "Inventory Sync" +msgstr "Synchronisation des inventaires" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:55 +msgid "Inventory Sync Failures" +msgstr "Erreurs de synchronisation des inventaires" + +#: client/src/forms/Inventories.js:67 +msgid "Inventory Variables" +msgstr "Variables d'inventaire" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:4 +msgid "JOB STATUS" +msgstr "ÉTAT DE LA TÂCHE" + +#: client/src/forms/JobTemplates.js:23 +msgid "JOB TEMPLATE" +msgstr "MODÈLES DE TÂCHE" + +#: client/src/app.js:429 +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:113 +#: client/src/main-menu/main-menu.partial.html:122 +#: client/src/main-menu/main-menu.partial.html:43 +msgid "JOBS" +msgstr "TÂCHES" + +#: client/src/forms/JobTemplates.js:248 +#: client/src/forms/JobTemplates.js:256 +#: client/src/forms/WorkflowMaker.js:134 +#: client/src/forms/WorkflowMaker.js:142 +msgid "Job Tags" +msgstr "Balises de tâche" + +#: client/src/lists/Templates.js:65 +msgid "Job Template" +msgstr "Modèle de tâche" + +#: client/src/lists/PortalJobTemplates.js:15 +#: client/src/lists/PortalJobTemplates.js:16 +msgid "Job Templates" +msgstr "Modèles de tâche" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:32 +#: client/src/forms/JobTemplates.js:48 +#: client/src/forms/JobTemplates.js:62 +#: client/src/forms/WorkflowMaker.js:110 +#: client/src/forms/WorkflowMaker.js:99 +msgid "Job Type" +msgstr "Type de tâche" + +#: client/src/lists/PortalJobs.js:15 +#: client/src/lists/PortalJobs.js:19 +#: client/src/partials/jobs.html:7 +msgid "Jobs" +msgstr "Tâches" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:106 +msgid "LDAP" +msgstr "LDAP" + +#: client/src/main-menu/main-menu.partial.html:83 +msgid "LOG OUT" +msgstr "SE DÉCONNECTER" + +#: client/src/notifications/notificationTemplates.form.js:270 +msgid "Label to be shown with notification" +msgstr "Libellé à afficher avec la notification" + +#: client/src/forms/JobTemplates.js:340 +#: client/src/forms/JobTemplates.js:345 +#: client/src/forms/Workflows.js:60 +#: client/src/forms/Workflows.js:65 +#: client/src/lists/Templates.js:47 +msgid "Labels" +msgstr "Libellés" + +#: client/src/forms/Users.js:35 +#: client/src/lists/Users.js:45 +msgid "Last Name" +msgstr "Nom" + +#: client/src/lists/Projects.js:53 +msgid "Last Updated" +msgstr "Dernière mise à jour" + +#: client/src/lists/PortalJobTemplates.js:39 +#: client/src/lists/Templates.js:84 +#: client/src/shared/form-generator.js:1703 +msgid "Launch" +msgstr "Lancer" + +#: client/src/management-jobs/card/card.partial.html:21 +msgid "Launch Management Job" +msgstr "Lancer la tâche de gestion" + +#: client/src/license/license.controller.js:42 +#: client/src/license/license.partial.html:8 +msgid "License" +msgstr "Licence" + +#: client/src/license/license.partial.html:102 +msgid "License File" +msgstr "Fichier de licence" + +#: client/src/license/license.partial.html:33 +msgid "License Key" +msgstr "Clé de licence" + +#: client/src/license/license.controller.js:42 +msgid "License Management" +msgstr "Gestion des licences" + +#: client/src/license/license.partial.html:21 +msgid "License Type" +msgstr "Type de licence" + +#: client/src/forms/JobTemplates.js:218 +#: client/src/forms/JobTemplates.js:225 +#: client/src/forms/WorkflowMaker.js:120 +#: client/src/forms/WorkflowMaker.js:127 +msgid "Limit" +msgstr "Limite" + +#: client/src/shared/socket/socket.service.js:170 +msgid "Live events: attempting to connect to the Tower server." +msgstr "Événements en direct : tentative de connexion au serveur Tower." + +#: client/src/shared/socket/socket.service.js:174 +msgid "" +"Live events: connected. Pages containing job status information will " +"automatically update in real-time." +msgstr "" +"Événements en direct : connecté. Les pages contenant des informations sur " +"l'état de la tâche seront automatiquement mises à jour en temps réel." + +#: client/src/shared/socket/socket.service.js:178 +msgid "Live events: error connecting to the Tower server." +msgstr "Événements en direct : erreur de connexion au serveur Tower." + +#: client/src/shared/form-generator.js:1977 +msgid "Loading..." +msgstr "Chargement en cours..." + +#: client/src/main-menu/main-menu.partial.html:188 +msgid "Log Out" +msgstr "Se déconnecter" + +#: client/src/configuration/system-form/configuration-system.controller.js:82 +msgid "Logging" +msgstr "Journalisation" + +#: client/src/management-jobs/card/card.route.js:21 +msgid "MANAGEMENT JOBS" +msgstr "TÂCHES DE GESTION" + +#: client/src/forms/Credentials.js:68 +msgid "Machine" +msgstr "Machine" + +#: client/src/forms/JobTemplates.js:137 +msgid "Machine Credential" +msgstr "Informations d'identification de la machine" + +#: client/src/setup-menu/setup-menu.partial.html:29 +msgid "" +"Manage the cleanup of old job history, activity streams, data marked for " +"deletion, and system tracking info." +msgstr "" +"Gérez le nettoyage de l'historique des anciennes tâches, des flux " +"d’activité, des données marquées pour suppression et des informations de " +"suivi du système." + +#: client/src/helpers/Credentials.js:111 +msgid "Management Certificate" +msgstr "Certificat de gestion" + +#: client/src/management-jobs/card/card.partial.html:4 +#: client/src/setup-menu/setup-menu.partial.html:28 +msgid "Management Jobs" +msgstr "Tâches de gestion" + +#: client/src/controllers/Projects.js:62 +msgid "Manual projects do not require a schedule" +msgstr "Les projets manuels ne nécessitent pas de planification" + +#: client/src/controllers/Projects.js:547 +#: client/src/controllers/Projects.js:61 +msgid "Manual projects do not require an SCM update" +msgstr "Les projets manuels ne nécessitent pas de mise à jour SCM" + +#: client/src/login/loginModal/loginModal.partial.html:28 +msgid "Maximum per-user sessions reached. Please sign in." +msgstr "" +"Nombre maximum de sessions par utilisateur atteintes. Veuillez vous " +"connecter." + +#: client/src/configuration/system-form/configuration-system.controller.js:80 +msgid "Misc. System" +msgstr "Système divers" + +#: client/src/portal-mode/portal-mode-jobs.partial.html:4 +msgid "My Jobs" +msgstr "Mes tâches" + +#: client/src/main-menu/main-menu.partial.html:160 +msgid "My View" +msgstr "Ma vue" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:18 +msgid "NO HOSTS FOUND" +msgstr "AUCUN HÔTE DÉTECTÉ" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:14 +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:13 +#: client/src/forms/Credentials.js:34 +#: client/src/forms/Inventories.js:29 +#: client/src/forms/JobTemplates.js:35 +#: client/src/forms/Organizations.js:26 +#: client/src/forms/Projects.js:31 +#: client/src/forms/Teams.js:126 +#: client/src/forms/Teams.js:27 +#: client/src/forms/Users.js:139 +#: client/src/forms/Users.js:164 +#: client/src/forms/Users.js:190 +#: client/src/forms/Workflows.js:34 +#: client/src/inventory-scripts/inventory-scripts.form.js:25 +#: client/src/inventory-scripts/inventory-scripts.list.js:20 +#: client/src/lists/CompletedJobs.js:43 +#: client/src/lists/Credentials.js:29 +#: client/src/lists/Inventories.js:46 +#: client/src/lists/PortalJobTemplates.js:24 +#: client/src/lists/PortalJobs.js:32 +#: client/src/lists/Projects.js:37 +#: client/src/lists/ScheduledJobs.js:32 +#: client/src/lists/Teams.js:25 +#: client/src/lists/Templates.js:26 +#: client/src/notifications/notificationTemplates.form.js:29 +#: client/src/notifications/notificationTemplates.list.js:33 +#: client/src/notifications/notifications.list.js:26 +msgid "Name" +msgstr "Nom" + +#: client/src/forms/Credentials.js:72 +msgid "Network" +msgstr "Réseau" + +#: client/src/forms/JobTemplates.js:182 +#: client/src/forms/JobTemplates.js:193 +msgid "Network Credential" +msgstr "Informations d'identification réseau" + +#: client/src/forms/JobTemplates.js:192 +msgid "" +"Network credentials are used by Ansible networking modules to connect to and " +"manage networking devices." +msgstr "" +"Les informations d'identification sont utilisées par les modules de mise en " +"réseau d'Ansible pour connecter et gérer les périphériques réseau." + +#: client/src/inventory-scripts/inventory-scripts.form.js:16 +msgid "New Custom Inventory" +msgstr "Nouvel inventaire personnalisé" + +#: client/src/forms/Inventories.js:18 +msgid "New Inventory" +msgstr "Nouvel inventaire" + +#: client/src/forms/JobTemplates.js:20 +msgid "New Job Template" +msgstr "Nouveau modèle de tâche" + +#: client/src/notifications/notificationTemplates.form.js:16 +msgid "New Notification Template" +msgstr "Nouveau modèle de notification" + +#: client/src/forms/Organizations.js:18 +msgid "New Organization" +msgstr "Nouvelle organisation" + +#: client/src/forms/Projects.js:18 +msgid "New Project" +msgstr "Nouveau projet" + +#: client/src/forms/Teams.js:18 +msgid "New Team" +msgstr "Nouvelle équipe" + +#: client/src/forms/Users.js:18 +msgid "New User" +msgstr "Nouvel utilisateur" + +#: client/src/forms/Workflows.js:19 +msgid "New Workflow Job Template" +msgstr "Nouveau modèle de tâche Workflow" + +#: client/src/controllers/Users.js:174 +msgid "New user successfully created!" +msgstr "Création de l'utilisateur réussie" + +#: client/src/lists/ScheduledJobs.js:50 +msgid "Next Run" +msgstr "Exécution suivante" + +#: client/src/lists/Credentials.js:24 +msgid "No Credentials Have Been Created" +msgstr "Informations d'identification non créées" + +#: client/src/controllers/Projects.js:123 +msgid "No SCM Configuration" +msgstr "Aucune configuration SCM" + +#: client/src/controllers/Projects.js:114 +msgid "No Updates Available" +msgstr "Aucune mise à jour disponible" + +#: client/src/lists/CompletedJobs.js:22 +msgid "No completed jobs" +msgstr "Aucune tâche terminée" + +#: client/src/license/license.controller.js:41 +msgid "No file selected." +msgstr "Aucun fichier sélectionné." + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:46 +msgid "No jobs were recently run." +msgstr "Aucune tâche récemment exécutée." + +#: client/src/forms/Teams.js:123 +#: client/src/forms/Users.js:187 +msgid "No permissions have been granted" +msgstr "Aucune permission accordée" + +#: client/src/lists/ScheduledJobs.js:18 +msgid "No schedules exist" +msgstr "Aucune planification existante" + +#: client/src/controllers/Users.js:16 +msgid "Normal User" +msgstr "Utilisateur normal" + +#: client/src/controllers/Projects.js:64 +msgid "Not configured for SCM" +msgstr "Non configuré pour le SCM" + +#: client/src/notifications/notificationTemplates.form.js:293 +msgid "Notification Color" +msgstr "Couleur des notifications" + +#: client/src/notifications/notificationTemplates.list.js:14 +msgid "Notification Templates" +msgstr "Modèles de notification" + +#: client/src/notifications/notifications.list.js:17 +#: client/src/setup-menu/setup-menu.partial.html:41 +msgid "Notifications" +msgstr "Notifications" + +#: client/src/notifications/notificationTemplates.form.js:306 +msgid "Notify Channel" +msgstr "Canal de notification" + +#: client/src/notifications/notificationTemplates.form.js:198 +msgid "Number associated with the \"Messaging Service\" in Twilio." +msgstr "Numéro associé au \"Service de messagerie\" de Twilio." + +#: client/src/shared/form-generator.js:547 +msgid "OFF" +msgstr "DÉSACTIVÉ" + +#: client/src/shared/form-generator.js:545 +msgid "ON" +msgstr "ACTIVÉ" + +#: client/src/organizations/list/organizations-list.partial.html:6 +msgid "ORGANIZATIONS" +msgstr "ORGANISATIONS" + +#: client/src/forms/WorkflowMaker.js:45 +msgid "On Failure" +msgstr "Lors d'un échec" + +#: client/src/forms/WorkflowMaker.js:40 +msgid "On Success" +msgstr "Lors d'une réussite" + +#: client/src/forms/Credentials.js:377 +msgid "" +"OpenStack domains define administrative boundaries. It is only needed for " +"Keystone v3 authentication URLs. Common scenarios include:" +msgstr "" +"Les domaines OpenStack définissent les limites administratives. Ils sont " +"nécessaires seulement pour les URL d'authentification Keystone v3. Les " +"scénarios courants incluent :" + +#: client/src/forms/JobTemplates.js:347 +#: client/src/forms/Workflows.js:67 +msgid "" +"Optional labels that describe this job template, such as 'dev' or 'test'. " +"Labels can be used to group and filter job templates and completed jobs in " +"the Tower display." +msgstr "" +"Libellés facultatifs décrivant ce modèle de tâche, par exemple 'dev' ou " +"'test'. Les libellés peuvent être utilisés pour regrouper et filtrer les " +"modèles de tâche et les tâches terminées dans l'affichage de Tower." + +#: client/src/forms/JobTemplates.js:284 +#: client/src/notifications/notificationTemplates.form.js:391 +msgid "Options" +msgstr "Options" + +#: client/src/forms/Credentials.js:49 +#: client/src/forms/Credentials.js:55 +#: client/src/forms/Inventories.js:42 +#: client/src/forms/Projects.js:43 +#: client/src/forms/Projects.js:49 +#: client/src/forms/Teams.js:39 +#: client/src/forms/Users.js:59 +#: client/src/forms/Workflows.js:47 +#: client/src/forms/Workflows.js:53 +#: client/src/inventory-scripts/inventory-scripts.form.js:37 +#: client/src/inventory-scripts/inventory-scripts.list.js:30 +#: client/src/lists/Inventories.js:52 +#: client/src/lists/Teams.js:35 +#: client/src/notifications/notificationTemplates.form.js:41 +msgid "Organization" +msgstr "Organisation" + +#: client/src/forms/Users.js:129 +#: client/src/setup-menu/setup-menu.partial.html:4 +msgid "Organizations" +msgstr "Organisations" + +#: client/src/forms/Credentials.js:80 +msgid "Others (Cloud Providers)" +msgstr "Autres (fournisseurs cloud)" + +#: client/src/lists/Credentials.js:45 +msgid "Owners" +msgstr "Propriétaires" + +#: client/src/login/loginModal/loginModal.partial.html:68 +msgid "PASSWORD" +msgstr "MOT DE PASSE" + +#: client/src/organizations/list/organizations-list.partial.html:44 +#: client/src/shared/form-generator.js:1880 +#: client/src/shared/list-generator/list-generator.factory.js:245 +msgid "PLEASE ADD ITEMS TO THIS LIST" +msgstr "AJOUTEZ DES ÉLÉMENTS À CETTE LISTE" + +#: client/src/main-menu/main-menu.partial.html:67 +msgid "PORTAL MODE" +msgstr "MODE PORTAIL" + +#: client/src/main-menu/main-menu.partial.html:19 +#: client/src/main-menu/main-menu.partial.html:95 +msgid "PROJECTS" +msgstr "PROJETS" + +#: client/src/notifications/notificationTemplates.form.js:237 +msgid "Pagerduty subdomain" +msgstr "Sous-domaine Pagerduty" + +#: client/src/forms/JobTemplates.js:358 +#: client/src/forms/Workflows.js:78 +msgid "" +"Pass extra command line variables to the playbook. This is the %s or %s " +"command line parameter for %s. Provide key/value pairs using either YAML or " +"JSON." +msgstr "" +"Transmettez des variables de ligne de commande supplémentaires au playbook. " +"Il s'agit du paramètre de ligne de commande %s ou %s pour %s. Entrez des " +"paires clé/valeur avec la syntaxe YAML ou JSON." + +#: client/src/forms/Credentials.js:227 +#: client/src/forms/Users.js:70 +#: client/src/helpers/Credentials.js:119 +#: client/src/helpers/Credentials.js:127 +#: client/src/helpers/Credentials.js:147 +#: client/src/helpers/Credentials.js:156 +#: client/src/helpers/Credentials.js:165 +#: client/src/helpers/Credentials.js:45 +#: client/src/helpers/Credentials.js:94 +#: client/src/notifications/shared/type-change.service.js:28 +msgid "Password" +msgstr "Mot de passe" + +#: client/src/helpers/Credentials.js:73 +msgid "Password (API Key)" +msgstr "Mot de passe (clé API)" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:20 +msgid "Past 24 Hours" +msgstr "Après 24 heures" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:15 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:26 +msgid "Past Month" +msgstr "Le mois dernier" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:23 +msgid "Past Week" +msgstr "La semaine dernière" + +#: client/src/helpers/Credentials.js:102 +msgid "" +"Paste the contents of the PEM file associated with the service account email." +"" +msgstr "" +"Collez le contenu du fichier PEM associé à l'adresse électronique du compte " +"de service." + +#: client/src/helpers/Credentials.js:114 +msgid "" +"Paste the contents of the PEM file that corresponds to the certificate you " +"uploaded in the Microsoft Azure console." +msgstr "" +"Collez le contenu du fichier PEM correspondant au certificat que vous avez " +"chargé dans la console Microsoft Azure." + +#: client/src/helpers/Credentials.js:66 +msgid "Paste the contents of the SSH private key file." +msgstr "Collez le contenu du fichier de clé privée SSH." + +#: client/src/helpers/Credentials.js:41 +msgid "Paste the contents of the SSH private key file.%s or click to close%s" +msgstr "" +"Collez le contenu du fichier de clé privée SSH.%s ou cliquez pour fermer%s" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:8 +msgid "Period" +msgstr "Période" + +#: client/src/controllers/Projects.js:284 +#: client/src/controllers/Users.js:141 +msgid "Permission Error" +msgstr "Erreur de permission" + +#: client/src/forms/Credentials.js:432 +#: client/src/forms/Inventories.js:142 +#: client/src/forms/JobTemplates.js:403 +#: client/src/forms/Organizations.js:64 +#: client/src/forms/Projects.js:227 +#: client/src/forms/Workflows.js:116 +msgid "Permissions" +msgstr "Permissions" + +#: client/src/forms/JobTemplates.js:120 +#: client/src/forms/JobTemplates.js:131 +msgid "Playbook" +msgstr "Playbook" + +#: client/src/forms/Projects.js:89 +msgid "Playbook Directory" +msgstr "Répertoire de playbooks" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:52 +msgid "Playbook Run" +msgstr "Exécution du playbook" + +#: client/src/license/license.partial.html:84 +msgid "" +"Please click the button below to visit Ansible's website to get a Tower " +"license key." +msgstr "" +"Cliquez sur le bouton ci-dessous pour visiter le site Web d'Ansible afin " +"d'obtenir une clé de licence Tower." + +#: client/src/shared/form-generator.js:828 +#: client/src/shared/form-generator.js:953 +msgid "" +"Please enter a URL that begins with ssh, http or https. The URL may not " +"contain the '@' character." +msgstr "" +"Veuillez saisir une URL commençant par ssh, http ou https. L'URL ne doit pas " +"contenir le caractère '@'." + +#: client/src/shared/form-generator.js:1189 +msgid "Please enter a number greater than %d and less than %d." +msgstr "Entrez un nombre supérieur à %d et inférieur à %d." + +#: client/src/shared/form-generator.js:1191 +msgid "Please enter a number greater than %d." +msgstr "Entrez un nombre supérieur à %d." + +#: client/src/shared/form-generator.js:1183 +msgid "Please enter a number." +msgstr "Entrez un nombre." + +#: client/src/login/loginModal/loginModal.partial.html:78 +msgid "Please enter a password." +msgstr "Entrez un mot de passe." + +#: client/src/login/loginModal/loginModal.partial.html:58 +msgid "Please enter a username." +msgstr "Entrez un nom d'utilisateur." + +#: client/src/shared/form-generator.js:818 +#: client/src/shared/form-generator.js:943 +msgid "Please enter a valid email address." +msgstr "Entrez une adresse électronique valide." + +#: client/src/shared/form-generator.js:1044 +#: client/src/shared/form-generator.js:813 +#: client/src/shared/form-generator.js:938 +msgid "Please enter a value." +msgstr "Entrez une valeur." + +#: client/src/lists/CompletedJobs.js:13 +msgid "Please save and run a job to view" +msgstr "Veuillez enregistrer et exécuter une tâche à afficher" + +#: client/src/notifications/notifications.list.js:15 +msgid "Please save before adding notifications" +msgstr "Veuillez enregistrer avant d'ajouter des notifications" + +#: client/src/forms/Teams.js:69 +msgid "Please save before adding users" +msgstr "Veuillez enregistrer avant d'ajouter des utilisateurs" + +#: client/src/forms/Inventories.js:138 +#: client/src/forms/Inventories.js:91 +#: client/src/forms/JobTemplates.js:396 +#: client/src/forms/Organizations.js:57 +#: client/src/forms/Projects.js:219 +#: client/src/forms/Teams.js:110 +#: client/src/forms/Workflows.js:109 +msgid "Please save before assigning permissions" +msgstr "Veuillez enregistrer avant d'attribuer des permissions" + +#: client/src/forms/Users.js:122 +#: client/src/forms/Users.js:179 +msgid "Please save before assigning to organizations" +msgstr "Veuillez enregistrer avant l'attribution à des organisations" + +#: client/src/forms/Users.js:148 +msgid "Please save before assigning to teams" +msgstr "Veuillez enregistrer avant l'attribution à des équipes" + +#: client/src/forms/Workflows.js:185 +msgid "Please save before defining the workflow graph" +msgstr "Veuillez enregistrer avant de définir le graphique du workflow" + +#: client/src/forms/WorkflowMaker.js:65 +msgid "Please select a Credential." +msgstr "Sélectionnez des informations d'identification." + +#: client/src/forms/JobTemplates.js:150 +msgid "" +"Please select a Machine Credential or check the Prompt on launch option." +msgstr "" +"Sélectionnez les informations d'identification de la machine ou cochez " +"l'option Me le demander au lancement." + +#: client/src/shared/form-generator.js:1224 +msgid "Please select a number between" +msgstr "Sélectionnez un nombre compris entre" + +#: client/src/shared/form-generator.js:1220 +msgid "Please select a number." +msgstr "Sélectionnez un nombre." + +#: client/src/shared/form-generator.js:1111 +#: client/src/shared/form-generator.js:1180 +#: client/src/shared/form-generator.js:1298 +#: client/src/shared/form-generator.js:1403 +msgid "Please select a value." +msgstr "Sélectionnez une valeur." + +#: client/src/forms/JobTemplates.js:83 +msgid "Please select an Inventory or check the Prompt on launch option." +msgstr "" +"Sélectionnez un inventaire ou cochez l'option Me le demander au lancement." + +#: client/src/forms/WorkflowMaker.js:86 +msgid "Please select an Inventory." +msgstr "Sélectionnez un inventaire." + +#: client/src/shared/form-generator.js:1217 +msgid "Please select at least one value." +msgstr "Sélectionnez une valeur au moins." + +#: client/src/notifications/shared/type-change.service.js:27 +msgid "Port" +msgstr "Port" + +#: client/src/forms/Credentials.js:257 +#: client/src/helpers/Credentials.js:36 +#: client/src/helpers/Credentials.js:60 +msgid "Private Key" +msgstr "Clé privée" + +#: client/src/forms/Credentials.js:264 +msgid "Private Key Passphrase" +msgstr "Phrase de passe pour la clé privée" + +#: client/src/forms/Credentials.js:279 +#: client/src/forms/Credentials.js:283 +msgid "Privilege Escalation" +msgstr "Élévation des privilèges" + +#: client/src/helpers/Credentials.js:90 +msgid "Privilege Escalation Password" +msgstr "Mot de passe pour l'élévation des privilèges" + +#: client/src/helpers/Credentials.js:89 +msgid "Privilege Escalation Username" +msgstr "Nom d'utilisateur pour l'élévation des privilèges" + +#: client/src/forms/JobTemplates.js:114 +#: client/src/forms/JobTemplates.js:97 +#: client/src/helpers/Credentials.js:103 +msgid "Project" +msgstr "Projet" + +#: client/src/helpers/Credentials.js:132 +msgid "Project (Tenant Name)" +msgstr "Projet (nom du client)" + +#: client/src/forms/Projects.js:75 +#: client/src/forms/Projects.js:83 +msgid "Project Base Path" +msgstr "Chemin de base du projet" + +#: client/src/forms/Credentials.js:363 +msgid "Project Name" +msgstr "Nom du projet" + +#: client/src/forms/Projects.js:100 +msgid "Project Path" +msgstr "Chemin du projet" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:66 +msgid "Project Sync Failures" +msgstr "Erreurs de synchronisation du projet" + +#: client/src/controllers/Projects.js:134 +msgid "Project lookup failed. GET returned:" +msgstr "La recherche de projet n'a pas abouti. GET renvoyé :" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:61 +#: client/src/lists/Projects.js:16 +#: client/src/lists/Projects.js:17 +msgid "Projects" +msgstr "Projets" + +#: client/src/forms/JobTemplates.js:159 +#: client/src/forms/JobTemplates.js:230 +#: client/src/forms/JobTemplates.js:261 +#: client/src/forms/JobTemplates.js:279 +#: client/src/forms/JobTemplates.js:369 +#: client/src/forms/JobTemplates.js:68 +#: client/src/forms/JobTemplates.js:92 +msgid "Prompt on launch" +msgstr "Me le demander au lancement" + +#: client/src/forms/JobTemplates.js:253 +#: client/src/forms/JobTemplates.js:271 +#: client/src/forms/WorkflowMaker.js:139 +#: client/src/forms/WorkflowMaker.js:154 +msgid "Provide a comma separated list of tags." +msgstr "Entrez une liste de balises séparées par des virgules." + +#: client/src/forms/JobTemplates.js:221 +#: client/src/forms/WorkflowMaker.js:123 +msgid "" +"Provide a host pattern to further constrain the list of hosts that will be " +"managed or affected by the playbook. Multiple patterns can be separated by " +"%s %s or %s" +msgstr "" +"Entrez un modèle d'hôte pour limiter davantage la liste des hôtes qui seront " +"gérés ou attribués par le playbook. Plusieurs modèles peuvent être séparés " +"par des % s% s ou des % s" + +#: client/src/forms/JobTemplates.js:313 +#: client/src/forms/JobTemplates.js:321 +msgid "Provisioning Callback URL" +msgstr "URL de rappel d'exécution de Tower job_template" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:107 +msgid "RADIUS" +msgstr "RADIUS" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:4 +msgid "RECENT JOB RUNS" +msgstr "RÉCENTES EXÉCUTIONS DE TÂCHE" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:42 +msgid "RECENTLY RUN JOBS" +msgstr "TÂCHES RÉCEMMENT EXÉCUTÉES" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:4 +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:52 +msgid "RECENTLY USED JOB TEMPLATES" +msgstr "MODÈLES DE TÂCHE RÉCEMMENT UTILISÉS" + +#: client/src/lists/Projects.js:76 +#: client/src/partials/jobs.html:15 +#: client/src/portal-mode/portal-mode-jobs.partial.html:12 +msgid "REFRESH" +msgstr "ACTUALISER" + +#: client/src/forms/JobTemplates.js:99 +msgid "RESET" +msgstr "RÉINITIALISER" + +#: client/src/helpers/Credentials.js:98 +msgid "RSA Private Key" +msgstr "Clé privée RSA" + +#: client/src/notifications/notificationTemplates.form.js:94 +#: client/src/notifications/notificationTemplates.form.js:99 +msgid "Recipient List" +msgstr "Liste de destinataires" + +#: client/src/bread-crumb/bread-crumb.partial.html:6 +#: client/src/lists/Projects.js:72 +msgid "Refresh the page" +msgstr "Actualiser la page" + +#: client/src/lists/CompletedJobs.js:75 +msgid "Relaunch using the same parameters" +msgstr "Relancer en utilisant les mêmes paramètres" + +#: client/src/forms/Teams.js:144 +#: client/src/forms/Users.js:214 +msgid "Remove" +msgstr "Supprimer" + +#: client/src/forms/Projects.js:153 +msgid "Remove any local modifications prior to performing an update." +msgstr "" +"Supprimez toutes les modifications locales avant d'effectuer une mise à jour." +"" + +#: client/src/license/license.partial.html:89 +msgid "Request License" +msgstr "Demander une licence" + +#: client/src/configuration/auth-form/sub-forms/auth-azure.form.js:41 +#: client/src/configuration/auth-form/sub-forms/auth-github-org.form.js:31 +#: client/src/configuration/auth-form/sub-forms/auth-github-team.form.js:31 +#: client/src/configuration/auth-form/sub-forms/auth-github.form.js:27 +#: client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js:39 +#: client/src/configuration/auth-form/sub-forms/auth-ldap.form.js:87 +#: client/src/configuration/auth-form/sub-forms/auth-radius.form.js:32 +#: client/src/configuration/auth-form/sub-forms/auth-saml.form.js:59 +#: client/src/configuration/jobs-form/configuration-jobs.form.js:67 +#: client/src/configuration/system-form/configuration-system.form.js:41 +#: client/src/configuration/system-form/sub-forms/system-activity-stream.form.js:25 +#: client/src/configuration/system-form/sub-forms/system-logging.form.js:50 +#: client/src/configuration/system-form/sub-forms/system-misc.form.js:29 +#: client/src/configuration/ui-form/configuration-ui.form.js:35 +msgid "Reset All" +msgstr "Tout réinitialiser" + +#: client/src/lists/Projects.js:42 +msgid "Revision" +msgstr "Révision" + +#: client/src/controllers/Projects.js:657 +msgid "Revision #" +msgstr "Révision n°" + +#: client/src/forms/Credentials.js:454 +#: client/src/forms/Inventories.js:120 +#: client/src/forms/Inventories.js:166 +#: client/src/forms/Organizations.js:88 +#: client/src/forms/Projects.js:249 +#: client/src/forms/Teams.js:137 +#: client/src/forms/Teams.js:99 +#: client/src/forms/Users.js:201 +msgid "Role" +msgstr "Rôle" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:108 +msgid "SAML" +msgstr "SAML" + +#: client/src/controllers/Projects.js:657 +msgid "SCM Branch" +msgstr "Branche SCM" + +#: client/src/forms/Projects.js:154 +msgid "SCM Clean" +msgstr "Nettoyage SCM" + +#: client/src/forms/Projects.js:130 +msgid "SCM Credential" +msgstr "Information d'identification SCM" + +#: client/src/forms/Projects.js:165 +msgid "SCM Delete" +msgstr "Suppression SCM" + +#: client/src/helpers/Credentials.js:93 +msgid "SCM Private Key" +msgstr "Clé privée SCM" + +#: client/src/forms/Projects.js:56 +msgid "SCM Type" +msgstr "Type SCM" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:49 +#: client/src/forms/Projects.js:175 +msgid "SCM Update" +msgstr "Mise à jour SCM" + +#: client/src/controllers/Projects.js:176 +msgid "SCM Update Cancel" +msgstr "Annulation de la mise à jour SCM" + +#: client/src/forms/Projects.js:145 +msgid "SCM Update Options" +msgstr "Options de mise à jour SCM" + +#: client/src/controllers/Projects.js:543 +#: client/src/controllers/Projects.js:57 +msgid "SCM update currently running" +msgstr "Mise à jour SCM en cours" + +#: client/src/main-menu/main-menu.partial.html:59 +msgid "SETTINGS" +msgstr "PARAMÈTRES" + +#: client/src/login/loginModal/loginModal.partial.html:97 +msgid "SIGN IN" +msgstr "SE CONNECTER" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 +msgid "SIGN IN WITH" +msgstr "SE CONNECTER AVEC" + +#: client/src/app.js:513 +msgid "SOCKETS" +msgstr "SOCKETS" + +#: client/src/helpers/Credentials.js:166 +msgid "SSH Key" +msgstr "Clé SSH" + +#: client/src/forms/Credentials.js:255 +msgid "SSH key description" +msgstr "Description de la clé SSH" + +#: client/src/notifications/notificationTemplates.form.js:384 +msgid "SSL Connection" +msgstr "Connexion SSL" + +#: client/src/forms/Credentials.js:120 +#: client/src/forms/Credentials.js:128 +msgid "STS Token" +msgstr "Token STS" + +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:64 +msgid "SUCCESSFUL" +msgstr "RÉUSSI" + +#: client/src/helpers/Credentials.js:149 +msgid "Satellite 6 Host" +msgstr "Hôte Satellite 6" + +#: client/src/shared/form-generator.js:1687 +msgid "Save" +msgstr "Enregistrer" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:81 +#: client/src/configuration/configuration.controller.js:181 +#: client/src/configuration/configuration.controller.js:240 +#: client/src/configuration/system-form/configuration-system.controller.js:60 +msgid "Save changes" +msgstr "Enregistrer les modifications" + +#: client/src/license/license.partial.html:122 +msgid "Save successful!" +msgstr "Enregistrement réussi" + +#: client/src/lists/Templates.js:92 +msgid "Schedule" +msgstr "Planifier" + +#: client/src/management-jobs/card/card.partial.html:26 +msgid "Schedule Management Job" +msgstr "Planifier la tâche de gestion" + +#: client/src/controllers/Projects.js:49 +msgid "Schedule future SCM updates" +msgstr "Planifier les prochaines mises à jour SCM" + +#: client/src/lists/Templates.js:95 +msgid "Schedule future job template runs" +msgstr "Planifier les prochaines exécutions de modèle de tâche" + +#: client/src/lists/ScheduledJobs.js:15 +msgid "Scheduled Jobs" +msgstr "Tâches planifiées" + +#: client/src/partials/jobs.html:10 +msgid "Schedules" +msgstr "Calendriers" + +#: client/src/inventory-scripts/inventory-scripts.form.js:59 +msgid "Script must begin with a hashbang sequence: i.e.... %s" +msgstr "Le script doit commencer par une séquence hashbang : c.-à-d. ....%s" + +#: client/src/forms/Credentials.js:105 +msgid "Secret Key" +msgstr "Clé secrète" + +#: client/src/forms/Credentials.js:125 +msgid "" +"Security Token Service (STS) is a web service that enables you to request " +"temporary, limited-privilege credentials for AWS Identity and Access " +"Management (IAM) users." +msgstr "" +"Le service de jeton de sécurité (STS) est un service Web qui permet de " +"demander des informations d'identification provisoires avec des privilèges " +"limités pour les utilisateurs d'AWS Identity and Access Management (IAM)." + +#: client/src/shared/form-generator.js:1691 +msgid "Select" +msgstr "Sélectionner" + +#: client/src/configuration/jobs-form/configuration-jobs.controller.js:87 +#: client/src/configuration/ui-form/configuration-ui.controller.js:82 +msgid "Select commands" +msgstr "Sélectionner des commandes" + +#: client/src/forms/Projects.js:98 +msgid "" +"Select from the list of directories found in the Project Base Path. Together " +"the base path and the playbook directory provide the full path used to " +"locate playbooks." +msgstr "" +"Faites une sélection à partir de la liste des répertoires trouvés dans le " +"chemin de base du projet. Le chemin de base et le répertoire de playbook " +"fournissent ensemble le chemin complet servant à localiser les playbooks." + +#: client/src/configuration/auth-form/configuration-auth.controller.js:226 +msgid "Select group types" +msgstr "Sélectionner des types de groupe" + +#: client/src/forms/JobTemplates.js:152 +#: client/src/forms/WorkflowMaker.js:67 +msgid "" +"Select the credential you want the job to use when accessing the remote " +"hosts. Choose the credential containing the username and SSH key or " +"password that Ansible will need to log into the remote hosts." +msgstr "" +"Sélectionnez les informations d'identification que la tâche doit utiliser " +"lors de l'accès à des hôtes distants. Choisissez les informations " +"d'identification contenant le nom d'utilisateur et la clé SSH ou le mot de " +"passe dont Ansible aura besoin pour se connecter aux hôtes distants." + +#: client/src/forms/JobTemplates.js:85 +#: client/src/forms/WorkflowMaker.js:88 +msgid "Select the inventory containing the hosts you want this job to manage." +msgstr "" +"Sélectionnez l'inventaire contenant les hôtes que vous souhaitez gérer." + +#: client/src/forms/JobTemplates.js:130 +msgid "Select the playbook to be executed by this job." +msgstr "Sélectionnez le playbook qui devra être exécuté par cette tâche." + +#: client/src/forms/JobTemplates.js:113 +msgid "" +"Select the project containing the playbook you want this job to execute." +msgstr "" +"Sélectionnez le projet contenant le playbook que cette tâche devra exécuter." + +#: client/src/configuration/system-form/configuration-system.controller.js:167 +msgid "Select types" +msgstr "Sélectionner des types" + +#: client/src/forms/JobTemplates.js:174 +msgid "" +"Selecting an optional cloud credential in the job template will pass along " +"the access credentials to the running playbook, allowing provisioning into " +"the cloud without manually passing parameters to the included modules." +msgstr "" +"La sélection d'informations identification cloud facultatives dans le modèle " +"de tâche transmettra les informations d'identification d'accès au playbook " +"en cours d'exécution, ce qui permet une authentification dans le cloud sans " +"transmettre manuellement les paramètres aux modules inclus." + +#: client/src/notifications/notificationTemplates.form.js:83 +msgid "Sender Email" +msgstr "Adresse électronique de l'expéditeur" + +#: client/src/helpers/Credentials.js:97 +msgid "Service Account Email Address" +msgstr "Adresse électronique du compte de service" + +#: client/src/forms/JobTemplates.js:60 +#: client/src/forms/WorkflowMaker.js:108 +msgid "" +"Setting the type to %s will execute the playbook and store any scanned " +"facts for use with Tower's System Tracking feature." +msgstr "" +"La définition du type sur %s exécute le playbook et stocke tous les faits " +"scannés à utiliser avec la fonctionnalité de suivi System Tracking de Tower." + +#: client/src/forms/JobTemplates.js:57 +msgid "Setting the type to %s will not execute the playbook." +msgstr "La définition du type sur %s n'exécute pas le playbook." + +#: client/src/forms/WorkflowMaker.js:106 +msgid "" +"Setting the type to %s will not execute the playbook. Instead, %s will check " +"playbook syntax, test environment setup and report problems." +msgstr "" +"La définition du type sur %s n'exécute pas le playbook. À la place, %s " +"vérifie la syntaxe du playbook, teste la configuration de l'environnement et " +"signale les problèmes." + +#: client/src/main-menu/main-menu.partial.html:147 +msgid "Settings" +msgstr "Paramètres" + +#: client/src/shared/form-generator.js:843 +msgid "Show" +msgstr "Afficher" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:34 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:45 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:56 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:77 +msgid "Sign in with %s" +msgstr "Se connecter avec %s" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:64 +msgid "Sign in with %s Organizations" +msgstr "Se connecter avec des organisations %s" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:62 +msgid "Sign in with %s Teams" +msgstr "Se connecter avec des équipes %s" + +#: client/src/forms/JobTemplates.js:266 +#: client/src/forms/JobTemplates.js:274 +#: client/src/forms/WorkflowMaker.js:149 +#: client/src/forms/WorkflowMaker.js:157 +msgid "Skip Tags" +msgstr "Balises de saut" + +#: client/src/forms/JobTemplates.js:272 +#: client/src/forms/WorkflowMaker.js:155 +msgid "" +"Skip tags are useful when you have a large playbook, and you want to skip " +"specific parts of a play or task." +msgstr "" +"Les balises de saut sont utiles si votre playbook est important et que vous " +"souhaitez ignorer certaines parties d'une scène ou d'une tâche." + +#: client/src/forms/Credentials.js:76 +msgid "Source Control" +msgstr "Contrôle de la source" + +#: client/src/forms/Projects.js:27 +msgid "Source Details" +msgstr "Détails de la source" + +#: client/src/notifications/notificationTemplates.form.js:196 +msgid "Source Phone Number" +msgstr "Numéro de téléphone de la source" + +#: client/src/notifications/notificationTemplates.form.js:332 +msgid "Specify HTTP Headers in JSON format" +msgstr "Spécifier les en-têtes HTTP au format JSON" + +#: client/src/forms/Credentials.js:285 +msgid "" +"Specify a method for %s operations. This is equivalent to specifying the %s " +"parameter, where %s could be %s" +msgstr "" +"Spécifiez une méthode pour les opérations %s. Cela équivaut à définir le " +"paramètre %s, où %s peut être %s" + +#: client/src/setup-menu/setup-menu.partial.html:17 +msgid "" +"Split up your organization to associate content and control permissions for " +"groups." +msgstr "" +"Divisez votre organisation afin d'associer du contenu et des permissions de " +"contrôle pour les groupes." + +#: client/src/lists/PortalJobTemplates.js:42 +#: client/src/lists/Templates.js:87 +msgid "Start a job using this template" +msgstr "Démarrer une tâche avec ce modèle" + +#: client/src/controllers/Projects.js:48 +#: client/src/controllers/Projects.js:540 +msgid "Start an SCM update" +msgstr "Démarrer une mise à jour SCM" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:49 +msgid "Status" +msgstr "État" + +#: client/src/license/license.partial.html:121 +msgid "Submit" +msgstr "Valider" + +#: client/src/license/license.partial.html:27 +msgid "Subscription" +msgstr "Abonnement" + +#: client/src/forms/Credentials.js:152 +#: client/src/forms/Credentials.js:163 +msgid "Subscription ID" +msgstr "ID d'abonnement" + +#: client/src/forms/Credentials.js:162 +msgid "Subscription ID is an Azure construct, which is mapped to a username." +msgstr "" +"L'ID d'abonnement est une construction Azure mappée à un nom d'utilisateur." + +#: client/src/notifications/notifications.list.js:38 +msgid "Success" +msgstr "Réussite" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:77 +msgid "Successful" +msgstr "Réussi" + +#: client/src/controllers/Users.js:18 +msgid "System Administrator" +msgstr "Administrateur système" + +#: client/src/controllers/Users.js:17 +msgid "System Auditor" +msgstr "Auditeur système" + +#: client/src/app.js:341 +msgid "TEAMS" +msgstr "ÉQUIPES" + +#: client/src/main-menu/main-menu.partial.html:113 +#: client/src/main-menu/main-menu.partial.html:35 +msgid "TEMPLATES" +msgstr "MODÈLES" + +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:106 +msgid "TIME" +msgstr "DURÉE" + +#: client/src/forms/JobTemplates.js:254 +#: client/src/forms/WorkflowMaker.js:140 +msgid "" +"Tags are useful when you have a large playbook, and you want to run a " +"specific part of a play or task." +msgstr "" +"Les balises sont utiles si votre playbook est important et que vous " +"souhaitez exécuter une partie donnée d'une scène ou d'une tâche." + +#: client/src/notifications/notificationTemplates.form.js:313 +msgid "Target URL" +msgstr "URL cible" + +#: client/src/forms/Credentials.js:461 +#: client/src/forms/Inventories.js:126 +#: client/src/forms/Inventories.js:173 +#: client/src/forms/Organizations.js:95 +#: client/src/forms/Projects.js:255 +msgid "Team Roles" +msgstr "Rôles d'équipe" + +#: client/src/forms/Users.js:155 +#: client/src/lists/Teams.js:16 +#: client/src/lists/Teams.js:17 +#: client/src/setup-menu/setup-menu.partial.html:16 +msgid "Teams" +msgstr "Équipes" + +#: client/src/lists/Templates.js:16 +msgid "Template" +msgstr "Modèle" + +#: client/src/lists/Templates.js:17 +#: client/src/lists/Templates.js:18 +msgid "Templates" +msgstr "Modèles" + +#: client/src/forms/Credentials.js:335 +msgid "Tenant ID" +msgstr "ID Client" + +#: client/src/notifications/notificationTemplates.list.js:65 +msgid "Test notification" +msgstr "Notification test" + +#: client/src/shared/form-generator.js:1409 +msgid "That value was not found. Please enter or select a valid value." +msgstr "" +"Cette valeur n'a pas été trouvée. Veuillez entrer ou sélectionner une valeur " +"valide." + +#: client/src/helpers/Credentials.js:105 +msgid "" +"The Project ID is the GCE assigned identification. It is constructed as two " +"words followed by a three digit number. Such as:" +msgstr "" +"L'ID du projet est l'identifiant attribué par GCE. Il se compose de deux " +"mots suivis d'un nombre à trois chiffres. Exemple :" + +#: client/src/controllers/Projects.js:693 +msgid "The SCM update process is running." +msgstr "Le processus de mise à jour SCM est en cours d'exécution." + +#: client/src/forms/Credentials.js:191 +msgid "" +"The email address assigned to the Google Compute Engine %sservice account." +msgstr "" +"Adresse électronique attribuée au compte de service Google Compute Engine %s." +"" + +#: client/src/helpers/Credentials.js:141 +msgid "The host to authenticate with." +msgstr "Hôte avec lequel s'authentifier." + +#: client/src/helpers/Credentials.js:75 +msgid "The host value" +msgstr "Valeur de l'hôte" + +#: client/src/forms/JobTemplates.js:208 +msgid "" +"The number of parallel or simultaneous processes to use while executing the " +"playbook. 0 signifies the default value from the %sansible configuration " +"file%s." +msgstr "" +"Nombre de processus parallèles ou simultanés à utiliser lors de l'exécution " +"du playbook. 0 indique la valeur par défaut du %sfichier de configuration " +"ansible%s." + +#: client/src/helpers/Credentials.js:74 +msgid "The project value" +msgstr "Valeur du projet" + +#: client/src/controllers/Projects.js:123 +msgid "" +"The selected project is not configured for SCM. To configure for SCM, edit " +"the project and provide SCM settings, and then run an update." +msgstr "" +"Le projet sélectionné n'est pas configuré pour SCM. Afin de le configurer " +"pour SCM, modifiez le projet et définissez les paramètres SCM, puis lancez " +"une mise à jour." + +#: client/src/lists/PortalJobTemplates.js:20 +msgid "There are no job templates to display at this time" +msgstr "Aucun modèle de tâche à afficher pour le moment" + +#: client/src/lists/PortalJobs.js:20 +msgid "There are no jobs to display at this time" +msgstr "Aucune tâche à afficher pour le moment" + +#: client/src/controllers/Projects.js:114 +msgid "" +"There is no SCM update information available for this project. An update has " +"not yet been completed. If you have not already done so, start an update " +"for this project." +msgstr "" +"Aucune information de mise à jour SCM n'est disponible pour ce projet. Une " +"mise à jour n'est pas encore terminée. Si vous n'avez pas encore lancé une " +"mise à jour pour ce projet, faites-le." + +#: client/src/configuration/configuration.controller.js:293 +msgid "There was an error resetting value. Returned status:" +msgstr "" +"Une erreur s'est produite lors de la réinitialisation de la valeur. État " +"renvoyé :" + +#: client/src/configuration/configuration.controller.js:424 +msgid "There was an error resetting values. Returned status:" +msgstr "" +"Une erreur s'est produite lors de la réinitialisation des valeurs. État " +"renvoyé :" + +#: client/src/helpers/Credentials.js:138 +msgid "" +"This is the tenant name. This value is usually the same as the username." +msgstr "" +"Il s'agit du nom du client. Cette valeur est habituellement la même que " +"celle du nom d'utilisateur." + +#: client/src/notifications/notifications.list.js:21 +msgid "" +"This list is populated by notification templates added from the " +"%sNotifications%s section" +msgstr "" +"Cette liste contient des modèles de notification ajoutés à partir de la " +"section %sNotifications%s" + +#: client/src/notifications/notificationTemplates.form.js:199 +msgid "This must be of the form %s." +msgstr "Elle doit se présenter au format %s." + +#: client/src/forms/Users.js:160 +msgid "This user is not a member of any teams" +msgstr "Cet utilisateur n'est pas membre d'une équipe" + +#: client/src/shared/form-generator.js:823 +#: client/src/shared/form-generator.js:948 +msgid "" +"This value does not match the password you entered previously. Please " +"confirm that password." +msgstr "" +"Cette valeur ne correspond pas au mot de passe que vous avez entré " +"précédemment. Veuillez confirmer ce mot de passe." + +#: client/src/configuration/configuration.controller.js:449 +msgid "" +"This will reset all configuration values to their factory defaults. Are you " +"sure you want to proceed?" +msgstr "" +"Cette opération rétablit toutes les valeurs de configuration sur leurs " +"valeurs par défaut. Voulez-vous vraiment continuer ?" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:14 +msgid "Time" +msgstr "Durée" + +#: client/src/license/license.partial.html:45 +msgid "Time Remaining" +msgstr "Durée restante" + +#: client/src/forms/Projects.js:191 +msgid "" +"Time in seconds to consider a project to be current. During job runs and " +"callbacks the task system will evaluate the timestamp of the latest project " +"update. If it is older than Cache Timeout, it is not considered current, and " +"a new project update will be performed." +msgstr "" +"Délai en secondes à prévoir pour qu'un projet soit actualisé. Durant " +"l'exécution des tâches et les rappels, le système de tâches évalue " +"l'horodatage de la dernière mise à jour du projet. Si elle est plus ancienne " +"que le délai d'expiration du cache, elle n'est pas considérée comme " +"actualisée, et une nouvelle mise à jour du projet sera effectuée." + +#: client/src/forms/Credentials.js:126 +msgid "" +"To learn more about the IAM STS Token, refer to the %sAmazon documentation%s." +"" +msgstr "" +"Pour en savoir plus sur le token STS d'IAM, reportez-vous à la documentation " +"d'%Amazon%s." + +#: client/src/shared/form-generator.js:848 +msgid "Toggle the display of plaintext." +msgstr "Bascule l'affichage du texte en clair." + +#: client/src/notifications/shared/type-change.service.js:34 +#: client/src/notifications/shared/type-change.service.js:40 +msgid "Token" +msgstr "Token" + +#: client/src/forms/Credentials.js:61 +#: client/src/forms/Credentials.js:85 +#: client/src/forms/Teams.js:132 +#: client/src/forms/Users.js:196 +#: client/src/forms/WorkflowMaker.js:34 +#: client/src/lists/CompletedJobs.js:50 +#: client/src/lists/Credentials.js:39 +#: client/src/lists/Projects.js:48 +#: client/src/lists/ScheduledJobs.js:42 +#: client/src/lists/Templates.js:31 +#: client/src/notifications/notificationTemplates.form.js:54 +#: client/src/notifications/notificationTemplates.list.js:38 +#: client/src/notifications/notifications.list.js:31 +msgid "Type" +msgstr "Type" + +#: client/src/forms/Credentials.js:25 +#: client/src/notifications/notificationTemplates.form.js:23 +msgid "Type Details" +msgstr "Détails sur le type" + +#: client/src/notifications/notificationTemplates.form.js:212 +#: client/src/notifications/notificationTemplates.form.js:97 +msgid "Type an option on each line." +msgstr "Tapez une option sur chaque ligne." + +#: client/src/notifications/notificationTemplates.form.js:141 +#: client/src/notifications/notificationTemplates.form.js:158 +#: client/src/notifications/notificationTemplates.form.js:370 +msgid "Type an option on each line. The pound symbol (#) is not required." +msgstr "" +"Tapez une option sur chaque ligne. Le symbole dièse (#) n'est pas nécessaire." +"" + +#: client/src/controllers/Projects.js:402 +#: client/src/controllers/Projects.js:684 +msgid "URL popover text" +msgstr "Texte popover de l'URL" + +#: client/src/login/loginModal/loginModal.partial.html:49 +msgid "USERNAME" +msgstr "NOM D'UTILISATEUR" + +#: client/src/app.js:365 +msgid "USERS" +msgstr "UTILISATEURS" + +#: client/src/controllers/Projects.js:220 +msgid "Update Not Found" +msgstr "Mise à jour introuvable" + +#: client/src/controllers/Projects.js:693 +msgid "Update in Progress" +msgstr "Mise à jour en cours" + +#: client/src/forms/Projects.js:172 +msgid "Update on Launch" +msgstr "Mettre à jour au lancement" + +#: client/src/license/license.partial.html:71 +msgid "Upgrade" +msgstr "Mettre à niveau" + +#: client/src/notifications/notificationTemplates.form.js:404 +msgid "Use SSL" +msgstr "Utiliser SSL" + +#: client/src/notifications/notificationTemplates.form.js:397 +msgid "Use TLS" +msgstr "Utiliser TLS" + +#: client/src/forms/Credentials.js:77 +msgid "" +"Used to check out and synchronize playbook repositories with a remote source " +"control management system such as Git, Subversion (svn), or Mercurial (hg). " +"These credentials are used by Projects." +msgstr "" +"Utilisé pour vérifier et synchroniser les référentiels de playbooks avec un " +"SCM à distance tel que Git, Subversion (svn) ou Mercurial (hg). Ces " +"informations d'identification sont utilisées par les Projets." + +#: client/src/forms/Credentials.js:449 +#: client/src/forms/Inventories.js:115 +#: client/src/forms/Inventories.js:161 +#: client/src/forms/Organizations.js:83 +#: client/src/forms/Projects.js:244 +#: client/src/forms/Teams.js:94 +msgid "User" +msgstr "Utilisateur" + +#: client/src/forms/Users.js:94 +msgid "User Type" +msgstr "Type d'utilisateur" + +#: client/src/forms/Users.js:49 +#: client/src/helpers/Credentials.js:117 +#: client/src/helpers/Credentials.js:32 +#: client/src/helpers/Credentials.js:56 +#: client/src/helpers/Credentials.js:88 +#: client/src/lists/Users.js:37 +#: client/src/notifications/notificationTemplates.form.js:64 +msgid "Username" +msgstr "Nom d'utilisateur" + +#: client/src/forms/Credentials.js:81 +msgid "" +"Usernames, passwords, and access keys for authenticating to the specified " +"cloud or infrastructure provider. These are used for dynamic inventory " +"sources and for cloud provisioning and deployment in playbook runs." +msgstr "" +"Noms d'utilisateur, mots de passe et clés d'accès pour s'authentifier auprès " +"du fournisseur de cloud ou d'infrastructure spécifié. Ceux-ci sont utilisés " +"pour les sources d'inventaire dynamique et pour l'authentification de " +"services dans le cloud et leur déploiement dans les playbooks." + +#: client/src/forms/Teams.js:75 +#: client/src/lists/Users.js:26 +#: client/src/lists/Users.js:27 +#: client/src/setup-menu/setup-menu.partial.html:10 +msgid "Users" +msgstr "Utilisateurs" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:7 +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:7 +msgid "VIEW ALL" +msgstr "TOUT AFFICHER" + +#: client/src/main-menu/main-menu.partial.html:75 +msgid "VIEW DOCUMENTATION" +msgstr "AFFICHER LA DOCUMENTATION" + +#: client/src/main-menu/main-menu.partial.html:51 +msgid "VIEW USER PAGE FOR {{ $root.current_user.username | uppercase }}" +msgstr "" +"AFFICHER LA PAGE UTILISATEUR POUR {{ $root.current_user.username | uppercase " +"}}" + +#: client/src/license/license.partial.html:10 +msgid "Valid License" +msgstr "Licence valide" + +#: client/src/forms/Inventories.js:55 +msgid "Variables" +msgstr "Variables" + +#: client/src/forms/Credentials.js:389 +msgid "Vault Password" +msgstr "Mot de passe Vault" + +#: client/src/forms/JobTemplates.js:235 +#: client/src/forms/JobTemplates.js:242 +msgid "Verbosity" +msgstr "Verbosité" + +#: client/src/about/about.controller.js:24 +#: client/src/license/license.partial.html:15 +msgid "Version" +msgstr "Version" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:58 +#: client/src/inventory-scripts/inventory-scripts.list.js:65 +#: client/src/lists/Credentials.js:80 +#: client/src/lists/Inventories.js:85 +#: client/src/lists/Teams.js:69 +#: client/src/lists/Templates.js:117 +#: client/src/lists/Users.js:78 +#: client/src/notifications/notificationTemplates.list.js:80 +msgid "View" +msgstr "Afficher" + +#: client/src/main-menu/main-menu.partial.html:173 +msgid "View Documentation" +msgstr "Afficher la documentation" + +#: client/src/forms/Inventories.js:65 +msgid "View JSON examples at %s" +msgstr "Afficher les exemples JSON à %s" + +#: client/src/forms/JobTemplates.js:450 +#: client/src/forms/Workflows.js:163 +#: client/src/shared/form-generator.js:1715 +msgid "View Survey" +msgstr "Afficher le questionnaire" + +#: client/src/forms/Inventories.js:66 +msgid "View YAML examples at %s" +msgstr "Afficher les exemples YAML à %s" + +#: client/src/setup-menu/setup-menu.partial.html:47 +msgid "View Your License" +msgstr "Afficher votre licence" + +#: client/src/setup-menu/setup-menu.partial.html:48 +msgid "View and edit your license information." +msgstr "Affichez et modifiez vos informations de licence." + +#: client/src/lists/Credentials.js:82 +msgid "View credential" +msgstr "Afficher les informations d'identification" + +#: client/src/setup-menu/setup-menu.partial.html:60 +msgid "View information about this version of Ansible Tower." +msgstr "Afficher les informations sur cette version d'Ansible Tower." + +#: client/src/lists/Inventories.js:87 +msgid "View inventory" +msgstr "Afficher l'inventaire" + +#: client/src/inventory-scripts/inventory-scripts.list.js:67 +msgid "View inventory script" +msgstr "Afficher le script d'inventaire" + +#: client/src/notifications/notificationTemplates.list.js:82 +msgid "View notification" +msgstr "Afficher la notification" + +#: client/src/lists/Teams.js:72 +msgid "View team" +msgstr "Afficher l'équipe" + +#: client/src/lists/Templates.js:119 +msgid "View template" +msgstr "Afficher le modèle" + +#: client/src/lists/Projects.js:108 +msgid "View the project" +msgstr "Afficher le projet" + +#: client/src/lists/ScheduledJobs.js:73 +msgid "View the schedule" +msgstr "Afficher le calendrier" + +#: client/src/lists/Users.js:81 +msgid "View user" +msgstr "Afficher l'utilisateur" + +#: client/src/forms/Workflows.js:22 +msgid "WORKFLOW" +msgstr "WORKFLOW" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:68 +#: client/src/configuration/configuration.controller.js:168 +#: client/src/configuration/configuration.controller.js:230 +#: client/src/configuration/system-form/configuration-system.controller.js:47 +msgid "Warning: Unsaved Changes" +msgstr "Avertissement : modifications non enregistrées" + +#: client/src/login/loginModal/loginModal.partial.html:17 +msgid "Welcome to Ansible Tower!  Please sign in." +msgstr "Bienvenue à Ansible Tower !  Veuillez vous connecter." + +#: client/src/license/license.partial.html:78 +msgid "" +"Welcome to Ansible Tower! Please complete the steps below to acquire a " +"license." +msgstr "" +"Bienvenue à Ansible Tower ! Veuillez suivre les étapes ci-dessous pour " +"obtenir une licence." + +#: client/src/forms/JobTemplates.js:55 +#: client/src/forms/WorkflowMaker.js:104 +msgid "" +"When this template is submitted as a job, setting the type to %s will " +"execute the playbook, running tasks on the selected hosts." +msgstr "" +"Lorsque ce modèle est validé en tant que tâche, le fait de définir le type " +"sur %s exécute le playbook en lançant les tâches sur les hôtes sélectionnés." + +#: client/src/forms/Workflows.js:187 +#: client/src/shared/form-generator.js:1719 +msgid "Workflow Editor" +msgstr "Workflow Editor" + +#: client/src/lists/Templates.js:70 +msgid "Workflow Job Template" +msgstr "Modèle de tâche Workflow" + +#: client/src/controllers/Projects.js:468 +msgid "You do not have access to view this property" +msgstr "Vous n'avez pas d'accès pour afficher cette propriété" + +#: client/src/controllers/Projects.js:284 +msgid "You do not have permission to add a project." +msgstr "Vous n'êtes pas autorisé à ajouter un projet." + +#: client/src/controllers/Users.js:141 +msgid "You do not have permission to add a user." +msgstr "Vous n'êtes pas autorisé à ajouter un utilisateur." + +#: client/src/configuration/auth-form/configuration-auth.controller.js:67 +#: client/src/configuration/configuration.controller.js:167 +#: client/src/configuration/configuration.controller.js:229 +#: client/src/configuration/system-form/configuration-system.controller.js:46 +msgid "" +"You have unsaved changes. Would you like to proceed without " +"saving?" +msgstr "" +"Des modifications n'ont pas été enregistrées. Voulez-vous continuer " +"sans les enregistrer ?" + +#: client/src/shared/form-generator.js:960 +msgid "Your password must be %d characters long." +msgstr "Votre mot de passe doit comporter %d caractères." + +#: client/src/shared/form-generator.js:965 +msgid "Your password must contain a lowercase letter." +msgstr "Votre mot de passe doit contenir une lettre minuscule." + +#: client/src/shared/form-generator.js:975 +msgid "Your password must contain a number." +msgstr "Votre mot de passe doit contenir un chiffre." + +#: client/src/shared/form-generator.js:970 +msgid "Your password must contain an uppercase letter." +msgstr "Votre mot de passe doit contenir une lettre majuscule." + +#: client/src/shared/form-generator.js:980 +msgid "Your password must contain one of the following characters: %s" +msgstr "Votre mot de passe doit contenir l'un des caractères suivants : %s" + +#: client/src/controllers/Projects.js:176 +msgid "Your request to cancel the update was submitted to the task manager." +msgstr "" +"Votre demande d'annulation de la mise à jour a été envoyée au gestionnaire " +"de tâches." + +#: client/src/login/loginModal/loginModal.partial.html:22 +msgid "Your session timed out due to inactivity. Please sign in." +msgstr "" +"Votre session a expiré en raison d'un temps d'inactivité. Veuillez vous " +"connecter." + +#: client/src/shared/form-generator.js:1224 +msgid "and" +msgstr "et" + +#: client/src/forms/Credentials.js:139 +#: client/src/forms/Credentials.js:362 +msgid "set in helpers/credentials" +msgstr "définir dans helpers/credentials" + +#: client/src/forms/Credentials.js:379 +msgid "v2 URLs%s - leave blank" +msgstr "v2 URLs%s - laisser vide" + +#: client/src/forms/Credentials.js:380 +msgid "v3 default%s - set to 'default'" +msgstr "v3 default%s - définir sur 'default'" + +#: client/src/forms/Credentials.js:381 +msgid "v3 multi-domain%s - your domain name" +msgstr "v3 multi-domain%s - votre nom de domaine" + diff --git a/awx/ui/po/ja.po b/awx/ui/po/ja.po new file mode 100644 index 0000000000..31e33bf0d2 --- /dev/null +++ b/awx/ui/po/ja.po @@ -0,0 +1,2803 @@ +# asasaki , 2017. #zanata +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Project-Id-Version: \n" +"MIME-Version: 1.0\n" +"PO-Revision-Date: 2017-01-10 10:27+0000\n" +"Last-Translator: asasaki \n" +"Language-Team: Japanese\n" +"Language: ja\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=1; plural=0\n" + +#: client/src/notifications/notificationTemplates.form.js:371 +msgid "%s or %s" +msgstr "%s または %s" + +#: client/src/controllers/Projects.js:397 +#: client/src/controllers/Projects.js:679 +msgid "" +"%sNote:%s Mercurial does not support password authentication for SSH. Do not " +"put the username and key in the URL. If using Bitbucket and SSH, do not " +"supply your Bitbucket username." +msgstr "" +"%s注:%s Mercurial は SSH のパスワード認証をサポートしません。ユーザー名およびキーを URL " +"に入れないでください。Bitbucket および SSH を使用している場合は、Bitbucket ユーザー名を入力しないでください。" + +#: client/src/controllers/Projects.js:384 +#: client/src/controllers/Projects.js:666 +msgid "" +"%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key " +"only, do not enter a username (other than git). Additionally, GitHub and " +"Bitbucket do not support password authentication when using SSH. GIT read " +"only protocol (git://) does not use username or password information." +msgstr "" +"%s注:%s GitHub または Bitbucket の SSH プロトコルを使用している場合、SSH キーのみを入力し、ユーザー名 (git 以外) " +"を入力しないでください。また、GitHub および Bitbucket は、SSH の使用時のパスワード認証をサポートしません。GIT " +"の読み取り専用プロトコル (git://) はユーザー名またはパスワード情報を使用しません。" + +#: client/src/forms/Credentials.js:287 +msgid "(defaults to %s)" +msgstr "(%s にデフォルト設定)" + +#: client/src/organizations/list/organizations-list.partial.html:15 +msgid "+ ADD" +msgstr "+ 追加" + +#: client/src/controllers/Users.js:185 +msgid "A value is required" +msgstr "値が必要" + +#: client/src/forms/Credentials.js:442 +#: client/src/forms/Inventories.js:153 +#: client/src/forms/JobTemplates.js:414 +#: client/src/forms/Organizations.js:75 +#: client/src/forms/Projects.js:237 +#: client/src/forms/Teams.js:86 +#: client/src/forms/Workflows.js:127 +#: client/src/inventory-scripts/inventory-scripts.list.js:45 +#: client/src/lists/Credentials.js:59 +#: client/src/lists/Inventories.js:68 +#: client/src/lists/Projects.js:67 +#: client/src/lists/Teams.js:50 +#: client/src/lists/Templates.js:62 +#: client/src/lists/Users.js:58 +#: client/src/notifications/notificationTemplates.list.js:52 +msgid "ADD" +msgstr "追加" + +#: client/src/notifications/notifications.list.js:68 +msgid "ADD NOTIFICATION TEMPLATE" +msgstr "通知テンプレートの追加" + +#: client/src/forms/Credentials.js:199 +msgid "API Key" +msgstr "API キー" + +#: client/src/notifications/notificationTemplates.form.js:248 +msgid "API Service/Integration Key" +msgstr "API サービス/統合キー" + +#: client/src/notifications/shared/type-change.service.js:52 +msgid "API Token" +msgstr "API トークン" + +#: client/src/setup-menu/setup-menu.partial.html:59 +msgid "About Tower" +msgstr "Tower について" + +#: client/src/forms/Credentials.js:92 +msgid "Access Key" +msgstr "アクセスキー" + +#: client/src/notifications/notificationTemplates.form.js:226 +msgid "Account SID" +msgstr "アカウント SID" + +#: client/src/notifications/notificationTemplates.form.js:184 +msgid "Account Token" +msgstr "アカウントトークン" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:20 +#: client/src/shared/list-generator/list-generator.factory.js:538 +msgid "Actions" +msgstr "アクション" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:17 +#: client/src/lists/Templates.js:40 +msgid "Activity" +msgstr "アクティビティー" + +#: client/src/configuration/system-form/configuration-system.controller.js:81 +msgid "Activity Stream" +msgstr "アクティビティーストリーム" + +#: client/src/forms/Inventories.js:104 +#: client/src/forms/Inventories.js:150 +#: client/src/forms/Organizations.js:72 +#: client/src/forms/Teams.js:83 +#: client/src/forms/Workflows.js:124 +msgid "Add" +msgstr "追加" + +#: client/src/lists/Credentials.js:17 +msgid "Add Credentials" +msgstr "認証情報の追加" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:12 +msgid "Add Existing Hosts" +msgstr "既存ホストの追加" + +#: client/src/lists/Inventories.js:15 +msgid "Add Inventories" +msgstr "インベントリーの追加" + +#: client/src/notifications/notifications.list.js:63 +msgid "Add Notification" +msgstr "通知の追加" + +#: client/src/lists/Projects.js:15 +msgid "Add Project" +msgstr "プロジェクトの追加" + +#: client/src/forms/JobTemplates.js:459 +#: client/src/forms/Workflows.js:172 +#: client/src/shared/form-generator.js:1707 +msgid "Add Survey" +msgstr "Survey の追加" + +#: client/src/lists/Teams.js:15 +msgid "Add Team" +msgstr "チームの追加" + +#: client/src/lists/Users.js:25 +msgid "Add Users" +msgstr "ユーザーの追加" + +#: client/src/forms/Credentials.js:440 +#: client/src/forms/Inventories.js:151 +#: client/src/forms/JobTemplates.js:412 +#: client/src/forms/Organizations.js:73 +#: client/src/forms/Projects.js:235 +msgid "Add a permission" +msgstr "パーミッションの追加" + +#: client/src/setup-menu/setup-menu.partial.html:23 +msgid "" +"Add passwords, SSH keys, etc. for Tower to use when launching jobs against " +"machines, or when syncing inventories or projects." +msgstr "" +"マシンに対してジョブを起動する際やインベントリーまたはプロジェクトの同期時に使用する Tower のパスワード、SSH キーなどを追加します。" + +#: client/src/forms/Teams.js:84 +msgid "Add user to team" +msgstr "ユーザーのチームへの追加" + +#: client/src/shared/form-generator.js:1450 +msgid "Admin" +msgstr "管理者" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:37 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:43 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:65 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:74 +msgid "All" +msgstr "すべて" + +#: client/src/portal-mode/portal-mode-jobs.partial.html:7 +msgid "All Jobs" +msgstr "すべてのジョブ" + +#: client/src/forms/JobTemplates.js:299 +#: client/src/forms/JobTemplates.js:306 +msgid "Allow Provisioning Callbacks" +msgstr "プロビジョニングコールバックの許可" + +#: client/src/setup-menu/setup-menu.partial.html:11 +msgid "Allow others to sign into Tower and own the content they create." +msgstr "他のユーザーが Tower にサインインし、独自に作成するコンテンツを所有できるようにします。" + +#: client/src/forms/WorkflowMaker.js:50 +msgid "Always" +msgstr "常時" + +#: client/src/controllers/Projects.js:220 +msgid "" +"An SCM update does not appear to be running for project: %s. Click the " +"%sRefresh%s button to view the latest status." +msgstr "SCM 更新がプロジェクトに対して実行されていないようです: %s。%s更新%s ボタンをクリックして最新のステータスを表示してください。" + +#: client/src/controllers/Projects.js:162 +msgid "Are you sure you want to delete the project below?" +msgstr "以下のプロジェクトを削除してもよろしいですか?" + +#: client/src/controllers/Users.js:102 +msgid "Are you sure you want to delete the user below?" +msgstr "以下のユーザーを削除してもよろしいですか?" + +#: client/src/controllers/Projects.js:647 +msgid "Are you sure you want to remove the %s below from %s?" +msgstr "%s から以下の %s を削除してもよろしいですか?" + +#: client/src/forms/Credentials.js:233 +#: client/src/forms/Credentials.js:271 +#: client/src/forms/Credentials.js:310 +#: client/src/forms/Credentials.js:395 +msgid "Ask at runtime?" +msgstr "実行時に確認しますか?" + +#: client/src/shared/form-generator.js:1452 +msgid "Auditor" +msgstr "監査者" + +#: client/src/forms/Credentials.js:73 +msgid "" +"Authentication for network device access. This can include SSH keys, " +"usernames, passwords, and authorize information. Network credentials are " +"used when submitting jobs to run playbooks against network devices." +msgstr "" +"ネットワークデバイスアクセスの認証です。これには、SSH " +"キー、ユーザー名、パスワード、および承認情報が含まれることがあります。ネットワーク認証情報は、ネットワークデバイスに対して Playbook " +"を実行するためのジョブの送信時に使用されます。" + +#: client/src/forms/Credentials.js:69 +msgid "" +"Authentication for remote machine access. This can include SSH keys, " +"usernames, passwords, and sudo information. Machine credentials are used " +"when submitting jobs to run playbooks against remote hosts." +msgstr "" +"リモートマシンアクセスの認証です。これには、SSH キー、ユーザー名、パスワード、および sudo " +"情報が含まれることがあります。マシン認証情報は、リモートホストに対して Playbook を実行するためのジョブの送信時に使用されます。" + +#: client/src/forms/Credentials.js:341 +msgid "Authorize" +msgstr "承認" + +#: client/src/forms/Credentials.js:349 +msgid "Authorize Password" +msgstr "パスワードの承認" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:101 +msgid "Azure AD" +msgstr "Azure AD" + +#: client/src/forms/Projects.js:80 +msgid "" +"Base path used for locating playbooks. Directories found inside this path " +"will be listed in the playbook directory drop-down. Together the base path " +"and selected playbook directory provide the full path used to locate " +"playbooks." +msgstr "" +"Playbook を見つけるために使用されるベースパスです。このパス内にあるディレクトリーは Playbook " +"ディレクトリーのドロップダウンに一覧表示されます。ベースパスと選択された Playbook ディレクトリーは、Playbook " +"を見つけるために使用される完全なパスを提供します。" + +#: client/src/forms/JobTemplates.js:293 +msgid "Become Privilege Escalation" +msgstr "Become (権限昇格)" + +#: client/src/license/license.partial.html:104 +msgid "Browse" +msgstr "参照" + +#: client/src/app.js:317 +msgid "CREDENTIALS" +msgstr "認証情報" + +#: client/src/forms/Projects.js:194 +msgid "Cache Timeout" +msgstr "キャッシュタイムアウト" + +#: client/src/forms/Projects.js:183 +msgid "Cache Timeout%s (seconds)%s" +msgstr "キャッシュタイムアウト%s (seconds)%s" + +#: client/src/controllers/Projects.js:156 +#: client/src/controllers/Users.js:95 +msgid "Call to %s failed. DELETE returned status:" +msgstr "%s の呼び出しに失敗しました。DELETE で返されたステータス:" + +#: client/src/controllers/Projects.js:201 +#: client/src/controllers/Projects.js:217 +msgid "Call to %s failed. GET status:" +msgstr "%s の呼び出しに失敗しました。GET ステータス:" + +#: client/src/controllers/Projects.js:641 +msgid "Call to %s failed. POST returned status:" +msgstr "%s の呼び出しに失敗しました。POST で返されたステータス:" + +#: client/src/controllers/Projects.js:180 +msgid "Call to %s failed. POST status:" +msgstr "%s の呼び出しに失敗しました。POST ステータス:" + +#: client/src/controllers/Projects.js:226 +msgid "Call to get project failed. GET status:" +msgstr "プロジェクトを取得するための呼び出しに失敗しました。GET ステータス:" + +#: client/src/configuration/configuration.controller.js:434 +#: client/src/shared/form-generator.js:1695 +msgid "Cancel" +msgstr "取り消し" + +#: client/src/controllers/Projects.js:196 +msgid "Cancel Not Allowed" +msgstr "取り消しは許可されていません" + +#: client/src/lists/Projects.js:121 +msgid "Cancel the SCM update" +msgstr "SCM 更新の取り消し" + +#: client/src/controllers/Projects.js:53 +msgid "Canceled. Click for details" +msgstr "取り消されました。クリックして詳細を確認してください。" + +#: client/src/forms/Projects.js:82 +msgid "Change %s under \"Configure Tower\" to change this location." +msgstr "この場所を変更するために「Tower の設定」下の %s を変更します。" + +#: client/src/shared/form-generator.js:1084 +msgid "Choose a %s" +msgstr "%s の選択" + +#: client/src/license/license.partial.html:97 +msgid "" +"Choose your license file, agree to the End User License Agreement, and click " +"submit." +msgstr "ライセンスファイルを選択し、使用許諾契約書に同意した後に「送信」をクリックします。" + +#: client/src/forms/Projects.js:151 +msgid "Clean" +msgstr "クリーニング" + +#: client/src/lists/Inventories.js:18 +msgid "" +"Click on a row to select it, and click Finished when done. Click the %s " +"button to create a new inventory." +msgstr "行をクリックしてこれを選択し、終了したら「終了」をクリックします。%s ボタンをクリックして新規インベントリーを作成します。" + +#: client/src/lists/Teams.js:18 +msgid "" +"Click on a row to select it, and click Finished when done. Click the %s " +"button to create a new team." +msgstr "行をクリックしてこれを選択し、終了したら「終了」をクリックします。%s ボタンをクリックして新規チームを作成します。" + +#: client/src/lists/Templates.js:19 +msgid "" +"Click on a row to select it, and click Finished when done. Use the %s button " +"to create a new job template." +msgstr "行をクリックしてこれを選択し、終了したら「終了」をクリックします。%s ボタンをクリックして新規ジョブテンプレートを作成します。" + +#: client/src/forms/Credentials.js:319 +msgid "Client ID" +msgstr "クライアント ID" + +#: client/src/notifications/notificationTemplates.form.js:259 +msgid "Client Identifier" +msgstr "クライアント識別子" + +#: client/src/forms/Credentials.js:328 +msgid "Client Secret" +msgstr "クライアントシークレット" + +#: client/src/shared/form-generator.js:1699 +msgid "Close" +msgstr "閉じる" + +#: client/src/forms/JobTemplates.js:164 +#: client/src/forms/JobTemplates.js:176 +msgid "Cloud Credential" +msgstr "クラウド認証情報" + +#: client/src/helpers/Credentials.js:158 +msgid "CloudForms Host" +msgstr "CloudForms ホスト" + +#: client/src/notifications/notificationTemplates.form.js:295 +msgid "Color can be one of %s." +msgstr "色を %s のいずれかにすることができます。" + +#: client/src/lists/CompletedJobs.js:18 +msgid "Completed Jobs" +msgstr "完了したジョブ" + +#: client/src/management-jobs/card/card.partial.html:32 +msgid "Configure Notifications" +msgstr "通知の設定" + +#: client/src/forms/Users.js:82 +msgid "Confirm Password" +msgstr "パスワードの確認" + +#: client/src/configuration/configuration.controller.js:441 +msgid "Confirm Reset" +msgstr "リセットの確認" + +#: client/src/configuration/configuration.controller.js:450 +msgid "Confirm factory reset" +msgstr "出荷時の設定へのリセットの確認" + +#: client/src/forms/JobTemplates.js:255 +#: client/src/forms/JobTemplates.js:273 +#: client/src/forms/WorkflowMaker.js:141 +#: client/src/forms/WorkflowMaker.js:156 +msgid "" +"Consult the Ansible documentation for further details on the usage of tags." +msgstr "タグの使用法についての詳細は、Ansible ドキュメントを参照してください。" + +#: client/src/forms/JobTemplates.js:241 +msgid "" +"Control the level of output ansible will produce as the playbook executes." +msgstr "Playbook の実行時に Ansible が生成する出力のレベルを制御します。" + +#: client/src/lists/Templates.js:100 +msgid "Copy" +msgstr "コピー" + +#: client/src/lists/Templates.js:103 +msgid "Copy template" +msgstr "テンプレートのコピー" + +#: client/src/forms/Credentials.js:18 +msgid "Create Credential" +msgstr "認証情報の作成" + +#: client/src/lists/Users.js:52 +msgid "Create New" +msgstr "新規作成" + +#: client/src/lists/Credentials.js:57 +msgid "Create a new credential" +msgstr "新規認証情報の作成" + +#: client/src/inventory-scripts/inventory-scripts.list.js:43 +msgid "Create a new custom inventory" +msgstr "新規カスタムインベントリーの作成" + +#: client/src/lists/Inventories.js:66 +msgid "Create a new inventory" +msgstr "新規インベントリーの作成" + +#: client/src/notifications/notificationTemplates.list.js:50 +#: client/src/notifications/notifications.list.js:66 +msgid "Create a new notification template" +msgstr "新規通知テンプレートの作成" + +#: client/src/organizations/list/organizations-list.partial.html:16 +msgid "Create a new organization" +msgstr "新規組織の作成" + +#: client/src/lists/Projects.js:65 +msgid "Create a new project" +msgstr "新規プロジェクトの作成" + +#: client/src/lists/Teams.js:48 +msgid "Create a new team" +msgstr "新規チームの作成" + +#: client/src/lists/Templates.js:60 +msgid "Create a new template" +msgstr "新規テンプレートの作成" + +#: client/src/lists/Users.js:56 +msgid "Create a new user" +msgstr "新規ユーザーの作成" + +#: client/src/setup-menu/setup-menu.partial.html:35 +msgid "Create and edit scripts to dynamically load hosts from any source." +msgstr "任意のソースからホストを動的にロードするためのスクリプトを作成し、編集します。" + +#: client/src/setup-menu/setup-menu.partial.html:42 +msgid "" +"Create templates for sending notifications with Email, HipChat, Slack, and " +"SMS." +msgstr "メール、HipChat、Slack、および SMS での通知を送信するためのテンプレートを作成します。" + +#: client/src/forms/JobTemplates.js:154 +#: client/src/forms/WorkflowMaker.js:60 +#: client/src/forms/WorkflowMaker.js:69 +msgid "Credential" +msgstr "認証情報" + +#: client/src/lists/Credentials.js:18 +#: client/src/lists/Credentials.js:19 +#: client/src/setup-menu/setup-menu.partial.html:22 +msgid "Credentials" +msgstr "認証情報" + +#: client/src/inventory-scripts/inventory-scripts.form.js:50 +#: client/src/inventory-scripts/inventory-scripts.form.js:60 +msgid "Custom Script" +msgstr "カスタムスクリプト" + +#: client/src/app.js:409 +msgid "DASHBOARD" +msgstr "ダッシュボード" + +#: client/src/controllers/Projects.js:649 +#: client/src/controllers/Users.js:104 +msgid "DELETE" +msgstr "削除" + +#: client/src/controllers/Projects.js:161 +#: client/src/controllers/Projects.js:646 +#: client/src/controllers/Users.js:101 +#: client/src/inventory-scripts/inventory-scripts.list.js:74 +#: client/src/lists/Credentials.js:90 +#: client/src/lists/Inventories.js:92 +#: client/src/lists/Teams.js:77 +#: client/src/lists/Templates.js:125 +#: client/src/lists/Users.js:87 +#: client/src/notifications/notificationTemplates.list.js:89 +msgid "Delete" +msgstr "削除" + +#: client/src/lists/Credentials.js:92 +msgid "Delete credential" +msgstr "認証情報の削除" + +#: client/src/lists/Inventories.js:94 +msgid "Delete inventory" +msgstr "インベントリーの削除" + +#: client/src/inventory-scripts/inventory-scripts.list.js:76 +msgid "Delete inventory script" +msgstr "インベントリースクリプトの削除" + +#: client/src/notifications/notificationTemplates.list.js:91 +msgid "Delete notification" +msgstr "通知の削除" + +#: client/src/forms/Projects.js:161 +msgid "Delete on Update" +msgstr "更新時のデプロイ" + +#: client/src/lists/Teams.js:81 +msgid "Delete team" +msgstr "チームの削除" + +#: client/src/lists/Templates.js:128 +msgid "Delete template" +msgstr "テンプレートの削除" + +#: client/src/lists/CompletedJobs.js:82 +msgid "Delete the job" +msgstr "ジョブの削除" + +#: client/src/forms/Projects.js:163 +msgid "" +"Delete the local repository in its entirety prior to performing an update." +msgstr "更新の実行前にローカルリポジトリーを完全に削除します。" + +#: client/src/lists/Projects.js:115 +msgid "Delete the project" +msgstr "プロジェクトの削除" + +#: client/src/lists/ScheduledJobs.js:80 +msgid "Delete the schedule" +msgstr "スケジュールの削除" + +#: client/src/lists/Users.js:91 +msgid "Delete user" +msgstr "ユーザーの削除" + +#: client/src/forms/Projects.js:163 +msgid "" +"Depending on the size of the repository this may significantly increase the " +"amount of time required to complete an update." +msgstr "リポジトリーのサイズにより、更新の完了までに必要な時間が大幅に長くなる可能性があります。" + +#: client/src/forms/Credentials.js:41 +#: client/src/forms/Inventories.js:37 +#: client/src/forms/JobTemplates.js:42 +#: client/src/forms/Organizations.js:33 +#: client/src/forms/Projects.js:38 +#: client/src/forms/Teams.js:34 +#: client/src/forms/Users.js:142 +#: client/src/forms/Users.js:167 +#: client/src/forms/Workflows.js:41 +#: client/src/inventory-scripts/inventory-scripts.form.js:32 +#: client/src/inventory-scripts/inventory-scripts.list.js:25 +#: client/src/lists/Credentials.js:34 +#: client/src/lists/PortalJobTemplates.js:29 +#: client/src/lists/Teams.js:30 +#: client/src/lists/Templates.js:36 +#: client/src/notifications/notificationTemplates.form.js:36 +msgid "Description" +msgstr "説明" + +#: client/src/notifications/notificationTemplates.form.js:138 +#: client/src/notifications/notificationTemplates.form.js:143 +#: client/src/notifications/notificationTemplates.form.js:155 +#: client/src/notifications/notificationTemplates.form.js:160 +#: client/src/notifications/notificationTemplates.form.js:372 +msgid "Destination Channels" +msgstr "送信先チャネル" + +#: client/src/notifications/notificationTemplates.form.js:367 +msgid "Destination Channels or Users" +msgstr "送信先チャネルまたはユーザー" + +#: client/src/notifications/notificationTemplates.form.js:209 +#: client/src/notifications/notificationTemplates.form.js:214 +msgid "Destination SMS Number" +msgstr "送信先 SMS 番号" + +#: client/src/license/license.partial.html:5 +#: client/src/shared/form-generator.js:1481 +msgid "Details" +msgstr "詳細" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:70 +#: client/src/configuration/configuration.controller.js:170 +#: client/src/configuration/configuration.controller.js:232 +#: client/src/configuration/system-form/configuration-system.controller.js:49 +msgid "Discard changes" +msgstr "変更の破棄" + +#: client/src/forms/Teams.js:148 +msgid "Dissasociate permission from team" +msgstr "チームからパーミッションの関連付けを解除" + +#: client/src/forms/Users.js:217 +msgid "Dissasociate permission from user" +msgstr "ユーザーからパーミッションの関連付けを解除" + +#: client/src/forms/Credentials.js:382 +#: client/src/helpers/Credentials.js:133 +msgid "Domain Name" +msgstr "ドメイン名" + +#: client/src/inventory-scripts/inventory-scripts.form.js:58 +msgid "" +"Drag and drop your custom inventory script file here or create one in the " +"field to import your custom inventory." +msgstr "" +"カスタムインベントリーのスクリプトファイルをここにドラッグアンドドロップするか、またはこのフィールドにカスタムインベントリーをインポートするためのファイルを作成します。" + +#: client/src/forms/Projects.js:174 +msgid "" +"Each time a job runs using this project, perform an update to the local " +"repository prior to starting the job." +msgstr "このプロジェクトでジョブを実行する際は常に、ジョブの開始前にローカルリポジトリーに対して更新を実行します。" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:63 +#: client/src/inventory-scripts/inventory-scripts.list.js:57 +#: client/src/lists/Credentials.js:71 +#: client/src/lists/Inventories.js:78 +#: client/src/lists/Teams.js:60 +#: client/src/lists/Templates.js:108 +#: client/src/lists/Users.js:68 +#: client/src/notifications/notificationTemplates.list.js:63 +#: client/src/notifications/notificationTemplates.list.js:72 +msgid "Edit" +msgstr "編集" + +#: client/src/forms/JobTemplates.js:466 +#: client/src/forms/Workflows.js:179 +#: client/src/shared/form-generator.js:1711 +msgid "Edit Survey" +msgstr "Survey の編集" + +#: client/src/lists/Credentials.js:73 +msgid "Edit credential" +msgstr "認証情報の編集" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:66 +msgid "Edit host" +msgstr "ホストの編集" + +#: client/src/lists/Inventories.js:80 +msgid "Edit inventory" +msgstr "インベントリーの編集" + +#: client/src/inventory-scripts/inventory-scripts.list.js:59 +msgid "Edit inventory script" +msgstr "インベントリースクリプトの編集" + +#: client/src/notifications/notificationTemplates.list.js:74 +msgid "Edit notification" +msgstr "通知の編集" + +#: client/src/lists/Teams.js:64 +msgid "Edit team" +msgstr "チームの編集" + +#: client/src/lists/Templates.js:110 +msgid "Edit template" +msgstr "テンプレートの編集" + +#: client/src/lists/Projects.js:102 +msgid "Edit the project" +msgstr "プロジェクトの編集" + +#: client/src/lists/ScheduledJobs.js:66 +msgid "Edit the schedule" +msgstr "スケジュールの編集" + +#: client/src/lists/Users.js:72 +msgid "Edit user" +msgstr "ユーザーの編集" + +#: client/src/controllers/Projects.js:196 +msgid "" +"Either you do not have access or the SCM update process completed. Click the " +"%sRefresh%s button to view the latest status." +msgstr "アクセスがないか、または SCM 更新プロセスが完了しました。%s更新%s ボタンをクリックして最新のステータスを表示します。" + +#: client/src/forms/Credentials.js:192 +#: client/src/forms/Users.js:42 +msgid "Email" +msgstr "メール" + +#: client/src/forms/JobTemplates.js:288 +msgid "Enable Privilege Escalation" +msgstr "権限昇格の有効化" + +#: client/src/forms/JobTemplates.js:303 +msgid "" +"Enables creation of a provisioning callback URL. Using the URL a host can " +"contact Tower and request a configuration update using this job template." +msgstr "" +"プロビジョニングコールバック URL の作成を有効にします。この URL を使用してホストは Tower " +"に接続でき、このジョブテンプレートを使用して接続の更新を要求できます。" + +#: client/src/helpers/Credentials.js:306 +msgid "Encrypted credentials are not supported." +msgstr "暗号化された認証情報はサポートされていません。" + +#: client/src/license/license.partial.html:108 +msgid "End User License Agreement" +msgstr "使用許諾契約書" + +#: client/src/forms/Inventories.js:60 +msgid "" +"Enter inventory variables using either JSON or YAML syntax. Use the radio " +"button to toggle between the two." +msgstr "" +"JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用して 2 つの間の切り替えを行います。" + +#: client/src/helpers/Credentials.js:159 +msgid "" +"Enter the hostname or IP address for the virtual %s machine which is hosting " +"the CloudForm appliance." +msgstr "CloudForm アプライアンスをホストしている仮想%sマシンのホスト名または IP アドレスを入力します。" + +#: client/src/helpers/Credentials.js:150 +msgid "" +"Enter the hostname or IP address name which %scorresponds to your Red Hat " +"Satellite 6 server." +msgstr "Red Hat Satellite 6 サーバーに対応するホスト名%sまたは IP アドレスを入力します。" + +#: client/src/helpers/Credentials.js:128 +msgid "" +"Enter the hostname or IP address which corresponds to your VMware vCenter." +msgstr "VMware vCenter に対応するホスト名または IP アドレスを入力します。" + +#: client/src/configuration/configuration.controller.js:292 +#: client/src/configuration/configuration.controller.js:370 +#: client/src/configuration/configuration.controller.js:404 +#: client/src/configuration/configuration.controller.js:423 +#: client/src/controllers/Projects.js:133 +#: client/src/controllers/Projects.js:155 +#: client/src/controllers/Projects.js:180 +#: client/src/controllers/Projects.js:201 +#: client/src/controllers/Projects.js:216 +#: client/src/controllers/Projects.js:225 +#: client/src/controllers/Projects.js:363 +#: client/src/controllers/Projects.js:557 +#: client/src/controllers/Projects.js:623 +#: client/src/controllers/Projects.js:641 +#: client/src/controllers/Users.js:182 +#: client/src/controllers/Users.js:267 +#: client/src/controllers/Users.js:321 +#: client/src/controllers/Users.js:94 +#: client/src/helpers/Credentials.js:310 +#: client/src/helpers/Credentials.js:326 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:119 +msgid "Error!" +msgstr "エラー!" + +#: client/src/controllers/Projects.js:381 +#: client/src/controllers/Projects.js:664 +msgid "Example URLs for GIT SCM include:" +msgstr "GIT SCM のサンプル URL には以下が含まれます:" + +#: client/src/controllers/Projects.js:394 +#: client/src/controllers/Projects.js:676 +msgid "Example URLs for Mercurial SCM include:" +msgstr "Mercurial SCM のサンプル URL には以下が含まれます:" + +#: client/src/controllers/Projects.js:389 +#: client/src/controllers/Projects.js:671 +msgid "Example URLs for Subversion SCM include:" +msgstr "Subversion SCM のサンプル URL には以下が含まれます:" + +#: client/src/license/license.partial.html:39 +msgid "Expires On" +msgstr "有効期限" + +#: client/src/forms/JobTemplates.js:352 +#: client/src/forms/JobTemplates.js:364 +#: client/src/forms/Workflows.js:72 +#: client/src/forms/Workflows.js:84 +msgid "Extra Variables" +msgstr "追加変数" + +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:67 +msgid "FAILED" +msgstr "失敗" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:80 +msgid "Failed" +msgstr "失敗" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:44 +msgid "Failed Hosts" +msgstr "失敗したホスト" + +#: client/src/controllers/Users.js:182 +msgid "Failed to add new user. POST returned status:" +msgstr "新規ユーザーを追加できませんでした。POST で返されたステータス:" + +#: client/src/helpers/Credentials.js:311 +msgid "Failed to create new Credential. POST status:" +msgstr "新規の認証情報を作成できませんでした。POST ステータス:" + +#: client/src/controllers/Projects.js:364 +msgid "Failed to create new project. POST returned status:" +msgstr "新規プロジェクトを作成できませんでした。POST で返されたステータス:" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:120 +msgid "Failed to get third-party login types. Returned status:" +msgstr "サードパーティーのログインタイプを取得できませんでした。返されたステータス:" + +#: client/src/controllers/Projects.js:558 +msgid "Failed to retrieve project: %s. GET status:" +msgstr "プロジェクトを取得できませんでした: %s. GET ステータス:" + +#: client/src/controllers/Users.js:268 +#: client/src/controllers/Users.js:322 +msgid "Failed to retrieve user: %s. GET status:" +msgstr "ユーザーを取得できませんでした: %s. GET ステータス:" + +#: client/src/configuration/configuration.controller.js:371 +msgid "Failed to save settings. Returned status:" +msgstr "設定を保存できませんでした。返されたステータス:" + +#: client/src/configuration/configuration.controller.js:405 +msgid "Failed to save toggle settings. Returned status:" +msgstr "トグルの設定を保存できませんでした。返されたステータス:" + +#: client/src/helpers/Credentials.js:327 +msgid "Failed to update Credential. PUT status:" +msgstr "認証情報を更新できませんでした。PUT ステータス:" + +#: client/src/controllers/Projects.js:623 +msgid "Failed to update project: %s. PUT status:" +msgstr "プロジェクトを更新できませんでした: %s. PUT ステータス:" + +#: client/src/notifications/notifications.list.js:49 +msgid "Failure" +msgstr "失敗" + +#: client/src/lists/CompletedJobs.js:56 +#: client/src/lists/PortalJobs.js:37 +msgid "Finished" +msgstr "終了日時" + +#: client/src/forms/Users.js:28 +#: client/src/lists/Users.js:41 +msgid "First Name" +msgstr "名" + +#: client/src/helpers/Credentials.js:142 +msgid "For example, %s" +msgstr "例: %s" + +#: client/src/notifications/notificationTemplates.form.js:142 +#: client/src/notifications/notificationTemplates.form.js:159 +#: client/src/notifications/notificationTemplates.form.js:213 +#: client/src/notifications/notificationTemplates.form.js:333 +#: client/src/notifications/notificationTemplates.form.js:371 +#: client/src/notifications/notificationTemplates.form.js:98 +msgid "For example:" +msgstr "例:" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:54 +msgid "" +"For hosts that are part of an external inventory, this flag cannot be " +"changed. It will be set by the inventory sync process." +msgstr "外部インベントリーの一部であるホストの場合、このフラグを変更できません。これはインベントリー同期プロセスで設定されます。" + +#: client/src/forms/JobTemplates.js:223 +#: client/src/forms/WorkflowMaker.js:125 +msgid "" +"For more information and examples see %sthe Patterns topic at docs.ansible." +"com%s." +msgstr "詳細情報およびサンプルについては、docs.ansible.com の %sパターンのトピックを参照してください%s。" + +#: client/src/forms/JobTemplates.js:199 +#: client/src/forms/JobTemplates.js:212 +msgid "Forks" +msgstr "フォーク" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:102 +msgid "GitHub" +msgstr "GitHub" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:103 +msgid "GitHub Org" +msgstr "GitHub 組織" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:104 +msgid "GithHub Team" +msgstr "GithHub チーム" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:105 +msgid "Google OAuth2" +msgstr "Google OAuth2" + +#: client/src/forms/Teams.js:118 +msgid "Granted Permissions" +msgstr "付与されたパーミッション" + +#: client/src/forms/Users.js:183 +msgid "Granted permissions" +msgstr "付与されたパーミッション" + +#: client/src/setup-menu/setup-menu.partial.html:5 +msgid "" +"Group all of your content to manage permissions across departments in your " +"company." +msgstr "会社内の複数の部署のパーミッションを管理するためにすべてのコンテンツを分類します。" + +#: client/src/notifications/notificationTemplates.form.js:324 +msgid "HTTP Headers" +msgstr "HTTP ヘッダー" + +#: client/src/forms/Credentials.js:140 +#: client/src/notifications/notificationTemplates.form.js:72 +msgid "Host" +msgstr "ホスト" + +#: client/src/helpers/Credentials.js:131 +msgid "Host (Authentication URL)" +msgstr "ホスト (認証 URL)" + +#: client/src/forms/JobTemplates.js:326 +#: client/src/forms/JobTemplates.js:335 +msgid "Host Config Key" +msgstr "ホスト設定キー" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:55 +msgid "Host Enabled" +msgstr "有効なホスト" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:39 +msgid "Hosts" +msgstr "ホスト" + +#: client/src/license/license.partial.html:52 +msgid "Hosts Available" +msgstr "利用可能なホスト" + +#: client/src/license/license.partial.html:64 +msgid "Hosts Remaining" +msgstr "残りのホスト" + +#: client/src/license/license.partial.html:58 +msgid "Hosts Used" +msgstr "使用されたホスト" + +#: client/src/license/license.partial.html:116 +msgid "I agree to the End User License Agreement" +msgstr "使用許諾契約書に同意します。" + +#: client/src/main-menu/main-menu.partial.html:104 +#: client/src/main-menu/main-menu.partial.html:27 +msgid "INVENTORIES" +msgstr "インベントリー" + +#: client/src/notifications/notificationTemplates.form.js:356 +msgid "IRC Nick" +msgstr "IRC ニック" + +#: client/src/notifications/notificationTemplates.form.js:345 +msgid "IRC Server Address" +msgstr "IRC サーバーアドレス" + +#: client/src/notifications/shared/type-change.service.js:58 +msgid "IRC Server Password" +msgstr "IRC サーバーパスワード" + +#: client/src/notifications/shared/type-change.service.js:57 +msgid "IRC Server Port" +msgstr "IRC サーバーポート" + +#: client/src/forms/JobTemplates.js:291 +msgid "" +"If enabled, run this playbook as an administrator. This is the equivalent of " +"passing the %s option to the %s command." +msgstr "有効な場合、この Playbook を管理者として実行します。これは %s オプションを %s コマンドに渡すことに相当します。" + +#: client/src/forms/Credentials.js:54 +msgid "" +"If no organization is given, the credential can only be used by the user " +"that creates the credential. Organization admins and system administrators " +"can assign an organization so that roles for the credential can be assigned " +"to users and teams in that organization." +msgstr "" +"組織が指定されない場合、認証情報はそれを作成するユーザーのみに使用されます。組織管理者およびシステム管理者は組織を割り当て、認証情報のロールを組織内のユーザーおよびチームに割り当てられるようにします。" + +#: client/src/license/license.partial.html:70 +msgid "" +"If you are ready to upgrade, please contact us by clicking the button below" +msgstr "アップグレードの準備ができましたら、以下のボタンをクリックしてお問い合わせください。" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:54 +msgid "" +"Indicates if a host is available and should be included in running jobs." +msgstr "ホストが利用可能かどうか、また実行中のジョブに組み込む必要があるかどうかを示します。" + +#: client/src/forms/JobTemplates.js:58 +msgid "" +"Instead, %s will check playbook syntax, test environment setup and report " +"problems." +msgstr "代わりに、%s は Playbook 構文、テスト環境セットアップおよびレポートの問題を検査します。" + +#: client/src/license/license.partial.html:11 +msgid "Invalid License" +msgstr "無効なライセンス" + +#: client/src/license/license.controller.js:69 +#: client/src/license/license.controller.js:76 +msgid "Invalid file format. Please upload valid JSON." +msgstr "無効なファイル形式です。有効な JSON をアップロードしてください。" + +#: client/src/login/loginModal/loginModal.partial.html:34 +msgid "Invalid username and/or password. Please try again." +msgstr "無効なユーザー名および/またはパスワードです。やり直してください。" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:50 +#: client/src/lists/Inventories.js:16 +#: client/src/lists/Inventories.js:17 +msgid "Inventories" +msgstr "インベントリー" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:41 +#: client/src/forms/JobTemplates.js:73 +#: client/src/forms/JobTemplates.js:86 +#: client/src/forms/WorkflowMaker.js:79 +#: client/src/forms/WorkflowMaker.js:89 +msgid "Inventory" +msgstr "インベントリー" + +#: client/src/inventory-scripts/inventory-scripts.list.js:12 +#: client/src/setup-menu/setup-menu.partial.html:34 +msgid "Inventory Scripts" +msgstr "インベントリースクリプト" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:46 +msgid "Inventory Sync" +msgstr "インベントリー同期" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:55 +msgid "Inventory Sync Failures" +msgstr "インベントリーの同期の失敗" + +#: client/src/forms/Inventories.js:67 +msgid "Inventory Variables" +msgstr "インベントリー変数" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:4 +msgid "JOB STATUS" +msgstr "ジョブステータス" + +#: client/src/forms/JobTemplates.js:23 +msgid "JOB TEMPLATE" +msgstr "ジョブテンプレート" + +#: client/src/app.js:429 +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:113 +#: client/src/main-menu/main-menu.partial.html:122 +#: client/src/main-menu/main-menu.partial.html:43 +msgid "JOBS" +msgstr "ジョブ" + +#: client/src/forms/JobTemplates.js:248 +#: client/src/forms/JobTemplates.js:256 +#: client/src/forms/WorkflowMaker.js:134 +#: client/src/forms/WorkflowMaker.js:142 +msgid "Job Tags" +msgstr "ジョブタグ" + +#: client/src/lists/Templates.js:65 +msgid "Job Template" +msgstr "ジョブテンプレート" + +#: client/src/lists/PortalJobTemplates.js:15 +#: client/src/lists/PortalJobTemplates.js:16 +msgid "Job Templates" +msgstr "ジョブテンプレート" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:32 +#: client/src/forms/JobTemplates.js:48 +#: client/src/forms/JobTemplates.js:62 +#: client/src/forms/WorkflowMaker.js:110 +#: client/src/forms/WorkflowMaker.js:99 +msgid "Job Type" +msgstr "ジョブタイプ" + +#: client/src/lists/PortalJobs.js:15 +#: client/src/lists/PortalJobs.js:19 +#: client/src/partials/jobs.html:7 +msgid "Jobs" +msgstr "ジョブ" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:106 +msgid "LDAP" +msgstr "LDAP" + +#: client/src/main-menu/main-menu.partial.html:83 +msgid "LOG OUT" +msgstr "ログアウト" + +#: client/src/notifications/notificationTemplates.form.js:270 +msgid "Label to be shown with notification" +msgstr "通知と共に表示されるラベル" + +#: client/src/forms/JobTemplates.js:340 +#: client/src/forms/JobTemplates.js:345 +#: client/src/forms/Workflows.js:60 +#: client/src/forms/Workflows.js:65 +#: client/src/lists/Templates.js:47 +msgid "Labels" +msgstr "ラベル" + +#: client/src/forms/Users.js:35 +#: client/src/lists/Users.js:45 +msgid "Last Name" +msgstr "姓" + +#: client/src/lists/Projects.js:53 +msgid "Last Updated" +msgstr "最終更新日時" + +#: client/src/lists/PortalJobTemplates.js:39 +#: client/src/lists/Templates.js:84 +#: client/src/shared/form-generator.js:1703 +msgid "Launch" +msgstr "起動" + +#: client/src/management-jobs/card/card.partial.html:21 +msgid "Launch Management Job" +msgstr "管理ジョブの起動" + +#: client/src/license/license.controller.js:42 +#: client/src/license/license.partial.html:8 +msgid "License" +msgstr "ライセンス" + +#: client/src/license/license.partial.html:102 +msgid "License File" +msgstr "ライセンスファイル" + +#: client/src/license/license.partial.html:33 +msgid "License Key" +msgstr "ライセンスキー" + +#: client/src/license/license.controller.js:42 +msgid "License Management" +msgstr "ライセンス管理" + +#: client/src/license/license.partial.html:21 +msgid "License Type" +msgstr "ライセンスタイプ" + +#: client/src/forms/JobTemplates.js:218 +#: client/src/forms/JobTemplates.js:225 +#: client/src/forms/WorkflowMaker.js:120 +#: client/src/forms/WorkflowMaker.js:127 +msgid "Limit" +msgstr "制限" + +#: client/src/shared/socket/socket.service.js:170 +msgid "Live events: attempting to connect to the Tower server." +msgstr "ライブイベント: Tower サーバーへの接続を試行しています。" + +#: client/src/shared/socket/socket.service.js:174 +msgid "" +"Live events: connected. Pages containing job status information will " +"automatically update in real-time." +msgstr "ライブイベント: 接続されています。ジョブステータス情報を含むページは自動的にリアルタイムで更新されます。" + +#: client/src/shared/socket/socket.service.js:178 +msgid "Live events: error connecting to the Tower server." +msgstr "ライブイベント: Tower サーバーへの接続時にエラーが発生しました。" + +#: client/src/shared/form-generator.js:1977 +msgid "Loading..." +msgstr "ロード中..." + +#: client/src/main-menu/main-menu.partial.html:188 +msgid "Log Out" +msgstr "ログアウト" + +#: client/src/configuration/system-form/configuration-system.controller.js:82 +msgid "Logging" +msgstr "ロギング" + +#: client/src/management-jobs/card/card.route.js:21 +msgid "MANAGEMENT JOBS" +msgstr "管理ジョブ" + +#: client/src/forms/Credentials.js:68 +msgid "Machine" +msgstr "マシン" + +#: client/src/forms/JobTemplates.js:137 +msgid "Machine Credential" +msgstr "マシンの認証情報" + +#: client/src/setup-menu/setup-menu.partial.html:29 +msgid "" +"Manage the cleanup of old job history, activity streams, data marked for " +"deletion, and system tracking info." +msgstr "古いジョブ履歴、アクティビティーストリーム、削除用にマークされたデータ、およびシステムトラッキング情報のクリーンアップを管理します。" + +#: client/src/helpers/Credentials.js:111 +msgid "Management Certificate" +msgstr "管理証明書" + +#: client/src/management-jobs/card/card.partial.html:4 +#: client/src/setup-menu/setup-menu.partial.html:28 +msgid "Management Jobs" +msgstr "管理ジョブ" + +#: client/src/controllers/Projects.js:62 +msgid "Manual projects do not require a schedule" +msgstr "手動プロジェクトにスケジュールは不要です" + +#: client/src/controllers/Projects.js:547 +#: client/src/controllers/Projects.js:61 +msgid "Manual projects do not require an SCM update" +msgstr "手動プロジェクトに SCM 更新は不要です" + +#: client/src/login/loginModal/loginModal.partial.html:28 +msgid "Maximum per-user sessions reached. Please sign in." +msgstr "ユーザーあたりの最大セッション数に達しました。サインインしてください。" + +#: client/src/configuration/system-form/configuration-system.controller.js:80 +msgid "Misc. System" +msgstr "その他のシステム" + +#: client/src/portal-mode/portal-mode-jobs.partial.html:4 +msgid "My Jobs" +msgstr "マイジョブ" + +#: client/src/main-menu/main-menu.partial.html:160 +msgid "My View" +msgstr "マイビュー" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:18 +msgid "NO HOSTS FOUND" +msgstr "ホストが見つかりませんでした" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:14 +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:13 +#: client/src/forms/Credentials.js:34 +#: client/src/forms/Inventories.js:29 +#: client/src/forms/JobTemplates.js:35 +#: client/src/forms/Organizations.js:26 +#: client/src/forms/Projects.js:31 +#: client/src/forms/Teams.js:126 +#: client/src/forms/Teams.js:27 +#: client/src/forms/Users.js:139 +#: client/src/forms/Users.js:164 +#: client/src/forms/Users.js:190 +#: client/src/forms/Workflows.js:34 +#: client/src/inventory-scripts/inventory-scripts.form.js:25 +#: client/src/inventory-scripts/inventory-scripts.list.js:20 +#: client/src/lists/CompletedJobs.js:43 +#: client/src/lists/Credentials.js:29 +#: client/src/lists/Inventories.js:46 +#: client/src/lists/PortalJobTemplates.js:24 +#: client/src/lists/PortalJobs.js:32 +#: client/src/lists/Projects.js:37 +#: client/src/lists/ScheduledJobs.js:32 +#: client/src/lists/Teams.js:25 +#: client/src/lists/Templates.js:26 +#: client/src/notifications/notificationTemplates.form.js:29 +#: client/src/notifications/notificationTemplates.list.js:33 +#: client/src/notifications/notifications.list.js:26 +msgid "Name" +msgstr "名前" + +#: client/src/forms/Credentials.js:72 +msgid "Network" +msgstr "ネットワーク" + +#: client/src/forms/JobTemplates.js:182 +#: client/src/forms/JobTemplates.js:193 +msgid "Network Credential" +msgstr "ネットワークの認証情報" + +#: client/src/forms/JobTemplates.js:192 +msgid "" +"Network credentials are used by Ansible networking modules to connect to and " +"manage networking devices." +msgstr "" +"ネットワーク認証情報は、ネットワークデバイスへの接続およびその管理を実行するために Ansible ネットワークモジュールによって使用されます。" + +#: client/src/inventory-scripts/inventory-scripts.form.js:16 +msgid "New Custom Inventory" +msgstr "新規カスタムインベントリー" + +#: client/src/forms/Inventories.js:18 +msgid "New Inventory" +msgstr "新規インベントリー" + +#: client/src/forms/JobTemplates.js:20 +msgid "New Job Template" +msgstr "新規ジョブテンプレート" + +#: client/src/notifications/notificationTemplates.form.js:16 +msgid "New Notification Template" +msgstr "新規通知テンプレート" + +#: client/src/forms/Organizations.js:18 +msgid "New Organization" +msgstr "新規組織" + +#: client/src/forms/Projects.js:18 +msgid "New Project" +msgstr "新規プロジェクト" + +#: client/src/forms/Teams.js:18 +msgid "New Team" +msgstr "新規チーム" + +#: client/src/forms/Users.js:18 +msgid "New User" +msgstr "新規ユーザー" + +#: client/src/forms/Workflows.js:19 +msgid "New Workflow Job Template" +msgstr "新規ワークフロージョブテンプレート" + +#: client/src/controllers/Users.js:174 +msgid "New user successfully created!" +msgstr "新規ユーザーが正常に作成されました!" + +#: client/src/lists/ScheduledJobs.js:50 +msgid "Next Run" +msgstr "次回の実行日時" + +#: client/src/lists/Credentials.js:24 +msgid "No Credentials Have Been Created" +msgstr "認証情報が作成されていません" + +#: client/src/controllers/Projects.js:123 +msgid "No SCM Configuration" +msgstr "SCM 設定がありません" + +#: client/src/controllers/Projects.js:114 +msgid "No Updates Available" +msgstr "利用可能な更新がありません" + +#: client/src/lists/CompletedJobs.js:22 +msgid "No completed jobs" +msgstr "完了したジョブがありません" + +#: client/src/license/license.controller.js:41 +msgid "No file selected." +msgstr "ファイルが選択されていません。" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:46 +msgid "No jobs were recently run." +msgstr "最近実行されたジョブがありません。" + +#: client/src/forms/Teams.js:123 +#: client/src/forms/Users.js:187 +msgid "No permissions have been granted" +msgstr "パーミッションが付与されていません" + +#: client/src/lists/ScheduledJobs.js:18 +msgid "No schedules exist" +msgstr "スケジュールがありません" + +#: client/src/controllers/Users.js:16 +msgid "Normal User" +msgstr "標準ユーザー" + +#: client/src/controllers/Projects.js:64 +msgid "Not configured for SCM" +msgstr "SCM 用に設定されていません" + +#: client/src/notifications/notificationTemplates.form.js:293 +msgid "Notification Color" +msgstr "通知の色" + +#: client/src/notifications/notificationTemplates.list.js:14 +msgid "Notification Templates" +msgstr "通知テンプレート" + +#: client/src/notifications/notifications.list.js:17 +#: client/src/setup-menu/setup-menu.partial.html:41 +msgid "Notifications" +msgstr "通知" + +#: client/src/notifications/notificationTemplates.form.js:306 +msgid "Notify Channel" +msgstr "通知チャネル" + +#: client/src/notifications/notificationTemplates.form.js:198 +msgid "Number associated with the \"Messaging Service\" in Twilio." +msgstr "Twilio の \"メッセージングサービス\" に関連付けられた数字。 " + +#: client/src/shared/form-generator.js:547 +msgid "OFF" +msgstr "オフ" + +#: client/src/shared/form-generator.js:545 +msgid "ON" +msgstr "オン" + +#: client/src/organizations/list/organizations-list.partial.html:6 +msgid "ORGANIZATIONS" +msgstr "組織" + +#: client/src/forms/WorkflowMaker.js:45 +msgid "On Failure" +msgstr "失敗した場合 " + +#: client/src/forms/WorkflowMaker.js:40 +msgid "On Success" +msgstr "成功した場合 " + +#: client/src/forms/Credentials.js:377 +msgid "" +"OpenStack domains define administrative boundaries. It is only needed for " +"Keystone v3 authentication URLs. Common scenarios include:" +msgstr "" +"OpenStack ドメインは管理上の境界を定義します。これは Keystone v3 認証 URL " +"にのみ必要です。共通するシナリオには以下が含まれます:" + +#: client/src/forms/JobTemplates.js:347 +#: client/src/forms/Workflows.js:67 +msgid "" +"Optional labels that describe this job template, such as 'dev' or 'test'. " +"Labels can be used to group and filter job templates and completed jobs in " +"the Tower display." +msgstr "" +"「dev」または「test」などのこのジョブテンプレートを説明するオプションラベルです。ラベルを使用し、Tower " +"のディスプレイでジョブテンプレートおよび完了したジョブの分類およびフィルターを実行できます。" + +#: client/src/forms/JobTemplates.js:284 +#: client/src/notifications/notificationTemplates.form.js:391 +msgid "Options" +msgstr "オプション" + +#: client/src/forms/Credentials.js:49 +#: client/src/forms/Credentials.js:55 +#: client/src/forms/Inventories.js:42 +#: client/src/forms/Projects.js:43 +#: client/src/forms/Projects.js:49 +#: client/src/forms/Teams.js:39 +#: client/src/forms/Users.js:59 +#: client/src/forms/Workflows.js:47 +#: client/src/forms/Workflows.js:53 +#: client/src/inventory-scripts/inventory-scripts.form.js:37 +#: client/src/inventory-scripts/inventory-scripts.list.js:30 +#: client/src/lists/Inventories.js:52 +#: client/src/lists/Teams.js:35 +#: client/src/notifications/notificationTemplates.form.js:41 +msgid "Organization" +msgstr "組織" + +#: client/src/forms/Users.js:129 +#: client/src/setup-menu/setup-menu.partial.html:4 +msgid "Organizations" +msgstr "組織" + +#: client/src/forms/Credentials.js:80 +msgid "Others (Cloud Providers)" +msgstr "その他 (クラウドプロバイダー)" + +#: client/src/lists/Credentials.js:45 +msgid "Owners" +msgstr "所有者" + +#: client/src/login/loginModal/loginModal.partial.html:68 +msgid "PASSWORD" +msgstr "パスワード" + +#: client/src/organizations/list/organizations-list.partial.html:44 +#: client/src/shared/form-generator.js:1880 +#: client/src/shared/list-generator/list-generator.factory.js:245 +msgid "PLEASE ADD ITEMS TO THIS LIST" +msgstr "項目をこの一覧に追加してください" + +#: client/src/main-menu/main-menu.partial.html:67 +msgid "PORTAL MODE" +msgstr "ポータルモード" + +#: client/src/main-menu/main-menu.partial.html:19 +#: client/src/main-menu/main-menu.partial.html:95 +msgid "PROJECTS" +msgstr "プロジェクト" + +#: client/src/notifications/notificationTemplates.form.js:237 +msgid "Pagerduty subdomain" +msgstr "Pagerduty サブドメイン" + +#: client/src/forms/JobTemplates.js:358 +#: client/src/forms/Workflows.js:78 +msgid "" +"Pass extra command line variables to the playbook. This is the %s or %s " +"command line parameter for %s. Provide key/value pairs using either YAML or " +"JSON." +msgstr "" +"追加のコマンドライン変数を Playbook に渡します。これは、%s の %s または %s コマンドラインパラメーターです。YAML または " +"JSON のいずれかを使用してキーと値のペアを指定します。" + +#: client/src/forms/Credentials.js:227 +#: client/src/forms/Users.js:70 +#: client/src/helpers/Credentials.js:119 +#: client/src/helpers/Credentials.js:127 +#: client/src/helpers/Credentials.js:147 +#: client/src/helpers/Credentials.js:156 +#: client/src/helpers/Credentials.js:165 +#: client/src/helpers/Credentials.js:45 +#: client/src/helpers/Credentials.js:94 +#: client/src/notifications/shared/type-change.service.js:28 +msgid "Password" +msgstr "パスワード" + +#: client/src/helpers/Credentials.js:73 +msgid "Password (API Key)" +msgstr "パスワード (API キー)" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:20 +msgid "Past 24 Hours" +msgstr "過去 24 時間" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:15 +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:26 +msgid "Past Month" +msgstr "過去 1 ヵ月" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:23 +msgid "Past Week" +msgstr "過去 1 週間" + +#: client/src/helpers/Credentials.js:102 +msgid "" +"Paste the contents of the PEM file associated with the service account email." +"" +msgstr "サービスアカウントメールに関連付けられた PEM ファイルの内容を貼り付けます。" + +#: client/src/helpers/Credentials.js:114 +msgid "" +"Paste the contents of the PEM file that corresponds to the certificate you " +"uploaded in the Microsoft Azure console." +msgstr "Microsoft Azure コンソールにアップロードした証明書に対応する PEM ファイルの内容を貼り付けます。" + +#: client/src/helpers/Credentials.js:66 +msgid "Paste the contents of the SSH private key file." +msgstr "SSH 秘密鍵ファイルの内容を貼り付けます。" + +#: client/src/helpers/Credentials.js:41 +msgid "Paste the contents of the SSH private key file.%s or click to close%s" +msgstr "SSH 秘密鍵ファイルの内容を貼り付けます。%s またはクリックして %s を閉じます。" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:8 +msgid "Period" +msgstr "期間" + +#: client/src/controllers/Projects.js:284 +#: client/src/controllers/Users.js:141 +msgid "Permission Error" +msgstr "パーミッションのエラー" + +#: client/src/forms/Credentials.js:432 +#: client/src/forms/Inventories.js:142 +#: client/src/forms/JobTemplates.js:403 +#: client/src/forms/Organizations.js:64 +#: client/src/forms/Projects.js:227 +#: client/src/forms/Workflows.js:116 +msgid "Permissions" +msgstr "パーミッション" + +#: client/src/forms/JobTemplates.js:120 +#: client/src/forms/JobTemplates.js:131 +msgid "Playbook" +msgstr "Playbook" + +#: client/src/forms/Projects.js:89 +msgid "Playbook Directory" +msgstr "Playbook ディレクトリー" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:52 +msgid "Playbook Run" +msgstr "Playbook 実行" + +#: client/src/license/license.partial.html:84 +msgid "" +"Please click the button below to visit Ansible's website to get a Tower " +"license key." +msgstr "以下のボタンをクリックし、Ansible の web サイトに移動して Tower ライセンスキーを取得します。" + +#: client/src/shared/form-generator.js:828 +#: client/src/shared/form-generator.js:953 +msgid "" +"Please enter a URL that begins with ssh, http or https. The URL may not " +"contain the '@' character." +msgstr "ssh、http または https で始まる URL を入力します。URL には「@」文字を含めることはできません。" + +#: client/src/shared/form-generator.js:1189 +msgid "Please enter a number greater than %d and less than %d." +msgstr "%d より大きく、%d より小さい数値を入力してください。" + +#: client/src/shared/form-generator.js:1191 +msgid "Please enter a number greater than %d." +msgstr "%d より大きい数値を入力してください。" + +#: client/src/shared/form-generator.js:1183 +msgid "Please enter a number." +msgstr "数値を入力してください。" + +#: client/src/login/loginModal/loginModal.partial.html:78 +msgid "Please enter a password." +msgstr "パスワードを入力してください。" + +#: client/src/login/loginModal/loginModal.partial.html:58 +msgid "Please enter a username." +msgstr "ユーザー名を入力してください。" + +#: client/src/shared/form-generator.js:818 +#: client/src/shared/form-generator.js:943 +msgid "Please enter a valid email address." +msgstr "有効なメールアドレスを入力してください。" + +#: client/src/shared/form-generator.js:1044 +#: client/src/shared/form-generator.js:813 +#: client/src/shared/form-generator.js:938 +msgid "Please enter a value." +msgstr "値を入力してください。" + +#: client/src/lists/CompletedJobs.js:13 +msgid "Please save and run a job to view" +msgstr "表示するジョブを保存し、実行してください。" + +#: client/src/notifications/notifications.list.js:15 +msgid "Please save before adding notifications" +msgstr "保存してから通知を追加します。" + +#: client/src/forms/Teams.js:69 +msgid "Please save before adding users" +msgstr "保存してからユーザーを追加します。" + +#: client/src/forms/Inventories.js:138 +#: client/src/forms/Inventories.js:91 +#: client/src/forms/JobTemplates.js:396 +#: client/src/forms/Organizations.js:57 +#: client/src/forms/Projects.js:219 +#: client/src/forms/Teams.js:110 +#: client/src/forms/Workflows.js:109 +msgid "Please save before assigning permissions" +msgstr "保存してからパーミッションを割り当てます。" + +#: client/src/forms/Users.js:122 +#: client/src/forms/Users.js:179 +msgid "Please save before assigning to organizations" +msgstr "保存してから組織に割り当てます。" + +#: client/src/forms/Users.js:148 +msgid "Please save before assigning to teams" +msgstr "保存してからチームに割り当てます。" + +#: client/src/forms/Workflows.js:185 +msgid "Please save before defining the workflow graph" +msgstr "保存してからワークフローグラフを定義します。" + +#: client/src/forms/WorkflowMaker.js:65 +msgid "Please select a Credential." +msgstr "認証情報を選択してください。" + +#: client/src/forms/JobTemplates.js:150 +msgid "" +"Please select a Machine Credential or check the Prompt on launch option." +msgstr "マシン認証情報を選択するか、または「起動プロンプト」オプションにチェックを付けます。" + +#: client/src/shared/form-generator.js:1224 +msgid "Please select a number between" +msgstr "Please select a number between" + +#: client/src/shared/form-generator.js:1220 +msgid "Please select a number." +msgstr "数値を選択してください。" + +#: client/src/shared/form-generator.js:1111 +#: client/src/shared/form-generator.js:1180 +#: client/src/shared/form-generator.js:1298 +#: client/src/shared/form-generator.js:1403 +msgid "Please select a value." +msgstr "値を選択してください。" + +#: client/src/forms/JobTemplates.js:83 +msgid "Please select an Inventory or check the Prompt on launch option." +msgstr "インベントリーを選択するか、または「起動プロンプト」オプションにチェックを付けてください。" + +#: client/src/forms/WorkflowMaker.js:86 +msgid "Please select an Inventory." +msgstr "インベントリーを選択してください。" + +#: client/src/shared/form-generator.js:1217 +msgid "Please select at least one value." +msgstr "1 つ以上の値を選択してください。" + +#: client/src/notifications/shared/type-change.service.js:27 +msgid "Port" +msgstr "ポート" + +#: client/src/forms/Credentials.js:257 +#: client/src/helpers/Credentials.js:36 +#: client/src/helpers/Credentials.js:60 +msgid "Private Key" +msgstr "秘密鍵" + +#: client/src/forms/Credentials.js:264 +msgid "Private Key Passphrase" +msgstr "秘密鍵のパスフレーズ" + +#: client/src/forms/Credentials.js:279 +#: client/src/forms/Credentials.js:283 +msgid "Privilege Escalation" +msgstr "権限昇格" + +#: client/src/helpers/Credentials.js:90 +msgid "Privilege Escalation Password" +msgstr "権限昇格のパスワード" + +#: client/src/helpers/Credentials.js:89 +msgid "Privilege Escalation Username" +msgstr "権限昇格のユーザー名" + +#: client/src/forms/JobTemplates.js:114 +#: client/src/forms/JobTemplates.js:97 +#: client/src/helpers/Credentials.js:103 +msgid "Project" +msgstr "プロジェクト" + +#: client/src/helpers/Credentials.js:132 +msgid "Project (Tenant Name)" +msgstr "プロジェクト (テナント名)" + +#: client/src/forms/Projects.js:75 +#: client/src/forms/Projects.js:83 +msgid "Project Base Path" +msgstr "プロジェクトのベースパス" + +#: client/src/forms/Credentials.js:363 +msgid "Project Name" +msgstr "プロジェクト名" + +#: client/src/forms/Projects.js:100 +msgid "Project Path" +msgstr "プロジェクトパス" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:66 +msgid "Project Sync Failures" +msgstr "プロジェクトの同期の失敗" + +#: client/src/controllers/Projects.js:134 +msgid "Project lookup failed. GET returned:" +msgstr "プロジェクトの検索に失敗しました。GET で以下が返されました:" + +#: client/src/dashboard/counts/dashboard-counts.directive.js:61 +#: client/src/lists/Projects.js:16 +#: client/src/lists/Projects.js:17 +msgid "Projects" +msgstr "プロジェクト" + +#: client/src/forms/JobTemplates.js:159 +#: client/src/forms/JobTemplates.js:230 +#: client/src/forms/JobTemplates.js:261 +#: client/src/forms/JobTemplates.js:279 +#: client/src/forms/JobTemplates.js:369 +#: client/src/forms/JobTemplates.js:68 +#: client/src/forms/JobTemplates.js:92 +msgid "Prompt on launch" +msgstr "起動プロンプト" + +#: client/src/forms/JobTemplates.js:253 +#: client/src/forms/JobTemplates.js:271 +#: client/src/forms/WorkflowMaker.js:139 +#: client/src/forms/WorkflowMaker.js:154 +msgid "Provide a comma separated list of tags." +msgstr "カンマ区切りのタグの一覧を指定してください。" + +#: client/src/forms/JobTemplates.js:221 +#: client/src/forms/WorkflowMaker.js:123 +msgid "" +"Provide a host pattern to further constrain the list of hosts that will be " +"managed or affected by the playbook. Multiple patterns can be separated by " +"%s %s or %s" +msgstr "" +"Playbook によって管理されるか、または影響されるホストの一覧をさらに制限するためのホストのパターンを指定してください。複数のパターンは %s " +"%s または %s で区切ることができます。" + +#: client/src/forms/JobTemplates.js:313 +#: client/src/forms/JobTemplates.js:321 +msgid "Provisioning Callback URL" +msgstr "プロビジョニングコールバック URL" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:107 +msgid "RADIUS" +msgstr "RADIUS" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:4 +msgid "RECENT JOB RUNS" +msgstr "最近のジョブ実行" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:42 +msgid "RECENTLY RUN JOBS" +msgstr "最近実行されたジョブ" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:4 +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:52 +msgid "RECENTLY USED JOB TEMPLATES" +msgstr "最近使用されたジョブテンプレート" + +#: client/src/lists/Projects.js:76 +#: client/src/partials/jobs.html:15 +#: client/src/portal-mode/portal-mode-jobs.partial.html:12 +msgid "REFRESH" +msgstr "更新" + +#: client/src/forms/JobTemplates.js:99 +msgid "RESET" +msgstr "リセット" + +#: client/src/helpers/Credentials.js:98 +msgid "RSA Private Key" +msgstr "RSA 秘密鍵" + +#: client/src/notifications/notificationTemplates.form.js:94 +#: client/src/notifications/notificationTemplates.form.js:99 +msgid "Recipient List" +msgstr "受信者リスト" + +#: client/src/bread-crumb/bread-crumb.partial.html:6 +#: client/src/lists/Projects.js:72 +msgid "Refresh the page" +msgstr "ページの更新" + +#: client/src/lists/CompletedJobs.js:75 +msgid "Relaunch using the same parameters" +msgstr "同一パラメーターによる起動" + +#: client/src/forms/Teams.js:144 +#: client/src/forms/Users.js:214 +msgid "Remove" +msgstr "削除" + +#: client/src/forms/Projects.js:153 +msgid "Remove any local modifications prior to performing an update." +msgstr "更新の実行前にローカルの変更を削除します。" + +#: client/src/license/license.partial.html:89 +msgid "Request License" +msgstr "ライセンスの要求" + +#: client/src/configuration/auth-form/sub-forms/auth-azure.form.js:41 +#: client/src/configuration/auth-form/sub-forms/auth-github-org.form.js:31 +#: client/src/configuration/auth-form/sub-forms/auth-github-team.form.js:31 +#: client/src/configuration/auth-form/sub-forms/auth-github.form.js:27 +#: client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js:39 +#: client/src/configuration/auth-form/sub-forms/auth-ldap.form.js:87 +#: client/src/configuration/auth-form/sub-forms/auth-radius.form.js:32 +#: client/src/configuration/auth-form/sub-forms/auth-saml.form.js:59 +#: client/src/configuration/jobs-form/configuration-jobs.form.js:67 +#: client/src/configuration/system-form/configuration-system.form.js:41 +#: client/src/configuration/system-form/sub-forms/system-activity-stream.form.js:25 +#: client/src/configuration/system-form/sub-forms/system-logging.form.js:50 +#: client/src/configuration/system-form/sub-forms/system-misc.form.js:29 +#: client/src/configuration/ui-form/configuration-ui.form.js:35 +msgid "Reset All" +msgstr "すべてのリセット" + +#: client/src/lists/Projects.js:42 +msgid "Revision" +msgstr "リビジョン" + +#: client/src/controllers/Projects.js:657 +msgid "Revision #" +msgstr "リビジョン #" + +#: client/src/forms/Credentials.js:454 +#: client/src/forms/Inventories.js:120 +#: client/src/forms/Inventories.js:166 +#: client/src/forms/Organizations.js:88 +#: client/src/forms/Projects.js:249 +#: client/src/forms/Teams.js:137 +#: client/src/forms/Teams.js:99 +#: client/src/forms/Users.js:201 +msgid "Role" +msgstr "ロール" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:108 +msgid "SAML" +msgstr "SAML" + +#: client/src/controllers/Projects.js:657 +msgid "SCM Branch" +msgstr "SCM ブランチ" + +#: client/src/forms/Projects.js:154 +msgid "SCM Clean" +msgstr "SCM クリーニング" + +#: client/src/forms/Projects.js:130 +msgid "SCM Credential" +msgstr "SCM 認証情報" + +#: client/src/forms/Projects.js:165 +msgid "SCM Delete" +msgstr "SCM 削除" + +#: client/src/helpers/Credentials.js:93 +msgid "SCM Private Key" +msgstr "SCM 秘密鍵" + +#: client/src/forms/Projects.js:56 +msgid "SCM Type" +msgstr "SCM タイプ" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:49 +#: client/src/forms/Projects.js:175 +msgid "SCM Update" +msgstr "SCM 更新" + +#: client/src/controllers/Projects.js:176 +msgid "SCM Update Cancel" +msgstr "SCM 更新の取り消し" + +#: client/src/forms/Projects.js:145 +msgid "SCM Update Options" +msgstr "SCM 更新オプション" + +#: client/src/controllers/Projects.js:543 +#: client/src/controllers/Projects.js:57 +msgid "SCM update currently running" +msgstr "現在実行中の SCM 更新" + +#: client/src/main-menu/main-menu.partial.html:59 +msgid "SETTINGS" +msgstr "設定" + +#: client/src/login/loginModal/loginModal.partial.html:97 +msgid "SIGN IN" +msgstr "サインイン" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 +msgid "SIGN IN WITH" +msgstr "サインイン:" + +#: client/src/app.js:513 +msgid "SOCKETS" +msgstr "ソケット" + +#: client/src/helpers/Credentials.js:166 +msgid "SSH Key" +msgstr "SSH キー" + +#: client/src/forms/Credentials.js:255 +msgid "SSH key description" +msgstr "SSH キーの説明" + +#: client/src/notifications/notificationTemplates.form.js:384 +msgid "SSL Connection" +msgstr "SSL 接続" + +#: client/src/forms/Credentials.js:120 +#: client/src/forms/Credentials.js:128 +msgid "STS Token" +msgstr "STS トークン" + +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:64 +msgid "SUCCESSFUL" +msgstr "成功" + +#: client/src/helpers/Credentials.js:149 +msgid "Satellite 6 Host" +msgstr "Satellite 6 ホスト" + +#: client/src/shared/form-generator.js:1687 +msgid "Save" +msgstr "保存" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:81 +#: client/src/configuration/configuration.controller.js:181 +#: client/src/configuration/configuration.controller.js:240 +#: client/src/configuration/system-form/configuration-system.controller.js:60 +msgid "Save changes" +msgstr "変更の保存" + +#: client/src/license/license.partial.html:122 +msgid "Save successful!" +msgstr "正常に保存が実行されました!" + +#: client/src/lists/Templates.js:92 +msgid "Schedule" +msgstr "スケジュール" + +#: client/src/management-jobs/card/card.partial.html:26 +msgid "Schedule Management Job" +msgstr "管理ジョブのスケジュール" + +#: client/src/controllers/Projects.js:49 +msgid "Schedule future SCM updates" +msgstr "将来の SCM 更新のスケジュール" + +#: client/src/lists/Templates.js:95 +msgid "Schedule future job template runs" +msgstr "将来のジョブテンプレート実行のスケジュール" + +#: client/src/lists/ScheduledJobs.js:15 +msgid "Scheduled Jobs" +msgstr "スケジュール済みのジョブ" + +#: client/src/partials/jobs.html:10 +msgid "Schedules" +msgstr "スケジュール" + +#: client/src/inventory-scripts/inventory-scripts.form.js:59 +msgid "Script must begin with a hashbang sequence: i.e.... %s" +msgstr "スクリプトは hashbang シーケンスで開始する必要があります (例: .... %s)。" + +#: client/src/forms/Credentials.js:105 +msgid "Secret Key" +msgstr "シークレットキー" + +#: client/src/forms/Credentials.js:125 +msgid "" +"Security Token Service (STS) is a web service that enables you to request " +"temporary, limited-privilege credentials for AWS Identity and Access " +"Management (IAM) users." +msgstr "" +"セキュリティートークンサービス (STS) は、AWS Identity and Access Management (IAM) " +"ユーザーの一時的な、権限の制限された認証情報を要求できる web サービスです。" + +#: client/src/shared/form-generator.js:1691 +msgid "Select" +msgstr "選択" + +#: client/src/configuration/jobs-form/configuration-jobs.controller.js:87 +#: client/src/configuration/ui-form/configuration-ui.controller.js:82 +msgid "Select commands" +msgstr "コマンドの選択" + +#: client/src/forms/Projects.js:98 +msgid "" +"Select from the list of directories found in the Project Base Path. Together " +"the base path and the playbook directory provide the full path used to " +"locate playbooks." +msgstr "" +"プロジェクトのベースパスにあるデイレクトリーの一覧から選択します。ベースパスと Playbook ディレクトリーは、Playbook " +"を見つけるために使用される完全なパスを提供します。" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:226 +msgid "Select group types" +msgstr "グループタイプの選択" + +#: client/src/forms/JobTemplates.js:152 +#: client/src/forms/WorkflowMaker.js:67 +msgid "" +"Select the credential you want the job to use when accessing the remote " +"hosts. Choose the credential containing the username and SSH key or " +"password that Ansible will need to log into the remote hosts." +msgstr "" +"リモートホストへのアクセス時にジョブで使用する認証情報を選択します。Ansible がリモートホストにログインするために必要なユーザー名および SSH " +"キーまたはパスワードが含まれる認証情報を選択してください。 " + +#: client/src/forms/JobTemplates.js:85 +#: client/src/forms/WorkflowMaker.js:88 +msgid "Select the inventory containing the hosts you want this job to manage." +msgstr "このジョブで管理するホストが含まれるインベントリーを選択してください。" + +#: client/src/forms/JobTemplates.js:130 +msgid "Select the playbook to be executed by this job." +msgstr "このジョブで実行される Playbook を選択してください。" + +#: client/src/forms/JobTemplates.js:113 +msgid "" +"Select the project containing the playbook you want this job to execute." +msgstr "このジョブで実行する Playbook が含まれるプロジェクトを選択してください。" + +#: client/src/configuration/system-form/configuration-system.controller.js:167 +msgid "Select types" +msgstr "タイプの選択" + +#: client/src/forms/JobTemplates.js:174 +msgid "" +"Selecting an optional cloud credential in the job template will pass along " +"the access credentials to the running playbook, allowing provisioning into " +"the cloud without manually passing parameters to the included modules." +msgstr "" +"ジョブテンプレートでオプションのクラウド認証情報を選択すると、アクセス認証情報が実行中の Playbook " +"に渡され、パラメーターを組み込みモジュールに手動で渡さなくてもクラウドへのプロビジョニングが許可されます。" + +#: client/src/notifications/notificationTemplates.form.js:83 +msgid "Sender Email" +msgstr "送信者のメール" + +#: client/src/helpers/Credentials.js:97 +msgid "Service Account Email Address" +msgstr "サービスアカウントのメールアドレス" + +#: client/src/forms/JobTemplates.js:60 +#: client/src/forms/WorkflowMaker.js:108 +msgid "" +"Setting the type to %s will execute the playbook and store any scanned " +"facts for use with Tower's System Tracking feature." +msgstr "" +"タイプを %s に設定すると Playbook が実行され、Tower のシステムトラッキング機能で使用するスキャンされたファクトが保存されます。" + +#: client/src/forms/JobTemplates.js:57 +msgid "Setting the type to %s will not execute the playbook." +msgstr "タイプを %s に設定すると Playbook は実行されません。" + +#: client/src/forms/WorkflowMaker.js:106 +msgid "" +"Setting the type to %s will not execute the playbook. Instead, %s will check " +"playbook syntax, test environment setup and report problems." +msgstr "" +"タイプを %s に設定すると Playbook は実行されません。その代わりに、%s は Playbook " +"構文、テスト環境セットアップおよびレポートの問題を検査します。" + +#: client/src/main-menu/main-menu.partial.html:147 +msgid "Settings" +msgstr "設定" + +#: client/src/shared/form-generator.js:843 +msgid "Show" +msgstr "表示" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:34 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:45 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:56 +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:77 +msgid "Sign in with %s" +msgstr "%s でサインイン" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:64 +msgid "Sign in with %s Organizations" +msgstr "%s 組織でサインイン" + +#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:62 +msgid "Sign in with %s Teams" +msgstr "%s チームでサインイン" + +#: client/src/forms/JobTemplates.js:266 +#: client/src/forms/JobTemplates.js:274 +#: client/src/forms/WorkflowMaker.js:149 +#: client/src/forms/WorkflowMaker.js:157 +msgid "Skip Tags" +msgstr "スキップタグ" + +#: client/src/forms/JobTemplates.js:272 +#: client/src/forms/WorkflowMaker.js:155 +msgid "" +"Skip tags are useful when you have a large playbook, and you want to skip " +"specific parts of a play or task." +msgstr "スキップタグは、Playbook のサイズが大きく、プレイまたはタスクの特定の部分をスキップする必要がある場合に役立ちます。" + +#: client/src/forms/Credentials.js:76 +msgid "Source Control" +msgstr "ソースコントロール" + +#: client/src/forms/Projects.js:27 +msgid "Source Details" +msgstr "ソース詳細" + +#: client/src/notifications/notificationTemplates.form.js:196 +msgid "Source Phone Number" +msgstr "発信元の電話番号" + +#: client/src/notifications/notificationTemplates.form.js:332 +msgid "Specify HTTP Headers in JSON format" +msgstr "JSON 形式での HTTP ヘッダーの指定" + +#: client/src/forms/Credentials.js:285 +msgid "" +"Specify a method for %s operations. This is equivalent to specifying the %s " +"parameter, where %s could be %s" +msgstr "%s 操作のメソッドを指定します。これは %s を指定することに相当します。%s は %s にすることができます。" + +#: client/src/setup-menu/setup-menu.partial.html:17 +msgid "" +"Split up your organization to associate content and control permissions for " +"groups." +msgstr "コンテンツを関連付け、グループのパーミッションを制御するために組織を分割します。" + +#: client/src/lists/PortalJobTemplates.js:42 +#: client/src/lists/Templates.js:87 +msgid "Start a job using this template" +msgstr "このテンプレートによるジョブの開始" + +#: client/src/controllers/Projects.js:48 +#: client/src/controllers/Projects.js:540 +msgid "Start an SCM update" +msgstr "SCM 更新の開始" + +#: client/src/dashboard/hosts/dashboard-hosts.list.js:49 +msgid "Status" +msgstr "ステータス" + +#: client/src/license/license.partial.html:121 +msgid "Submit" +msgstr "送信" + +#: client/src/license/license.partial.html:27 +msgid "Subscription" +msgstr "サブスクリプション" + +#: client/src/forms/Credentials.js:152 +#: client/src/forms/Credentials.js:163 +msgid "Subscription ID" +msgstr "サブスクリプション ID" + +#: client/src/forms/Credentials.js:162 +msgid "Subscription ID is an Azure construct, which is mapped to a username." +msgstr "サブスクリプション ID は、ユーザー名にマップされる Azure コンストラクトです。" + +#: client/src/notifications/notifications.list.js:38 +msgid "Success" +msgstr "成功" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:77 +msgid "Successful" +msgstr "成功" + +#: client/src/controllers/Users.js:18 +msgid "System Administrator" +msgstr "システム管理者" + +#: client/src/controllers/Users.js:17 +msgid "System Auditor" +msgstr "システム監査者" + +#: client/src/app.js:341 +msgid "TEAMS" +msgstr "チーム" + +#: client/src/main-menu/main-menu.partial.html:113 +#: client/src/main-menu/main-menu.partial.html:35 +msgid "TEMPLATES" +msgstr "テンプレート" + +#: client/src/dashboard/graphs/job-status/job-status-graph.directive.js:106 +msgid "TIME" +msgstr "時間" + +#: client/src/forms/JobTemplates.js:254 +#: client/src/forms/WorkflowMaker.js:140 +msgid "" +"Tags are useful when you have a large playbook, and you want to run a " +"specific part of a play or task." +msgstr "タグは、Playbook のサイズが大きく、プレイまたはタスクの特定の部分を実行する必要がある場合に役立ちます。" + +#: client/src/notifications/notificationTemplates.form.js:313 +msgid "Target URL" +msgstr "ターゲット URL" + +#: client/src/forms/Credentials.js:461 +#: client/src/forms/Inventories.js:126 +#: client/src/forms/Inventories.js:173 +#: client/src/forms/Organizations.js:95 +#: client/src/forms/Projects.js:255 +msgid "Team Roles" +msgstr "チームロール" + +#: client/src/forms/Users.js:155 +#: client/src/lists/Teams.js:16 +#: client/src/lists/Teams.js:17 +#: client/src/setup-menu/setup-menu.partial.html:16 +msgid "Teams" +msgstr "チーム" + +#: client/src/lists/Templates.js:16 +msgid "Template" +msgstr "テンプレート" + +#: client/src/lists/Templates.js:17 +#: client/src/lists/Templates.js:18 +msgid "Templates" +msgstr "テンプレート" + +#: client/src/forms/Credentials.js:335 +msgid "Tenant ID" +msgstr "テナント ID" + +#: client/src/notifications/notificationTemplates.list.js:65 +msgid "Test notification" +msgstr "テスト通知" + +#: client/src/shared/form-generator.js:1409 +msgid "That value was not found. Please enter or select a valid value." +msgstr "値が見つかりませんでした。有効な値を入力または選択してください。" + +#: client/src/helpers/Credentials.js:105 +msgid "" +"The Project ID is the GCE assigned identification. It is constructed as two " +"words followed by a three digit number. Such as:" +msgstr "プロジェクト ID は GCE によって割り当てられる識別情報です。これは、以下のように 2 語とそれに続く 3 桁の数字で構成されます。" + +#: client/src/controllers/Projects.js:693 +msgid "The SCM update process is running." +msgstr "SCM 更新プロセスが実行中です。" + +#: client/src/forms/Credentials.js:191 +msgid "" +"The email address assigned to the Google Compute Engine %sservice account." +msgstr "Google Compute Engine %sサービスアカウントに割り当てられたメールアドレス。" + +#: client/src/helpers/Credentials.js:141 +msgid "The host to authenticate with." +msgstr "認証するホスト。" + +#: client/src/helpers/Credentials.js:75 +msgid "The host value" +msgstr "ホスト値" + +#: client/src/forms/JobTemplates.js:208 +msgid "" +"The number of parallel or simultaneous processes to use while executing the " +"playbook. 0 signifies the default value from the %sansible configuration " +"file%s." +msgstr "" +"Playbook の実行中に使用する並列または同時プロセスの数です。0 は %sansible 設定ファイル%s のデフォルト値を表します。" + +#: client/src/helpers/Credentials.js:74 +msgid "The project value" +msgstr "プロジェクト値" + +#: client/src/controllers/Projects.js:123 +msgid "" +"The selected project is not configured for SCM. To configure for SCM, edit " +"the project and provide SCM settings, and then run an update." +msgstr "" +"選択されたプロジェクトは SCM に対して設定されていません。SCM の設定を行うには、プロジェクトを編集して SCM " +"設定を指定してから更新を実行します。" + +#: client/src/lists/PortalJobTemplates.js:20 +msgid "There are no job templates to display at this time" +msgstr "現時点で表示できるジョブテンプレートはありません" + +#: client/src/lists/PortalJobs.js:20 +msgid "There are no jobs to display at this time" +msgstr "現時点で表示できるジョブはありません" + +#: client/src/controllers/Projects.js:114 +msgid "" +"There is no SCM update information available for this project. An update has " +"not yet been completed. If you have not already done so, start an update " +"for this project." +msgstr "" +"このプロジェクトに利用できる SCM " +"更新情報はありません。更新はまだ完了していません。まだ更新を実行していない場合は、このプロジェクトの更新を開始してください。" + +#: client/src/configuration/configuration.controller.js:293 +msgid "There was an error resetting value. Returned status:" +msgstr "値のリセット中にエラーが発生しました。返されたステータス:" + +#: client/src/configuration/configuration.controller.js:424 +msgid "There was an error resetting values. Returned status:" +msgstr "値のリセット中にエラーが発生しました。返されたステータス:" + +#: client/src/helpers/Credentials.js:138 +msgid "" +"This is the tenant name. This value is usually the same as the username." +msgstr "これはテナント名です。通常、この値はユーザー名と同じです。" + +#: client/src/notifications/notifications.list.js:21 +msgid "" +"This list is populated by notification templates added from the " +"%sNotifications%s section" +msgstr "この一覧は、%s通知%s セクションで追加される通知テンプレートで事前に設定されます。" + +#: client/src/notifications/notificationTemplates.form.js:199 +msgid "This must be of the form %s." +msgstr "これは %s 形式でなければなりません。" + +#: client/src/forms/Users.js:160 +msgid "This user is not a member of any teams" +msgstr "このユーザーはいずれのチームのメンバーでもありません。" + +#: client/src/shared/form-generator.js:823 +#: client/src/shared/form-generator.js:948 +msgid "" +"This value does not match the password you entered previously. Please " +"confirm that password." +msgstr "この値は、以前に入力されたパスワードと一致しません。パスワードを確認してください。" + +#: client/src/configuration/configuration.controller.js:449 +msgid "" +"This will reset all configuration values to their factory defaults. Are you " +"sure you want to proceed?" +msgstr "これにより、すべての設定値が出荷時の設定にリセットされます。本当に続行してもよいですか?" + +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:14 +msgid "Time" +msgstr "時間" + +#: client/src/license/license.partial.html:45 +msgid "Time Remaining" +msgstr "残りの時間" + +#: client/src/forms/Projects.js:191 +msgid "" +"Time in seconds to consider a project to be current. During job runs and " +"callbacks the task system will evaluate the timestamp of the latest project " +"update. If it is older than Cache Timeout, it is not considered current, and " +"a new project update will be performed." +msgstr "" +"プロジェクトが最新であることを判別するための時間 (秒単位) " +"です。ジョブ実行およびコールバック時に、タスクシステムは最新のプロジェクト更新のタイムスタンプを評価します。これがキャッシュタイムアウトよりも古い場合には、最新とは見なされず、新規のプロジェクト更新が実行されます。" + +#: client/src/forms/Credentials.js:126 +msgid "" +"To learn more about the IAM STS Token, refer to the %sAmazon documentation%s." +"" +msgstr "IAM STS トークンについての詳細は、%sAmazon ドキュメント%s を参照してください。" + +#: client/src/shared/form-generator.js:848 +msgid "Toggle the display of plaintext." +msgstr "プレーンテキストの表示を切り替えます。" + +#: client/src/notifications/shared/type-change.service.js:34 +#: client/src/notifications/shared/type-change.service.js:40 +msgid "Token" +msgstr "トークン" + +#: client/src/forms/Credentials.js:61 +#: client/src/forms/Credentials.js:85 +#: client/src/forms/Teams.js:132 +#: client/src/forms/Users.js:196 +#: client/src/forms/WorkflowMaker.js:34 +#: client/src/lists/CompletedJobs.js:50 +#: client/src/lists/Credentials.js:39 +#: client/src/lists/Projects.js:48 +#: client/src/lists/ScheduledJobs.js:42 +#: client/src/lists/Templates.js:31 +#: client/src/notifications/notificationTemplates.form.js:54 +#: client/src/notifications/notificationTemplates.list.js:38 +#: client/src/notifications/notifications.list.js:31 +msgid "Type" +msgstr "タイプ" + +#: client/src/forms/Credentials.js:25 +#: client/src/notifications/notificationTemplates.form.js:23 +msgid "Type Details" +msgstr "タイプの詳細" + +#: client/src/notifications/notificationTemplates.form.js:212 +#: client/src/notifications/notificationTemplates.form.js:97 +msgid "Type an option on each line." +msgstr "各行にオプションを入力します。" + +#: client/src/notifications/notificationTemplates.form.js:141 +#: client/src/notifications/notificationTemplates.form.js:158 +#: client/src/notifications/notificationTemplates.form.js:370 +msgid "Type an option on each line. The pound symbol (#) is not required." +msgstr "各行にオプションを入力します。シャープ記号 (#) は不要です。" + +#: client/src/controllers/Projects.js:402 +#: client/src/controllers/Projects.js:684 +msgid "URL popover text" +msgstr "URL ポップオーバーテキスト" + +#: client/src/login/loginModal/loginModal.partial.html:49 +msgid "USERNAME" +msgstr "ユーザー名" + +#: client/src/app.js:365 +msgid "USERS" +msgstr "ユーザー" + +#: client/src/controllers/Projects.js:220 +msgid "Update Not Found" +msgstr "更新が見つかりません" + +#: client/src/controllers/Projects.js:693 +msgid "Update in Progress" +msgstr "更新が進行中です" + +#: client/src/forms/Projects.js:172 +msgid "Update on Launch" +msgstr "起動時の更新" + +#: client/src/license/license.partial.html:71 +msgid "Upgrade" +msgstr "アップグレード" + +#: client/src/notifications/notificationTemplates.form.js:404 +msgid "Use SSL" +msgstr "SSL の使用" + +#: client/src/notifications/notificationTemplates.form.js:397 +msgid "Use TLS" +msgstr "TLS の使用" + +#: client/src/forms/Credentials.js:77 +msgid "" +"Used to check out and synchronize playbook repositories with a remote source " +"control management system such as Git, Subversion (svn), or Mercurial (hg). " +"These credentials are used by Projects." +msgstr "" +"Git、Subversion (svn)、または Mercurial (hg) などのリモートソースコントロール管理システムで Playbook " +"リポジトリーをチェックアウトし、同期するために使用されます。これらの認証情報はプロジェクトで使用されます。" + +#: client/src/forms/Credentials.js:449 +#: client/src/forms/Inventories.js:115 +#: client/src/forms/Inventories.js:161 +#: client/src/forms/Organizations.js:83 +#: client/src/forms/Projects.js:244 +#: client/src/forms/Teams.js:94 +msgid "User" +msgstr "ユーザー" + +#: client/src/forms/Users.js:94 +msgid "User Type" +msgstr "ユーザータイプ" + +#: client/src/forms/Users.js:49 +#: client/src/helpers/Credentials.js:117 +#: client/src/helpers/Credentials.js:32 +#: client/src/helpers/Credentials.js:56 +#: client/src/helpers/Credentials.js:88 +#: client/src/lists/Users.js:37 +#: client/src/notifications/notificationTemplates.form.js:64 +msgid "Username" +msgstr "ユーザー名" + +#: client/src/forms/Credentials.js:81 +msgid "" +"Usernames, passwords, and access keys for authenticating to the specified " +"cloud or infrastructure provider. These are used for dynamic inventory " +"sources and for cloud provisioning and deployment in playbook runs." +msgstr "" +"指定されたクラウドまたはインフラストラクチャープロバイダーに対する認証を行うためのユーザー名、パスワード、およびアクセスキーです。これらは動的なインベントリーソースおよび " +"Playbook 実行のクラウドプロビジョニングおよびデプロイメントに使用されます。" + +#: client/src/forms/Teams.js:75 +#: client/src/lists/Users.js:26 +#: client/src/lists/Users.js:27 +#: client/src/setup-menu/setup-menu.partial.html:10 +msgid "Users" +msgstr "ユーザー" + +#: client/src/dashboard/lists/job-templates/job-templates-list.partial.html:7 +#: client/src/dashboard/lists/jobs/jobs-list.partial.html:7 +msgid "VIEW ALL" +msgstr "すべてを表示" + +#: client/src/main-menu/main-menu.partial.html:75 +msgid "VIEW DOCUMENTATION" +msgstr "ドキュメントの表示" + +#: client/src/main-menu/main-menu.partial.html:51 +msgid "VIEW USER PAGE FOR {{ $root.current_user.username | uppercase }}" +msgstr "{{ $root.current_user.username | uppercase }} のユーザーページの表示" + +#: client/src/license/license.partial.html:10 +msgid "Valid License" +msgstr "有効なライセンス" + +#: client/src/forms/Inventories.js:55 +msgid "Variables" +msgstr "変数" + +#: client/src/forms/Credentials.js:389 +msgid "Vault Password" +msgstr "Vault パスワード" + +#: client/src/forms/JobTemplates.js:235 +#: client/src/forms/JobTemplates.js:242 +msgid "Verbosity" +msgstr "詳細" + +#: client/src/about/about.controller.js:24 +#: client/src/license/license.partial.html:15 +msgid "Version" +msgstr "バージョン" + +#: client/src/dashboard/graphs/dashboard-graphs.partial.html:58 +#: client/src/inventory-scripts/inventory-scripts.list.js:65 +#: client/src/lists/Credentials.js:80 +#: client/src/lists/Inventories.js:85 +#: client/src/lists/Teams.js:69 +#: client/src/lists/Templates.js:117 +#: client/src/lists/Users.js:78 +#: client/src/notifications/notificationTemplates.list.js:80 +msgid "View" +msgstr "表示" + +#: client/src/main-menu/main-menu.partial.html:173 +msgid "View Documentation" +msgstr "ドキュメントの表示" + +#: client/src/forms/Inventories.js:65 +msgid "View JSON examples at %s" +msgstr "JSON サンプルを %s に表示" + +#: client/src/forms/JobTemplates.js:450 +#: client/src/forms/Workflows.js:163 +#: client/src/shared/form-generator.js:1715 +msgid "View Survey" +msgstr "Survey の表示" + +#: client/src/forms/Inventories.js:66 +msgid "View YAML examples at %s" +msgstr "YAML サンプルを %s に表示" + +#: client/src/setup-menu/setup-menu.partial.html:47 +msgid "View Your License" +msgstr "ライセンスの表示" + +#: client/src/setup-menu/setup-menu.partial.html:48 +msgid "View and edit your license information." +msgstr "ライセンス情報を表示し、編集します。" + +#: client/src/lists/Credentials.js:82 +msgid "View credential" +msgstr "認証情報の表示" + +#: client/src/setup-menu/setup-menu.partial.html:60 +msgid "View information about this version of Ansible Tower." +msgstr "本バージョンの Ansible Tower 情報を表示します。" + +#: client/src/lists/Inventories.js:87 +msgid "View inventory" +msgstr "インベントリーの表示" + +#: client/src/inventory-scripts/inventory-scripts.list.js:67 +msgid "View inventory script" +msgstr "インベントリースクリプトの表示" + +#: client/src/notifications/notificationTemplates.list.js:82 +msgid "View notification" +msgstr "通知の表示" + +#: client/src/lists/Teams.js:72 +msgid "View team" +msgstr "チームの表示" + +#: client/src/lists/Templates.js:119 +msgid "View template" +msgstr "テンプレートの表示" + +#: client/src/lists/Projects.js:108 +msgid "View the project" +msgstr "プロジェクトの表示" + +#: client/src/lists/ScheduledJobs.js:73 +msgid "View the schedule" +msgstr "スケジュールの表示" + +#: client/src/lists/Users.js:81 +msgid "View user" +msgstr "ユーザーの表示" + +#: client/src/forms/Workflows.js:22 +msgid "WORKFLOW" +msgstr "ワークフロー" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:68 +#: client/src/configuration/configuration.controller.js:168 +#: client/src/configuration/configuration.controller.js:230 +#: client/src/configuration/system-form/configuration-system.controller.js:47 +msgid "Warning: Unsaved Changes" +msgstr "警告: 変更が保存されていません" + +#: client/src/login/loginModal/loginModal.partial.html:17 +msgid "Welcome to Ansible Tower!  Please sign in." +msgstr "Ansible Tower へようこそ!  サインインしてください。" + +#: client/src/license/license.partial.html:78 +msgid "" +"Welcome to Ansible Tower! Please complete the steps below to acquire a " +"license." +msgstr "Ansible Tower へようこそ! ライセンスを取得するために以下のステップを完了してください。" + +#: client/src/forms/JobTemplates.js:55 +#: client/src/forms/WorkflowMaker.js:104 +msgid "" +"When this template is submitted as a job, setting the type to %s will " +"execute the playbook, running tasks on the selected hosts." +msgstr "" +"このテンプレートがジョブとして送信される場合、タイプを %s に設定すると Playbook が実行され、選択されたホストでタスクが実行されます。" + +#: client/src/forms/Workflows.js:187 +#: client/src/shared/form-generator.js:1719 +msgid "Workflow Editor" +msgstr "ワークフローエディター" + +#: client/src/lists/Templates.js:70 +msgid "Workflow Job Template" +msgstr "ワークフロージョブテンプレート" + +#: client/src/controllers/Projects.js:468 +msgid "You do not have access to view this property" +msgstr "これを適切に表示するためのアクセス権がありません。" + +#: client/src/controllers/Projects.js:284 +msgid "You do not have permission to add a project." +msgstr "プロジェクトを追加するパーミッションがありません。" + +#: client/src/controllers/Users.js:141 +msgid "You do not have permission to add a user." +msgstr "ユーザーを追加するパーミッションがありません。" + +#: client/src/configuration/auth-form/configuration-auth.controller.js:67 +#: client/src/configuration/configuration.controller.js:167 +#: client/src/configuration/configuration.controller.js:229 +#: client/src/configuration/system-form/configuration-system.controller.js:46 +msgid "" +"You have unsaved changes. Would you like to proceed without " +"saving?" +msgstr "保存されていない変更があります。変更せずに次に進みますか?" + +#: client/src/shared/form-generator.js:960 +msgid "Your password must be %d characters long." +msgstr "パスワードの長さは %d 文字にしてください。" + +#: client/src/shared/form-generator.js:965 +msgid "Your password must contain a lowercase letter." +msgstr "パスワードには小文字を含める必要があります。" + +#: client/src/shared/form-generator.js:975 +msgid "Your password must contain a number." +msgstr "パスワードには数字を含める必要があります。" + +#: client/src/shared/form-generator.js:970 +msgid "Your password must contain an uppercase letter." +msgstr "パスワードには大文字を含める必要があります。" + +#: client/src/shared/form-generator.js:980 +msgid "Your password must contain one of the following characters: %s" +msgstr "パスワードには以下の文字のいずれかを使用する必要があります: %s" + +#: client/src/controllers/Projects.js:176 +msgid "Your request to cancel the update was submitted to the task manager." +msgstr "更新を取り消す要求がタスクマネージャーに送信されました。" + +#: client/src/login/loginModal/loginModal.partial.html:22 +msgid "Your session timed out due to inactivity. Please sign in." +msgstr "アイドル時間によりセッションがタイムアウトしました。サインインしてください。" + +#: client/src/shared/form-generator.js:1224 +msgid "and" +msgstr "and" + +#: client/src/forms/Credentials.js:139 +#: client/src/forms/Credentials.js:362 +msgid "set in helpers/credentials" +msgstr "ヘルパー/認証情報で設定" + +#: client/src/forms/Credentials.js:379 +msgid "v2 URLs%s - leave blank" +msgstr "v2 URL%s - 空白にする" + +#: client/src/forms/Credentials.js:380 +msgid "v3 default%s - set to 'default'" +msgstr "v3 デフォルト%s - 「デフォルト」に設定" + +#: client/src/forms/Credentials.js:381 +msgid "v3 multi-domain%s - your domain name" +msgstr "v3 マルチドメイン%s - ドメイン名" + From cbf0c2704d7865140f1ccdaf534511ce9154ec07 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 12 Jan 2017 21:06:08 -0500 Subject: [PATCH 112/154] Separate API and UI l10n make targets. A couple reasons for this: - The command for generating the API l10n files is currently broken, and I want to get the translation folks looking at the UI ASAP. - These things require different environments: - Generating the UI files requires Grunt, which is only available *before* packaging. We generate the static files before invoking mock or pbuilder. - Generating the API files requires Django, which is only available inside the virtual environment. This will likely need to be invoked somewhere inside of the install playbooks. --- Makefile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c0c8f62d75..5acfa44f86 100644 --- a/Makefile +++ b/Makefile @@ -559,9 +559,12 @@ messages: fi; \ $(PYTHON) manage.py makemessages -l $(LANG) --keep-pot -# generate l10n .json .mo -languages: $(UI_DEPS_FLAG_FILE) check-po +# generate l10n .json +ui-languages: $(UI_DEPS_FLAG_FILE) check-po $(NPM_BIN) --prefix awx/ui run languages + +# generate l10n .mo +api-languages: @if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/tower/bin/activate; \ fi; \ @@ -592,8 +595,7 @@ ui-devel: $(UI_DEPS_FLAG_FILE) ui-release: $(UI_RELEASE_FLAG_FILE) -# todo: include languages target when .po deliverables are added to source control -$(UI_RELEASE_FLAG_FILE): $(UI_DEPS_FLAG_FILE) +$(UI_RELEASE_FLAG_FILE): ui-languages $(UI_DEPS_FLAG_FILE) $(NPM_BIN) --prefix awx/ui run build-release touch $(UI_RELEASE_FLAG_FILE) From 6b0573da717be6bc5be40bb184e54af753317fde Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 12 Jan 2017 21:07:03 -0500 Subject: [PATCH 113/154] Uncomment line that requests UI l10n files. --- awx/ui/client/src/i18n.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/awx/ui/client/src/i18n.js b/awx/ui/client/src/i18n.js index 9471e04616..b37d05a8c0 100644 --- a/awx/ui/client/src/i18n.js +++ b/awx/ui/client/src/i18n.js @@ -24,10 +24,7 @@ export default var langUrl = langInfo.replace('-', '_'); //gettextCatalog.debug = true; gettextCatalog.setCurrentLanguage(langInfo); - // TODO: the line below is commented out temporarily until - // the .po files are received from the i18n team, in order to avoid - // 404 file not found console errors in dev - // gettextCatalog.loadRemote('/static/languages/' + langUrl + '.json'); + gettextCatalog.loadRemote('/static/languages/' + langUrl + '.json'); }; }]) .factory('i18n', ['gettextCatalog', From b1dc0546fd0b45a12a4829005d3bff7cfb1ae545 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Thu, 12 Jan 2017 17:32:29 -0800 Subject: [PATCH 114/154] fixing selection on the copy/move groups & hosts lists --- .../manage/copy-move/copy-move-groups.controller.js | 2 +- .../manage/copy-move/copy-move-hosts.controller.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/copy-move/copy-move-groups.controller.js b/awx/ui/client/src/inventories/manage/copy-move/copy-move-groups.controller.js index 8c5549addb..9153dad34d 100644 --- a/awx/ui/client/src/inventories/manage/copy-move/copy-move-groups.controller.js +++ b/awx/ui/client/src/inventories/manage/copy-move/copy-move-groups.controller.js @@ -11,7 +11,7 @@ $scope.item = group; $scope.submitMode = $stateParams.groups === undefined ? 'move' : 'copy'; - $scope['toggle_'+ list.iterator] = function(id){ + $scope.toggle_row = function(id){ // toggle off anything else currently selected _.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;}); // yoink the currently selected thing diff --git a/awx/ui/client/src/inventories/manage/copy-move/copy-move-hosts.controller.js b/awx/ui/client/src/inventories/manage/copy-move/copy-move-hosts.controller.js index a01387c173..5c95523036 100644 --- a/awx/ui/client/src/inventories/manage/copy-move/copy-move-hosts.controller.js +++ b/awx/ui/client/src/inventories/manage/copy-move/copy-move-hosts.controller.js @@ -8,10 +8,10 @@ ['$scope', '$state', '$stateParams', 'generateList', 'HostManageService', 'GetBasePath', 'CopyMoveGroupList', 'host', 'Dataset', function($scope, $state, $stateParams, GenerateList, HostManageService, GetBasePath, CopyMoveGroupList, host, Dataset){ var list = CopyMoveGroupList; - + $scope.item = host; $scope.submitMode = 'copy'; - $scope['toggle_'+ list.iterator] = function(id){ + $scope.toggle_row = function(id){ // toggle off anything else currently selected _.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;}); // yoink the currently selected thing From 5344c66f45cfefd90205e0725e0c5222516577c4 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 11:50:27 -0500 Subject: [PATCH 115/154] Only show groups with a dynamic inventory source in the workflow editor --- awx/ui/client/src/lists/InventorySources.js | 8 ++++++-- awx/ui/client/src/shared/generator-helpers.js | 6 ++++++ awx/ui/client/src/templates/main.js | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/lists/InventorySources.js b/awx/ui/client/src/lists/InventorySources.js index 127352c72b..0b822b516c 100644 --- a/awx/ui/client/src/lists/InventorySources.js +++ b/awx/ui/client/src/lists/InventorySources.js @@ -18,9 +18,13 @@ export default fields: { name: { - key: true, label: 'Name', - columnClass: 'col-md-11' + ngBind: 'inventory_source.summary_fields.group.name', + columnClass: 'col-md-11', + simpleTip: { + awToolTip: "Inventory: {{inventory_source.summary_fields.inventory.name}}", + dataPlacement: "top" + } } }, diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js index e6d517b7f0..34978cdf5e 100644 --- a/awx/ui/client/src/shared/generator-helpers.js +++ b/awx/ui/client/src/shared/generator-helpers.js @@ -591,6 +591,9 @@ angular.module('GeneratorHelpers', [systemStatus.name]) } } else { + if(field.simpleTip) { + html += ``; + } // Add icon: if (field.ngShowIcon) { html += " "; @@ -615,6 +618,9 @@ angular.module('GeneratorHelpers', [systemStatus.name]) if (field.text) { html += field.text; } + if(field.simpleTip) { + html += ``; + } } if (list.name === 'hosts' || list.name === 'groups') { diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index e925b2e9f6..d712faa3d8 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -103,7 +103,8 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA }, inventory_source_search: { value: { - page_size: '5' + page_size: '5', + not__source: '' }, squash: true, dynamic: true From eaf68004d4972028ad26f2adc323f55a746aeb87 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 11:58:48 -0500 Subject: [PATCH 116/154] Fixed unit test failure --- awx/ui/tests/spec/workflows/workflow-add.controller-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js index e4ec2d6562..d59b21543f 100644 --- a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js +++ b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js @@ -84,6 +84,10 @@ describe('Controller: WorkflowAdd', () => { .whenGET('/api') .respond(200, ''); + httpBackend + .whenGET('/static/languages/en_US.json') + .respond(200, ''); + TemplatesService.getLabelOptions = jasmine.createSpy('getLabelOptions').and.returnValue(getLabelsDeferred.promise); TemplatesService.createWorkflowJobTemplate = jasmine.createSpy('createWorkflowJobTemplate').and.returnValue(createWorkflowJobTemplateDeferred.promise); From 5f4dcfe8b4553a3204d7161a843bf6b66293109e Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Fri, 13 Jan 2017 12:44:07 -0500 Subject: [PATCH 117/154] say goodbye to 2016 --- awx/templates/rest_framework/api.html | 2 +- awx/ui/client/src/about/about.partial.html | 2 +- awx/ui/client/src/footer/footer.partial.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/templates/rest_framework/api.html b/awx/templates/rest_framework/api.html index 746521f542..3b75c4a35c 100644 --- a/awx/templates/rest_framework/api.html +++ b/awx/templates/rest_framework/api.html @@ -52,7 +52,7 @@
diff --git a/awx/ui/client/src/about/about.partial.html b/awx/ui/client/src/about/about.partial.html index bcb2a5cd33..e1c44a588b 100644 --- a/awx/ui/client/src/about/about.partial.html +++ b/awx/ui/client/src/about/about.partial.html @@ -23,7 +23,7 @@
diff --git a/awx/ui/client/src/footer/footer.partial.html b/awx/ui/client/src/footer/footer.partial.html index 9aaaeb7f75..4a34bde28a 100644 --- a/awx/ui/client/src/footer/footer.partial.html +++ b/awx/ui/client/src/footer/footer.partial.html @@ -1,3 +1,3 @@ From 1f40569a32a62ff2fb3ca85146fc214f0f5f13e1 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 13:07:36 -0500 Subject: [PATCH 118/154] Changed backend mock to /static regex to catch all static requests and return 200 --- awx/ui/tests/spec/workflows/workflow-add.controller-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js index d59b21543f..57e9e6ca6c 100644 --- a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js +++ b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js @@ -84,9 +84,9 @@ describe('Controller: WorkflowAdd', () => { .whenGET('/api') .respond(200, ''); - httpBackend - .whenGET('/static/languages/en_US.json') - .respond(200, ''); + $httpBackend + .whenGET(/\/static\/*/) + .respond(200, {}); TemplatesService.getLabelOptions = jasmine.createSpy('getLabelOptions').and.returnValue(getLabelsDeferred.promise); TemplatesService.createWorkflowJobTemplate = jasmine.createSpy('createWorkflowJobTemplate').and.returnValue(createWorkflowJobTemplateDeferred.promise); From 387976043069e261c8a2ab73ccb355cc87f3cdcb Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 13:30:17 -0500 Subject: [PATCH 119/154] Only try to regex replace quotes in strings --- awx/ui/client/src/shared/smart-search/queryset.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index c759e86669..f333abc57b 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -80,7 +80,9 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear return concated; } else { - value = value.replace(/"|'/g, ""); + if(value && typeof value === 'string') { + value = value.replace(/"|'/g, ""); + } return `${key}=${value}&`; } } From b6493cacd59110d7f02ba9cb7407ca45fd54475b Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 12 Jan 2017 17:35:55 -0500 Subject: [PATCH 120/154] small tweaks to make workflow endpoints load better --- awx/api/serializers.py | 2 +- awx/main/access.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 31126da821..649d55fe4c 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2370,7 +2370,7 @@ class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer): if view and view.request: request_method = view.request.method if request_method in ['PATCH']: - obj = view.get_object() + obj = self.instance char_prompts = copy.copy(obj.char_prompts) char_prompts.update(self.extract_char_prompts(data)) else: diff --git a/awx/main/access.py b/awx/main/access.py index 75b53d2527..6f4f76ee4d 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1395,7 +1395,8 @@ class WorkflowJobTemplateNodeAccess(BaseAccess): qs = self.model.objects.filter( workflow_job_template__in=WorkflowJobTemplate.accessible_objects( self.user, 'read_role')) - qs = qs.prefetch_related('success_nodes', 'failure_nodes', 'always_nodes') + qs = qs.prefetch_related('success_nodes', 'failure_nodes', 'always_nodes', + 'unified_job_template') return qs def can_use_prompted_resources(self, data): From ba8f0ccff111bd060272677db5ce134715740edd Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 13 Jan 2017 11:44:50 -0500 Subject: [PATCH 121/154] require constituent resource access to relaunch WJ --- awx/main/access.py | 9 ++------- awx/main/models/workflow.py | 3 --- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 75b53d2527..43508c6c81 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1656,11 +1656,6 @@ class WorkflowJobAccess(BaseAccess): if self.user not in wfjt.execute_role: return False - # WFJT is valid base for WJ, launch permitted - last_modified = wfjt.nodes_last_modified() - if last_modified and obj.created > last_modified: - return True - # user's WFJT access doesn't guarentee permission to launch, introspect nodes return self.can_recreate(obj) @@ -1672,8 +1667,8 @@ class WorkflowJobAccess(BaseAccess): if not node_access.can_add({'reference_obj': node}): wj_add_perm = False if not wj_add_perm and self.save_messages: - self.messages['workflow_job_template'] = _('Template has been modified since job was launched, ' - 'and you do not have permission to its resources.') + self.messages['workflow_job_template'] = _('You do not have permission to the workflow job ' + 'resources required for relaunch.') return wj_add_perm def can_cancel(self, obj): diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index a92fbd5560..11d4c37601 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -327,9 +327,6 @@ class WorkflowJobOptions(BaseModel): new_workflow_job.copy_nodes_from_original(original=self) return new_workflow_job - def nodes_last_modified(self): - return self.workflow_nodes.aggregate(models.Max('modified'))['modified__max'] - class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin): class Meta: From a93ad270f27b51c0372e096ef016c0a3e0e7053f Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 13 Jan 2017 14:44:58 -0500 Subject: [PATCH 122/154] update workflow serializer fixture to use serializer instance --- awx/main/tests/unit/api/serializers/test_workflow_serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py index b444531206..b8697db71f 100644 --- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py @@ -125,6 +125,7 @@ class TestWorkflowJobTemplateNodeSerializerCharPrompts(): serializer = WorkflowJobTemplateNodeSerializer() node = WorkflowJobTemplateNode(pk=1) node.char_prompts = {'limit': 'webservers'} + serializer.instance = node view = FakeView(node) view.request = FakeRequest() view.request.method = "PATCH" From e0cd4bc7aee2eeceb01856b3c729e67c329e2eaf Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Fri, 13 Jan 2017 14:47:42 -0500 Subject: [PATCH 123/154] Get Japanese API l10 working Worked with matburt on this. There were some inconsistencies between our code and the po files. --- awx/locale/ja/LC_MESSAGES/django.po | 6 +++--- awx/main/models/inventory.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/awx/locale/ja/LC_MESSAGES/django.po b/awx/locale/ja/LC_MESSAGES/django.po index 3d734acfd1..34aee63799 100644 --- a/awx/locale/ja/LC_MESSAGES/django.po +++ b/awx/locale/ja/LC_MESSAGES/django.po @@ -1961,17 +1961,17 @@ msgstr "認証情報がクラウドソースに必要です。" #: main/models/inventory.py:1005 #, python-format -msgid "Invalid %(source)s region%(plural)s: %(region)s" +msgid "Invalid %(source)s region: %(region)s" msgstr "無効な %(source)s リージョン: %(region)s" #: main/models/inventory.py:1031 #, python-format -msgid "Invalid filter expression%(plural)s: %(filter)s" +msgid "Invalid filter expression: %(filter)s" msgstr "無効なフィルター式: %(filter)s" #: main/models/inventory.py:1050 #, python-format -msgid "Invalid group by choice%(plural)s: %(choice)s" +msgid "Invalid group by choice: %(choice)s" msgstr "無効なグループ (選択による): %(choice)s" #: main/models/inventory.py:1198 diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index e7183f356d..b01d44802c 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1002,9 +1002,8 @@ class InventorySourceOptions(BaseModel): if r not in valid_regions and r not in invalid_regions: invalid_regions.append(r) if invalid_regions: - raise ValidationError(_('Invalid %(source)s region%(plural)s: %(region)s') % { - 'source': self.source, 'plural': '' if len(invalid_regions) == 1 else 's', - 'region': ', '.join(invalid_regions)}) + raise ValidationError(_('Invalid %(source)s region: %(region)s') % { + 'source': self.source, 'region': ', '.join(invalid_regions)}) return ','.join(regions) source_vars_dict = VarsDictProperty('source_vars') @@ -1028,9 +1027,8 @@ class InventorySourceOptions(BaseModel): if instance_filter_name not in self.INSTANCE_FILTER_NAMES: invalid_filters.append(instance_filter) if invalid_filters: - raise ValidationError(_('Invalid filter expression%(plural)s: %(filter)s') % - {'plural': '' if len(invalid_filters) == 1 else 's', - 'filter': ', '.join(invalid_filters)}) + raise ValidationError(_('Invalid filter expression: %(filter)s') % + {'filter': ', '.join(invalid_filters)}) return instance_filters def clean_group_by(self): @@ -1047,9 +1045,8 @@ class InventorySourceOptions(BaseModel): if c not in valid_choices and c not in invalid_choices: invalid_choices.append(c) if invalid_choices: - raise ValidationError(_('Invalid group by choice%(plural)s: %(choice)s') % - {'plural': '' if len(invalid_choices) == 1 else 's', - 'choice': ', '.join(invalid_choices)}) + raise ValidationError(_('Invalid group by choice: %(choice)s') % + {'choice': ', '.join(invalid_choices)}) return ','.join(choices) From 8b7435ccd32a9f0ab81ccbc1f8f00366b2320edb Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Fri, 13 Jan 2017 14:49:22 -0500 Subject: [PATCH 124/154] Manually add .mo files (for now) to get API l10 working. --- .gitignore | 1 - awx/locale/en-us/LC_MESSAGES/django.mo | Bin 0 -> 378 bytes awx/locale/fr/LC_MESSAGES/ansible-tower-ui.mo | Bin 0 -> 55644 bytes awx/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 108818 bytes awx/locale/ja/LC_MESSAGES/ansible-tower-ui.mo | Bin 0 -> 59326 bytes awx/locale/ja/LC_MESSAGES/django.mo | Bin 0 -> 113949 bytes 6 files changed, 1 deletion(-) create mode 100644 awx/locale/en-us/LC_MESSAGES/django.mo create mode 100644 awx/locale/fr/LC_MESSAGES/ansible-tower-ui.mo create mode 100644 awx/locale/fr/LC_MESSAGES/django.mo create mode 100644 awx/locale/ja/LC_MESSAGES/ansible-tower-ui.mo create mode 100644 awx/locale/ja/LC_MESSAGES/django.mo diff --git a/.gitignore b/.gitignore index ca9dd12298..afd8aa7187 100644 --- a/.gitignore +++ b/.gitignore @@ -106,7 +106,6 @@ reports *.log.[0-9] *.results local/ -*.mo # AWX python libs populated by requirements.txt awx/lib/.deps_built diff --git a/awx/locale/en-us/LC_MESSAGES/django.mo b/awx/locale/en-us/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..927001688e1cd1b6cde5d28b82bb242a91913423 GIT binary patch literal 378 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 OWMUdE!#tek^nU=}IpTr< literal 0 HcmV?d00001 diff --git a/awx/locale/fr/LC_MESSAGES/django.mo b/awx/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..7eb3f5d74cf53cb148bda537e05fe1355c91062a GIT binary patch literal 108818 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 literal 0 HcmV?d00001 diff --git a/awx/locale/ja/LC_MESSAGES/django.mo b/awx/locale/ja/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..fa812f4296dc7c04892e8b60d0fdd9e556b7c38e GIT binary patch literal 113949 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 UPukK)FL>o4v&p>Ab?b`%2SY!XVE_OC literal 0 HcmV?d00001 From f5173a9d27176925d1c43c3321a52007048b34c3 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Fri, 13 Jan 2017 15:37:23 -0500 Subject: [PATCH 125/154] put the word granularity in double quotes --- awx/api/templates/api/system_job_template_launch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/templates/api/system_job_template_launch.md b/awx/api/templates/api/system_job_template_launch.md index 4543014005..9433bcadfa 100644 --- a/awx/api/templates/api/system_job_template_launch.md +++ b/awx/api/templates/api/system_job_template_launch.md @@ -13,7 +13,7 @@ Which will act on data older than 30 days. For `cleanup_facts`: -`{"older_than": "4w", `granularity`: "3d"}` +`{"older_than": "4w", "granularity": "3d"}` Which will reduce the granularity of scan data to one scan per 3 days when the data is older than 4w. From 4d66c6dd0aa34858937e81951fb469dedce176ac Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 15:54:29 -0500 Subject: [PATCH 126/154] Generate the correct base path for groups and hosts so that paginate works properly --- .../inventories/manage/inventory-manage.route.js | 16 ++++++++++++---- .../src/shared/paginate/paginate.controller.js | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js index ddcbf85e1d..87d2daacb4 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.route.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js @@ -98,9 +98,13 @@ export default { }, // target ui-views with name@inventoryManage state 'groupsList@inventoryManage': { - templateProvider: function(InventoryGroups, generateList, $templateRequest) { + templateProvider: function(InventoryGroups, generateList, $templateRequest, $stateParams, GetBasePath) { + let list = _.cloneDeep(InventoryGroups); + if($stateParams && $stateParams.group) { + list.basePath = GetBasePath('groups') + $stateParams.group[$stateParams.group.length-1] + '/children'; + } let html = generateList.build({ - list: InventoryGroups, + list: list, mode: 'edit' }); html = generateList.wrapPanel(html); @@ -112,9 +116,13 @@ export default { controller: GroupsListController }, 'hostsList@inventoryManage': { - templateProvider: function(InventoryHosts, generateList) { + templateProvider: function(InventoryHosts, generateList, $stateParams, GetBasePath) { + let list = _.cloneDeep(InventoryHosts); + if($stateParams && $stateParams.group) { + list.basePath = GetBasePath('groups') + $stateParams.group[$stateParams.group.length-1] + '/all_hosts'; + } let html = generateList.build({ - list: InventoryHosts, + list: list, mode: 'edit' }); return generateList.wrapPanel(html); diff --git a/awx/ui/client/src/shared/paginate/paginate.controller.js b/awx/ui/client/src/shared/paginate/paginate.controller.js index 407f9e10f6..bd9ff29032 100644 --- a/awx/ui/client/src/shared/paginate/paginate.controller.js +++ b/awx/ui/client/src/shared/paginate/paginate.controller.js @@ -18,7 +18,7 @@ export default ['$scope', '$stateParams', '$state', '$filter', 'GetBasePath', 'Q return; } path = GetBasePath($scope.basePath) || $scope.basePath; - queryset = _.merge($stateParams[`${$scope.iterator}_search`], { page: page.toString() }); + queryset = _.merge($stateParams[`${$scope.iterator}_search`], { page: page }); $state.go('.', { [$scope.iterator + '_search']: queryset }); @@ -59,7 +59,7 @@ export default ['$scope', '$stateParams', '$state', '$filter', 'GetBasePath', 'Q return `1 - ${pageSize}`; } else { let floor = (($scope.current() - 1) * parseInt(pageSize)) + 1; - let ceil = floor + parseInt(pageSize); + let ceil = floor + parseInt(pageSize) < $scope.dataset.count ? floor + parseInt(pageSize) : $scope.dataset.count; return `${floor} - ${ceil}`; } } From 05c3f5fbea430b2773cde0ac62e28ffdc6b26a0b Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 16:10:21 -0500 Subject: [PATCH 127/154] Fixed bug that was preventing wfjt survey deletion --- .../src/templates/survey-maker/surveys/delete.factory.js | 4 +++- .../client/src/templates/survey-maker/surveys/init.factory.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js index 0dbe5b4c94..48565727f4 100644 --- a/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js +++ b/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js @@ -13,6 +13,7 @@ export default var scope = params.scope, id = params.id, + templateType = params.templateType, url; @@ -35,7 +36,8 @@ export default scope.$emit("SurveyDeleted"); } else { - url = GetBasePath('job_templates')+ id + '/survey_spec/'; + let basePath = templateType === 'workflow_job_template' ? GetBasePath('workflow_job_templates') : GetBasePath('job_templates'); + url = basePath + id + '/survey_spec/'; Rest.setUrl(url); Rest.destroy() diff --git a/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js index d72148875d..e1508e4c4a 100644 --- a/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js +++ b/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js @@ -52,7 +52,8 @@ export default // and closing the modal after success DeleteSurvey({ scope: scope, - id: id + id: id, + templateType: templateType }); }; From 912033f223fda10ead340005a9bd6ded4f638fcd Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Fri, 13 Jan 2017 17:11:08 -0500 Subject: [PATCH 128/154] workflow doc RBAC rules audit --- docs/workflow.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/workflow.md b/docs/workflow.md index d7336bce08..5bb5c759b8 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -58,9 +58,12 @@ Workflow jobs cannot be copied directly, instead a workflow job is implicitly co * Verify that CRUD operations on all workflow resources are working properly. Note workflow job nodes cannot be created or deleted independently, but verifications are needed to make sure when a workflow job is deleted, all its related workflow job nodes are deleted. * Verify the RBAC property of workflow resources. In specific: * Workflow job templates can only be accessible by superusers ---- system admin, admin of the same organization and system auditor and auditor of the same organization with read permission only. - * Workflow jobs follows the permission rules of its associated workflow job template. - * Workflow job template nodes rely their permission rules on the permission rules of both their associated workflow job template and unified job template. - * Workflow job nodes follows the permission rules of both its associated workflow job and unified job. + * Workflow job read and delete permissions follow from its associated workflow job template. + * Workflow job relaunch permission consists of the union of execute permission to its associated workflow job template, and the permission to re-create all the nodes inside of the workflow job. + * Workflow job template nodes rely their permission rules on the permission rules of both their associated workflow job template and unified job template for creation and editing. + * Workflow job nodes can be deleted with only permission to their workflow job template. + * Workflow job nodes are viewable if its workflow job is viewable. + * No CRUD actions are possible on workflow job nodes by any user, and they may only be deleted by deleting their workflow job. * Verify that workflow job template nodes can be created under, or (dis)associated with workflow job templates. * Verify that only the permitted types of job template types can be associated with a workflow job template node. Currently the permitted types are *job templates, inventory sources and projects*. * Verify that workflow job template nodes under the same workflow job template can be associated to form parent-child relationship of decision trees. In specific, one node takes another as its child node by POSTing another node's id to one of the three endpoints: `/success_nodes/`, `/failure_nodes/` and `/always_nodes/`. From 2af4a2d558a5d5f251d0ecb2e44197e5df27cc25 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Fri, 13 Jan 2017 15:05:33 -0800 Subject: [PATCH 129/154] adding extra check for string in encodeParams --- awx/ui/client/src/shared/smart-search/queryset.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index f333abc57b..3b7fb90c5e 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -74,7 +74,9 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear if (Array.isArray(value)){ let concated = ''; angular.forEach(value, function(item){ - item = item.replace(/"|'/g, ""); + if(item && typeof item === 'string') { + item = item.replace(/"|'/g, ""); + } concated += `${key}=${item}&`; }); return concated; From 66e5cf88ade3b8fca8b441bc22c47d878ee12743 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 13 Jan 2017 18:49:42 -0500 Subject: [PATCH 130/154] Fixed basePath at the root level of the inventory manage (thanks @jared) --- .../src/inventories/manage/inventory-manage.route.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js index 87d2daacb4..ca7589a3e1 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.route.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js @@ -101,7 +101,11 @@ export default { templateProvider: function(InventoryGroups, generateList, $templateRequest, $stateParams, GetBasePath) { let list = _.cloneDeep(InventoryGroups); if($stateParams && $stateParams.group) { - list.basePath = GetBasePath('groups') + $stateParams.group[$stateParams.group.length-1] + '/children'; + list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/children'; + } + else { + //reaches here if the user is on the root level group + list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/root_groups'; } let html = generateList.build({ list: list, @@ -119,7 +123,11 @@ export default { templateProvider: function(InventoryHosts, generateList, $stateParams, GetBasePath) { let list = _.cloneDeep(InventoryHosts); if($stateParams && $stateParams.group) { - list.basePath = GetBasePath('groups') + $stateParams.group[$stateParams.group.length-1] + '/all_hosts'; + list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/all_hosts'; + } + else { + //reaches here if the user is on the root level group + list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts'; } let html = generateList.build({ list: list, From 2217594f2739f82613f7b4db68e8dcb53bb54ff1 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Fri, 13 Jan 2017 16:56:57 -0800 Subject: [PATCH 131/154] Changing "Use TLS/Use SSL" radio buttons on the notification template form (for email) --- .../src/notifications/add/add.controller.js | 21 +++++++-- .../src/notifications/edit/edit.controller.js | 46 +++++++++++++------ .../notificationTemplates.form.js | 26 +++++------ 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/awx/ui/client/src/notifications/add/add.controller.js b/awx/ui/client/src/notifications/add/add.controller.js index 1f007c3e2f..1ebb74d96a 100644 --- a/awx/ui/client/src/notifications/add/add.controller.js +++ b/awx/ui/client/src/notifications/add/add.controller.js @@ -115,6 +115,17 @@ export default ['$rootScope', 'Rest', 'Wait', 'NotificationsFormObject', }); }; + $scope.emailOptionsChange = function () { + if ($scope.email_options === 'use_ssl') { + $scope.use_ssl = true; + $scope.use_tls = false; + } + else if ($scope.email_options === 'use_tls') { + $scope.use_ssl = false; + $scope.use_tls = true; + } + }; + // Save $scope.formSave = function() { var params, @@ -156,13 +167,13 @@ export default ['$rootScope', 'Rest', 'Wait', 'NotificationsFormObject', .filter(i => (form.fields[i].ngShow && form.fields[i].ngShow.indexOf(v) > -1)) .map(i => [i, processValue($scope[i], i, form.fields[i])])); - delete params.notification_configuration.checkbox_group; + delete params.notification_configuration.email_options; - for (var j = 0; j < form.fields.checkbox_group.fields.length; j++) { - if (form.fields.checkbox_group.fields[j].ngShow && form.fields.checkbox_group.fields[j].ngShow.indexOf(v) > -1) { - params.notification_configuration[form.fields.checkbox_group.fields[j].name] = Boolean($scope[form.fields.checkbox_group.fields[j].name]); + for(var j = 0; j < form.fields.email_options.options.length; j++) { + if(form.fields.email_options.options[j].ngShow && form.fields.email_options.options[j].ngShow.indexOf(v) > -1) { + params.notification_configuration[form.fields.email_options.options[j].value] = Boolean($scope[form.fields.email_options.options[j].value]); + } } - } Wait('start'); Rest.setUrl(url); diff --git a/awx/ui/client/src/notifications/edit/edit.controller.js b/awx/ui/client/src/notifications/edit/edit.controller.js index 57f4a25055..331e185347 100644 --- a/awx/ui/client/src/notifications/edit/edit.controller.js +++ b/awx/ui/client/src/notifications/edit/edit.controller.js @@ -67,15 +67,25 @@ export default ['Rest', 'Wait', master[fld] = data[fld]; } - if (form.fields[fld].type === 'checkbox_group') { - // Loop across the group and put the child data on scope - for (var j = 0; j < form.fields[fld].fields.length; j++) { - if (data.notification_configuration[form.fields[fld].fields[j].name]) { - $scope[form.fields[fld].fields[j].name] = data.notification_configuration[form.fields[fld].fields[j].name]; - master[form.fields[fld].fields[j].name] = data.notification_configuration[form.fields[fld].fields[j].name]; - } + if(form.fields[fld].type === 'radio_group') { + if(data.notification_configuration.use_ssl === true){ + $scope.email_options = "use_ssl"; + master.email_options = "use_ssl"; + $scope.use_ssl = true; + master.use_ssl = true; + $scope.use_tls = false; + master.use_tls = false; } - } else { + if(data.notification_configuration.use_tls === true){ + $scope.email_options = "use_tls"; + master.email_options = "use_tls"; + $scope.use_ssl = false; + master.use_ssl = false; + $scope.use_tls = true; + master.use_tls = true; + } + } + else { if (data.notification_configuration[fld]) { $scope[fld] = data.notification_configuration[fld]; master[fld] = data.notification_configuration[fld]; @@ -193,6 +203,17 @@ export default ['Rest', 'Wait', }); }; + $scope.emailOptionsChange = function () { + if ($scope.email_options === 'use_ssl') { + $scope.use_ssl = true; + $scope.use_tls = false; + } + else if ($scope.email_options === 'use_tls') { + $scope.use_ssl = false; + $scope.use_tls = true; + } + }; + $scope.formSave = function() { var params, v = $scope.notification_type.value; @@ -233,13 +254,10 @@ export default ['Rest', 'Wait', .filter(i => (form.fields[i].ngShow && form.fields[i].ngShow.indexOf(v) > -1)) .map(i => [i, processValue($scope[i], i, form.fields[i])])); - delete params.notification_configuration.checkbox_group; + delete params.notification_configuration.email_options; - for (var j = 0; j < form.fields.checkbox_group.fields.length; j++) { - if (form.fields.checkbox_group.fields[j].ngShow && form.fields.checkbox_group.fields[j].ngShow.indexOf(v) > -1) { - params.notification_configuration[form.fields.checkbox_group.fields[j].name] = Boolean($scope[form.fields.checkbox_group.fields[j].name]); - } - } + params.notification_configuration.use_ssl = $scope.use_ssl; + params.notification_configuration.use_tls = $scope.use_tls; Wait('start'); Rest.setUrl(url + id + '/'); diff --git a/awx/ui/client/src/notifications/notificationTemplates.form.js b/awx/ui/client/src/notifications/notificationTemplates.form.js index 51fd4125d0..63a87c3bb2 100644 --- a/awx/ui/client/src/notifications/notificationTemplates.form.js +++ b/awx/ui/client/src/notifications/notificationTemplates.form.js @@ -388,25 +388,23 @@ export default ['i18n', function(i18n) { subForm: 'typeSubForm', ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' }, - checkbox_group: { - label: i18n._('Options'), - type: 'checkbox_group', + email_options: { + label: 'Options', + type: 'radio_group', subForm: 'typeSubForm', ngShow: "notification_type.value == 'email'", - fields: [{ - name: 'use_tls', - label: i18n._('Use TLS'), - type: 'checkbox', + class: 'squeeze', + ngChange: "emailOptionsChange()", + options: [{ + value: 'use_tls', + label: 'Use TLS', ngShow: "notification_type.value == 'email' ", - labelClass: 'checkbox-options stack-inline', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' + labelClass: 'checkbox-options stack-inline' }, { - name: 'use_ssl', - label: i18n._('Use SSL'), - type: 'checkbox', + value: 'use_ssl', + label: 'Use SSL', ngShow: "notification_type.value == 'email'", - labelClass: 'checkbox-options stack-inline', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' + labelClass: 'checkbox-options stack-inline' }] } }, From d88fa050097b107c5d289a2e2c1662715b084e70 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Sat, 14 Jan 2017 11:09:52 -0500 Subject: [PATCH 132/154] workflow RBAC docs feedback --- docs/workflow.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/workflow.md b/docs/workflow.md index 5bb5c759b8..0843dee289 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -61,9 +61,10 @@ Workflow jobs cannot be copied directly, instead a workflow job is implicitly co * Workflow job read and delete permissions follow from its associated workflow job template. * Workflow job relaunch permission consists of the union of execute permission to its associated workflow job template, and the permission to re-create all the nodes inside of the workflow job. * Workflow job template nodes rely their permission rules on the permission rules of both their associated workflow job template and unified job template for creation and editing. - * Workflow job nodes can be deleted with only permission to their workflow job template. + * Workflow job template nodes can be deleted with permission to their workflow job template (even lacking permission to its job template). * Workflow job nodes are viewable if its workflow job is viewable. * No CRUD actions are possible on workflow job nodes by any user, and they may only be deleted by deleting their workflow job. + * Workflow jobs can be deleted by superusers and org admins of the organization of its associated workflow job template, and no one else. * Verify that workflow job template nodes can be created under, or (dis)associated with workflow job templates. * Verify that only the permitted types of job template types can be associated with a workflow job template node. Currently the permitted types are *job templates, inventory sources and projects*. * Verify that workflow job template nodes under the same workflow job template can be associated to form parent-child relationship of decision trees. In specific, one node takes another as its child node by POSTing another node's id to one of the three endpoints: `/success_nodes/`, `/failure_nodes/` and `/always_nodes/`. From a58433ed9bad119a248cbf5a8efbcf01e947756c Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 16 Jan 2017 08:31:35 -0500 Subject: [PATCH 133/154] activity stream, display name of resource as title related to #4185 * When activity stream is gone to from a resource edit view, it should: * only show activity for the particular object * display the name of the resource as a title This commit patch half-ass does those two things. So what's missing? That questions is best answered by looking at how the feature behaves in tower 3.0.3. You'll notice that you are "locked" on the resource you are viewing the activity stream for. With this commit: * You aren't locked * The removal of the resource-id filter (smart-search) is borked. So what do we need to do?: * Lock the view to the resource being viewed How do we need to do it?: * router params.activity_search.values is a "static" thing. It is used to tell smart-search to not generate search tags. This is what we want to do. However, this is a contructor-only parameter. Further, the value "value" gets passed around to all sorts of other UI contructors that are hard to get access to when they are needed to try and dynamically set smart-search ignore search-tags. --- .../client/src/bread-crumb/bread-crumb.directive.js | 3 +++ .../client/src/shared/stateDefinitions.factory.js | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js index 7e58100ea0..3c64a0b701 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js +++ b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js @@ -47,6 +47,9 @@ export default order_by: '-timestamp', page_size: '20', }; + if (streamConfig.activityStreamTarget && streamConfig.activityStreamId) { + stateGoParams.activity_search[streamConfig.activityStreamTarget] = $state.params[streamConfig.activityStreamId]; + } } else { stateGoParams.activity_search = { diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js index 96e7493598..0d13e4ed7f 100644 --- a/awx/ui/client/src/shared/stateDefinitions.factory.js +++ b/awx/ui/client/src/shared/stateDefinitions.factory.js @@ -177,7 +177,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat break; case 'edit': url = params.urls && params.urls.edit ? params.urls.edit : (params.url ? params.url : `/:${form.name}_id`); - formNode = $stateExtender.buildDefinition({ + let formNodeState = { name: params.name || `${params.parent}.edit`, url: url, ncyBreadcrumb: { @@ -215,8 +215,15 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat return Rest.get(); } ] - } - }); + }, + }; + if (params.data && params.data.activityStreamTarget) { + formNodeState.data = {}; + formNodeState.data.activityStreamId = params.data.activityStreamTarget + '_id'; + + } + formNode = $stateExtender.buildDefinition(formNodeState); + if (params.resolve && params.resolve.edit) { formNode.resolve = _.merge(formNode.resolve, params.resolve.edit); } From 2088b1fcfe811d90b0553a282e54a76fc0feca13 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 16 Jan 2017 11:07:38 -0500 Subject: [PATCH 134/154] omit search-tags dynamically --- .../activity-stream/activitystream.controller.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/awx/ui/client/src/activity-stream/activitystream.controller.js b/awx/ui/client/src/activity-stream/activitystream.controller.js index 05609d3bc5..ada72d572c 100644 --- a/awx/ui/client/src/activity-stream/activitystream.controller.js +++ b/awx/ui/client/src/activity-stream/activitystream.controller.js @@ -12,6 +12,7 @@ function activityStreamController($scope, $state, subTitle, Stream, GetTargetTitle, list, Dataset) { init(); + initOmitSmartTags(); function init() { // search init @@ -33,6 +34,20 @@ function activityStreamController($scope, $state, subTitle, Stream, GetTargetTit }); } + // Specification of smart-tags omission from the UI is done in the route/state init. + // A limitation is that this specficiation is static and the key for which to be omitted from + // the smart-tags must be known at that time. + // In the case of activity stream, we won't to dynamically ommit the resource for which we are + // displaying the activity stream for. i.e. 'project', 'credential', etc. + function initOmitSmartTags() { + let defaults, route = _.find($state.$current.path, (step) => { + return step.params.hasOwnProperty('activity_search'); + }); + if (route && $state.params.target !== undefined) { + defaults = route.params.activity_search.config.value; + defaults[$state.params.target] = null; + } + } } export default ['$scope', '$state', 'subTitle', 'Stream', 'GetTargetTitle', 'StreamList', 'Dataset', activityStreamController]; From cd5eed9828f0f441ad50e1d5135845360506c33e Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 16 Jan 2017 13:54:07 -0500 Subject: [PATCH 135/154] remove extra censoring code from tower, not needed since Ansible 2.1+ --- awx/lib/tower_display_callback/module.py | 32 +----------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/tower_display_callback/module.py index 7336da2f08..beeddffbd5 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/tower_display_callback/module.py @@ -77,41 +77,11 @@ class BaseCallbackModule(CallbackBase): super(BaseCallbackModule, self).__init__() self.task_uuids = set() - def censor_result(self, res, no_log=False): - if not isinstance(res, dict): - if no_log: - return "the output has been hidden due to the fact that 'no_log: true' was specified for this result" - return res - if res.get('_ansible_no_log', no_log): - new_res = {} - for k in self.CENSOR_FIELD_WHITELIST: - if k in res: - new_res[k] = res[k] - if k == 'cmd' and k in res: - if isinstance(res['cmd'], list): - res['cmd'] = ' '.join(res['cmd']) - if re.search(r'\s', res['cmd']): - new_res['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$', - r'\1 ', - res['cmd']) - new_res['censored'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" - res = new_res - if 'results' in res: - if isinstance(res['results'], list): - for i in xrange(len(res['results'])): - res['results'][i] = self.censor_result(res['results'][i], res.get('_ansible_no_log', no_log)) - elif res.get('_ansible_no_log', False): - res['results'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" - return res - @contextlib.contextmanager def capture_event_data(self, event, **event_data): event_data.setdefault('uuid', str(uuid.uuid4())) - if 'res' in event_data: - event_data['res'] = self.censor_result(copy.copy(event_data['res'])) - if event not in self.EVENTS_WITHOUT_TASK: task = event_data.pop('task', None) else: @@ -258,7 +228,7 @@ class BaseCallbackModule(CallbackBase): if task_uuid in self.task_uuids: # FIXME: When this task UUID repeats, it means the play is using the # free strategy, so different hosts may be running different tasks - # within a play. + # within a play. return self.task_uuids.add(task_uuid) self.set_task(task) From 9592c67c3194993b744474572c8b18abd5b43117 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 16 Jan 2017 14:16:24 -0500 Subject: [PATCH 136/154] fixing flake8 errors --- awx/lib/tower_display_callback/module.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/tower_display_callback/module.py index beeddffbd5..02c5eee432 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/tower_display_callback/module.py @@ -19,8 +19,6 @@ from __future__ import (absolute_import, division, print_function) # Python import contextlib -import copy -import re import sys import uuid From d8e94f3f15c2f927e12eb50e6f2d0125245088cc Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 16 Jan 2017 16:36:06 -0500 Subject: [PATCH 137/154] make workflow extra vars look disabled --- .../workflow-results.block.less | 24 +++++++++++++++++++ .../workflow-results.partial.html | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/awx/ui/client/src/workflow-results/workflow-results.block.less b/awx/ui/client/src/workflow-results/workflow-results.block.less index edd5412d2d..0173ed8eb8 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.block.less +++ b/awx/ui/client/src/workflow-results/workflow-results.block.less @@ -110,3 +110,27 @@ text-transform: uppercase; margin-left: 20px; } + +.WorkflowResults-extraVarsHelp { + margin-left: 10px; + color: @default-icon; +} + +.WorkflowResults .CodeMirror.cm-s-default, +.WorkflowResults .CodeMirror-line { + background-color: #f6f6f6; +} + +.WorkflowResults .CodeMirror-gutter.CodeMirror-lint-markers, +.WorkflowResults .CodeMirror-gutter.CodeMirror-linenumbers { + background-color: #ebebeb; + color: @b7grey; +} + +.WorkflowResults .CodeMirror-lines { + cursor: default; +} + +.WorkflowResults .CodeMirror-cursors { + display: none; +} diff --git a/awx/ui/client/src/workflow-results/workflow-results.partial.html b/awx/ui/client/src/workflow-results/workflow-results.partial.html index ff2cd94a0d..4ff65548c6 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.partial.html +++ b/awx/ui/client/src/workflow-results/workflow-results.partial.html @@ -121,6 +121,10 @@