diff --git a/awx/api/filters.py b/awx/api/filters.py index 6885d73326..fbbbba2d05 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -77,7 +77,7 @@ class FieldLookupBackend(BaseFilterBackend): SUPPORTED_LOOKUPS = ('exact', 'iexact', 'contains', 'icontains', 'startswith', 'istartswith', 'endswith', 'iendswith', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'in', - 'isnull') + 'isnull', 'search') def get_field_from_lookup(self, model, lookup): field = None @@ -148,6 +148,15 @@ class FieldLookupBackend(BaseFilterBackend): re.compile(value) except re.error as e: raise ValueError(e.args[0]) + elif new_lookup.endswith('__search'): + related_model = getattr(field, 'related_model', None) + if not related_model: + raise ValueError('%s is not searchable' % new_lookup[:-8]) + new_lookups = [] + for rm_field in related_model._meta.fields: + if rm_field.name in ('username', 'first_name', 'last_name', 'email', 'name', 'description'): + new_lookups.append('{}__{}__icontains'.format(new_lookup[:-8], rm_field.name)) + return value, new_lookups else: value = self.value_to_python_for_field(field, value) return value, new_lookup @@ -160,6 +169,7 @@ class FieldLookupBackend(BaseFilterBackend): or_filters = [] chain_filters = [] role_filters = [] + search_filters = [] for key, values in request.query_params.lists(): if key in self.RESERVED_NAMES: continue @@ -181,6 +191,16 @@ class FieldLookupBackend(BaseFilterBackend): role_filters.append(values[0]) continue + # Search across related objects. + if key.endswith('__search'): + for value in values: + for search_term in force_text(value).replace(',', ' ').split(): + search_value, new_keys = self.value_to_python(queryset.model, key, search_term) + assert isinstance(new_keys, list) + for new_key in new_keys: + search_filters.append((new_key, search_value)) + continue + # Custom chain__ and or__ filters, mutually exclusive (both can # precede not__). q_chain = False @@ -211,7 +231,7 @@ class FieldLookupBackend(BaseFilterBackend): and_filters.append((q_not, new_key, value)) # Now build Q objects for database query filter. - if and_filters or or_filters or chain_filters or role_filters: + if and_filters or or_filters or chain_filters or role_filters or search_filters: args = [] for n, k, v in and_filters: if n: @@ -234,6 +254,11 @@ class FieldLookupBackend(BaseFilterBackend): else: q |= Q(**{k:v}) args.append(q) + if search_filters: + q = Q() + for k,v in search_filters: + q |= Q(**{k:v}) + args.append(q) for n,k,v in chain_filters: if n: q = ~Q(**{k:v}) diff --git a/awx/api/generics.py b/awx/api/generics.py index 1062135a28..73b92cfcc5 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -267,10 +267,25 @@ class ListAPIView(generics.ListAPIView, GenericAPIView): fields = [] for field in self.model._meta.fields: if field.name in ('username', 'first_name', 'last_name', 'email', - 'name', 'description', 'email'): + 'name', 'description'): fields.append(field.name) return fields + @property + def related_search_fields(self): + fields = [] + for field in self.model._meta.fields: + if field.name.endswith('_role'): + continue + if getattr(field, 'related_model', None): + fields.append('{}__search'.format(field.name)) + for rel in self.model._meta.related_objects: + name = rel.get_accessor_name() + if name.endswith('_set'): + continue + fields.append('{}__search'.format(name)) + return fields + class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView): # Base class for a list view that allows creating new objects. diff --git a/awx/api/metadata.py b/awx/api/metadata.py index 6dd186c9ef..fb5c4d6493 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -182,6 +182,10 @@ class Metadata(metadata.SimpleMetadata): if getattr(view, 'search_fields', None): metadata['search_fields'] = view.search_fields + # Add related search fields if available from the view. + if getattr(view, 'related_search_fields', None): + metadata['related_search_fields'] = view.related_search_fields + return metadata diff --git a/awx/api/templates/api/_list_common.md b/awx/api/templates/api/_list_common.md index 36e6819276..706ae732a5 100644 --- a/awx/api/templates/api/_list_common.md +++ b/awx/api/templates/api/_list_common.md @@ -56,6 +56,10 @@ within all designated text fields of a model. _Added in AWX 1.4_ +(_Added in Ansible Tower 3.1.0_) Search across related fields: + + ?related__search=findme + ## Filtering Any additional query string parameters may be used to filter the list of diff --git a/awx/api/views.py b/awx/api/views.py index 826941ec5f..f19b61e4e9 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -65,6 +65,9 @@ from awx.api.generics import * # noqa from awx.conf.license import get_license, feature_enabled, feature_exists, LicenseForbids from awx.main.models import * # noqa from awx.main.utils import * # noqa +from awx.main.utils import ( + callback_filter_out_ansible_extra_vars +) from awx.api.permissions import * # noqa from awx.api.renderers import * # noqa from awx.api.serializers import * # noqa @@ -2663,7 +2666,7 @@ class JobTemplateCallback(GenericAPIView): # Send a signal to celery that the job should be started. kv = {"inventory_sources_already_updated": inventory_sources_already_updated} if extra_vars is not None: - kv['extra_vars'] = extra_vars + kv['extra_vars'] = callback_filter_out_ansible_extra_vars(extra_vars) result = job.signal_start(**kv) if not result: data = dict(msg=_('Error starting job!')) diff --git a/awx/conf/settings.py b/awx/conf/settings.py index c08b161237..de8c82e1c5 100644 --- a/awx/conf/settings.py +++ b/awx/conf/settings.py @@ -1,6 +1,7 @@ # Python import contextlib import logging +import sys import threading import time @@ -86,6 +87,7 @@ class SettingsWrapper(UserSettingsHolder): self.__dict__['_awx_conf_settings'] = self self.__dict__['_awx_conf_preload_expires'] = None self.__dict__['_awx_conf_preload_lock'] = threading.RLock() + self.__dict__['_awx_conf_init_readonly'] = False def _get_supported_settings(self): return settings_registry.get_registered_settings() @@ -110,6 +112,20 @@ class SettingsWrapper(UserSettingsHolder): return # Otherwise update local preload timeout. self.__dict__['_awx_conf_preload_expires'] = time.time() + SETTING_CACHE_TIMEOUT + # Check for any settings that have been defined in Python files and + # make those read-only to avoid overriding in the database. + if not self._awx_conf_init_readonly and 'migrate_to_database_settings' not in sys.argv: + defaults_snapshot = self._get_default('DEFAULTS_SNAPSHOT') + for key in self._get_writeable_settings(): + init_default = defaults_snapshot.get(key, None) + try: + file_default = self._get_default(key) + except AttributeError: + file_default = None + if file_default != init_default and file_default is not None: + logger.warning('Setting %s has been marked read-only!', key) + settings_registry._registry[key]['read_only'] = True + self.__dict__['_awx_conf_init_readonly'] = True # If local preload timer has expired, check to see if another process # has already preloaded the cache and skip preloading if so. if cache.get('_awx_conf_preload_expires', empty) is not empty: diff --git a/awx/locale/django.pot b/awx/locale/django.pot index 7b67733c8b..81993a2a2b 100644 --- a/awx/locale/django.pot +++ b/awx/locale/django.pot @@ -1,12 +1,14 @@ -# Ansible Tower POT file. -# Copyright (c) 2016 Ansible, Inc. -# All Rights Reserved. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. # +#, fuzzy msgid "" msgstr "" -"Project-Id-Version: ansible-tower-3.1.0\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-12-01 06:37-0500\n" +"POT-Creation-Date: 2016-12-14 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,1033 +17,1039 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: awx/api/authentication.py:67 +#: api/authentication.py:67 msgid "Invalid token header. No credentials provided." msgstr "" -#: awx/api/authentication.py:70 +#: api/authentication.py:70 msgid "Invalid token header. Token string should not contain spaces." msgstr "" -#: awx/api/authentication.py:105 +#: api/authentication.py:105 msgid "User inactive or deleted" msgstr "" -#: awx/api/authentication.py:161 +#: api/authentication.py:161 msgid "Invalid task token" msgstr "" -#: awx/api/conf.py:12 +#: api/conf.py:12 msgid "Idle Time Force Log Out" msgstr "" -#: awx/api/conf.py:13 +#: api/conf.py:13 msgid "" "Number of seconds that a user is inactive before they will need to login " "again." msgstr "" -#: awx/api/conf.py:14 awx/api/conf.py:24 awx/api/conf.py:33 awx/sso/conf.py:124 -#: awx/sso/conf.py:135 awx/sso/conf.py:147 awx/sso/conf.py:162 +#: api/conf.py:14 api/conf.py:24 api/conf.py:33 sso/conf.py:124 +#: sso/conf.py:135 sso/conf.py:147 sso/conf.py:162 msgid "Authentication" msgstr "" -#: awx/api/conf.py:22 +#: api/conf.py:22 msgid "Maximum number of simultaneous logins" msgstr "" -#: awx/api/conf.py:23 +#: api/conf.py:23 msgid "" "Maximum number of simultaneous logins a user may have. To disable enter -1." msgstr "" -#: awx/api/conf.py:31 +#: api/conf.py:31 msgid "Enable HTTP Basic Auth" msgstr "" -#: awx/api/conf.py:32 +#: api/conf.py:32 msgid "Enable HTTP Basic Auth for the API Browser." msgstr "" -#: awx/api/generics.py:446 +#: api/generics.py:446 msgid "\"id\" is required to disassociate" msgstr "" -#: awx/api/metadata.py:50 +#: api/metadata.py:50 msgid "Database ID for this {}." msgstr "" -#: awx/api/metadata.py:51 +#: api/metadata.py:51 msgid "Name of this {}." msgstr "" -#: awx/api/metadata.py:52 +#: api/metadata.py:52 msgid "Optional description of this {}." msgstr "" -#: awx/api/metadata.py:53 +#: api/metadata.py:53 msgid "Data type for this {}." msgstr "" -#: awx/api/metadata.py:54 +#: api/metadata.py:54 msgid "URL for this {}." msgstr "" -#: awx/api/metadata.py:55 +#: api/metadata.py:55 msgid "Data structure with URLs of related resources." msgstr "" -#: awx/api/metadata.py:56 +#: api/metadata.py:56 msgid "Data structure with name/description for related resources." msgstr "" -#: awx/api/metadata.py:57 +#: api/metadata.py:57 msgid "Timestamp when this {} was created." msgstr "" -#: awx/api/metadata.py:58 +#: api/metadata.py:58 msgid "Timestamp when this {} was last modified." msgstr "" -#: awx/api/parsers.py:31 +#: api/parsers.py:31 #, python-format msgid "JSON parse error - %s" msgstr "" -#: awx/api/serializers.py:248 +#: api/serializers.py:248 msgid "Playbook Run" msgstr "" -#: awx/api/serializers.py:249 +#: api/serializers.py:249 msgid "Command" msgstr "" -#: awx/api/serializers.py:250 +#: api/serializers.py:250 msgid "SCM Update" msgstr "" -#: awx/api/serializers.py:251 +#: api/serializers.py:251 msgid "Inventory Sync" msgstr "" -#: awx/api/serializers.py:252 +#: api/serializers.py:252 msgid "Management Job" msgstr "" -#: awx/api/serializers.py:636 awx/api/serializers.py:694 awx/api/views.py:3994 +#: api/serializers.py:253 +msgid "Workflow Job" +msgstr "" + +#: api/serializers.py:655 api/serializers.py:713 api/views.py:3914 #, python-format msgid "" "Standard Output too large to display (%(text_size)d bytes), only download " "supported for sizes over %(supported_size)d bytes" msgstr "" -#: awx/api/serializers.py:709 +#: api/serializers.py:728 msgid "Write-only field used to change the password." msgstr "" -#: awx/api/serializers.py:711 +#: api/serializers.py:730 msgid "Set if the account is managed by an external service" msgstr "" -#: awx/api/serializers.py:735 +#: api/serializers.py:754 msgid "Password required for new User." msgstr "" -#: awx/api/serializers.py:819 +#: api/serializers.py:838 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "" -#: awx/api/serializers.py:971 +#: api/serializers.py:990 msgid "Organization is missing" msgstr "" -#: awx/api/serializers.py:977 +#: api/serializers.py:996 msgid "Array of playbooks available within this project." msgstr "" -#: awx/api/serializers.py:1159 +#: api/serializers.py:1178 #, python-format msgid "Invalid port specification: %s" msgstr "" -#: awx/api/serializers.py:1187 awx/main/validators.py:192 +#: api/serializers.py:1206 main/validators.py:192 msgid "Must be valid JSON or YAML." msgstr "" -#: awx/api/serializers.py:1244 +#: api/serializers.py:1263 msgid "Invalid group name." msgstr "" -#: awx/api/serializers.py:1319 +#: api/serializers.py:1338 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" -#: awx/api/serializers.py:1372 +#: api/serializers.py:1391 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "" -#: awx/api/serializers.py:1376 +#: api/serializers.py:1395 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" -#: awx/api/serializers.py:1378 +#: api/serializers.py:1397 msgid "'source_script' doesn't exist." msgstr "" -#: awx/api/serializers.py:1737 +#: api/serializers.py:1756 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:1742 +#: api/serializers.py:1761 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:1747 +#: api/serializers.py:1766 msgid "" -"Write-only field used to add organization to owner role. If provided, do not " -"give either team or team. Only valid for creation." +"Inherit permissions from organization roles. If provided on creation, do not " +"give either user or team." msgstr "" -#: awx/api/serializers.py:1763 +#: api/serializers.py:1782 msgid "Missing 'user', 'team', or 'organization'." msgstr "" -#: awx/api/serializers.py:1776 +#: api/serializers.py:1795 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "" -#: awx/api/serializers.py:1868 +#: api/serializers.py:1887 msgid "This field is required." msgstr "" -#: awx/api/serializers.py:1870 awx/api/serializers.py:1872 +#: api/serializers.py:1889 api/serializers.py:1891 msgid "Playbook not found for project." msgstr "" -#: awx/api/serializers.py:1874 +#: api/serializers.py:1893 msgid "Must select playbook for project." msgstr "" -#: awx/api/serializers.py:1938 awx/main/models/jobs.py:279 +#: api/serializers.py:1957 main/models/jobs.py:280 msgid "Scan jobs must be assigned a fixed inventory." msgstr "" -#: awx/api/serializers.py:1940 awx/main/models/jobs.py:282 +#: api/serializers.py:1959 main/models/jobs.py:283 msgid "Job types 'run' and 'check' must have assigned a project." msgstr "" -#: awx/api/serializers.py:1943 +#: api/serializers.py:1962 msgid "Survey Enabled cannot be used with scan jobs." msgstr "" -#: awx/api/serializers.py:2005 +#: api/serializers.py:2024 msgid "Invalid job template." msgstr "" -#: awx/api/serializers.py:2090 +#: api/serializers.py:2109 msgid "Credential not found or deleted." msgstr "" -#: awx/api/serializers.py:2092 +#: api/serializers.py:2111 msgid "Job Template Project is missing or undefined." msgstr "" -#: awx/api/serializers.py:2094 +#: api/serializers.py:2113 msgid "Job Template Inventory is missing or undefined." msgstr "" -#: awx/api/serializers.py:2379 +#: api/serializers.py:2398 #, python-format msgid "%(job_type)s is not a valid job type. The choices are %(choices)s." msgstr "" -#: awx/api/serializers.py:2384 +#: api/serializers.py:2403 msgid "Workflow job template is missing during creation." msgstr "" -#: awx/api/serializers.py:2389 +#: api/serializers.py:2408 #, python-format msgid "Cannot nest a %s inside a WorkflowJobTemplate" msgstr "" -#: awx/api/serializers.py:2625 +#: api/serializers.py:2646 #, python-format msgid "Job Template '%s' is missing or undefined." msgstr "" -#: awx/api/serializers.py:2651 +#: api/serializers.py:2672 msgid "Must be a valid JSON or YAML dictionary." msgstr "" -#: awx/api/serializers.py:2796 +#: api/serializers.py:2817 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" -#: awx/api/serializers.py:2819 +#: api/serializers.py:2840 msgid "No values specified for field '{}'" msgstr "" -#: awx/api/serializers.py:2824 +#: api/serializers.py:2845 msgid "Missing required fields for Notification Configuration: {}." msgstr "" -#: awx/api/serializers.py:2827 +#: api/serializers.py:2848 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "" -#: awx/api/serializers.py:2880 +#: api/serializers.py:2901 msgid "Inventory Source must be a cloud resource." msgstr "" -#: awx/api/serializers.py:2882 +#: api/serializers.py:2903 msgid "Manual Project can not have a schedule set." msgstr "" -#: awx/api/serializers.py:2904 +#: api/serializers.py:2925 msgid "DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ" msgstr "" -#: awx/api/serializers.py:2906 +#: api/serializers.py:2927 msgid "Multiple DTSTART is not supported." msgstr "" -#: awx/api/serializers.py:2908 +#: api/serializers.py:2929 msgid "RRULE require in rrule." msgstr "" -#: awx/api/serializers.py:2910 +#: api/serializers.py:2931 msgid "Multiple RRULE is not supported." msgstr "" -#: awx/api/serializers.py:2912 +#: api/serializers.py:2933 msgid "INTERVAL required in rrule." msgstr "" -#: awx/api/serializers.py:2914 +#: api/serializers.py:2935 msgid "TZID is not supported." msgstr "" -#: awx/api/serializers.py:2916 +#: api/serializers.py:2937 msgid "SECONDLY is not supported." msgstr "" -#: awx/api/serializers.py:2918 +#: api/serializers.py:2939 msgid "Multiple BYMONTHDAYs not supported." msgstr "" -#: awx/api/serializers.py:2920 +#: api/serializers.py:2941 msgid "Multiple BYMONTHs not supported." msgstr "" -#: awx/api/serializers.py:2922 +#: api/serializers.py:2943 msgid "BYDAY with numeric prefix not supported." msgstr "" -#: awx/api/serializers.py:2924 +#: api/serializers.py:2945 msgid "BYYEARDAY not supported." msgstr "" -#: awx/api/serializers.py:2926 +#: api/serializers.py:2947 msgid "BYWEEKNO not supported." msgstr "" -#: awx/api/serializers.py:2930 +#: api/serializers.py:2951 msgid "COUNT > 999 is unsupported." msgstr "" -#: awx/api/serializers.py:2934 +#: api/serializers.py:2955 msgid "rrule parsing failed validation." msgstr "" -#: awx/api/serializers.py:2952 +#: api/serializers.py:2973 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" msgstr "" -#: awx/api/serializers.py:2954 +#: api/serializers.py:2975 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." msgstr "" -#: awx/api/serializers.py:2957 +#: api/serializers.py:2978 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated " "with." msgstr "" -#: awx/api/serializers.py:2960 +#: api/serializers.py:2981 msgid "The action taken with respect to the given object(s)." msgstr "" -#: awx/api/serializers.py:3060 +#: api/serializers.py:3081 msgid "Unable to login with provided credentials." msgstr "" -#: awx/api/serializers.py:3062 +#: api/serializers.py:3083 msgid "Must include \"username\" and \"password\"." msgstr "" -#: awx/api/views.py:95 awx/templates/rest_framework/api.html:28 +#: api/views.py:96 +msgid "Your license does not allow use of the activity stream." +msgstr "" + +#: api/views.py:106 +msgid "Your license does not permit use of system tracking." +msgstr "" + +#: api/views.py:116 +msgid "Your license does not allow use of workflows." +msgstr "" + +#: api/views.py:124 templates/rest_framework/api.html:28 msgid "REST API" msgstr "" -#: awx/api/views.py:102 awx/templates/rest_framework/api.html:4 +#: api/views.py:131 templates/rest_framework/api.html:4 msgid "Ansible Tower REST API" msgstr "" -#: awx/api/views.py:118 +#: api/views.py:147 msgid "Version 1" msgstr "" -#: awx/api/views.py:169 +#: api/views.py:198 msgid "Ping" msgstr "" -#: awx/api/views.py:198 awx/conf/apps.py:10 +#: api/views.py:227 conf/apps.py:12 msgid "Configuration" msgstr "" -#: awx/api/views.py:248 +#: api/views.py:280 msgid "Invalid license data" msgstr "" -#: awx/api/views.py:250 +#: api/views.py:282 msgid "Missing 'eula_accepted' property" msgstr "" -#: awx/api/views.py:254 +#: api/views.py:286 msgid "'eula_accepted' value is invalid" msgstr "" -#: awx/api/views.py:257 +#: api/views.py:289 msgid "'eula_accepted' must be True" msgstr "" -#: awx/api/views.py:263 +#: api/views.py:296 msgid "Invalid JSON" msgstr "" -#: awx/api/views.py:270 +#: api/views.py:304 msgid "Invalid License" msgstr "" -#: awx/api/views.py:278 +#: api/views.py:314 msgid "Invalid license" msgstr "" -#: awx/api/views.py:289 +#: api/views.py:322 #, python-format msgid "Failed to remove license (%s)" msgstr "" -#: awx/api/views.py:294 +#: api/views.py:327 msgid "Dashboard" msgstr "" -#: awx/api/views.py:400 +#: api/views.py:433 msgid "Dashboard Jobs Graphs" msgstr "" -#: awx/api/views.py:436 +#: api/views.py:469 #, python-format msgid "Unknown period \"%s\"" msgstr "" -#: awx/api/views.py:450 +#: api/views.py:483 msgid "Schedules" msgstr "" -#: awx/api/views.py:469 +#: api/views.py:502 msgid "Schedule Jobs List" msgstr "" -#: awx/api/views.py:675 +#: api/views.py:711 msgid "Your Tower license only permits a single organization to exist." msgstr "" -#: awx/api/views.py:803 awx/api/views.py:968 awx/api/views.py:1069 -#: awx/api/views.py:1356 awx/api/views.py:1517 awx/api/views.py:1614 -#: awx/api/views.py:1758 awx/api/views.py:1954 awx/api/views.py:2212 -#: awx/api/views.py:2528 awx/api/views.py:3121 awx/api/views.py:3194 -#: awx/api/views.py:3330 awx/api/views.py:3911 awx/api/views.py:4163 -#: awx/api/views.py:4180 -msgid "Your license does not allow use of the activity stream." -msgstr "" - -#: awx/api/views.py:906 awx/api/views.py:1270 +#: api/views.py:932 api/views.py:1284 msgid "Role 'id' field is missing." msgstr "" -#: awx/api/views.py:912 awx/api/views.py:4282 +#: api/views.py:938 api/views.py:4182 msgid "You cannot assign an Organization role as a child role for a Team." msgstr "" -#: awx/api/views.py:919 awx/api/views.py:4288 +#: api/views.py:942 api/views.py:4196 +msgid "You cannot grant system-level permissions to a team." +msgstr "" + +#: api/views.py:949 api/views.py:4188 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" msgstr "" -#: awx/api/views.py:1018 +#: api/views.py:1039 msgid "Cannot delete project." msgstr "" -#: awx/api/views.py:1047 +#: api/views.py:1068 msgid "Project Schedules" msgstr "" -#: awx/api/views.py:1156 awx/api/views.py:2307 awx/api/views.py:3301 +#: api/views.py:1168 api/views.py:2252 api/views.py:3225 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" -#: awx/api/views.py:1230 +#: api/views.py:1244 msgid "Me" msgstr "" -#: awx/api/views.py:1274 awx/api/views.py:4237 +#: api/views.py:1288 api/views.py:4137 msgid "You may not perform any action with your own admin_role." msgstr "" -#: awx/api/views.py:1280 awx/api/views.py:4241 +#: api/views.py:1294 api/views.py:4141 msgid "You may not change the membership of a users admin_role" msgstr "" -#: awx/api/views.py:1285 awx/api/views.py:4246 +#: api/views.py:1299 api/views.py:4146 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" msgstr "" -#: awx/api/views.py:1289 awx/api/views.py:4250 +#: api/views.py:1303 api/views.py:4150 msgid "You cannot grant private credential access to another user" msgstr "" -#: awx/api/views.py:1397 +#: api/views.py:1401 #, python-format msgid "Cannot change %s." msgstr "" -#: awx/api/views.py:1403 +#: api/views.py:1407 msgid "Cannot delete user." msgstr "" -#: awx/api/views.py:1559 +#: api/views.py:1553 msgid "Cannot delete inventory script." msgstr "" -#: awx/api/views.py:1777 -msgid "Your license does not permit use of system tracking." -msgstr "" - -#: awx/api/views.py:1824 +#: api/views.py:1788 msgid "Fact not found." msgstr "" -#: awx/api/views.py:2154 +#: api/views.py:2108 msgid "Inventory Source List" msgstr "" -#: awx/api/views.py:2182 +#: api/views.py:2136 msgid "Cannot delete inventory source." msgstr "" -#: awx/api/views.py:2190 +#: api/views.py:2144 msgid "Inventory Source Schedules" msgstr "" -#: awx/api/views.py:2229 +#: api/views.py:2173 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" -#: awx/api/views.py:2433 +#: api/views.py:2380 msgid "Job Template Schedules" msgstr "" -#: awx/api/views.py:2452 awx/api/views.py:2462 +#: api/views.py:2399 api/views.py:2409 msgid "Your license does not allow adding surveys." msgstr "" -#: awx/api/views.py:2469 +#: api/views.py:2416 msgid "'name' missing from survey spec." msgstr "" -#: awx/api/views.py:2471 +#: api/views.py:2418 msgid "'description' missing from survey spec." msgstr "" -#: awx/api/views.py:2473 +#: api/views.py:2420 msgid "'spec' missing from survey spec." msgstr "" -#: awx/api/views.py:2475 +#: api/views.py:2422 msgid "'spec' must be a list of items." msgstr "" -#: awx/api/views.py:2477 +#: api/views.py:2424 msgid "'spec' doesn't contain any items." msgstr "" -#: awx/api/views.py:2482 +#: api/views.py:2429 #, python-format msgid "Survey question %s is not a json object." msgstr "" -#: awx/api/views.py:2484 +#: api/views.py:2431 #, python-format msgid "'type' missing from survey question %s." msgstr "" -#: awx/api/views.py:2486 +#: api/views.py:2433 #, python-format msgid "'question_name' missing from survey question %s." msgstr "" -#: awx/api/views.py:2488 +#: api/views.py:2435 #, python-format msgid "'variable' missing from survey question %s." msgstr "" -#: awx/api/views.py:2490 +#: api/views.py:2437 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "" -#: awx/api/views.py:2495 +#: api/views.py:2442 #, python-format msgid "'required' missing from survey question %s." msgstr "" -#: awx/api/views.py:2702 +#: api/views.py:2641 msgid "No matching host could be found!" msgstr "" -#: awx/api/views.py:2705 +#: api/views.py:2644 msgid "Multiple hosts matched the request!" msgstr "" -#: awx/api/views.py:2710 +#: api/views.py:2649 msgid "Cannot start automatically, user input required!" msgstr "" -#: awx/api/views.py:2717 +#: api/views.py:2656 msgid "Host callback job already pending." msgstr "" -#: awx/api/views.py:2730 +#: api/views.py:2669 msgid "Error starting job!" msgstr "" -#: awx/api/views.py:3053 +#: api/views.py:2995 msgid "Workflow Job Template Schedules" msgstr "" -#: awx/api/views.py:3208 awx/api/views.py:3933 +#: api/views.py:3131 api/views.py:3853 msgid "Superuser privileges needed." msgstr "" -#: awx/api/views.py:3238 +#: api/views.py:3161 msgid "System Job Template Schedules" msgstr "" -#: awx/api/views.py:3428 +#: api/views.py:3344 msgid "Job Host Summaries List" msgstr "" -#: awx/api/views.py:3470 +#: api/views.py:3386 msgid "Job Event Children List" msgstr "" -#: awx/api/views.py:3479 +#: api/views.py:3395 msgid "Job Event Hosts List" msgstr "" -#: awx/api/views.py:3488 +#: api/views.py:3404 msgid "Job Events List" msgstr "" -#: awx/api/views.py:3509 +#: api/views.py:3436 msgid "Job Plays List" msgstr "" -#: awx/api/views.py:3584 +#: api/views.py:3513 msgid "Job Play Tasks List" msgstr "" -#: awx/api/views.py:3599 +#: api/views.py:3529 msgid "Job not found." msgstr "" -#: awx/api/views.py:3603 +#: api/views.py:3533 msgid "'event_id' not provided." msgstr "" -#: awx/api/views.py:3607 +#: api/views.py:3537 msgid "Parent event not found." msgstr "" -#: awx/api/views.py:3879 +#: api/views.py:3809 msgid "Ad Hoc Command Events List" msgstr "" -#: awx/api/views.py:4043 +#: api/views.py:3963 #, python-format msgid "Error generating stdout download file: %s" msgstr "" -#: awx/api/views.py:4089 +#: api/views.py:4009 msgid "Delete not allowed while there are pending notifications" msgstr "" -#: awx/api/views.py:4096 +#: api/views.py:4016 msgid "NotificationTemplate Test" msgstr "" -#: awx/api/views.py:4231 +#: api/views.py:4131 msgid "User 'id' field is missing." msgstr "" -#: awx/api/views.py:4274 +#: api/views.py:4174 msgid "Team 'id' field is missing." msgstr "" -#: awx/conf/conf.py:20 +#: conf/conf.py:20 msgid "Bud Frogs" msgstr "" -#: awx/conf/conf.py:21 +#: conf/conf.py:21 msgid "Bunny" msgstr "" -#: awx/conf/conf.py:22 +#: conf/conf.py:22 msgid "Cheese" msgstr "" -#: awx/conf/conf.py:23 +#: conf/conf.py:23 msgid "Daemon" msgstr "" -#: awx/conf/conf.py:24 +#: conf/conf.py:24 msgid "Default Cow" msgstr "" -#: awx/conf/conf.py:25 +#: conf/conf.py:25 msgid "Dragon" msgstr "" -#: awx/conf/conf.py:26 +#: conf/conf.py:26 msgid "Elephant in Snake" msgstr "" -#: awx/conf/conf.py:27 +#: conf/conf.py:27 msgid "Elephant" msgstr "" -#: awx/conf/conf.py:28 +#: conf/conf.py:28 msgid "Eyes" msgstr "" -#: awx/conf/conf.py:29 +#: conf/conf.py:29 msgid "Hello Kitty" msgstr "" -#: awx/conf/conf.py:30 +#: conf/conf.py:30 msgid "Kitty" msgstr "" -#: awx/conf/conf.py:31 +#: conf/conf.py:31 msgid "Luke Koala" msgstr "" -#: awx/conf/conf.py:32 +#: conf/conf.py:32 msgid "Meow" msgstr "" -#: awx/conf/conf.py:33 +#: conf/conf.py:33 msgid "Milk" msgstr "" -#: awx/conf/conf.py:34 +#: conf/conf.py:34 msgid "Moofasa" msgstr "" -#: awx/conf/conf.py:35 +#: conf/conf.py:35 msgid "Moose" msgstr "" -#: awx/conf/conf.py:36 +#: conf/conf.py:36 msgid "Ren" msgstr "" -#: awx/conf/conf.py:37 +#: conf/conf.py:37 msgid "Sheep" msgstr "" -#: awx/conf/conf.py:38 +#: conf/conf.py:38 msgid "Small Cow" msgstr "" -#: awx/conf/conf.py:39 +#: conf/conf.py:39 msgid "Stegosaurus" msgstr "" -#: awx/conf/conf.py:40 +#: conf/conf.py:40 msgid "Stimpy" msgstr "" -#: awx/conf/conf.py:41 +#: conf/conf.py:41 msgid "Super Milker" msgstr "" -#: awx/conf/conf.py:42 +#: conf/conf.py:42 msgid "Three Eyes" msgstr "" -#: awx/conf/conf.py:43 +#: conf/conf.py:43 msgid "Turkey" msgstr "" -#: awx/conf/conf.py:44 +#: conf/conf.py:44 msgid "Turtle" msgstr "" -#: awx/conf/conf.py:45 +#: conf/conf.py:45 msgid "Tux" msgstr "" -#: awx/conf/conf.py:46 +#: conf/conf.py:46 msgid "Udder" msgstr "" -#: awx/conf/conf.py:47 +#: conf/conf.py:47 msgid "Vader Koala" msgstr "" -#: awx/conf/conf.py:48 +#: conf/conf.py:48 msgid "Vader" msgstr "" -#: awx/conf/conf.py:49 +#: conf/conf.py:49 msgid "WWW" msgstr "" -#: awx/conf/conf.py:52 +#: conf/conf.py:52 msgid "Cow Selection" msgstr "" -#: awx/conf/conf.py:53 +#: conf/conf.py:53 msgid "Select which cow to use with cowsay when running jobs." msgstr "" -#: awx/conf/conf.py:54 awx/conf/conf.py:75 +#: conf/conf.py:54 conf/conf.py:75 msgid "Cows" msgstr "" -#: awx/conf/conf.py:73 +#: conf/conf.py:73 msgid "Example Read-Only Setting" msgstr "" -#: awx/conf/conf.py:74 +#: conf/conf.py:74 msgid "Example setting that cannot be changed." msgstr "" -#: awx/conf/conf.py:90 +#: conf/conf.py:93 msgid "Example Setting" msgstr "" -#: awx/conf/conf.py:91 +#: conf/conf.py:94 msgid "Example setting which can be different for each user." msgstr "" -#: awx/conf/conf.py:92 awx/conf/registry.py:67 awx/conf/views.py:46 +#: conf/conf.py:95 conf/registry.py:67 conf/views.py:46 msgid "User" msgstr "" -#: awx/conf/fields.py:38 +#: conf/fields.py:38 msgid "Enter a valid URL" msgstr "" -#: awx/conf/license.py:23 +#: conf/license.py:19 msgid "Your Tower license does not allow that." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:41 +#: conf/management/commands/migrate_to_database_settings.py:41 msgid "Only show which settings would be commented/migrated." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:48 +#: conf/management/commands/migrate_to_database_settings.py:48 msgid "Skip over settings that would raise an error when commenting/migrating." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:55 +#: conf/management/commands/migrate_to_database_settings.py:55 msgid "Skip commenting out settings in files." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:61 +#: conf/management/commands/migrate_to_database_settings.py:61 msgid "Backup existing settings files with this suffix." msgstr "" -#: awx/conf/registry.py:55 +#: conf/registry.py:55 msgid "All" msgstr "" -#: awx/conf/registry.py:56 +#: conf/registry.py:56 msgid "Changed" msgstr "" -#: awx/conf/registry.py:68 +#: conf/registry.py:68 msgid "User-Defaults" msgstr "" -#: awx/conf/views.py:38 +#: conf/views.py:38 msgid "Setting Categories" msgstr "" -#: awx/conf/views.py:61 +#: conf/views.py:61 msgid "Setting Detail" msgstr "" -#: awx/main/access.py:255 +#: main/access.py:255 #, python-format msgid "Bad data found in related field %s." msgstr "" -#: awx/main/access.py:296 +#: main/access.py:296 msgid "License is missing." msgstr "" -#: awx/main/access.py:298 +#: main/access.py:298 msgid "License has expired." msgstr "" -#: awx/main/access.py:303 +#: main/access.py:303 #, python-format msgid "License count of %s instances has been reached." msgstr "" -#: awx/main/access.py:305 +#: main/access.py:305 #, python-format msgid "License count of %s instances has been exceeded." msgstr "" -#: awx/main/access.py:307 +#: main/access.py:307 msgid "Host count exceeds available instances." msgstr "" -#: awx/main/access.py:311 +#: main/access.py:311 #, python-format msgid "Feature %s is not enabled in the active license." msgstr "" -#: awx/main/access.py:313 +#: main/access.py:313 msgid "Features not found in active license." msgstr "" -#: awx/main/access.py:507 awx/main/access.py:574 awx/main/access.py:694 -#: awx/main/access.py:965 awx/main/access.py:1206 awx/main/access.py:1594 +#: main/access.py:507 main/access.py:574 main/access.py:694 main/access.py:957 +#: main/access.py:1198 main/access.py:1587 msgid "Resource is being used by running jobs" msgstr "" -#: awx/main/access.py:618 +#: main/access.py:618 msgid "Unable to change inventory on a host." msgstr "" -#: awx/main/access.py:630 awx/main/access.py:675 +#: main/access.py:630 main/access.py:675 msgid "Cannot associate two items from different inventories." msgstr "" -#: awx/main/access.py:663 +#: main/access.py:663 msgid "Unable to change inventory on a group." msgstr "" -#: awx/main/access.py:885 +#: main/access.py:877 msgid "Unable to change organization on a team." msgstr "" -#: awx/main/access.py:898 +#: main/access.py:890 msgid "The {} role cannot be assigned to a team" msgstr "" -#: awx/main/access.py:900 +#: main/access.py:892 msgid "The admin_role for a User cannot be assigned to a team" msgstr "" -#: awx/main/apps.py:9 +#: main/apps.py:9 msgid "Main" msgstr "" -#: awx/main/conf.py:17 +#: main/conf.py:17 msgid "Enable Activity Stream" msgstr "" -#: awx/main/conf.py:18 +#: main/conf.py:18 msgid "Enable capturing activity for the Tower activity stream." msgstr "" -#: awx/main/conf.py:19 awx/main/conf.py:29 awx/main/conf.py:39 -#: awx/main/conf.py:48 awx/main/conf.py:60 awx/main/conf.py:78 -#: awx/main/conf.py:103 +#: main/conf.py:19 main/conf.py:29 main/conf.py:39 main/conf.py:48 +#: main/conf.py:60 main/conf.py:78 main/conf.py:103 msgid "System" msgstr "" -#: awx/main/conf.py:27 +#: main/conf.py:27 msgid "Enable Activity Stream for Inventory Sync" msgstr "" -#: awx/main/conf.py:28 +#: main/conf.py:28 msgid "" "Enable capturing activity for the Tower activity stream when running " "inventory sync." msgstr "" -#: awx/main/conf.py:37 +#: main/conf.py:37 msgid "All Users Visible to Organization Admins" msgstr "" -#: awx/main/conf.py:38 +#: main/conf.py:38 msgid "" "Controls whether any Organization Admin can view all users, even those not " "associated with their Organization." msgstr "" -#: awx/main/conf.py:46 +#: main/conf.py:46 msgid "Enable Tower Administrator Alerts" msgstr "" -#: awx/main/conf.py:47 +#: main/conf.py:47 msgid "" "Allow Tower to email Admin users for system events that may require " "attention." msgstr "" -#: awx/main/conf.py:57 +#: main/conf.py:57 msgid "Base URL of the Tower host" msgstr "" -#: awx/main/conf.py:58 +#: main/conf.py:58 msgid "" "This setting is used by services like notifications to render a valid url to " "the Tower host." msgstr "" -#: awx/main/conf.py:67 +#: main/conf.py:67 msgid "Remote Host Headers" msgstr "" -#: awx/main/conf.py:68 +#: main/conf.py:68 msgid "" "HTTP headers and meta keys to search to determine remote host name or IP. " "Add additional items to this list, such as \"HTTP_X_FORWARDED_FOR\", if " @@ -1056,1459 +1064,1615 @@ msgid "" "REMOTE_HOST_HEADERS = ['HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR', 'REMOTE_HOST']" msgstr "" -#: awx/main/conf.py:99 +#: main/conf.py:99 msgid "Tower License" msgstr "" -#: awx/main/conf.py:100 +#: main/conf.py:100 msgid "" "The license controls which features and functionality are enabled in Tower. " "Use /api/v1/config/ to update or change the license." msgstr "" -#: awx/main/conf.py:110 +#: main/conf.py:110 msgid "Ansible Modules Allowed for Ad Hoc Jobs" msgstr "" -#: awx/main/conf.py:111 +#: main/conf.py:111 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "" -#: awx/main/conf.py:112 awx/main/conf.py:121 awx/main/conf.py:130 -#: awx/main/conf.py:139 awx/main/conf.py:148 awx/main/conf.py:158 -#: awx/main/conf.py:168 awx/main/conf.py:178 awx/main/conf.py:187 -#: awx/main/conf.py:199 awx/main/conf.py:211 awx/main/conf.py:223 +#: main/conf.py:112 main/conf.py:121 main/conf.py:130 main/conf.py:139 +#: main/conf.py:148 main/conf.py:158 main/conf.py:168 main/conf.py:178 +#: main/conf.py:187 main/conf.py:199 main/conf.py:211 main/conf.py:223 msgid "Jobs" msgstr "" -#: awx/main/conf.py:119 +#: main/conf.py:119 msgid "Enable job isolation" msgstr "" -#: awx/main/conf.py:120 +#: main/conf.py:120 msgid "" "Isolates an Ansible job from protected parts of the Tower system to prevent " "exposing sensitive information." msgstr "" -#: awx/main/conf.py:128 +#: main/conf.py:128 msgid "Job isolation execution path" msgstr "" -#: awx/main/conf.py:129 +#: main/conf.py:129 msgid "" "Create temporary working directories for isolated jobs in this location." msgstr "" -#: awx/main/conf.py:137 +#: main/conf.py:137 msgid "Paths to hide from isolated jobs" msgstr "" -#: awx/main/conf.py:138 +#: main/conf.py:138 msgid "Additional paths to hide from isolated processes." msgstr "" -#: awx/main/conf.py:146 +#: main/conf.py:146 msgid "Paths to expose to isolated jobs" msgstr "" -#: awx/main/conf.py:147 +#: main/conf.py:147 msgid "" "Whitelist of paths that would otherwise be hidden to expose to isolated jobs." msgstr "" -#: awx/main/conf.py:156 +#: main/conf.py:156 msgid "Standard Output Maximum Display Size" msgstr "" -#: awx/main/conf.py:157 +#: main/conf.py:157 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." msgstr "" -#: awx/main/conf.py:166 +#: main/conf.py:166 msgid "Job Event Standard Output Maximum Display Size" msgstr "" -#: awx/main/conf.py:167 +#: main/conf.py:167 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." msgstr "" -#: awx/main/conf.py:176 +#: main/conf.py:176 msgid "Maximum Scheduled Jobs" msgstr "" -#: awx/main/conf.py:177 +#: main/conf.py:177 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." msgstr "" -#: awx/main/conf.py:185 +#: main/conf.py:185 msgid "Ansible Callback Plugins" msgstr "" -#: awx/main/conf.py:186 +#: main/conf.py:186 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs." msgstr "" -#: awx/main/conf.py:196 +#: main/conf.py:196 msgid "Default Job Timeout" msgstr "" -#: awx/main/conf.py:197 +#: main/conf.py:197 msgid "" "Maximum time to allow jobs to run. Use value of 0 to indicate that no " "timeout should be imposed. A timeout set on an individual job template will " "override this." msgstr "" -#: awx/main/conf.py:208 +#: main/conf.py:208 msgid "Default Inventory Update Timeout" msgstr "" -#: awx/main/conf.py:209 +#: main/conf.py:209 msgid "" "Maximum time to allow inventory updates to run. Use value of 0 to indicate " "that no timeout should be imposed. A timeout set on an individual inventory " "source will override this." msgstr "" -#: awx/main/conf.py:220 +#: main/conf.py:220 msgid "Default Project Update Timeout" msgstr "" -#: awx/main/conf.py:221 +#: main/conf.py:221 msgid "" "Maximum time to allow project updates to run. Use value of 0 to indicate " "that no timeout should be imposed. A timeout set on an individual project " "will override this." msgstr "" -#: awx/main/models/activity_stream.py:22 +#: main/conf.py:231 +msgid "Logging Aggregator Receiving Host" +msgstr "" + +#: main/conf.py:232 +msgid "External host maintain a log collector to send logs to" +msgstr "" + +#: main/conf.py:233 main/conf.py:242 main/conf.py:252 main/conf.py:261 +#: main/conf.py:271 main/conf.py:286 main/conf.py:297 main/conf.py:306 +msgid "Logging" +msgstr "" + +#: main/conf.py:240 +msgid "Logging Aggregator Receiving Port" +msgstr "" + +#: main/conf.py:241 +msgid "Port that the log collector is listening on" +msgstr "" + +#: main/conf.py:250 +msgid "Logging Aggregator Type: Logstash, Loggly, Datadog, etc" +msgstr "" + +#: main/conf.py:251 +msgid "The type of log aggregator service to format messages for" +msgstr "" + +#: main/conf.py:259 +msgid "Logging Aggregator Username to Authenticate With" +msgstr "" + +#: main/conf.py:260 +msgid "Username for Logstash or others (basic auth)" +msgstr "" + +#: main/conf.py:269 +msgid "Logging Aggregator Password to Authenticate With" +msgstr "" + +#: main/conf.py:270 +msgid "Password for Logstash or others (basic auth)" +msgstr "" + +#: main/conf.py:278 +msgid "Loggers to send data to the log aggregator from" +msgstr "" + +#: main/conf.py:279 +msgid "" +"List of loggers that will send HTTP logs to the collector, these can include " +"any or all of: \n" +"activity_stream - logs duplicate to records entered in activity stream\n" +"job_events - callback data from Ansible job events\n" +"system_tracking - data generated from scan jobs\n" +"Sending generic Tower logs must be configured through local_settings." +"pyinstead of this mechanism." +msgstr "" + +#: main/conf.py:293 +msgid "" +"Flag denoting to send individual messages for each fact in system tracking" +msgstr "" + +#: main/conf.py:294 +msgid "" +"If not set, the data from system tracking will be sent inside of a single " +"dictionary, but if set, separate requests will be sent for each package, " +"service, etc. that is found in the scan." +msgstr "" + +#: main/conf.py:304 +msgid "Flag denoting whether to use the external logger system" +msgstr "" + +#: main/conf.py:305 +msgid "" +"If not set, only normal settings data will be used to configure loggers." +msgstr "" + +#: main/models/activity_stream.py:22 msgid "Entity Created" msgstr "" -#: awx/main/models/activity_stream.py:23 +#: main/models/activity_stream.py:23 msgid "Entity Updated" msgstr "" -#: awx/main/models/activity_stream.py:24 +#: main/models/activity_stream.py:24 msgid "Entity Deleted" msgstr "" -#: awx/main/models/activity_stream.py:25 +#: main/models/activity_stream.py:25 msgid "Entity Associated with another Entity" msgstr "" -#: awx/main/models/activity_stream.py:26 +#: main/models/activity_stream.py:26 msgid "Entity was Disassociated with another Entity" msgstr "" -#: awx/main/models/ad_hoc_commands.py:96 +#: main/models/ad_hoc_commands.py:96 msgid "No valid inventory." msgstr "" -#: awx/main/models/ad_hoc_commands.py:103 awx/main/models/jobs.py:162 +#: main/models/ad_hoc_commands.py:103 main/models/jobs.py:163 msgid "You must provide a machine / SSH credential." msgstr "" -#: awx/main/models/ad_hoc_commands.py:114 -#: awx/main/models/ad_hoc_commands.py:122 +#: main/models/ad_hoc_commands.py:114 main/models/ad_hoc_commands.py:122 msgid "Invalid type for ad hoc command" msgstr "" -#: awx/main/models/ad_hoc_commands.py:117 +#: main/models/ad_hoc_commands.py:117 msgid "Unsupported module for ad hoc commands." msgstr "" -#: awx/main/models/ad_hoc_commands.py:125 +#: main/models/ad_hoc_commands.py:125 #, python-format msgid "No argument passed to %s module." msgstr "" -#: awx/main/models/ad_hoc_commands.py:220 awx/main/models/jobs.py:766 +#: main/models/ad_hoc_commands.py:220 main/models/jobs.py:767 msgid "Host Failed" msgstr "" -#: awx/main/models/ad_hoc_commands.py:221 awx/main/models/jobs.py:767 +#: main/models/ad_hoc_commands.py:221 main/models/jobs.py:768 msgid "Host OK" msgstr "" -#: awx/main/models/ad_hoc_commands.py:222 awx/main/models/jobs.py:770 +#: main/models/ad_hoc_commands.py:222 main/models/jobs.py:771 msgid "Host Unreachable" msgstr "" -#: awx/main/models/ad_hoc_commands.py:227 awx/main/models/jobs.py:769 +#: main/models/ad_hoc_commands.py:227 main/models/jobs.py:770 msgid "Host Skipped" msgstr "" -#: awx/main/models/ad_hoc_commands.py:237 awx/main/models/jobs.py:797 +#: main/models/ad_hoc_commands.py:237 main/models/jobs.py:798 msgid "Debug" msgstr "" -#: awx/main/models/ad_hoc_commands.py:238 awx/main/models/jobs.py:798 +#: main/models/ad_hoc_commands.py:238 main/models/jobs.py:799 msgid "Verbose" msgstr "" -#: awx/main/models/ad_hoc_commands.py:239 awx/main/models/jobs.py:799 +#: main/models/ad_hoc_commands.py:239 main/models/jobs.py:800 msgid "Deprecated" msgstr "" -#: awx/main/models/ad_hoc_commands.py:240 awx/main/models/jobs.py:800 +#: main/models/ad_hoc_commands.py:240 main/models/jobs.py:801 msgid "Warning" msgstr "" -#: awx/main/models/ad_hoc_commands.py:241 awx/main/models/jobs.py:801 +#: main/models/ad_hoc_commands.py:241 main/models/jobs.py:802 msgid "System Warning" msgstr "" -#: awx/main/models/ad_hoc_commands.py:242 awx/main/models/jobs.py:802 -#: awx/main/models/unified_jobs.py:62 +#: main/models/ad_hoc_commands.py:242 main/models/jobs.py:803 +#: main/models/unified_jobs.py:62 msgid "Error" msgstr "" -#: awx/main/models/base.py:45 awx/main/models/base.py:51 -#: awx/main/models/base.py:56 +#: main/models/base.py:45 main/models/base.py:51 main/models/base.py:56 msgid "Run" msgstr "" -#: awx/main/models/base.py:46 awx/main/models/base.py:52 -#: awx/main/models/base.py:57 +#: main/models/base.py:46 main/models/base.py:52 main/models/base.py:57 msgid "Check" msgstr "" -#: awx/main/models/base.py:47 +#: main/models/base.py:47 msgid "Scan" msgstr "" -#: awx/main/models/base.py:61 +#: main/models/base.py:61 msgid "Read Inventory" msgstr "" -#: awx/main/models/base.py:62 +#: main/models/base.py:62 msgid "Edit Inventory" msgstr "" -#: awx/main/models/base.py:63 +#: main/models/base.py:63 msgid "Administrate Inventory" msgstr "" -#: awx/main/models/base.py:64 +#: main/models/base.py:64 msgid "Deploy To Inventory" msgstr "" -#: awx/main/models/base.py:65 +#: main/models/base.py:65 msgid "Deploy To Inventory (Dry Run)" msgstr "" -#: awx/main/models/base.py:66 +#: main/models/base.py:66 msgid "Scan an Inventory" msgstr "" -#: awx/main/models/base.py:67 +#: main/models/base.py:67 msgid "Create a Job Template" msgstr "" -#: awx/main/models/credential.py:33 +#: main/models/credential.py:33 msgid "Machine" msgstr "" -#: awx/main/models/credential.py:34 +#: main/models/credential.py:34 msgid "Network" msgstr "" -#: awx/main/models/credential.py:35 +#: main/models/credential.py:35 msgid "Source Control" msgstr "" -#: awx/main/models/credential.py:36 +#: main/models/credential.py:36 msgid "Amazon Web Services" msgstr "" -#: awx/main/models/credential.py:37 +#: main/models/credential.py:37 msgid "Rackspace" msgstr "" -#: awx/main/models/credential.py:38 awx/main/models/inventory.py:712 +#: main/models/credential.py:38 main/models/inventory.py:713 msgid "VMware vCenter" msgstr "" -#: awx/main/models/credential.py:39 awx/main/models/inventory.py:713 +#: main/models/credential.py:39 main/models/inventory.py:714 msgid "Red Hat Satellite 6" msgstr "" -#: awx/main/models/credential.py:40 awx/main/models/inventory.py:714 +#: main/models/credential.py:40 main/models/inventory.py:715 msgid "Red Hat CloudForms" msgstr "" -#: awx/main/models/credential.py:41 awx/main/models/inventory.py:709 +#: main/models/credential.py:41 main/models/inventory.py:710 msgid "Google Compute Engine" msgstr "" -#: awx/main/models/credential.py:42 awx/main/models/inventory.py:710 +#: main/models/credential.py:42 main/models/inventory.py:711 msgid "Microsoft Azure Classic (deprecated)" msgstr "" -#: awx/main/models/credential.py:43 awx/main/models/inventory.py:711 +#: main/models/credential.py:43 main/models/inventory.py:712 msgid "Microsoft Azure Resource Manager" msgstr "" -#: awx/main/models/credential.py:44 awx/main/models/inventory.py:715 +#: main/models/credential.py:44 main/models/inventory.py:716 msgid "OpenStack" msgstr "" -#: awx/main/models/credential.py:48 +#: main/models/credential.py:48 msgid "None" msgstr "" -#: awx/main/models/credential.py:49 +#: main/models/credential.py:49 msgid "Sudo" msgstr "" -#: awx/main/models/credential.py:50 +#: main/models/credential.py:50 msgid "Su" msgstr "" -#: awx/main/models/credential.py:51 +#: main/models/credential.py:51 msgid "Pbrun" msgstr "" -#: awx/main/models/credential.py:52 +#: main/models/credential.py:52 msgid "Pfexec" msgstr "" -#: awx/main/models/credential.py:101 +#: main/models/credential.py:101 msgid "Host" msgstr "" -#: awx/main/models/credential.py:102 +#: main/models/credential.py:102 msgid "The hostname or IP address to use." msgstr "" -#: awx/main/models/credential.py:108 +#: main/models/credential.py:108 msgid "Username" msgstr "" -#: awx/main/models/credential.py:109 +#: main/models/credential.py:109 msgid "Username for this credential." msgstr "" -#: awx/main/models/credential.py:115 +#: main/models/credential.py:115 msgid "Password" msgstr "" -#: awx/main/models/credential.py:116 +#: main/models/credential.py:116 msgid "" "Password for this credential (or \"ASK\" to prompt the user for machine " "credentials)." msgstr "" -#: awx/main/models/credential.py:123 +#: main/models/credential.py:123 msgid "Security Token" msgstr "" -#: awx/main/models/credential.py:124 +#: main/models/credential.py:124 msgid "Security Token for this credential" msgstr "" -#: awx/main/models/credential.py:130 +#: main/models/credential.py:130 msgid "Project" msgstr "" -#: awx/main/models/credential.py:131 +#: main/models/credential.py:131 msgid "The identifier for the project." msgstr "" -#: awx/main/models/credential.py:137 +#: main/models/credential.py:137 msgid "Domain" msgstr "" -#: awx/main/models/credential.py:138 +#: main/models/credential.py:138 msgid "The identifier for the domain." msgstr "" -#: awx/main/models/credential.py:143 +#: main/models/credential.py:143 msgid "SSH private key" msgstr "" -#: awx/main/models/credential.py:144 +#: main/models/credential.py:144 msgid "RSA or DSA private key to be used instead of password." msgstr "" -#: awx/main/models/credential.py:150 +#: main/models/credential.py:150 msgid "SSH key unlock" msgstr "" -#: awx/main/models/credential.py:151 +#: main/models/credential.py:151 msgid "" "Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " "user for machine credentials)." msgstr "" -#: awx/main/models/credential.py:159 +#: main/models/credential.py:159 msgid "Privilege escalation method." msgstr "" -#: awx/main/models/credential.py:165 +#: main/models/credential.py:165 msgid "Privilege escalation username." msgstr "" -#: awx/main/models/credential.py:171 +#: main/models/credential.py:171 msgid "Password for privilege escalation method." msgstr "" -#: awx/main/models/credential.py:177 +#: main/models/credential.py:177 msgid "Vault password (or \"ASK\" to prompt the user)." msgstr "" -#: awx/main/models/credential.py:181 +#: main/models/credential.py:181 msgid "Whether to use the authorize mechanism." msgstr "" -#: awx/main/models/credential.py:187 +#: main/models/credential.py:187 msgid "Password used by the authorize mechanism." msgstr "" -#: awx/main/models/credential.py:193 +#: main/models/credential.py:193 msgid "Client Id or Application Id for the credential" msgstr "" -#: awx/main/models/credential.py:199 +#: main/models/credential.py:199 msgid "Secret Token for this credential" msgstr "" -#: awx/main/models/credential.py:205 +#: main/models/credential.py:205 msgid "Subscription identifier for this credential" msgstr "" -#: awx/main/models/credential.py:211 +#: main/models/credential.py:211 msgid "Tenant identifier for this credential" msgstr "" -#: awx/main/models/credential.py:281 +#: main/models/credential.py:281 msgid "Host required for VMware credential." msgstr "" -#: awx/main/models/credential.py:283 +#: main/models/credential.py:283 msgid "Host required for OpenStack credential." msgstr "" -#: awx/main/models/credential.py:292 +#: main/models/credential.py:292 msgid "Access key required for AWS credential." msgstr "" -#: awx/main/models/credential.py:294 +#: main/models/credential.py:294 msgid "Username required for Rackspace credential." msgstr "" -#: awx/main/models/credential.py:297 +#: main/models/credential.py:297 msgid "Username required for VMware credential." msgstr "" -#: awx/main/models/credential.py:299 +#: main/models/credential.py:299 msgid "Username required for OpenStack credential." msgstr "" -#: awx/main/models/credential.py:305 +#: main/models/credential.py:305 msgid "Secret key required for AWS credential." msgstr "" -#: awx/main/models/credential.py:307 +#: main/models/credential.py:307 msgid "API key required for Rackspace credential." msgstr "" -#: awx/main/models/credential.py:309 +#: main/models/credential.py:309 msgid "Password required for VMware credential." msgstr "" -#: awx/main/models/credential.py:311 +#: main/models/credential.py:311 msgid "Password or API key required for OpenStack credential." msgstr "" -#: awx/main/models/credential.py:317 +#: main/models/credential.py:317 msgid "Project name required for OpenStack credential." msgstr "" -#: awx/main/models/credential.py:344 +#: main/models/credential.py:344 msgid "SSH key unlock must be set when SSH key is encrypted." msgstr "" -#: awx/main/models/credential.py:350 +#: main/models/credential.py:350 msgid "Credential cannot be assigned to both a user and team." msgstr "" -#: awx/main/models/fact.py:21 +#: main/models/fact.py:21 msgid "Host for the facts that the fact scan captured." msgstr "" -#: awx/main/models/fact.py:26 +#: main/models/fact.py:26 msgid "Date and time of the corresponding fact scan gathering time." msgstr "" -#: awx/main/models/fact.py:29 +#: main/models/fact.py:29 msgid "" "Arbitrary JSON structure of module facts captured at timestamp for a single " "host." msgstr "" -#: awx/main/models/inventory.py:45 +#: main/models/inventory.py:45 msgid "inventories" msgstr "" -#: awx/main/models/inventory.py:52 +#: main/models/inventory.py:52 msgid "Organization containing this inventory." msgstr "" -#: awx/main/models/inventory.py:58 +#: main/models/inventory.py:58 msgid "Inventory variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:63 +#: main/models/inventory.py:63 msgid "Flag indicating whether any hosts in this inventory have failed." msgstr "" -#: awx/main/models/inventory.py:68 +#: main/models/inventory.py:68 msgid "Total number of hosts in this inventory." msgstr "" -#: awx/main/models/inventory.py:73 +#: main/models/inventory.py:73 msgid "Number of hosts in this inventory with active failures." msgstr "" -#: awx/main/models/inventory.py:78 +#: main/models/inventory.py:78 msgid "Total number of groups in this inventory." msgstr "" -#: awx/main/models/inventory.py:83 +#: main/models/inventory.py:83 msgid "Number of groups in this inventory with active failures." msgstr "" -#: awx/main/models/inventory.py:88 +#: main/models/inventory.py:88 msgid "" "Flag indicating whether this inventory has any external inventory sources." msgstr "" -#: awx/main/models/inventory.py:93 +#: main/models/inventory.py:93 msgid "" "Total number of external inventory sources configured within this inventory." msgstr "" -#: awx/main/models/inventory.py:98 +#: main/models/inventory.py:98 msgid "Number of external inventory sources in this inventory with failures." msgstr "" -#: awx/main/models/inventory.py:339 +#: main/models/inventory.py:339 msgid "Is this host online and available for running jobs?" msgstr "" -#: awx/main/models/inventory.py:349 +#: main/models/inventory.py:345 +msgid "" +"The value used by the remote inventory source to uniquely identify the host" +msgstr "" + +#: main/models/inventory.py:350 msgid "Host variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:371 +#: main/models/inventory.py:372 msgid "Flag indicating whether the last job failed for this host." msgstr "" -#: awx/main/models/inventory.py:376 +#: main/models/inventory.py:377 msgid "" "Flag indicating whether this host was created/updated from any external " "inventory sources." msgstr "" -#: awx/main/models/inventory.py:382 +#: main/models/inventory.py:383 msgid "Inventory source(s) that created or modified this host." msgstr "" -#: awx/main/models/inventory.py:473 +#: main/models/inventory.py:474 msgid "Group variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:479 +#: main/models/inventory.py:480 msgid "Hosts associated directly with this group." msgstr "" -#: awx/main/models/inventory.py:484 +#: main/models/inventory.py:485 msgid "Total number of hosts directly or indirectly in this group." msgstr "" -#: awx/main/models/inventory.py:489 +#: main/models/inventory.py:490 msgid "Flag indicating whether this group has any hosts with active failures." msgstr "" -#: awx/main/models/inventory.py:494 +#: main/models/inventory.py:495 msgid "Number of hosts in this group with active failures." msgstr "" -#: awx/main/models/inventory.py:499 +#: main/models/inventory.py:500 msgid "Total number of child groups contained within this group." msgstr "" -#: awx/main/models/inventory.py:504 +#: main/models/inventory.py:505 msgid "Number of child groups within this group that have active failures." msgstr "" -#: awx/main/models/inventory.py:509 +#: main/models/inventory.py:510 msgid "" "Flag indicating whether this group was created/updated from any external " "inventory sources." msgstr "" -#: awx/main/models/inventory.py:515 +#: main/models/inventory.py:516 msgid "Inventory source(s) that created or modified this group." msgstr "" -#: awx/main/models/inventory.py:705 awx/main/models/projects.py:42 -#: awx/main/models/unified_jobs.py:383 +#: main/models/inventory.py:706 main/models/projects.py:42 +#: main/models/unified_jobs.py:386 msgid "Manual" msgstr "" -#: awx/main/models/inventory.py:706 +#: main/models/inventory.py:707 msgid "Local File, Directory or Script" msgstr "" -#: awx/main/models/inventory.py:707 +#: main/models/inventory.py:708 msgid "Rackspace Cloud Servers" msgstr "" -#: awx/main/models/inventory.py:708 +#: main/models/inventory.py:709 msgid "Amazon EC2" msgstr "" -#: awx/main/models/inventory.py:716 +#: main/models/inventory.py:717 msgid "Custom Script" msgstr "" -#: awx/main/models/inventory.py:827 +#: main/models/inventory.py:828 msgid "Inventory source variables in YAML or JSON format." msgstr "" -#: awx/main/models/inventory.py:846 +#: main/models/inventory.py:847 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." msgstr "" -#: awx/main/models/inventory.py:852 +#: main/models/inventory.py:853 msgid "Limit groups automatically created from inventory source (EC2 only)." msgstr "" -#: awx/main/models/inventory.py:856 +#: main/models/inventory.py:857 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:860 +#: main/models/inventory.py:861 msgid "Overwrite local variables from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:892 +#: main/models/inventory.py:893 msgid "Availability Zone" msgstr "" -#: awx/main/models/inventory.py:893 +#: main/models/inventory.py:894 msgid "Image ID" msgstr "" -#: awx/main/models/inventory.py:894 +#: main/models/inventory.py:895 msgid "Instance ID" msgstr "" -#: awx/main/models/inventory.py:895 +#: main/models/inventory.py:896 msgid "Instance Type" msgstr "" -#: awx/main/models/inventory.py:896 +#: main/models/inventory.py:897 msgid "Key Name" msgstr "" -#: awx/main/models/inventory.py:897 +#: main/models/inventory.py:898 msgid "Region" msgstr "" -#: awx/main/models/inventory.py:898 +#: main/models/inventory.py:899 msgid "Security Group" msgstr "" -#: awx/main/models/inventory.py:899 +#: main/models/inventory.py:900 msgid "Tags" msgstr "" -#: awx/main/models/inventory.py:900 +#: main/models/inventory.py:901 msgid "VPC ID" msgstr "" -#: awx/main/models/inventory.py:901 +#: main/models/inventory.py:902 msgid "Tag None" msgstr "" -#: awx/main/models/inventory.py:972 +#: main/models/inventory.py:973 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." msgstr "" -#: awx/main/models/inventory.py:979 +#: main/models/inventory.py:980 msgid "Credential is required for a cloud source." msgstr "" -#: awx/main/models/inventory.py:1004 +#: main/models/inventory.py:1005 #, python-format msgid "Invalid %(source)s region%(plural)s: %(region)s" msgstr "" -#: awx/main/models/inventory.py:1030 +#: main/models/inventory.py:1031 #, python-format msgid "Invalid filter expression%(plural)s: %(filter)s" msgstr "" -#: awx/main/models/inventory.py:1049 +#: main/models/inventory.py:1050 #, python-format msgid "Invalid group by choice%(plural)s: %(choice)s" msgstr "" -#: awx/main/models/inventory.py:1197 +#: main/models/inventory.py:1198 #, python-format msgid "" "Unable to configure this item for cloud sync. It is already managed by %s." msgstr "" -#: awx/main/models/inventory.py:1292 +#: main/models/inventory.py:1293 msgid "Inventory script contents" msgstr "" -#: awx/main/models/inventory.py:1297 +#: main/models/inventory.py:1298 msgid "Organization owning this inventory script" msgstr "" -#: awx/main/models/jobs.py:170 +#: main/models/jobs.py:171 msgid "You must provide a network credential." msgstr "" -#: awx/main/models/jobs.py:178 +#: main/models/jobs.py:179 msgid "" "Must provide a credential for a cloud provider, such as Amazon Web Services " "or Rackspace." msgstr "" -#: awx/main/models/jobs.py:270 +#: main/models/jobs.py:271 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "" -#: awx/main/models/jobs.py:274 +#: main/models/jobs.py:275 msgid "Job Template must provide 'credential' or allow prompting for it." msgstr "" -#: awx/main/models/jobs.py:363 +#: main/models/jobs.py:364 msgid "Cannot override job_type to or from a scan job." msgstr "" -#: awx/main/models/jobs.py:366 +#: main/models/jobs.py:367 msgid "Inventory cannot be changed at runtime for scan jobs." msgstr "" -#: awx/main/models/jobs.py:432 awx/main/models/projects.py:235 +#: main/models/jobs.py:433 main/models/projects.py:243 msgid "SCM Revision" msgstr "" -#: awx/main/models/jobs.py:433 +#: main/models/jobs.py:434 msgid "The SCM Revision from the Project used for this job, if available" msgstr "" -#: awx/main/models/jobs.py:441 +#: main/models/jobs.py:442 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" msgstr "" -#: awx/main/models/jobs.py:665 +#: main/models/jobs.py:666 msgid "job host summaries" msgstr "" -#: awx/main/models/jobs.py:768 +#: main/models/jobs.py:769 msgid "Host Failure" msgstr "" -#: awx/main/models/jobs.py:771 awx/main/models/jobs.py:785 +#: main/models/jobs.py:772 main/models/jobs.py:786 msgid "No Hosts Remaining" msgstr "" -#: awx/main/models/jobs.py:772 +#: main/models/jobs.py:773 msgid "Host Polling" msgstr "" -#: awx/main/models/jobs.py:773 +#: main/models/jobs.py:774 msgid "Host Async OK" msgstr "" -#: awx/main/models/jobs.py:774 +#: main/models/jobs.py:775 msgid "Host Async Failure" msgstr "" -#: awx/main/models/jobs.py:775 +#: main/models/jobs.py:776 msgid "Item OK" msgstr "" -#: awx/main/models/jobs.py:776 +#: main/models/jobs.py:777 msgid "Item Failed" msgstr "" -#: awx/main/models/jobs.py:777 +#: main/models/jobs.py:778 msgid "Item Skipped" msgstr "" -#: awx/main/models/jobs.py:778 +#: main/models/jobs.py:779 msgid "Host Retry" msgstr "" -#: awx/main/models/jobs.py:780 +#: main/models/jobs.py:781 msgid "File Difference" msgstr "" -#: awx/main/models/jobs.py:781 +#: main/models/jobs.py:782 msgid "Playbook Started" msgstr "" -#: awx/main/models/jobs.py:782 +#: main/models/jobs.py:783 msgid "Running Handlers" msgstr "" -#: awx/main/models/jobs.py:783 +#: main/models/jobs.py:784 msgid "Including File" msgstr "" -#: awx/main/models/jobs.py:784 +#: main/models/jobs.py:785 msgid "No Hosts Matched" msgstr "" -#: awx/main/models/jobs.py:786 +#: main/models/jobs.py:787 msgid "Task Started" msgstr "" -#: awx/main/models/jobs.py:788 +#: main/models/jobs.py:789 msgid "Variables Prompted" msgstr "" -#: awx/main/models/jobs.py:789 +#: main/models/jobs.py:790 msgid "Gathering Facts" msgstr "" -#: awx/main/models/jobs.py:790 +#: main/models/jobs.py:791 msgid "internal: on Import for Host" msgstr "" -#: awx/main/models/jobs.py:791 +#: main/models/jobs.py:792 msgid "internal: on Not Import for Host" msgstr "" -#: awx/main/models/jobs.py:792 +#: main/models/jobs.py:793 msgid "Play Started" msgstr "" -#: awx/main/models/jobs.py:793 +#: main/models/jobs.py:794 msgid "Playbook Complete" msgstr "" -#: awx/main/models/jobs.py:1237 +#: main/models/jobs.py:1240 msgid "Remove jobs older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1238 +#: main/models/jobs.py:1241 msgid "Remove activity stream entries older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1239 +#: main/models/jobs.py:1242 msgid "Purge and/or reduce the granularity of system tracking data" msgstr "" -#: awx/main/models/label.py:29 +#: main/models/label.py:29 msgid "Organization this label belongs to." msgstr "" -#: awx/main/models/notifications.py:31 +#: main/models/notifications.py:31 msgid "Email" msgstr "" -#: awx/main/models/notifications.py:32 +#: main/models/notifications.py:32 msgid "Slack" msgstr "" -#: awx/main/models/notifications.py:33 +#: main/models/notifications.py:33 msgid "Twilio" msgstr "" -#: awx/main/models/notifications.py:34 +#: main/models/notifications.py:34 msgid "Pagerduty" msgstr "" -#: awx/main/models/notifications.py:35 +#: main/models/notifications.py:35 msgid "HipChat" msgstr "" -#: awx/main/models/notifications.py:36 +#: main/models/notifications.py:36 msgid "Webhook" msgstr "" -#: awx/main/models/notifications.py:37 +#: main/models/notifications.py:37 msgid "IRC" msgstr "" -#: awx/main/models/notifications.py:127 awx/main/models/unified_jobs.py:57 +#: main/models/notifications.py:127 main/models/unified_jobs.py:57 msgid "Pending" msgstr "" -#: awx/main/models/notifications.py:128 awx/main/models/unified_jobs.py:60 +#: main/models/notifications.py:128 main/models/unified_jobs.py:60 msgid "Successful" msgstr "" -#: awx/main/models/notifications.py:129 awx/main/models/unified_jobs.py:61 +#: main/models/notifications.py:129 main/models/unified_jobs.py:61 msgid "Failed" msgstr "" -#: awx/main/models/organization.py:157 +#: main/models/organization.py:157 msgid "Execute Commands on the Inventory" msgstr "" -#: awx/main/models/organization.py:211 +#: main/models/organization.py:211 msgid "Token not invalidated" msgstr "" -#: awx/main/models/organization.py:212 +#: main/models/organization.py:212 msgid "Token is expired" msgstr "" -#: awx/main/models/organization.py:213 +#: main/models/organization.py:213 msgid "Maximum per-user sessions reached" msgstr "" -#: awx/main/models/organization.py:216 +#: main/models/organization.py:216 msgid "Invalid token" msgstr "" -#: awx/main/models/organization.py:233 +#: main/models/organization.py:233 msgid "Reason the auth token was invalidated." msgstr "" -#: awx/main/models/organization.py:272 +#: main/models/organization.py:272 msgid "Invalid reason specified" msgstr "" -#: awx/main/models/projects.py:43 +#: main/models/projects.py:43 msgid "Git" msgstr "" -#: awx/main/models/projects.py:44 +#: main/models/projects.py:44 msgid "Mercurial" msgstr "" -#: awx/main/models/projects.py:45 +#: main/models/projects.py:45 msgid "Subversion" msgstr "" -#: awx/main/models/projects.py:71 +#: main/models/projects.py:71 msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." msgstr "" -#: awx/main/models/projects.py:80 +#: main/models/projects.py:80 msgid "SCM Type" msgstr "" -#: awx/main/models/projects.py:86 +#: main/models/projects.py:81 +msgid "Specifies the source control system used to store the project." +msgstr "" + +#: main/models/projects.py:87 msgid "SCM URL" msgstr "" -#: awx/main/models/projects.py:92 +#: main/models/projects.py:88 +msgid "The location where the project is stored." +msgstr "" + +#: main/models/projects.py:94 msgid "SCM Branch" msgstr "" -#: awx/main/models/projects.py:93 +#: main/models/projects.py:95 msgid "Specific branch, tag or commit to checkout." msgstr "" -#: awx/main/models/projects.py:125 +#: main/models/projects.py:99 +msgid "Discard any local changes before syncing the project." +msgstr "" + +#: main/models/projects.py:103 +msgid "Delete the project before syncing." +msgstr "" + +#: main/models/projects.py:116 +msgid "The amount of time to run before the task is canceled." +msgstr "" + +#: main/models/projects.py:130 msgid "Invalid SCM URL." msgstr "" -#: awx/main/models/projects.py:128 +#: main/models/projects.py:133 msgid "SCM URL is required." msgstr "" -#: awx/main/models/projects.py:137 +#: main/models/projects.py:142 msgid "Credential kind must be 'scm'." msgstr "" -#: awx/main/models/projects.py:152 +#: main/models/projects.py:157 msgid "Invalid credential." msgstr "" -#: awx/main/models/projects.py:236 +#: main/models/projects.py:229 +msgid "Update the project when a job is launched that uses the project." +msgstr "" + +#: main/models/projects.py:234 +msgid "" +"The number of seconds after the last project update ran that a newproject " +"update will be launched as a job dependency." +msgstr "" + +#: main/models/projects.py:244 msgid "The last revision fetched by a project update" msgstr "" -#: awx/main/models/projects.py:243 +#: main/models/projects.py:251 msgid "Playbook Files" msgstr "" -#: awx/main/models/projects.py:244 +#: main/models/projects.py:252 msgid "List of playbooks found in the project" msgstr "" -#: awx/main/models/rbac.py:122 +#: main/models/rbac.py:122 msgid "roles" msgstr "" -#: awx/main/models/rbac.py:435 +#: main/models/rbac.py:438 msgid "role_ancestors" msgstr "" -#: awx/main/models/unified_jobs.py:56 +#: main/models/schedules.py:69 +msgid "Enables processing of this schedule by Tower." +msgstr "" + +#: main/models/schedules.py:75 +msgid "The first occurrence of the schedule occurs on or after this time." +msgstr "" + +#: main/models/schedules.py:81 +msgid "" +"The last occurrence of the schedule occurs before this time, aftewards the " +"schedule expires." +msgstr "" + +#: main/models/schedules.py:85 +msgid "A value representing the schedules iCal recurrence rule." +msgstr "" + +#: main/models/schedules.py:91 +msgid "The next time that the scheduled action will run." +msgstr "" + +#: main/models/unified_jobs.py:56 msgid "New" msgstr "" -#: awx/main/models/unified_jobs.py:58 +#: main/models/unified_jobs.py:58 msgid "Waiting" msgstr "" -#: awx/main/models/unified_jobs.py:59 +#: main/models/unified_jobs.py:59 msgid "Running" msgstr "" -#: awx/main/models/unified_jobs.py:63 +#: main/models/unified_jobs.py:63 msgid "Canceled" msgstr "" -#: awx/main/models/unified_jobs.py:67 +#: main/models/unified_jobs.py:67 msgid "Never Updated" msgstr "" -#: awx/main/models/unified_jobs.py:71 awx/ui/templates/ui/index.html:85 -#: awx/ui/templates/ui/index.html.py:104 +#: main/models/unified_jobs.py:71 ui/templates/ui/index.html:85 +#: ui/templates/ui/index.html.py:104 msgid "OK" msgstr "" -#: awx/main/models/unified_jobs.py:72 +#: main/models/unified_jobs.py:72 msgid "Missing" msgstr "" -#: awx/main/models/unified_jobs.py:76 +#: main/models/unified_jobs.py:76 msgid "No External Source" msgstr "" -#: awx/main/models/unified_jobs.py:83 +#: main/models/unified_jobs.py:83 msgid "Updating" msgstr "" -#: awx/main/models/unified_jobs.py:384 +#: main/models/unified_jobs.py:387 msgid "Relaunch" msgstr "" -#: awx/main/models/unified_jobs.py:385 +#: main/models/unified_jobs.py:388 msgid "Callback" msgstr "" -#: awx/main/models/unified_jobs.py:386 +#: main/models/unified_jobs.py:389 msgid "Scheduled" msgstr "" -#: awx/main/models/unified_jobs.py:387 +#: main/models/unified_jobs.py:390 msgid "Dependency" msgstr "" -#: awx/main/models/unified_jobs.py:388 +#: main/models/unified_jobs.py:391 msgid "Workflow" msgstr "" -#: awx/main/notifications/base.py:17 awx/main/notifications/email_backend.py:28 +#: main/models/unified_jobs.py:437 +msgid "The Tower node the job executed on." +msgstr "" + +#: main/models/unified_jobs.py:463 +msgid "The date and time the job was queued for starting." +msgstr "" + +#: main/models/unified_jobs.py:469 +msgid "The date and time the job finished execution." +msgstr "" + +#: main/models/unified_jobs.py:475 +msgid "Elapsed time in seconds that the job ran." +msgstr "" + +#: main/models/unified_jobs.py:497 +msgid "" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" +msgstr "" + +#: main/notifications/base.py:17 main/notifications/email_backend.py:28 msgid "" "{} #{} had status {} on Ansible Tower, view details at {}\n" "\n" msgstr "" -#: awx/main/notifications/hipchat_backend.py:46 +#: main/notifications/hipchat_backend.py:46 msgid "Error sending messages: {}" msgstr "" -#: awx/main/notifications/hipchat_backend.py:48 +#: main/notifications/hipchat_backend.py:48 msgid "Error sending message to hipchat: {}" msgstr "" -#: awx/main/notifications/irc_backend.py:54 +#: main/notifications/irc_backend.py:54 msgid "Exception connecting to irc server: {}" msgstr "" -#: awx/main/notifications/pagerduty_backend.py:39 +#: main/notifications/pagerduty_backend.py:39 msgid "Exception connecting to PagerDuty: {}" msgstr "" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:52 -#: awx/main/notifications/twilio_backend.py:46 +#: main/notifications/pagerduty_backend.py:48 +#: main/notifications/slack_backend.py:52 +#: main/notifications/twilio_backend.py:46 msgid "Exception sending messages: {}" msgstr "" -#: awx/main/notifications/twilio_backend.py:36 +#: main/notifications/twilio_backend.py:36 msgid "Exception connecting to Twilio: {}" msgstr "" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: main/notifications/webhook_backend.py:38 +#: main/notifications/webhook_backend.py:40 msgid "Error sending notification webhook: {}" msgstr "" -#: awx/main/tasks.py:119 +#: main/tasks.py:139 msgid "Ansible Tower host usage over 90%" msgstr "" -#: awx/main/tasks.py:124 +#: main/tasks.py:144 msgid "Ansible Tower license will expire soon" msgstr "" -#: awx/main/tasks.py:177 +#: main/tasks.py:197 msgid "status_str must be either succeeded or failed" msgstr "" -#: awx/main/utils.py:88 +#: main/utils/common.py:88 #, python-format msgid "Unable to convert \"%s\" to boolean" msgstr "" -#: awx/main/utils.py:242 +#: main/utils/common.py:242 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "" -#: awx/main/utils.py:249 awx/main/utils.py:261 awx/main/utils.py:280 +#: main/utils/common.py:249 main/utils/common.py:261 main/utils/common.py:280 #, python-format msgid "Invalid %s URL" msgstr "" -#: awx/main/utils.py:251 awx/main/utils.py:289 +#: main/utils/common.py:251 main/utils/common.py:289 #, python-format msgid "Unsupported %s URL" msgstr "" -#: awx/main/utils.py:291 +#: main/utils/common.py:291 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "" -#: awx/main/utils.py:293 +#: main/utils/common.py:293 #, python-format msgid "Host is required for %s URL" msgstr "" -#: awx/main/utils.py:311 +#: main/utils/common.py:311 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "" -#: awx/main/utils.py:317 +#: main/utils/common.py:317 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "" -#: awx/main/validators.py:60 +#: main/validators.py:60 #, python-format msgid "Invalid certificate or key: %r..." msgstr "" -#: awx/main/validators.py:74 +#: main/validators.py:74 #, python-format msgid "Invalid private key: unsupported type \"%s\"" msgstr "" -#: awx/main/validators.py:78 +#: main/validators.py:78 #, python-format msgid "Unsupported PEM object type: \"%s\"" msgstr "" -#: awx/main/validators.py:103 +#: main/validators.py:103 msgid "Invalid base64-encoded data" msgstr "" -#: awx/main/validators.py:122 +#: main/validators.py:122 msgid "Exactly one private key is required." msgstr "" -#: awx/main/validators.py:124 +#: main/validators.py:124 msgid "At least one private key is required." msgstr "" -#: awx/main/validators.py:126 +#: main/validators.py:126 #, python-format msgid "" "At least %(min_keys)d private keys are required, only %(key_count)d provided." msgstr "" -#: awx/main/validators.py:129 +#: main/validators.py:129 #, python-format msgid "Only one private key is allowed, %(key_count)d provided." msgstr "" -#: awx/main/validators.py:131 +#: main/validators.py:131 #, python-format msgid "" "No more than %(max_keys)d private keys are allowed, %(key_count)d provided." msgstr "" -#: awx/main/validators.py:136 +#: main/validators.py:136 msgid "Exactly one certificate is required." msgstr "" -#: awx/main/validators.py:138 +#: main/validators.py:138 msgid "At least one certificate is required." msgstr "" -#: awx/main/validators.py:140 +#: main/validators.py:140 #, python-format msgid "" "At least %(min_certs)d certificates are required, only %(cert_count)d " "provided." msgstr "" -#: awx/main/validators.py:143 +#: main/validators.py:143 #, python-format msgid "Only one certificate is allowed, %(cert_count)d provided." msgstr "" -#: awx/main/validators.py:145 +#: main/validators.py:145 #, python-format msgid "" "No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." msgstr "" -#: awx/main/views.py:20 +#: main/views.py:20 msgid "API Error" msgstr "" -#: awx/main/views.py:49 +#: main/views.py:49 msgid "Bad Request" msgstr "" -#: awx/main/views.py:50 +#: main/views.py:50 msgid "The request could not be understood by the server." msgstr "" -#: awx/main/views.py:57 +#: main/views.py:57 msgid "Forbidden" msgstr "" -#: awx/main/views.py:58 +#: main/views.py:58 msgid "You don't have permission to access the requested resource." msgstr "" -#: awx/main/views.py:65 +#: main/views.py:65 msgid "Not Found" msgstr "" -#: awx/main/views.py:66 +#: main/views.py:66 msgid "The requested resource could not be found." msgstr "" -#: awx/main/views.py:73 +#: main/views.py:73 msgid "Server Error" msgstr "" -#: awx/main/views.py:74 +#: main/views.py:74 msgid "A server error has occurred." msgstr "" -#: awx/settings/defaults.py:593 +#: settings/defaults.py:600 msgid "Chicago" msgstr "" -#: awx/settings/defaults.py:594 +#: settings/defaults.py:601 msgid "Dallas/Ft. Worth" msgstr "" -#: awx/settings/defaults.py:595 +#: settings/defaults.py:602 msgid "Northern Virginia" msgstr "" -#: awx/settings/defaults.py:596 +#: settings/defaults.py:603 msgid "London" msgstr "" -#: awx/settings/defaults.py:597 +#: settings/defaults.py:604 msgid "Sydney" msgstr "" -#: awx/settings/defaults.py:598 +#: settings/defaults.py:605 msgid "Hong Kong" msgstr "" -#: awx/settings/defaults.py:625 +#: settings/defaults.py:632 msgid "US East (Northern Virginia)" msgstr "" -#: awx/settings/defaults.py:626 +#: settings/defaults.py:633 msgid "US East (Ohio)" msgstr "" -#: awx/settings/defaults.py:627 +#: settings/defaults.py:634 msgid "US West (Oregon)" msgstr "" -#: awx/settings/defaults.py:628 +#: settings/defaults.py:635 msgid "US West (Northern California)" msgstr "" -#: awx/settings/defaults.py:629 +#: settings/defaults.py:636 msgid "EU (Frankfurt)" msgstr "" -#: awx/settings/defaults.py:630 +#: settings/defaults.py:637 msgid "EU (Ireland)" msgstr "" -#: awx/settings/defaults.py:631 +#: settings/defaults.py:638 msgid "Asia Pacific (Singapore)" msgstr "" -#: awx/settings/defaults.py:632 +#: settings/defaults.py:639 msgid "Asia Pacific (Sydney)" msgstr "" -#: awx/settings/defaults.py:633 +#: settings/defaults.py:640 msgid "Asia Pacific (Tokyo)" msgstr "" -#: awx/settings/defaults.py:634 +#: settings/defaults.py:641 msgid "Asia Pacific (Seoul)" msgstr "" -#: awx/settings/defaults.py:635 +#: settings/defaults.py:642 msgid "Asia Pacific (Mumbai)" msgstr "" -#: awx/settings/defaults.py:636 +#: settings/defaults.py:643 msgid "South America (Sao Paulo)" msgstr "" -#: awx/settings/defaults.py:637 +#: settings/defaults.py:644 msgid "US West (GovCloud)" msgstr "" -#: awx/settings/defaults.py:638 +#: settings/defaults.py:645 msgid "China (Beijing)" msgstr "" -#: awx/settings/defaults.py:687 +#: settings/defaults.py:694 msgid "US East (B)" msgstr "" -#: awx/settings/defaults.py:688 +#: settings/defaults.py:695 msgid "US East (C)" msgstr "" -#: awx/settings/defaults.py:689 +#: settings/defaults.py:696 msgid "US East (D)" msgstr "" -#: awx/settings/defaults.py:690 +#: settings/defaults.py:697 msgid "US Central (A)" msgstr "" -#: awx/settings/defaults.py:691 +#: settings/defaults.py:698 msgid "US Central (B)" msgstr "" -#: awx/settings/defaults.py:692 +#: settings/defaults.py:699 msgid "US Central (C)" msgstr "" -#: awx/settings/defaults.py:693 +#: settings/defaults.py:700 msgid "US Central (F)" msgstr "" -#: awx/settings/defaults.py:694 +#: settings/defaults.py:701 msgid "Europe West (B)" msgstr "" -#: awx/settings/defaults.py:695 +#: settings/defaults.py:702 msgid "Europe West (C)" msgstr "" -#: awx/settings/defaults.py:696 +#: settings/defaults.py:703 msgid "Europe West (D)" msgstr "" -#: awx/settings/defaults.py:697 +#: settings/defaults.py:704 msgid "Asia East (A)" msgstr "" -#: awx/settings/defaults.py:698 +#: settings/defaults.py:705 msgid "Asia East (B)" msgstr "" -#: awx/settings/defaults.py:699 +#: settings/defaults.py:706 msgid "Asia East (C)" msgstr "" -#: awx/settings/defaults.py:723 +#: settings/defaults.py:730 msgid "US Central" msgstr "" -#: awx/settings/defaults.py:724 +#: settings/defaults.py:731 msgid "US East" msgstr "" -#: awx/settings/defaults.py:725 +#: settings/defaults.py:732 msgid "US East 2" msgstr "" -#: awx/settings/defaults.py:726 +#: settings/defaults.py:733 msgid "US North Central" msgstr "" -#: awx/settings/defaults.py:727 +#: settings/defaults.py:734 msgid "US South Central" msgstr "" -#: awx/settings/defaults.py:728 +#: settings/defaults.py:735 msgid "US West" msgstr "" -#: awx/settings/defaults.py:729 +#: settings/defaults.py:736 msgid "Europe North" msgstr "" -#: awx/settings/defaults.py:730 +#: settings/defaults.py:737 msgid "Europe West" msgstr "" -#: awx/settings/defaults.py:731 +#: settings/defaults.py:738 msgid "Asia Pacific East" msgstr "" -#: awx/settings/defaults.py:732 +#: settings/defaults.py:739 msgid "Asia Pacific Southeast" msgstr "" -#: awx/settings/defaults.py:733 +#: settings/defaults.py:740 msgid "Japan East" msgstr "" -#: awx/settings/defaults.py:734 +#: settings/defaults.py:741 msgid "Japan West" msgstr "" -#: awx/settings/defaults.py:735 +#: settings/defaults.py:742 msgid "Brazil South" msgstr "" -#: awx/sso/apps.py:9 +#: sso/apps.py:9 msgid "Single Sign-On" msgstr "" -#: awx/sso/conf.py:27 +#: sso/conf.py:27 msgid "" "Mapping to organization admins/users from social auth accounts. This " "setting\n" @@ -2546,7 +2710,7 @@ msgid "" " remove_admins." msgstr "" -#: awx/sso/conf.py:76 +#: sso/conf.py:76 msgid "" "Mapping of team members (users) from social auth accounts. Keys are team\n" "names (will be created if not present). Values are dictionaries of options\n" @@ -2575,40 +2739,40 @@ msgid "" " the rules above will be removed from the team." msgstr "" -#: awx/sso/conf.py:119 +#: sso/conf.py:119 msgid "Authentication Backends" msgstr "" -#: awx/sso/conf.py:120 +#: sso/conf.py:120 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." msgstr "" -#: awx/sso/conf.py:133 +#: sso/conf.py:133 msgid "Social Auth Organization Map" msgstr "" -#: awx/sso/conf.py:145 +#: sso/conf.py:145 msgid "Social Auth Team Map" msgstr "" -#: awx/sso/conf.py:157 +#: sso/conf.py:157 msgid "Social Auth User Fields" msgstr "" -#: awx/sso/conf.py:158 +#: sso/conf.py:158 msgid "" "When set to an empty list `[]`, this setting prevents new user accounts from " "being created. Only users who have previously logged in using social auth or " "have a user account with a matching email address will be able to login." msgstr "" -#: awx/sso/conf.py:176 +#: sso/conf.py:176 msgid "LDAP Server URI" msgstr "" -#: awx/sso/conf.py:177 +#: sso/conf.py:177 msgid "" "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-" "SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be " @@ -2616,19 +2780,18 @@ msgid "" "disabled if this parameter is empty." msgstr "" -#: awx/sso/conf.py:181 awx/sso/conf.py:199 awx/sso/conf.py:211 -#: awx/sso/conf.py:222 awx/sso/conf.py:238 awx/sso/conf.py:257 -#: awx/sso/conf.py:278 awx/sso/conf.py:294 awx/sso/conf.py:313 -#: awx/sso/conf.py:330 awx/sso/conf.py:345 awx/sso/conf.py:360 -#: awx/sso/conf.py:377 awx/sso/conf.py:415 awx/sso/conf.py:456 +#: sso/conf.py:181 sso/conf.py:199 sso/conf.py:211 sso/conf.py:223 +#: sso/conf.py:239 sso/conf.py:258 sso/conf.py:279 sso/conf.py:295 +#: sso/conf.py:314 sso/conf.py:331 sso/conf.py:347 sso/conf.py:362 +#: sso/conf.py:379 sso/conf.py:417 sso/conf.py:458 msgid "LDAP" msgstr "" -#: awx/sso/conf.py:193 +#: sso/conf.py:193 msgid "LDAP Bind DN" msgstr "" -#: awx/sso/conf.py:194 +#: sso/conf.py:194 msgid "" "DN (Distinguished Name) of user to bind for all search queries. Normally in " "the format \"CN=Some User,OU=Users,DC=example,DC=com\" but may also be " @@ -2636,27 +2799,27 @@ msgid "" "user account we will use to login to query LDAP for other user information." msgstr "" -#: awx/sso/conf.py:209 +#: sso/conf.py:209 msgid "LDAP Bind Password" msgstr "" -#: awx/sso/conf.py:210 +#: sso/conf.py:210 msgid "Password used to bind LDAP user account." msgstr "" -#: awx/sso/conf.py:220 +#: sso/conf.py:221 msgid "LDAP Start TLS" msgstr "" -#: awx/sso/conf.py:221 +#: sso/conf.py:222 msgid "Whether to enable TLS when the LDAP connection is not using SSL." msgstr "" -#: awx/sso/conf.py:231 +#: sso/conf.py:232 msgid "LDAP Connection Options" msgstr "" -#: awx/sso/conf.py:232 +#: sso/conf.py:233 msgid "" "Additional options to set for the LDAP connection. LDAP referrals are " "disabled by default (to prevent certain LDAP queries from hanging with AD). " @@ -2665,11 +2828,11 @@ msgid "" "values that can be set." msgstr "" -#: awx/sso/conf.py:250 +#: sso/conf.py:251 msgid "LDAP User Search" msgstr "" -#: awx/sso/conf.py:251 +#: sso/conf.py:252 msgid "" "LDAP search query to find users. Any user that matches the given pattern " "will be able to login to Tower. The user should also be mapped into an " @@ -2678,11 +2841,11 @@ msgid "" "possible. See python-ldap documentation as linked at the top of this section." msgstr "" -#: awx/sso/conf.py:272 +#: sso/conf.py:273 msgid "LDAP User DN Template" msgstr "" -#: awx/sso/conf.py:273 +#: sso/conf.py:274 msgid "" "Alternative to user search, if user DNs are all of the same format. This " "approach will be more efficient for user lookups than searching if it is " @@ -2690,11 +2853,11 @@ msgid "" "will be used instead of AUTH_LDAP_USER_SEARCH." msgstr "" -#: awx/sso/conf.py:288 +#: sso/conf.py:289 msgid "LDAP User Attribute Map" msgstr "" -#: awx/sso/conf.py:289 +#: sso/conf.py:290 msgid "" "Mapping of LDAP user schema to Tower API user attributes (key is user " "attribute name, value is LDAP attribute name). The default setting is valid " @@ -2702,54 +2865,54 @@ msgid "" "change the values (not the keys) of the dictionary/hash-table." msgstr "" -#: awx/sso/conf.py:308 +#: sso/conf.py:309 msgid "LDAP Group Search" msgstr "" -#: awx/sso/conf.py:309 +#: sso/conf.py:310 msgid "" "Users in Tower are mapped to organizations based on their membership in LDAP " "groups. This setting defines the LDAP search query to find groups. Note that " "this, unlike the user search above, does not support LDAPSearchUnion." msgstr "" -#: awx/sso/conf.py:326 +#: sso/conf.py:327 msgid "LDAP Group Type" msgstr "" -#: awx/sso/conf.py:327 +#: sso/conf.py:328 msgid "" "The group type may need to be changed based on the type of the LDAP server. " "Values are listed at: http://pythonhosted.org/django-auth-ldap/groups." "html#types-of-groups" msgstr "" -#: awx/sso/conf.py:340 +#: sso/conf.py:342 msgid "LDAP Require Group" msgstr "" -#: awx/sso/conf.py:341 +#: sso/conf.py:343 msgid "" "Group DN required to login. If specified, user must be a member of this " "group to login via LDAP. If not set, everyone in LDAP that matches the user " "search will be able to login via Tower. Only one require group is supported." msgstr "" -#: awx/sso/conf.py:356 +#: sso/conf.py:358 msgid "LDAP Deny Group" msgstr "" -#: awx/sso/conf.py:357 +#: sso/conf.py:359 msgid "" "Group DN denied from login. If specified, user will not be allowed to login " "if a member of this group. Only one deny group is supported." msgstr "" -#: awx/sso/conf.py:370 +#: sso/conf.py:372 msgid "LDAP User Flags By Group" msgstr "" -#: awx/sso/conf.py:371 +#: sso/conf.py:373 msgid "" "User profile flags updated from group membership (key is user attribute " "name, value is group DN). These are boolean fields that are matched based " @@ -2758,11 +2921,11 @@ msgid "" "false at login time based on current LDAP settings." msgstr "" -#: awx/sso/conf.py:389 +#: sso/conf.py:391 msgid "LDAP Organization Map" msgstr "" -#: awx/sso/conf.py:390 +#: sso/conf.py:392 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " "what users are placed into what Tower organizations relative to their LDAP " @@ -2789,11 +2952,11 @@ msgid "" "remove_admins." msgstr "" -#: awx/sso/conf.py:438 +#: sso/conf.py:440 msgid "LDAP Team Map" msgstr "" -#: awx/sso/conf.py:439 +#: sso/conf.py:441 msgid "" "Mapping between team members (users) and LDAP groups. Keys are team names " "(will be created if not present). Values are dictionaries of options for " @@ -2812,88 +2975,87 @@ msgid "" "of the given groups will be removed from the team." msgstr "" -#: awx/sso/conf.py:482 +#: sso/conf.py:484 msgid "RADIUS Server" msgstr "" -#: awx/sso/conf.py:483 +#: sso/conf.py:485 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication will be disabled if this " "setting is empty." msgstr "" -#: awx/sso/conf.py:485 awx/sso/conf.py:499 awx/sso/conf.py:511 +#: sso/conf.py:487 sso/conf.py:501 sso/conf.py:513 msgid "RADIUS" msgstr "" -#: awx/sso/conf.py:497 +#: sso/conf.py:499 msgid "RADIUS Port" msgstr "" -#: awx/sso/conf.py:498 +#: sso/conf.py:500 msgid "Port of RADIUS server." msgstr "" -#: awx/sso/conf.py:509 +#: sso/conf.py:511 msgid "RADIUS Secret" msgstr "" -#: awx/sso/conf.py:510 +#: sso/conf.py:512 msgid "Shared secret for authenticating to RADIUS server." msgstr "" -#: awx/sso/conf.py:525 +#: sso/conf.py:528 msgid "Google OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:526 +#: sso/conf.py:529 msgid "" "Create a project at https://console.developers.google.com/ to obtain an " "OAuth2 key and secret for a web application. Ensure that the Google+ API is " "enabled. Provide this URL as the callback URL for your application." msgstr "" -#: awx/sso/conf.py:530 awx/sso/conf.py:541 awx/sso/conf.py:552 -#: awx/sso/conf.py:564 awx/sso/conf.py:578 awx/sso/conf.py:590 -#: awx/sso/conf.py:602 +#: sso/conf.py:533 sso/conf.py:544 sso/conf.py:555 sso/conf.py:568 +#: sso/conf.py:582 sso/conf.py:594 sso/conf.py:606 msgid "Google OAuth2" msgstr "" -#: awx/sso/conf.py:539 +#: sso/conf.py:542 msgid "Google OAuth2 Key" msgstr "" -#: awx/sso/conf.py:540 +#: sso/conf.py:543 msgid "" "The OAuth2 key from your web application at https://console.developers." "google.com/." msgstr "" -#: awx/sso/conf.py:550 +#: sso/conf.py:553 msgid "Google OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:551 +#: sso/conf.py:554 msgid "" "The OAuth2 secret from your web application at https://console.developers." "google.com/." msgstr "" -#: awx/sso/conf.py:561 +#: sso/conf.py:565 msgid "Google OAuth2 Whitelisted Domains" msgstr "" -#: awx/sso/conf.py:562 +#: sso/conf.py:566 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." msgstr "" -#: awx/sso/conf.py:573 +#: sso/conf.py:577 msgid "Google OAuth2 Extra Arguments" msgstr "" -#: awx/sso/conf.py:574 +#: sso/conf.py:578 msgid "" "Extra arguments for Google OAuth2 login. When only allowing a single domain " "to authenticate, set to `{\"hd\": \"yourdomain.com\"}` and Google will not " @@ -2901,60 +3063,60 @@ msgid "" "Google accounts." msgstr "" -#: awx/sso/conf.py:588 +#: sso/conf.py:592 msgid "Google OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:600 +#: sso/conf.py:604 msgid "Google OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:616 +#: sso/conf.py:620 msgid "GitHub OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:617 +#: sso/conf.py:621 msgid "" "Create a developer application at https://github.com/settings/developers to " "obtain an OAuth2 key (Client ID) and secret (Client Secret). Provide this " "URL as the callback URL for your application." msgstr "" -#: awx/sso/conf.py:621 awx/sso/conf.py:632 awx/sso/conf.py:642 -#: awx/sso/conf.py:653 awx/sso/conf.py:665 +#: sso/conf.py:625 sso/conf.py:636 sso/conf.py:646 sso/conf.py:658 +#: sso/conf.py:670 msgid "GitHub OAuth2" msgstr "" -#: awx/sso/conf.py:630 +#: sso/conf.py:634 msgid "GitHub OAuth2 Key" msgstr "" -#: awx/sso/conf.py:631 +#: sso/conf.py:635 msgid "The OAuth2 key (Client ID) from your GitHub developer application." msgstr "" -#: awx/sso/conf.py:640 +#: sso/conf.py:644 msgid "GitHub OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:641 +#: sso/conf.py:645 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." msgstr "" -#: awx/sso/conf.py:651 +#: sso/conf.py:656 msgid "GitHub OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:663 +#: sso/conf.py:668 msgid "GitHub OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:679 +#: sso/conf.py:684 msgid "GitHub Organization OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:680 awx/sso/conf.py:754 +#: sso/conf.py:685 sso/conf.py:760 msgid "" "Create an organization-owned application at https://github.com/organizations/" "/settings/applications and obtain an OAuth2 key (Client ID) and " @@ -2962,86 +3124,86 @@ msgid "" "application." msgstr "" -#: awx/sso/conf.py:684 awx/sso/conf.py:695 awx/sso/conf.py:705 -#: awx/sso/conf.py:716 awx/sso/conf.py:727 awx/sso/conf.py:739 +#: sso/conf.py:689 sso/conf.py:700 sso/conf.py:710 sso/conf.py:722 +#: sso/conf.py:733 sso/conf.py:745 msgid "GitHub Organization OAuth2" msgstr "" -#: awx/sso/conf.py:693 +#: sso/conf.py:698 msgid "GitHub Organization OAuth2 Key" msgstr "" -#: awx/sso/conf.py:694 awx/sso/conf.py:768 +#: sso/conf.py:699 sso/conf.py:774 msgid "The OAuth2 key (Client ID) from your GitHub organization application." msgstr "" -#: awx/sso/conf.py:703 +#: sso/conf.py:708 msgid "GitHub Organization OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:704 awx/sso/conf.py:778 +#: sso/conf.py:709 sso/conf.py:784 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." msgstr "" -#: awx/sso/conf.py:713 +#: sso/conf.py:719 msgid "GitHub Organization Name" msgstr "" -#: awx/sso/conf.py:714 +#: sso/conf.py:720 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." msgstr "" -#: awx/sso/conf.py:725 +#: sso/conf.py:731 msgid "GitHub Organization OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:737 +#: sso/conf.py:743 msgid "GitHub Organization OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:753 +#: sso/conf.py:759 msgid "GitHub Team OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:758 awx/sso/conf.py:769 awx/sso/conf.py:779 -#: awx/sso/conf.py:790 awx/sso/conf.py:801 awx/sso/conf.py:813 +#: sso/conf.py:764 sso/conf.py:775 sso/conf.py:785 sso/conf.py:797 +#: sso/conf.py:808 sso/conf.py:820 msgid "GitHub Team OAuth2" msgstr "" -#: awx/sso/conf.py:767 +#: sso/conf.py:773 msgid "GitHub Team OAuth2 Key" msgstr "" -#: awx/sso/conf.py:777 +#: sso/conf.py:783 msgid "GitHub Team OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:787 +#: sso/conf.py:794 msgid "GitHub Team ID" msgstr "" -#: awx/sso/conf.py:788 +#: sso/conf.py:795 msgid "" "Find the numeric team ID using the Github API: http://fabian-kostadinov." "github.io/2015/01/16/how-to-find-a-github-team-id/." msgstr "" -#: awx/sso/conf.py:799 +#: sso/conf.py:806 msgid "GitHub Team OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:811 +#: sso/conf.py:818 msgid "GitHub Team OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:827 +#: sso/conf.py:834 msgid "Azure AD OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:828 +#: sso/conf.py:835 msgid "" "Register an Azure AD application as described by https://msdn.microsoft.com/" "en-us/library/azure/dn132599.aspx and obtain an OAuth2 key (Client ID) and " @@ -3049,118 +3211,117 @@ msgid "" "application." msgstr "" -#: awx/sso/conf.py:832 awx/sso/conf.py:843 awx/sso/conf.py:853 -#: awx/sso/conf.py:864 awx/sso/conf.py:876 +#: sso/conf.py:839 sso/conf.py:850 sso/conf.py:860 sso/conf.py:872 +#: sso/conf.py:884 msgid "Azure AD OAuth2" msgstr "" -#: awx/sso/conf.py:841 +#: sso/conf.py:848 msgid "Azure AD OAuth2 Key" msgstr "" -#: awx/sso/conf.py:842 +#: sso/conf.py:849 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "" -#: awx/sso/conf.py:851 +#: sso/conf.py:858 msgid "Azure AD OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:852 +#: sso/conf.py:859 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." msgstr "" -#: awx/sso/conf.py:862 +#: sso/conf.py:870 msgid "Azure AD OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:874 +#: sso/conf.py:882 msgid "Azure AD OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:895 +#: sso/conf.py:903 msgid "SAML Service Provider Callback URL" msgstr "" -#: awx/sso/conf.py:896 +#: sso/conf.py:904 msgid "" "Register Tower as a service provider (SP) with each identity provider (IdP) " "you have configured. Provide your SP Entity ID and this callback URL for " "your application." msgstr "" -#: awx/sso/conf.py:899 awx/sso/conf.py:913 awx/sso/conf.py:927 -#: awx/sso/conf.py:941 awx/sso/conf.py:955 awx/sso/conf.py:972 -#: awx/sso/conf.py:994 awx/sso/conf.py:1013 awx/sso/conf.py:1033 -#: awx/sso/conf.py:1067 awx/sso/conf.py:1080 +#: sso/conf.py:907 sso/conf.py:921 sso/conf.py:934 sso/conf.py:948 +#: sso/conf.py:962 sso/conf.py:980 sso/conf.py:1002 sso/conf.py:1021 +#: sso/conf.py:1041 sso/conf.py:1075 sso/conf.py:1088 msgid "SAML" msgstr "" -#: awx/sso/conf.py:910 +#: sso/conf.py:918 msgid "SAML Service Provider Metadata URL" msgstr "" -#: awx/sso/conf.py:911 +#: sso/conf.py:919 msgid "" "If your identity provider (IdP) allows uploading an XML metadata file, you " "can download one from this URL." msgstr "" -#: awx/sso/conf.py:924 +#: sso/conf.py:931 msgid "SAML Service Provider Entity ID" msgstr "" -#: awx/sso/conf.py:925 +#: sso/conf.py:932 msgid "" -"Set to a URL for a domain name you own (does not need to be a valid URL; " -"only used as a unique ID)." +"The application-defined unique identifier used as the audience of the SAML " +"service provider (SP) configuration." msgstr "" -#: awx/sso/conf.py:938 +#: sso/conf.py:945 msgid "SAML Service Provider Public Certificate" msgstr "" -#: awx/sso/conf.py:939 +#: sso/conf.py:946 msgid "" "Create a keypair for Tower to use as a service provider (SP) and include the " "certificate content here." msgstr "" -#: awx/sso/conf.py:952 +#: sso/conf.py:959 msgid "SAML Service Provider Private Key" msgstr "" -#: awx/sso/conf.py:953 +#: sso/conf.py:960 msgid "" "Create a keypair for Tower to use as a service provider (SP) and include the " "private key content here." msgstr "" -#: awx/sso/conf.py:970 +#: sso/conf.py:978 msgid "SAML Service Provider Organization Info" msgstr "" -#: awx/sso/conf.py:971 +#: sso/conf.py:979 msgid "Configure this setting with information about your app." msgstr "" -#: awx/sso/conf.py:992 +#: sso/conf.py:1000 msgid "SAML Service Provider Technical Contact" msgstr "" -#: awx/sso/conf.py:993 awx/sso/conf.py:1012 +#: sso/conf.py:1001 sso/conf.py:1020 msgid "Configure this setting with your contact information." msgstr "" -#: awx/sso/conf.py:1011 +#: sso/conf.py:1019 msgid "SAML Service Provider Support Contact" msgstr "" -#: awx/sso/conf.py:1026 +#: sso/conf.py:1034 msgid "SAML Enabled Identity Providers" msgstr "" -#: awx/sso/conf.py:1027 +#: sso/conf.py:1035 msgid "" "Configure the Entity ID, SSO URL and certificate for each identity provider " "(IdP) in use. Multiple SAML IdPs are supported. Some IdPs may provide user " @@ -3169,237 +3330,217 @@ msgid "" "Attribute names may be overridden for each IdP." msgstr "" -#: awx/sso/conf.py:1065 +#: sso/conf.py:1073 msgid "SAML Organization Map" msgstr "" -#: awx/sso/conf.py:1078 +#: sso/conf.py:1086 msgid "SAML Team Map" msgstr "" -#: awx/sso/fields.py:123 -#, python-brace-format +#: sso/fields.py:123 msgid "Invalid connection option(s): {invalid_options}." msgstr "" -#: awx/sso/fields.py:182 +#: sso/fields.py:182 msgid "Base" msgstr "" -#: awx/sso/fields.py:183 +#: sso/fields.py:183 msgid "One Level" msgstr "" -#: awx/sso/fields.py:184 +#: sso/fields.py:184 msgid "Subtree" msgstr "" -#: awx/sso/fields.py:202 -#, python-brace-format +#: sso/fields.py:202 msgid "Expected a list of three items but got {length} instead." msgstr "" -#: awx/sso/fields.py:203 -#, python-brace-format +#: sso/fields.py:203 msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" -#: awx/sso/fields.py:239 -#, python-brace-format +#: sso/fields.py:239 msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." msgstr "" -#: awx/sso/fields.py:266 -#, python-brace-format +#: sso/fields.py:266 msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "" -#: awx/sso/fields.py:283 -#, python-brace-format +#: sso/fields.py:283 msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" -#: awx/sso/fields.py:308 -#, python-brace-format +#: sso/fields.py:308 msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "" -#: awx/sso/fields.py:324 awx/sso/fields.py:491 -#, python-brace-format +#: sso/fields.py:324 sso/fields.py:491 msgid "" "Expected None, True, False, a string or list of strings but got {input_type} " "instead." msgstr "" -#: awx/sso/fields.py:360 -#, python-brace-format +#: sso/fields.py:360 msgid "Missing key(s): {missing_keys}." msgstr "" -#: awx/sso/fields.py:361 -#, python-brace-format +#: sso/fields.py:361 msgid "Invalid key(s): {invalid_keys}." msgstr "" -#: awx/sso/fields.py:410 awx/sso/fields.py:527 -#, python-brace-format +#: sso/fields.py:410 sso/fields.py:527 msgid "Invalid key(s) for organization map: {invalid_keys}." msgstr "" -#: awx/sso/fields.py:428 -#, python-brace-format +#: sso/fields.py:428 msgid "Missing required key for team map: {invalid_keys}." msgstr "" -#: awx/sso/fields.py:429 awx/sso/fields.py:546 -#, python-brace-format +#: sso/fields.py:429 sso/fields.py:546 msgid "Invalid key(s) for team map: {invalid_keys}." msgstr "" -#: awx/sso/fields.py:545 -#, python-brace-format +#: sso/fields.py:545 msgid "Missing required key for team map: {missing_keys}." msgstr "" -#: awx/sso/fields.py:563 -#, python-brace-format +#: sso/fields.py:563 msgid "Missing required key(s) for org info record: {missing_keys}." msgstr "" -#: awx/sso/fields.py:576 -#, python-brace-format +#: sso/fields.py:576 msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "" -#: awx/sso/fields.py:595 -#, python-brace-format +#: sso/fields.py:595 msgid "Missing required key(s) for contact: {missing_keys}." msgstr "" -#: awx/sso/fields.py:607 -#, python-brace-format +#: sso/fields.py:607 msgid "Missing required key(s) for IdP: {missing_keys}." msgstr "" -#: awx/sso/pipeline.py:24 -#, python-brace-format +#: sso/pipeline.py:24 msgid "An account cannot be found for {0}" msgstr "" -#: awx/sso/pipeline.py:30 +#: sso/pipeline.py:30 msgid "Your account is inactive" msgstr "" -#: awx/sso/validators.py:19 awx/sso/validators.py:44 +#: sso/validators.py:19 sso/validators.py:44 #, python-format msgid "DN must include \"%%(user)s\" placeholder for username: %s" msgstr "" -#: awx/sso/validators.py:26 +#: sso/validators.py:26 #, python-format msgid "Invalid DN: %s" msgstr "" -#: awx/sso/validators.py:56 +#: sso/validators.py:56 #, python-format msgid "Invalid filter: %s" msgstr "" -#: awx/templates/error.html:4 awx/ui/templates/ui/index.html:8 +#: templates/error.html:4 ui/templates/ui/index.html:8 msgid "Ansible Tower" msgstr "" -#: awx/templates/rest_framework/api.html:39 +#: templates/rest_framework/api.html:39 msgid "Ansible Tower API Guide" msgstr "" -#: awx/templates/rest_framework/api.html:40 +#: templates/rest_framework/api.html:40 msgid "Back to Ansible Tower" msgstr "" -#: awx/templates/rest_framework/api.html:41 +#: templates/rest_framework/api.html:41 msgid "Resize" msgstr "" -#: awx/templates/rest_framework/base.html:78 -#: awx/templates/rest_framework/base.html:92 +#: templates/rest_framework/base.html:78 templates/rest_framework/base.html:92 #, python-format msgid "Make a GET request on the %(name)s resource" msgstr "" -#: awx/templates/rest_framework/base.html:80 +#: templates/rest_framework/base.html:80 msgid "Specify a format for the GET request" msgstr "" -#: awx/templates/rest_framework/base.html:86 +#: templates/rest_framework/base.html:86 #, python-format msgid "" "Make a GET request on the %(name)s resource with the format set to `" "%(format)s`" msgstr "" -#: awx/templates/rest_framework/base.html:100 +#: templates/rest_framework/base.html:100 #, python-format msgid "Make an OPTIONS request on the %(name)s resource" msgstr "" -#: awx/templates/rest_framework/base.html:106 +#: templates/rest_framework/base.html:106 #, python-format msgid "Make a DELETE request on the %(name)s resource" msgstr "" -#: awx/templates/rest_framework/base.html:113 +#: templates/rest_framework/base.html:113 msgid "Filters" msgstr "" -#: awx/templates/rest_framework/base.html:172 -#: awx/templates/rest_framework/base.html:186 +#: templates/rest_framework/base.html:172 +#: templates/rest_framework/base.html:186 #, python-format msgid "Make a POST request on the %(name)s resource" msgstr "" -#: awx/templates/rest_framework/base.html:216 -#: awx/templates/rest_framework/base.html:230 +#: templates/rest_framework/base.html:216 +#: templates/rest_framework/base.html:230 #, python-format msgid "Make a PUT request on the %(name)s resource" msgstr "" -#: awx/templates/rest_framework/base.html:233 +#: templates/rest_framework/base.html:233 #, python-format msgid "Make a PATCH request on the %(name)s resource" msgstr "" -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:38 awx/ui/conf.py:53 +#: ui/apps.py:9 ui/conf.py:22 ui/conf.py:38 ui/conf.py:53 msgid "UI" msgstr "" -#: awx/ui/conf.py:16 +#: ui/conf.py:16 msgid "Off" msgstr "" -#: awx/ui/conf.py:17 +#: ui/conf.py:17 msgid "Anonymous" msgstr "" -#: awx/ui/conf.py:18 +#: ui/conf.py:18 msgid "Detailed" msgstr "" -#: awx/ui/conf.py:20 +#: ui/conf.py:20 msgid "Analytics Tracking State" msgstr "" -#: awx/ui/conf.py:21 +#: ui/conf.py:21 msgid "Enable or Disable Analytics Tracking." msgstr "" -#: awx/ui/conf.py:31 +#: ui/conf.py:31 msgid "Custom Login Info" msgstr "" -#: awx/ui/conf.py:32 +#: ui/conf.py:32 msgid "" "If needed, you can add specific information (such as a legal notice or a " "disclaimer) to a text box in the login modal using this setting. Any content " @@ -3408,42 +3549,42 @@ msgid "" "(paragraphs) must be escaped as `\\n` within the block of text." msgstr "" -#: awx/ui/conf.py:48 +#: ui/conf.py:48 msgid "Custom Logo" msgstr "" -#: awx/ui/conf.py:49 +#: ui/conf.py:49 msgid "" "To set up a custom logo, provide a file that you create. For the custom logo " "to look its best, use a `.png` file with a transparent background. GIF, PNG " "and JPEG formats are supported." msgstr "" -#: awx/ui/fields.py:29 +#: ui/fields.py:29 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." msgstr "" -#: awx/ui/fields.py:30 +#: ui/fields.py:30 msgid "Invalid base64-encoded data in data URL." msgstr "" -#: awx/ui/templates/ui/index.html:49 +#: ui/templates/ui/index.html:49 msgid "" "Your session will expire in 60 seconds, would you like to continue?" msgstr "" -#: awx/ui/templates/ui/index.html:64 +#: ui/templates/ui/index.html:64 msgid "CANCEL" msgstr "" -#: awx/ui/templates/ui/index.html:116 +#: ui/templates/ui/index.html:116 msgid "Set how many days of data should be retained." msgstr "" -#: awx/ui/templates/ui/index.html:122 +#: ui/templates/ui/index.html:122 msgid "" "Please enter an integer that is not " @@ -3452,7 +3593,7 @@ msgid "" "span>." msgstr "" -#: awx/ui/templates/ui/index.html:127 +#: ui/templates/ui/index.html:127 msgid "" "For facts collected older than the time period specified, save one fact scan " "(snapshot) per time window (frequency). For example, facts older than 30 " @@ -3464,11 +3605,11 @@ msgid "" "
" msgstr "" -#: awx/ui/templates/ui/index.html:136 +#: ui/templates/ui/index.html:136 msgid "Select a time period after which to remove old facts" msgstr "" -#: awx/ui/templates/ui/index.html:150 +#: ui/templates/ui/index.html:150 msgid "" "Please enter an integer " @@ -3477,11 +3618,11 @@ msgid "" "that is lower than 9999." msgstr "" -#: awx/ui/templates/ui/index.html:155 +#: ui/templates/ui/index.html:155 msgid "Select a frequency for snapshot retention" msgstr "" -#: awx/ui/templates/ui/index.html:169 +#: ui/templates/ui/index.html:169 msgid "" "Please enter an integer." msgstr "" -#: awx/ui/templates/ui/index.html:175 +#: ui/templates/ui/index.html:175 msgid "working..." msgstr "" diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 7f87694cbe..797a211b79 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -1253,6 +1253,11 @@ class Command(NoArgsCommand): except re.error: raise CommandError('invalid regular expression for --host-filter') + ''' + TODO: Remove this deprecation when we remove support for rax.py + ''' + self.logger.info("Rackspace inventory sync is Deprecated in Tower 3.1.0 and support for Rackspace will be removed in a future release.") + begin = time.time() self.load_inventory_from_database() diff --git a/awx/main/migrations/0034_v310_release.py b/awx/main/migrations/0034_v310_release.py index fa46beec20..c3849468f8 100644 --- a/awx/main/migrations/0034_v310_release.py +++ b/awx/main/migrations/0034_v310_release.py @@ -47,6 +47,11 @@ class Migration(migrations.Migration): name='uuid', field=models.CharField(max_length=40), ), + migrations.AlterField( + model_name='credential', + name='become_method', + field=models.CharField(default=b'', help_text='Privilege escalation method.', max_length=32, blank=True, choices=[(b'', 'None'), (b'sudo', 'Sudo'), (b'su', 'Su'), (b'pbrun', 'Pbrun'), (b'pfexec', 'Pfexec'), (b'dzdo', 'DZDO'), (b'pmrun', 'Pmrun')]), + ), # Add Workflows migrations.AlterField( model_name='unifiedjob', diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index aa0bf3243c..a7f77e87c2 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -50,6 +50,8 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), + ('dzdo', _('DZDO')), + ('pmrun', _('Pmrun')), #('runas', _('Runas')), ] diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 19b62c7694..8a6b9fc91d 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -654,6 +654,16 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin): def get_notification_friendly_name(self): return "Job" + ''' + Canceling a job also cancels the implicit project update with launch_type + run. + ''' + def cancel(self): + res = super(Job, self).cancel() + if self.project_update: + self.project_update.cancel() + return res + class JobHostSummary(CreatedModifiedModel): ''' diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index 8f96d1656c..c2fe3b1c4f 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -210,7 +210,7 @@ class AuthToken(BaseModel): REASON_CHOICES = [ ('', _('Token not invalidated')), ('timeout_reached', _('Token is expired')), - ('limit_reached', _('Maximum per-user sessions reached')), + ('limit_reached', _('The maximum number of allowed sessions for this user has been exceeded.')), # invalid_token is not a used data-base value, but is returned by the # api when a token is not found ('invalid_token', _('Invalid token')), diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index fcf2749055..526f0a0300 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -366,7 +366,9 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl @classmethod def _get_unified_jt_copy_names(cls): - return (super(WorkflowJobTemplate, cls)._get_unified_jt_copy_names() + + base_list = super(WorkflowJobTemplate, cls)._get_unified_jt_copy_names() + base_list.remove('labels') + return (base_list + ['survey_spec', 'survey_enabled', 'organization']) def get_absolute_url(self): diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index efba3fdb75..e92eb429e1 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -346,6 +346,7 @@ class TaskManager(): 'Celery, so it has been marked as failed.', )) task_obj.save() + _send_notification_templates(task_obj, 'failed') connection.on_commit(lambda: task_obj.websocket_emit_status('failed')) logger.error("Task %s appears orphaned... marking as failed" % task) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index addbe4c8f2..505172070c 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -109,8 +109,12 @@ def send_notifications(notification_list, job_id=None): raise TypeError("notification_list should be of type list") if job_id is not None: job_actual = UnifiedJob.objects.get(id=job_id) - for notification_id in notification_list: - notification = Notification.objects.get(id=notification_id) + + notifications = Notification.objects.filter(id__in=notification_list) + if job_id is not None: + job_actual.notifications.add(*notifications) + + for notification in notifications: try: sent = notification.notification_template.send(notification.subject, notification.body) notification.status = "successful" @@ -121,8 +125,6 @@ def send_notifications(notification_list, job_id=None): notification.error = smart_str(e) finally: notification.save() - if job_id is not None: - job_actual.notifications.add(notification) @task(bind=True, queue='default') @@ -618,7 +620,7 @@ class BaseTask(Task): for child_proc in child_procs: os.kill(child_proc.pid, signal.SIGKILL) os.kill(main_proc.pid, signal.SIGKILL) - except TypeError: + except (TypeError, psutil.Error): os.kill(job.pid, signal.SIGKILL) else: os.kill(job.pid, signal.SIGTERM) @@ -708,6 +710,11 @@ class BaseTask(Task): stdout_handle.close() except Exception: pass + + instance = self.update_model(pk) + if instance.cancel_flag: + status = 'canceled' + instance = self.update_model(pk, status=status, result_traceback=tb, output_replacements=output_replacements, **extra_update_fields) @@ -809,7 +816,7 @@ class RunJob(BaseTask): env['REST_API_URL'] = settings.INTERNAL_API_URL env['REST_API_TOKEN'] = job.task_auth_token or '' env['TOWER_HOST'] = settings.TOWER_URL_BASE - env['MAX_EVENT_RES'] = settings.MAX_EVENT_RES_DATA + env['MAX_EVENT_RES'] = str(settings.MAX_EVENT_RES_DATA) env['CALLBACK_QUEUE'] = settings.CALLBACK_QUEUE env['CALLBACK_CONNECTION'] = settings.BROKER_URL if getattr(settings, 'JOB_CALLBACK_DEBUG', False): @@ -1045,17 +1052,18 @@ class RunJob(BaseTask): local_project_sync = job.project.create_project_update(launch_type="sync") local_project_sync.job_type = 'run' local_project_sync.save() + # save the associated project update before calling run() so that a + # cancel() call on the job can cancel the project update + job = self.update_model(job.pk, project_update=local_project_sync) + project_update_task = local_project_sync._get_task_class() try: project_update_task().run(local_project_sync.id) - job.scm_revision = job.project.scm_revision - job.project_update = local_project_sync - job.save() + job = self.update_model(job.pk, scm_revision=job.project.scm_revision) except Exception: - job.status = 'failed' - job.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \ - ('project_update', local_project_sync.name, local_project_sync.id) - job.save() + job = self.update_model(job.pk, status='failed', + job_explanation=('Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % + ('project_update', local_project_sync.name, local_project_sync.id))) raise def post_run_hook(self, job, status, **kwargs): @@ -1737,6 +1745,10 @@ class RunAdHocCommand(BaseTask): d[re.compile(r'^pfexec password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'^RUNAS password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'^runas password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'^DZDO password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'^dzdo password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'^PMRUN password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'^pmrun password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'^SSH password:\s*?$', re.M)] = 'ssh_password' d[re.compile(r'^Password:\s*?$', re.M)] = 'ssh_password' return d diff --git a/awx/main/tests/unit/test_network_credential.py b/awx/main/tests/unit/test_network_credential.py index 6517c14a89..7ae97fe76b 100644 --- a/awx/main/tests/unit/test_network_credential.py +++ b/awx/main/tests/unit/test_network_credential.py @@ -77,7 +77,7 @@ def test_net_cred_ssh_agent(mocker, get_ssh_version): mocker.patch.object(run_job, 'post_run_hook', return_value=None) run_job.run(mock_job.id) - assert run_job.update_model.call_count == 3 + assert run_job.update_model.call_count == 4 job_args = run_job.update_model.call_args_list[1][1].get('job_args') assert 'ssh-add' in job_args diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index b83772bb59..2cefe0007b 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -38,17 +38,17 @@ def test_send_notifications_list(mocker): mock_job = mocker.MagicMock(spec=UnifiedJob) patches.append(mocker.patch('awx.main.models.UnifiedJob.objects.get', return_value=mock_job)) - mock_notification = mocker.MagicMock(spec=Notification, subject="test", body={'hello': 'world'}) - patches.append(mocker.patch('awx.main.models.Notification.objects.get', return_value=mock_notification)) + mock_notifications = [mocker.MagicMock(spec=Notification, subject="test", body={'hello': 'world'})] + patches.append(mocker.patch('awx.main.models.Notification.objects.filter', return_value=mock_notifications)) with apply_patches(patches): send_notifications([1,2], job_id=1) - assert Notification.objects.get.call_count == 2 - assert mock_notification.status == "successful" - assert mock_notification.save.called + assert Notification.objects.filter.call_count == 1 + assert mock_notifications[0].status == "successful" + assert mock_notifications[0].save.called assert mock_job.notifications.add.called - assert mock_job.notifications.add.called_with(mock_notification) + assert mock_job.notifications.add.called_with(*mock_notifications) @pytest.mark.parametrize("current_instances,call_count", [(91, 2), (89,1)]) diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 00937a84c1..bed0588287 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -43,7 +43,8 @@ __all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore', 'copy_m2m_relationships' ,'cache_list_capabilities', 'to_python_boolean', 'ignore_inventory_computed_fields', 'ignore_inventory_group_removal', '_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided', - 'get_current_apps', 'set_current_apps', 'OutputEventFilter'] + 'get_current_apps', 'set_current_apps', 'OutputEventFilter', + 'callback_filter_out_ansible_extra_vars',] def get_object_or_400(klass, *args, **kwargs): @@ -824,3 +825,12 @@ class OutputEventFilter(object): self._current_event_data = next_event_data else: self._current_event_data = None + + +def callback_filter_out_ansible_extra_vars(extra_vars): + extra_vars_redacted = {} + for key, value in extra_vars.iteritems(): + if not key.startswith('ansible_'): + extra_vars_redacted[key] = value + return extra_vars_redacted + diff --git a/awx/plugins/library/scan_packages.py b/awx/plugins/library/scan_packages.py index 13b28542f6..d5aafc66e6 100755 --- a/awx/plugins/library/scan_packages.py +++ b/awx/plugins/library/scan_packages.py @@ -22,19 +22,19 @@ EXAMPLES = ''' # { # "source": "apt", # "version": "1.0.6-5", -# "architecture": "amd64", +# "arch": "amd64", # "name": "libbz2-1.0" # }, # { # "source": "apt", # "version": "2.7.1-4ubuntu1", -# "architecture": "amd64", +# "arch": "amd64", # "name": "patch" # }, # { # "source": "apt", # "version": "4.8.2-19ubuntu1", -# "architecture": "amd64", +# "arch": "amd64", # "name": "gcc-4.8-base" # }, ... ] } } ''' @@ -64,7 +64,7 @@ def deb_package_list(): ac_pkg = apt_cache[package].installed package_details = dict(name=package, version=ac_pkg.version, - architecture=ac_pkg.architecture, + arch=ac_pkg.architecture, source='apt') installed_packages.append(package_details) return installed_packages diff --git a/awx/settings/development.py b/awx/settings/development.py index f2d72a1113..ebe81260d1 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -82,14 +82,13 @@ PASSWORD_HASHERS = ( # Configure a default UUID for development only. SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' -# Store a snapshot of default settings at this point (only for migrating from -# file to database settings). -if 'migrate_to_database_settings' in sys.argv: - DEFAULTS_SNAPSHOT = {} - this_module = sys.modules[__name__] - for setting in dir(this_module): - if setting == setting.upper(): - DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting)) +# Store a snapshot of default settings at this point before loading any +# customizable config files. +DEFAULTS_SNAPSHOT = {} +this_module = sys.modules[__name__] +for setting in dir(this_module): + if setting == setting.upper(): + DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting)) # If there is an `/etc/tower/settings.py`, include it. # If there is a `/etc/tower/conf.d/*.py`, include them. diff --git a/awx/settings/production.py b/awx/settings/production.py index 103f775d86..f056a4ea31 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -57,14 +57,13 @@ LOGGING['handlers']['fact_receiver']['filename'] = '/var/log/tower/fact_receiver LOGGING['handlers']['system_tracking_migrations']['filename'] = '/var/log/tower/tower_system_tracking_migrations.log' LOGGING['handlers']['rbac_migrations']['filename'] = '/var/log/tower/tower_rbac_migrations.log' -# Store a snapshot of default settings at this point (only for migrating from -# file to database settings). -if 'migrate_to_database_settings' in sys.argv: - DEFAULTS_SNAPSHOT = {} - this_module = sys.modules[__name__] - for setting in dir(this_module): - if setting == setting.upper(): - DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting)) +# Store a snapshot of default settings at this point before loading any +# customizable config files. +DEFAULTS_SNAPSHOT = {} +this_module = sys.modules[__name__] +for setting in dir(this_module): + if setting == setting.upper(): + DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting)) # Load settings from any .py files in the global conf.d directory specified in # the environment, defaulting to /etc/tower/conf.d/. diff --git a/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js b/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js index cba0ecaddf..dc6c4a819d 100644 --- a/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js +++ b/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js @@ -20,15 +20,13 @@ export default ['templateUrl', function(templateUrl) { {label: 'Hosts', value: 'host'}, {label: 'Inventories', value: 'inventory'}, {label: 'Inventory Scripts', value: 'inventory_script'}, - {label: 'Job Templates', value: 'job_template'}, {label: 'Jobs', value: 'job'}, {label: 'Organizations', value: 'organization'}, {label: 'Projects', value: 'project'}, {label: 'Schedules', value: 'schedule'}, {label: 'Teams', value: 'team'}, {label: 'Templates', value: 'template'}, - {label: 'Users', value: 'user'}, - {label: 'Workflow Job Templates', value: 'workflow_job_template'} + {label: 'Users', value: 'user'} ]; CreateSelect2({ diff --git a/awx/ui/client/src/controllers/Credentials.js b/awx/ui/client/src/controllers/Credentials.js index 1681205992..a64056ccd4 100644 --- a/awx/ui/client/src/controllers/Credentials.js +++ b/awx/ui/client/src/controllers/Credentials.js @@ -36,6 +36,35 @@ export function CredentialsList($scope, $rootScope, $location, $log, $scope.selected = []; } + $scope.$on(`${list.iterator}_options`, function(event, data){ + $scope.options = data.data.actions.GET; + optionsRequestDataProcessing(); + }); + + $scope.$watchCollection(`${$scope.list.name}`, function() { + 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(){ + $scope[list.name].forEach(function(item, item_idx) { + var itm = $scope[list.name][item_idx]; + + // Set the item type label + if (list.fields.kind && $scope.options && + $scope.options.hasOwnProperty('kind')) { + $scope.options.kind.choices.every(function(choice) { + if (choice[0] === item.kind) { + itm.kind_label = choice[1]; + return false; + } + return true; + }); + } + }); + } + $scope.addCredential = function() { $state.go('credentials.add'); }; diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index c68c6c5826..cbf588200c 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -13,7 +13,7 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $stateParams, - ClearScope, Find, DeleteJob, RelaunchJob, AllJobsList, ScheduledJobsList, GetBasePath, Dataset, GetChoices) { + ClearScope, Find, DeleteJob, RelaunchJob, AllJobsList, ScheduledJobsList, GetBasePath, Dataset) { ClearScope(); @@ -28,40 +28,44 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $ $scope[list.name] = $scope[`${list.iterator}_dataset`].results; $scope.showJobType = true; + } + + $scope.$on(`${list.iterator}_options`, function(event, data){ + $scope.options = data.data.actions.GET; + optionsRequestDataProcessing(); + }); - _.forEach($scope[list.name], buildTooltips); - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); + $scope.$watchCollection(`${$scope.list.name}`, function() { + optionsRequestDataProcessing(); } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - if(item.summary_fields && item.summary_fields.source_workflow_job && - item.summary_fields.source_workflow_job.id){ - item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`; - } - // Set the item type label - if (list.fields.type) { - $scope.type_choices.every(function(choice) { - if (choice.value === item.type) { - itm.type_label = choice.label; + ); + + // iterate over the list and add fields like type label, after the + // OPTIONS request returns, or the list is sorted/paginated/searched + function optionsRequestDataProcessing(){ + + $scope[list.name].forEach(function(item, item_idx) { + var itm = $scope[list.name][item_idx]; + + if(item.summary_fields && item.summary_fields.source_workflow_job && + item.summary_fields.source_workflow_job.id){ + item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`; + } + + // Set the item type label + if (list.fields.type && $scope.options && + $scope.options.hasOwnProperty('type')) { + $scope.options.type.choices.every(function(choice) { + if (choice[0] === item.type) { + itm.type_label = choice[1]; return false; } return true; }); } - }); - }); - - GetChoices({ - scope: $scope, - url: GetBasePath('unified_jobs'), - field: 'type', - variable: 'type_choices', - callback: 'choicesReady' + buildTooltips(itm); }); } - function buildTooltips(job) { job.status_tip = 'Job ' + job.status + ". Click for details."; } @@ -131,5 +135,5 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $ } JobsListController.$inject = ['$state', '$rootScope', '$log', '$scope', '$compile', '$stateParams', - 'ClearScope', 'Find', 'DeleteJob', 'RelaunchJob', 'AllJobsList', 'ScheduledJobsList', 'GetBasePath', 'Dataset', 'GetChoices' + 'ClearScope', 'Find', 'DeleteJob', 'RelaunchJob', 'AllJobsList', 'ScheduledJobsList', 'GetBasePath', 'Dataset' ]; diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 17076a2a73..d0c845a058 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -38,10 +38,39 @@ export function ProjectsList($scope, $rootScope, $location, $log, $stateParams, $rootScope.flashMessage = null; } - $scope.$watch(`${list.name}`, function() { - _.forEach($scope[list.name], buildTooltips); + $scope.$on(`${list.iterator}_options`, function(event, data){ + $scope.options = data.data.actions.GET; + optionsRequestDataProcessing(); }); + $scope.$watchCollection(`${$scope.list.name}`, function() { + 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(){ + $scope[list.name].forEach(function(item, item_idx) { + var itm = $scope[list.name][item_idx]; + + // Set the item type label + if (list.fields.scm_type && $scope.options && + $scope.options.hasOwnProperty('scm_type')) { + $scope.options.scm_type.choices.every(function(choice) { + if (choice[0] === item.scm_type) { + itm.type_label = choice[1]; + return false; + } + return true; + }); + } + + buildTooltips(itm); + + }); + } + function buildTooltips(project) { project.statusIcon = GetProjectIcon(project.status); project.statusTip = GetProjectToolTip(project.status); diff --git a/awx/ui/client/src/controllers/Teams.js b/awx/ui/client/src/controllers/Teams.js index 0e3a47d855..464a10ee10 100644 --- a/awx/ui/client/src/controllers/Teams.js +++ b/awx/ui/client/src/controllers/Teams.js @@ -217,6 +217,7 @@ export function TeamsEdit($scope, $rootScope, $stateParams, $rootScope.flashMessage = null; if ($scope[form.name + '_form'].$valid) { var data = processNewData(form.fields); + Rest.setUrl(defaultUrl); Rest.put(data).success(function() { $state.go($state.current, null, { reload: true }); }) diff --git a/awx/ui/client/src/controllers/Users.js b/awx/ui/client/src/controllers/Users.js index 9aade4fbf8..9a0aa2ea76 100644 --- a/awx/ui/client/src/controllers/Users.js +++ b/awx/ui/client/src/controllers/Users.js @@ -311,7 +311,7 @@ export function UsersEdit($scope, $rootScope, $location, $scope.formSave = function() { $rootScope.flashMessage = null; if ($scope[form.name + '_form'].$valid) { - Rest.setUrl(defaultUrl + id + '/'); + Rest.setUrl(defaultUrl + '/'); var data = processNewData(form.fields); Rest.put(data).success(function() { $state.go($state.current, null, { reload: true }); diff --git a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js b/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js index 928f03a1c4..82aca5b377 100644 --- a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js +++ b/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js @@ -47,7 +47,7 @@ export default }; scope.editJobTemplate = function (jobTemplateId) { - $state.go('templates.editJobTemplate', {id: jobTemplateId}); + $state.go('templates.editJobTemplate', {job_template_id: jobTemplateId}); }; } }]; diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js index 7845d44daf..9d816c9ce4 100644 --- a/awx/ui/client/src/forms/Credentials.js +++ b/awx/ui/client/src/forms/Credentials.js @@ -263,7 +263,7 @@ export default "ssh_key_unlock": { label: i18n._('Private Key Passphrase'), type: 'sensitive', - ngShow: "kind.value == 'ssh' || kind.value == 'scm'", + ngShow: "kind.value === 'ssh' || kind.value === 'scm' || kind.value === 'net'", ngDisabled: "keyEntered === false || ssh_key_unlock_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)", subCheckbox: { variable: 'ssh_key_unlock_ask', diff --git a/awx/ui/client/src/forms/JobTemplates.js b/awx/ui/client/src/forms/JobTemplates.js index 23be861723..07f7ddf7c2 100644 --- a/awx/ui/client/src/forms/JobTemplates.js +++ b/awx/ui/client/src/forms/JobTemplates.js @@ -307,6 +307,17 @@ export default dataContainer: "body", labelClass: 'stack-inline', ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' + }, { + name: 'allow_simultaneous', + label: i18n._('Enable Concurrent Jobs'), + type: 'checkbox', + column: 2, + awPopOver: "

" + i18n._("If enabled, simultaneous runs of this job template will be allowed.") + "

", + dataPlacement: 'right', + dataTitle: i18n._('Enable Concurrent Jobs'), + dataContainer: "body", + labelClass: 'stack-inline', + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' }] }, callback_url: { diff --git a/awx/ui/client/src/forms/Projects.js b/awx/ui/client/src/forms/Projects.js index 62ee007ace..54c7209dea 100644 --- a/awx/ui/client/src/forms/Projects.js +++ b/awx/ui/client/src/forms/Projects.js @@ -185,7 +185,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) type: 'number', integer: true, min: 0, - ngShow: "scm_update_on_launch && projectSelected && scm_type.value !== 'manual'", + ngShow: "scm_update_on_launch && scm_type.value !== 'manual'", spinner: true, "default": '0', awPopOver: '

' + i18n._('Time in seconds to consider a project to be current. During job runs and callbacks the task system will ' + diff --git a/awx/ui/client/src/helpers/ActivityStream.js b/awx/ui/client/src/helpers/ActivityStream.js index 7dc8c9ecc0..37da4f2857 100644 --- a/awx/ui/client/src/helpers/ActivityStream.js +++ b/awx/ui/client/src/helpers/ActivityStream.js @@ -25,9 +25,6 @@ export default case 'inventory': rtnTitle = 'INVENTORIES'; break; - case 'job_template': - rtnTitle = 'JOB TEMPLATES'; - break; case 'credential': rtnTitle = 'CREDENTIALS'; break; @@ -55,9 +52,6 @@ export default case 'template': rtnTitle = 'TEMPLATES'; break; - case 'workflow_job_template': - rtnTitle = 'WORKFLOW JOB TEMPLATES'; - break; } return rtnTitle; 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 37af28407b..9231e7a5b2 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 @@ -5,10 +5,10 @@ *************************************************/ export default - ['$scope', '$state', '$stateParams', 'generateList', 'GroupManageService', 'GetBasePath', 'CopyMoveGroupList', 'group', - function($scope, $state, $stateParams, GenerateList, GroupManageService, GetBasePath, CopyMoveGroupList, group){ - var list = CopyMoveGroupList, - view = GenerateList; + ['$scope', '$state', '$stateParams', 'GroupManageService', 'GetBasePath', 'CopyMoveGroupList', 'group', 'Dataset', + function($scope, $state, $stateParams, GroupManageService, GetBasePath, CopyMoveGroupList, group, Dataset){ + var list = CopyMoveGroupList; + $scope.item = group; $scope.submitMode = $stateParams.groups === undefined ? 'move' : 'copy'; $scope['toggle_'+ list.iterator] = function(id){ @@ -58,33 +58,18 @@ $(el).prop('disabled', (idx, value) => !value); }); }; - var init = function(){ - var url = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/'; - url += $stateParams.group ? '?not__id__in=' + group.id + ',' + _.last($stateParams.group) : '?not__id=' + group.id; - list.basePath = url; - $scope.atRootLevel = $stateParams.group ? false : true; - view.inject(list, { - mode: 'lookup', - id: 'copyMove-list', - scope: $scope, - input_type: 'radio' - }); - // @issue: OLD SEARCH - // SearchInit({ - // scope: $scope, - // set: list.name, - // list: list, - // url: url - // }); - // PaginateInit({ - // scope: $scope, - // list: list, - // url : url, - // mode: 'lookup' - // }); - // $scope.search(list.iterator, null, true, false); - // remove the current group from list - }; + function init(){ + var url = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/'; + url += $stateParams.group ? '?not__id__in=' + group.id + ',' + _.last($stateParams.group) : '?not__id=' + group.id; + list.basePath = url; + $scope.atRootLevel = $stateParams.group ? false : true; + + // search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + } + init(); }]; 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 9d278fde69..a01387c173 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 @@ -5,10 +5,10 @@ *************************************************/ export default - ['$scope', '$state', '$stateParams', 'generateList', 'HostManageService', 'GetBasePath', 'CopyMoveGroupList', 'host', - function($scope, $state, $stateParams, GenerateList, HostManageService, GetBasePath, CopyMoveGroupList, host){ - var list = CopyMoveGroupList, - view = GenerateList; + ['$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){ @@ -40,29 +40,11 @@ } }; var init = function(){ - var url = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/'; - list.basePath = url; - view.inject(list, { - mode: 'lookup', - id: 'copyMove-list', - scope: $scope, - input_type: 'radio' - }); + // search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - // @issue: OLD SEARCH - // SearchInit({ - // scope: $scope, - // set: list.name, - // list: list, - // url: url - // }); - // PaginateInit({ - // scope: $scope, - // list: list, - // url : url, - // mode: 'lookup' - // }); - // $scope.search(list.iterator, null, true, false); }; init(); }]; diff --git a/awx/ui/client/src/inventories/manage/copy-move/copy-move.partial.html b/awx/ui/client/src/inventories/manage/copy-move/copy-move.partial.html index 1847837b92..030f0c7e3a 100644 --- a/awx/ui/client/src/inventories/manage/copy-move/copy-move.partial.html +++ b/awx/ui/client/src/inventories/manage/copy-move/copy-move.partial.html @@ -10,7 +10,7 @@ Move -

+
Use the inventory root
diff --git a/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js b/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js index 6e2c190e3f..f15705778d 100644 --- a/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js +++ b/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js @@ -10,14 +10,31 @@ import CopyMoveHostsController from './copy-move-hosts.controller'; var copyMoveGroupRoute = { name: 'inventoryManage.copyMoveGroup', - url: '/copy-move-group/{group_id}', + url: '/copy-move-group/{group_id:int}', + searchPrefix: 'copy', data: { group_id: 'group_id', }, + params: { + copy_search: { + value: { + not__id__in: null + }, + dynamic: true, + squash: '' + } + }, ncyBreadcrumb: { label: "COPY OR MOVE {{item.name}}" }, resolve: { + Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath', 'group', + function(list, qs, $stateParams, GetBasePath, group) { + $stateParams.copy_search.not__id__in = ($stateParams.group.length > 0 ? group.id + ',' + _.last($stateParams.group) : group.id); + let path = GetBasePath(list.name); + return qs.search(path, $stateParams.copy_search); + } + ], group: ['GroupManageService', '$stateParams', function(GroupManageService, $stateParams){ return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]); }] @@ -26,16 +43,33 @@ var copyMoveGroupRoute = { 'form@inventoryManage' : { controller: CopyMoveGroupsController, templateUrl: templateUrl('inventories/manage/copy-move/copy-move'), + }, + 'copyMoveList@inventoryManage.copyMoveGroup': { + templateProvider: function(CopyMoveGroupList, generateList) { + let html = generateList.build({ + list: CopyMoveGroupList, + mode: 'lookup', + input_type: 'radio' + }); + return html; + } } } }; var copyMoveHostRoute = { name: 'inventoryManage.copyMoveHost', url: '/copy-move-host/{host_id}', + searchPrefix: 'copy', ncyBreadcrumb: { label: "COPY OR MOVE {{item.name}}" }, resolve: { + Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath', + function(list, qs, $stateParams, GetBasePath) { + let path = GetBasePath(list.name); + return qs.search(path, $stateParams.copy_search); + } + ], host: ['HostManageService', '$stateParams', function(HostManageService, $stateParams){ return HostManageService.get({id: $stateParams.host_id}).then(res => res.data.results[0]); }] @@ -44,6 +78,16 @@ var copyMoveHostRoute = { 'form@inventoryManage': { templateUrl: templateUrl('inventories/manage/copy-move/copy-move'), controller: CopyMoveHostsController, + }, + 'copyMoveList@inventoryManage.copyMoveHost': { + templateProvider: function(CopyMoveGroupList, generateList) { + let html = generateList.build({ + list: CopyMoveGroupList, + mode: 'lookup', + input_type: 'radio' + }); + return html; + } } } }; diff --git a/awx/ui/client/src/job-results/job-results.block.less b/awx/ui/client/src/job-results/job-results.block.less index 2f24e98cbc..ae10b535e7 100644 --- a/awx/ui/client/src/job-results/job-results.block.less +++ b/awx/ui/client/src/job-results/job-results.block.less @@ -176,3 +176,27 @@ job-results-standard-out { flex: 1; display: flex } + +.JobResults-extraVarsHelp { + margin-left: 10px; + color: @default-icon; +} + +.JobResults .CodeMirror.cm-s-default, +.JobResults .CodeMirror-line { + background-color: #f6f6f6; +} + +.JobResults .CodeMirror-gutter.CodeMirror-lint-markers, +.JobResults .CodeMirror-gutter.CodeMirror-linenumbers { + background-color: #ebebeb; + color: @b7grey; +} + +.JobResults .CodeMirror-lines { + cursor: default; +} + +.JobResults .CodeMirror-cursors { + display: none; +} 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 331e3f85b9..6fe760b55e 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -353,6 +353,10 @@