Merge branch 'release_3.1.0' into disableScheduleToggle

This commit is contained in:
jlmitch5
2017-01-31 11:37:45 -05:00
committed by GitHub
101 changed files with 3293 additions and 1494 deletions

View File

@@ -45,7 +45,7 @@ ifeq ($(OFFICIAL),yes)
AW_REPO_URL ?= http://releases.ansible.com/ansible-tower AW_REPO_URL ?= http://releases.ansible.com/ansible-tower
else else
RELEASE ?= $(BUILD) RELEASE ?= $(BUILD)
AW_REPO_URL ?= http://jenkins.testing.ansible.com/ansible-tower_nightlies_RTYUIOPOIUYTYU/$(GIT_BRANCH) AW_REPO_URL ?= http://jenkins.testing.ansible.com/ansible-tower_nightlies_f8b8c5588b2505970227a7b0900ef69040ad5a00/$(GIT_BRANCH)
endif endif
# Allow AMI license customization # Allow AMI license customization
@@ -473,7 +473,7 @@ pylint: reports
check: flake8 pep8 # pyflakes pylint check: flake8 pep8 # pyflakes pylint
TEST_DIRS ?= awx/main/tests TEST_DIRS ?= awx/main/tests awx/conf/tests
# Run all API unit tests. # Run all API unit tests.
test: test:
@if [ "$(VENV_BASE)" ]; then \ @if [ "$(VENV_BASE)" ]; then \
@@ -485,7 +485,7 @@ test_unit:
@if [ "$(VENV_BASE)" ]; then \ @if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/tower/bin/activate; \ . $(VENV_BASE)/tower/bin/activate; \
fi; \ fi; \
py.test awx/main/tests/unit py.test awx/main/tests/unit awx/conf/tests/unit
# Run all API unit tests with coverage enabled. # Run all API unit tests with coverage enabled.
test_coverage: test_coverage:

View File

@@ -285,6 +285,10 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
if name.endswith('_set'): if name.endswith('_set'):
continue continue
fields.append('{}__search'.format(name)) fields.append('{}__search'.format(name))
for relationship in self.model._meta.local_many_to_many:
if relationship.related_model._meta.app_label != 'main':
continue
fields.append('{}__search'.format(relationship.name))
return fields return fields

View File

@@ -1956,16 +1956,25 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO
return res return res
def validate(self, attrs): def validate(self, attrs):
survey_enabled = attrs.get('survey_enabled', self.instance and self.instance.survey_enabled or False) def get_field_from_model_or_attrs(fd):
job_type = attrs.get('job_type', self.instance and self.instance.job_type or None) return attrs.get(fd, self.instance and getattr(self.instance, fd) or None)
inventory = attrs.get('inventory', self.instance and self.instance.inventory or None)
project = attrs.get('project', self.instance and self.instance.project or None)
survey_enabled = get_field_from_model_or_attrs('survey_enabled')
job_type = get_field_from_model_or_attrs('job_type')
inventory = get_field_from_model_or_attrs('inventory')
credential = get_field_from_model_or_attrs('credential')
project = get_field_from_model_or_attrs('project')
prompting_error_message = _("Must either set a default value or ask to prompt on launch.")
if job_type == "scan": if job_type == "scan":
if inventory is None or attrs.get('ask_inventory_on_launch', False): if inventory is None or attrs.get('ask_inventory_on_launch', False):
raise serializers.ValidationError({'inventory': _('Scan jobs must be assigned a fixed inventory.')}) raise serializers.ValidationError({'inventory': _('Scan jobs must be assigned a fixed inventory.')})
elif project is None: elif project is None:
raise serializers.ValidationError({'project': _("Job types 'run' and 'check' must have assigned a project.")}) raise serializers.ValidationError({'project': _("Job types 'run' and 'check' must have assigned a project.")})
elif credential is None and not get_field_from_model_or_attrs('ask_credential_on_launch'):
raise serializers.ValidationError({'credential': prompting_error_message})
elif inventory is None and not get_field_from_model_or_attrs('ask_inventory_on_launch'):
raise serializers.ValidationError({'inventory': prompting_error_message})
if survey_enabled and job_type == PERM_INVENTORY_SCAN: if survey_enabled and job_type == PERM_INVENTORY_SCAN:
raise serializers.ValidationError({'survey_enabled': _('Survey Enabled cannot be used with scan jobs.')}) raise serializers.ValidationError({'survey_enabled': _('Survey Enabled cannot be used with scan jobs.')})
@@ -2964,6 +2973,10 @@ class ActivityStreamSerializer(BaseSerializer):
changes = serializers.SerializerMethodField() changes = serializers.SerializerMethodField()
object_association = serializers.SerializerMethodField() object_association = serializers.SerializerMethodField()
# Needed related fields that are not in the default summary fields
extra_listing = [
('workflow_job_template_node', ('id', 'unified_job_template_id'))
]
class Meta: class Meta:
model = ActivityStream model = ActivityStream
@@ -3005,7 +3018,7 @@ class ActivityStreamSerializer(BaseSerializer):
rel = {} rel = {}
if obj.actor is not None: if obj.actor is not None:
rel['actor'] = reverse('api:user_detail', args=(obj.actor.pk,)) rel['actor'] = reverse('api:user_detail', args=(obj.actor.pk,))
for fk, __ in SUMMARIZABLE_FK_FIELDS.items(): for fk, __ in SUMMARIZABLE_FK_FIELDS.items() + self.extra_listing:
if not hasattr(obj, fk): if not hasattr(obj, fk):
continue continue
allm2m = getattr(obj, fk).all() allm2m = getattr(obj, fk).all()
@@ -3027,7 +3040,7 @@ class ActivityStreamSerializer(BaseSerializer):
def get_summary_fields(self, obj): def get_summary_fields(self, obj):
summary_fields = OrderedDict() summary_fields = OrderedDict()
for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items(): for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items() + self.extra_listing:
try: try:
if not hasattr(obj, fk): if not hasattr(obj, fk):
continue continue

View File

@@ -3108,7 +3108,7 @@ class WorkflowJobTemplateActivityStreamList(WorkflowsEnforcementMixin, ActivityS
self.check_parent_access(parent) self.check_parent_access(parent)
qs = self.request.user.get_queryset(self.model) qs = self.request.user.get_queryset(self.model)
return qs.filter(Q(workflow_job_template=parent) | return qs.filter(Q(workflow_job_template=parent) |
Q(workflow_job_template_node__workflow_job_template=parent)) Q(workflow_job_template_node__workflow_job_template=parent)).distinct()
class WorkflowJobList(WorkflowsEnforcementMixin, ListCreateAPIView): class WorkflowJobList(WorkflowsEnforcementMixin, ListCreateAPIView):
@@ -3475,6 +3475,13 @@ class HostJobEventsList(BaseJobEventsList):
parent_model = Host parent_model = Host
def get_queryset(self):
parent_obj = self.get_parent_object()
self.check_parent_access(parent_obj)
qs = self.request.user.get_queryset(self.model).filter(
Q(host=parent_obj) | Q(hosts=parent_obj)).distinct()
return qs
class GroupJobEventsList(BaseJobEventsList): class GroupJobEventsList(BaseJobEventsList):

View File

@@ -22,4 +22,3 @@ class ConfConfig(AppConfig):
if 'http_receiver' not in LOGGING_DICT['loggers']['awx']['handlers']: if 'http_receiver' not in LOGGING_DICT['loggers']['awx']['handlers']:
LOGGING_DICT['loggers']['awx']['handlers'] += ['http_receiver'] LOGGING_DICT['loggers']['awx']['handlers'] += ['http_receiver']
configure_logging(settings.LOGGING_CONFIG, LOGGING_DICT) configure_logging(settings.LOGGING_CONFIG, LOGGING_DICT)
# checks.register(SettingsWrapper._check_settings)

View File

@@ -18,9 +18,18 @@ __all__ = ['settings_registry']
class SettingsRegistry(object): class SettingsRegistry(object):
"""Registry of all API-configurable settings and categories.""" """Registry of all API-configurable settings and categories."""
def __init__(self): def __init__(self, settings=None):
"""
:param settings: a ``django.conf.LazySettings`` object used to lookup
file-based field values (e.g., ``local_settings.py``
and ``/etc/tower/conf.d/example.py``). If unspecified,
defaults to ``django.conf.settings``.
"""
if settings is None:
from django.conf import settings
self._registry = OrderedDict() self._registry = OrderedDict()
self._dependent_settings = {} self._dependent_settings = {}
self.settings = settings
def register(self, setting, **kwargs): def register(self, setting, **kwargs):
if setting in self._registry: if setting in self._registry:
@@ -94,7 +103,6 @@ class SettingsRegistry(object):
return bool(self._registry.get(setting, {}).get('encrypted', False)) return bool(self._registry.get(setting, {}).get('encrypted', False))
def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs): def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs):
from django.conf import settings
from rest_framework.fields import empty from rest_framework.fields import empty
field_kwargs = {} field_kwargs = {}
field_kwargs.update(self._registry[setting]) field_kwargs.update(self._registry[setting])
@@ -124,12 +132,12 @@ class SettingsRegistry(object):
original_field_instance = original_field_class(**field_kwargs) original_field_instance = original_field_class(**field_kwargs)
if category_slug == 'user' and for_user: if category_slug == 'user' and for_user:
try: try:
field_instance.default = original_field_instance.to_representation(getattr(settings, setting)) field_instance.default = original_field_instance.to_representation(getattr(self.settings, setting))
except: except:
logger.warning('Unable to retrieve default value for user setting "%s".', setting, exc_info=True) logger.warning('Unable to retrieve default value for user setting "%s".', setting, exc_info=True)
elif not field_instance.read_only or field_instance.default is empty: elif not field_instance.read_only or field_instance.default is empty or not field_instance.default:
try: try:
field_instance.default = original_field_instance.to_representation(settings._awx_conf_settings._get_default(setting)) field_instance.default = original_field_instance.to_representation(self.settings._awx_conf_settings._get_default(setting))
except AttributeError: except AttributeError:
pass pass
except: except:

View File

@@ -7,8 +7,7 @@ import time
# Django # Django
from django.conf import settings, UserSettingsHolder from django.conf import settings, UserSettingsHolder
from django.core.cache import cache from django.core.cache import cache as django_cache
from django.core import checks
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import ProgrammingError, OperationalError from django.db import ProgrammingError, OperationalError
@@ -65,35 +64,46 @@ def _log_database_error():
class SettingsWrapper(UserSettingsHolder): class SettingsWrapper(UserSettingsHolder):
@classmethod @classmethod
def initialize(cls): def initialize(cls, cache=None, registry=None):
"""
Used to initialize and wrap the Django settings context.
:param cache: the Django cache backend to use for caching setting
values. ``django.core.cache`` is used by default.
:param registry: the settings registry instance used. The global
``awx.conf.settings_registry`` is used by default.
"""
if not getattr(settings, '_awx_conf_settings', False): if not getattr(settings, '_awx_conf_settings', False):
settings_wrapper = cls(settings._wrapped) settings_wrapper = cls(
settings._wrapped,
cache=cache or django_cache,
registry=registry or settings_registry
)
settings._wrapped = settings_wrapper settings._wrapped = settings_wrapper
@classmethod def __init__(self, default_settings, cache, registry):
def _check_settings(cls, app_configs, **kwargs): """
errors = [] This constructor is generally not called directly, but by
# FIXME: Warn if database not available! ``SettingsWrapper.initialize`` at app startup time when settings are
for setting in Setting.objects.filter(key__in=settings_registry.get_registered_settings(), user__isnull=True): parsed.
field = settings_registry.get_setting_field(setting.key) """
try:
field.to_internal_value(setting.value)
except Exception as e:
errors.append(checks.Error(str(e)))
return errors
def __init__(self, default_settings): # These values have to be stored via self.__dict__ in this way to get
# around the magic __setattr__ method on this class (which is used to
# store API-assigned settings in the database).
self.__dict__['default_settings'] = default_settings self.__dict__['default_settings'] = default_settings
self.__dict__['_awx_conf_settings'] = self self.__dict__['_awx_conf_settings'] = self
self.__dict__['_awx_conf_preload_expires'] = None self.__dict__['_awx_conf_preload_expires'] = None
self.__dict__['_awx_conf_preload_lock'] = threading.RLock() self.__dict__['_awx_conf_preload_lock'] = threading.RLock()
self.__dict__['_awx_conf_init_readonly'] = False self.__dict__['_awx_conf_init_readonly'] = False
self.__dict__['cache'] = cache
self.__dict__['registry'] = registry
def _get_supported_settings(self): def _get_supported_settings(self):
return settings_registry.get_registered_settings() return self.registry.get_registered_settings()
def _get_writeable_settings(self): def _get_writeable_settings(self):
return settings_registry.get_registered_settings(read_only=False) return self.registry.get_registered_settings(read_only=False)
def _get_cache_value(self, value): def _get_cache_value(self, value):
if value is None: if value is None:
@@ -124,11 +134,11 @@ class SettingsWrapper(UserSettingsHolder):
file_default = None file_default = None
if file_default != init_default and file_default is not None: if file_default != init_default and file_default is not None:
logger.warning('Setting %s has been marked read-only!', key) logger.warning('Setting %s has been marked read-only!', key)
settings_registry._registry[key]['read_only'] = True self.registry._registry[key]['read_only'] = True
self.__dict__['_awx_conf_init_readonly'] = True self.__dict__['_awx_conf_init_readonly'] = True
# If local preload timer has expired, check to see if another process # If local preload timer has expired, check to see if another process
# has already preloaded the cache and skip preloading if so. # has already preloaded the cache and skip preloading if so.
if cache.get('_awx_conf_preload_expires', empty) is not empty: if self.cache.get('_awx_conf_preload_expires', empty) is not empty:
return return
# Initialize all database-configurable settings with a marker value so # Initialize all database-configurable settings with a marker value so
# to indicate from the cache that the setting is not configured without # to indicate from the cache that the setting is not configured without
@@ -138,7 +148,7 @@ class SettingsWrapper(UserSettingsHolder):
for setting in Setting.objects.filter(key__in=settings_to_cache.keys(), user__isnull=True).order_by('pk'): for setting in Setting.objects.filter(key__in=settings_to_cache.keys(), user__isnull=True).order_by('pk'):
if settings_to_cache[setting.key] != SETTING_CACHE_NOTSET: if settings_to_cache[setting.key] != SETTING_CACHE_NOTSET:
continue continue
if settings_registry.is_setting_encrypted(setting.key): if self.registry.is_setting_encrypted(setting.key):
value = decrypt_field(setting, 'value') value = decrypt_field(setting, 'value')
else: else:
value = setting.value value = setting.value
@@ -148,7 +158,7 @@ class SettingsWrapper(UserSettingsHolder):
for key, value in settings_to_cache.items(): for key, value in settings_to_cache.items():
if value != SETTING_CACHE_NOTSET: if value != SETTING_CACHE_NOTSET:
continue continue
field = settings_registry.get_setting_field(key) field = self.registry.get_setting_field(key)
try: try:
settings_to_cache[key] = self._get_cache_value(field.get_default()) settings_to_cache[key] = self._get_cache_value(field.get_default())
except SkipField: except SkipField:
@@ -157,13 +167,13 @@ class SettingsWrapper(UserSettingsHolder):
settings_to_cache = dict([(Setting.get_cache_key(k), v) for k, v in settings_to_cache.items()]) settings_to_cache = dict([(Setting.get_cache_key(k), v) for k, v in settings_to_cache.items()])
settings_to_cache['_awx_conf_preload_expires'] = self._awx_conf_preload_expires settings_to_cache['_awx_conf_preload_expires'] = self._awx_conf_preload_expires
logger.debug('cache set_many(%r, %r)', settings_to_cache, SETTING_CACHE_TIMEOUT) logger.debug('cache set_many(%r, %r)', settings_to_cache, SETTING_CACHE_TIMEOUT)
cache.set_many(settings_to_cache, SETTING_CACHE_TIMEOUT) self.cache.set_many(settings_to_cache, SETTING_CACHE_TIMEOUT)
def _get_local(self, name): def _get_local(self, name):
self._preload_cache() self._preload_cache()
cache_key = Setting.get_cache_key(name) cache_key = Setting.get_cache_key(name)
try: try:
cache_value = cache.get(cache_key, empty) cache_value = self.cache.get(cache_key, empty)
except ValueError: except ValueError:
cache_value = empty cache_value = empty
logger.debug('cache get(%r, %r) -> %r', cache_key, empty, cache_value) logger.debug('cache get(%r, %r) -> %r', cache_key, empty, cache_value)
@@ -177,7 +187,7 @@ class SettingsWrapper(UserSettingsHolder):
value = {} value = {}
else: else:
value = cache_value value = cache_value
field = settings_registry.get_setting_field(name) field = self.registry.get_setting_field(name)
if value is empty: if value is empty:
setting = None setting = None
if not field.read_only: if not field.read_only:
@@ -198,8 +208,10 @@ class SettingsWrapper(UserSettingsHolder):
if value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE: if value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE:
value = SETTING_CACHE_NOTSET value = SETTING_CACHE_NOTSET
if cache_value != value: if cache_value != value:
logger.debug('cache set(%r, %r, %r)', cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT) logger.debug('cache set(%r, %r, %r)', cache_key,
cache.set(cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT) self._get_cache_value(value),
SETTING_CACHE_TIMEOUT)
self.cache.set(cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT)
if value == SETTING_CACHE_NOTSET and not SETTING_CACHE_DEFAULTS: if value == SETTING_CACHE_NOTSET and not SETTING_CACHE_DEFAULTS:
try: try:
value = field.get_default() value = field.get_default()
@@ -214,7 +226,9 @@ class SettingsWrapper(UserSettingsHolder):
else: else:
return field.run_validation(value) return field.run_validation(value)
except: except:
logger.warning('The current value "%r" for setting "%s" is invalid.', value, name, exc_info=True) logger.warning(
'The current value "%r" for setting "%s" is invalid.',
value, name, exc_info=True)
return empty return empty
def _get_default(self, name): def _get_default(self, name):
@@ -234,7 +248,7 @@ class SettingsWrapper(UserSettingsHolder):
return self._get_default(name) return self._get_default(name)
def _set_local(self, name, value): def _set_local(self, name, value):
field = settings_registry.get_setting_field(name) field = self.registry.get_setting_field(name)
if field.read_only: if field.read_only:
logger.warning('Attempt to set read only setting "%s".', name) logger.warning('Attempt to set read only setting "%s".', name)
raise ImproperlyConfigured('Setting "%s" is read only.'.format(name)) raise ImproperlyConfigured('Setting "%s" is read only.'.format(name))
@@ -244,7 +258,8 @@ class SettingsWrapper(UserSettingsHolder):
setting_value = field.run_validation(data) setting_value = field.run_validation(data)
db_value = field.to_representation(setting_value) db_value = field.to_representation(setting_value)
except Exception as e: except Exception as e:
logger.exception('Unable to assign value "%r" to setting "%s".', value, name, exc_info=True) logger.exception('Unable to assign value "%r" to setting "%s".',
value, name, exc_info=True)
raise e raise e
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first() setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
@@ -264,7 +279,7 @@ class SettingsWrapper(UserSettingsHolder):
setattr(self.default_settings, name, value) setattr(self.default_settings, name, value)
def _del_local(self, name): def _del_local(self, name):
field = settings_registry.get_setting_field(name) field = self.registry.get_setting_field(name)
if field.read_only: if field.read_only:
logger.warning('Attempt to delete read only setting "%s".', name) logger.warning('Attempt to delete read only setting "%s".', name)
raise ImproperlyConfigured('Setting "%s" is read only.'.format(name)) raise ImproperlyConfigured('Setting "%s" is read only.'.format(name))
@@ -282,7 +297,8 @@ class SettingsWrapper(UserSettingsHolder):
def __dir__(self): def __dir__(self):
keys = [] keys = []
with _log_database_error(): with _log_database_error():
for setting in Setting.objects.filter(key__in=self._get_supported_settings(), user__isnull=True): for setting in Setting.objects.filter(
key__in=self._get_supported_settings(), user__isnull=True):
# Skip returning settings that have been overridden but are # Skip returning settings that have been overridden but are
# considered to be "not set". # considered to be "not set".
if setting.value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE: if setting.value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE:

View File

@@ -0,0 +1,2 @@
# Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved.

View File

@@ -0,0 +1,311 @@
# Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved.
from uuid import uuid4
from django.conf import LazySettings
from django.core.cache.backends.locmem import LocMemCache
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
from rest_framework.fields import empty
import pytest
from awx.conf import fields
from awx.conf.settings import SettingsWrapper
from awx.conf.registry import SettingsRegistry
@pytest.fixture()
def reg(request):
"""
This fixture initializes an awx settings registry object and passes it as
an argument into the test function.
"""
cache = LocMemCache(str(uuid4()), {}) # make a new random cache each time
settings = LazySettings()
registry = SettingsRegistry(settings)
# @pytest.mark.readonly can be used to mark specific setting values as
# "read-only". This is analogous to manually specifying a setting on the
# filesystem (e.g., in a local_settings.py in development, or in
# /etc/tower/conf.d/<something>.py)
defaults = request.node.get_marker('readonly')
if defaults:
settings.configure(**defaults.kwargs)
settings._wrapped = SettingsWrapper(settings._wrapped,
cache,
registry)
return registry
def test_simple_setting_registration(reg):
assert reg.get_registered_settings() == []
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
)
assert reg.get_registered_settings() == ['AWX_SOME_SETTING_ENABLED']
def test_simple_setting_unregistration(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
)
assert reg.get_registered_settings() == ['AWX_SOME_SETTING_ENABLED']
reg.unregister('AWX_SOME_SETTING_ENABLED')
assert reg.get_registered_settings() == []
def test_duplicate_setting_registration(reg):
"ensure that settings cannot be registered twice."
with pytest.raises(ImproperlyConfigured):
for i in range(2):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
)
def test_field_class_required_for_registration(reg):
"settings must specify a field class to register"
with pytest.raises(ImproperlyConfigured):
reg.register('AWX_SOME_SETTING_ENABLED')
def test_get_registered_settings_by_slug(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
)
assert reg.get_registered_settings(category_slug='system') == [
'AWX_SOME_SETTING_ENABLED'
]
assert reg.get_registered_settings(category_slug='other') == []
def test_get_registered_read_only_settings(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system'
)
reg.register(
'AWX_SOME_READ_ONLY',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
read_only=True
)
assert reg.get_registered_settings(read_only=True) ==[
'AWX_SOME_READ_ONLY'
]
assert reg.get_registered_settings(read_only=False) == [
'AWX_SOME_SETTING_ENABLED'
]
assert reg.get_registered_settings() == [
'AWX_SOME_SETTING_ENABLED',
'AWX_SOME_READ_ONLY'
]
def test_get_registered_settings_with_required_features(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
feature_required='superpowers',
)
assert reg.get_registered_settings(features_enabled=[]) == []
assert reg.get_registered_settings(features_enabled=['superpowers']) == [
'AWX_SOME_SETTING_ENABLED'
]
def test_get_dependent_settings(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system'
)
reg.register(
'AWX_SOME_DEPENDENT_SETTING',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
depends_on=['AWX_SOME_SETTING_ENABLED']
)
assert reg.get_dependent_settings('AWX_SOME_SETTING_ENABLED') == set([
'AWX_SOME_DEPENDENT_SETTING'
])
def test_get_registered_categories(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system'
)
reg.register(
'AWX_SOME_OTHER_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('OtherSystem'),
category_slug='other-system'
)
assert reg.get_registered_categories() == {
'all': _('All'),
'changed': _('Changed'),
'system': _('System'),
'other-system': _('OtherSystem'),
}
def test_get_registered_categories_with_required_features(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('System'),
category_slug='system',
feature_required='superpowers'
)
reg.register(
'AWX_SOME_OTHER_SETTING_ENABLED',
field_class=fields.BooleanField,
category=_('OtherSystem'),
category_slug='other-system',
feature_required='sortapowers'
)
assert reg.get_registered_categories(features_enabled=[]) == {
'all': _('All'),
'changed': _('Changed'),
}
assert reg.get_registered_categories(features_enabled=['superpowers']) == {
'all': _('All'),
'changed': _('Changed'),
'system': _('System'),
}
assert reg.get_registered_categories(features_enabled=['sortapowers']) == {
'all': _('All'),
'changed': _('Changed'),
'other-system': _('OtherSystem'),
}
assert reg.get_registered_categories(
features_enabled=['superpowers', 'sortapowers']
) == {
'all': _('All'),
'changed': _('Changed'),
'system': _('System'),
'other-system': _('OtherSystem'),
}
def test_is_setting_encrypted(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
reg.register(
'AWX_SOME_ENCRYPTED_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
encrypted=True
)
assert reg.is_setting_encrypted('AWX_SOME_SETTING_ENABLED') is False
assert reg.is_setting_encrypted('AWX_SOME_ENCRYPTED_SETTING') is True
def test_simple_field(reg):
reg.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
placeholder='Example Value',
feature_required='superpowers'
)
field = reg.get_setting_field('AWX_SOME_SETTING')
assert isinstance(field, fields.CharField)
assert field.category == _('System')
assert field.category_slug == 'system'
assert field.default is empty
assert field.placeholder == 'Example Value'
assert field.feature_required == 'superpowers'
def test_field_with_custom_attribute(reg):
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category_slug='system',
)
field = reg.get_setting_field('AWX_SOME_SETTING_ENABLED',
category_slug='other-system')
assert field.category_slug == 'other-system'
def test_field_with_custom_mixin(reg):
class GreatMixin(object):
def is_great(self):
return True
reg.register(
'AWX_SOME_SETTING_ENABLED',
field_class=fields.BooleanField,
category_slug='system',
)
field = reg.get_setting_field('AWX_SOME_SETTING_ENABLED',
mixin_class=GreatMixin)
assert isinstance(field, fields.BooleanField)
assert isinstance(field, GreatMixin)
assert field.is_great() is True
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_default_value_from_settings(reg):
reg.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
)
field = reg.get_setting_field('AWX_SOME_SETTING')
assert field.default == 'DEFAULT'
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_default_value_from_settings_with_custom_representation(reg):
class LowercaseCharField(fields.CharField):
def to_representation(self, value):
return value.lower()
reg.register(
'AWX_SOME_SETTING',
field_class=LowercaseCharField,
category=_('System'),
category_slug='system',
)
field = reg.get_setting_field('AWX_SOME_SETTING')
assert field.default == 'default'

View File

@@ -0,0 +1,264 @@
# Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved.
from contextlib import contextmanager
from uuid import uuid4
import time
from django.conf import LazySettings
from django.core.cache.backends.locmem import LocMemCache
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
from rest_framework import fields
import pytest
from awx.conf import models
from awx.conf.settings import SettingsWrapper, SETTING_CACHE_NOTSET
from awx.conf.registry import SettingsRegistry
@contextmanager
def apply_patches(_patches):
[p.start() for p in _patches]
yield
[p.stop() for p in _patches]
@pytest.fixture()
def settings(request):
"""
This fixture initializes a Django settings object that wraps our
`awx.conf.settings.SettingsWrapper` and passes it as an argument into the
test function.
This mimics the work done by `awx.conf.settings.SettingsWrapper.initialize`
on `django.conf.settings`.
"""
cache = LocMemCache(str(uuid4()), {}) # make a new random cache each time
settings = LazySettings()
registry = SettingsRegistry(settings)
# @pytest.mark.readonly can be used to mark specific setting values as
# "read-only". This is analogous to manually specifying a setting on the
# filesystem (e.g., in a local_settings.py in development, or in
# /etc/tower/conf.d/<something>.py)
readonly_marker = request.node.get_marker('readonly')
defaults = readonly_marker.kwargs if readonly_marker else {}
defaults['DEFAULTS_SNAPSHOT'] = {}
settings.configure(**defaults)
settings._wrapped = SettingsWrapper(settings._wrapped,
cache,
registry)
return settings
@pytest.mark.readonly(DEBUG=True)
def test_unregistered_setting(settings):
"native Django settings are not stored in DB, and aren't cached"
assert settings.DEBUG is True
assert settings.cache.get('DEBUG') is None
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting(settings):
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert len(settings.registry.get_registered_settings(read_only=False)) == 0
settings = settings.registry.get_registered_settings(read_only=True)
assert settings == ['AWX_SOME_SETTING']
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting_with_empty_default(settings):
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
default='',
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert len(settings.registry.get_registered_settings(read_only=False)) == 0
settings = settings.registry.get_registered_settings(read_only=True)
assert settings == ['AWX_SOME_SETTING']
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_read_only_defaults_are_cached(settings):
"read-only settings are stored in the cache"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT'
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_cache_respects_timeout(settings):
"only preload the cache every SETTING_CACHE_TIMEOUT settings"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
cache_expiration = settings.cache.get('_awx_conf_preload_expires')
assert cache_expiration > time.time()
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert settings.cache.get('_awx_conf_preload_expires') == cache_expiration
def test_default_setting(settings, mocker):
"settings that specify a default are inserted into the cache"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
default='DEFAULT'
)
settings_to_cache = mocker.Mock(**{'order_by.return_value': []})
with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=settings_to_cache):
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT'
def test_empty_setting(settings, mocker):
"settings with no default and no defined value are not valid"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
settings_to_cache = [
mocker.Mock(**{'order_by.return_value': []}),
mocker.Mock(**{'order_by.return_value.first.return_value': None})
]
with mocker.patch('awx.conf.models.Setting.objects.filter', side_effect=settings_to_cache):
with pytest.raises(AttributeError):
settings.AWX_SOME_SETTING
assert settings.cache.get('AWX_SOME_SETTING') == SETTING_CACHE_NOTSET
def test_setting_from_db(settings, mocker):
"settings can be loaded from the database"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
default='DEFAULT'
)
settings_to_cache = [
mocker.Mock(**{'order_by.return_value': [
mocker.Mock(key='AWX_SOME_SETTING', value='FROM_DB')
]}),
]
with mocker.patch('awx.conf.models.Setting.objects.filter', side_effect=settings_to_cache):
assert settings.AWX_SOME_SETTING == 'FROM_DB'
assert settings.cache.get('AWX_SOME_SETTING') == 'FROM_DB'
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting_assignment(settings):
"read-only settings cannot be overwritten"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
with pytest.raises(ImproperlyConfigured):
settings.AWX_SOME_SETTING = 'CHANGED'
assert settings.AWX_SOME_SETTING == 'DEFAULT'
def test_db_setting_create(settings, mocker):
"settings are stored in the database when set for the first time"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
setting_list = mocker.Mock(**{'order_by.return_value.first.return_value': None})
with apply_patches([
mocker.patch('awx.conf.models.Setting.objects.filter',
return_value=setting_list),
mocker.patch('awx.conf.models.Setting.objects.create', mocker.Mock())
]):
settings.AWX_SOME_SETTING = 'NEW-VALUE'
models.Setting.objects.create.assert_called_with(
key='AWX_SOME_SETTING',
user=None,
value='NEW-VALUE'
)
def test_db_setting_update(settings, mocker):
"settings are updated in the database when their value changes"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
existing_setting = mocker.Mock(key='AWX_SOME_SETTING', value='FROM_DB')
setting_list = mocker.Mock(**{
'order_by.return_value.first.return_value': existing_setting
})
with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=setting_list):
settings.AWX_SOME_SETTING = 'NEW-VALUE'
assert existing_setting.value == 'NEW-VALUE'
existing_setting.save.assert_called_with(update_fields=['value'])
def test_db_setting_deletion(settings, mocker):
"settings are auto-deleted from the database"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
existing_setting = mocker.Mock(key='AWX_SOME_SETTING', value='FROM_DB')
with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=[existing_setting]):
del settings.AWX_SOME_SETTING
assert existing_setting.delete.call_count == 1
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting_deletion(settings):
"read-only settings cannot be deleted"
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
with pytest.raises(ImproperlyConfigured):
del settings.AWX_SOME_SETTING
assert settings.AWX_SOME_SETTING == 'DEFAULT'

View File

@@ -71,7 +71,10 @@ def terminate_ssh_control_masters():
# Terminate then kill control master processes. Workaround older # Terminate then kill control master processes. Workaround older
# version of psutil that may not have wait_procs implemented. # version of psutil that may not have wait_procs implemented.
for proc in ssh_cm_procs: for proc in ssh_cm_procs:
proc.terminate() try:
proc.terminate()
except psutil.NoSuchProcess:
continue
procs_gone, procs_alive = psutil.wait_procs(ssh_cm_procs, timeout=5) procs_gone, procs_alive = psutil.wait_procs(ssh_cm_procs, timeout=5)
for proc in procs_alive: for proc in procs_alive:
proc.kill() proc.kill()

File diff suppressed because it is too large Load Diff

View File

@@ -2109,7 +2109,7 @@ class ActivityStreamAccess(BaseAccess):
'job_template', 'job', 'ad_hoc_command', 'job_template', 'job', 'ad_hoc_command',
'notification_template', 'notification', 'label', 'role', 'actor', 'notification_template', 'notification', 'label', 'role', 'actor',
'schedule', 'custom_inventory_script', 'unified_job_template', 'schedule', 'custom_inventory_script', 'unified_job_template',
'workflow_job_template', 'workflow_job') 'workflow_job_template', 'workflow_job', 'workflow_job_template_node')
if self.user.is_superuser or self.user.is_system_auditor: if self.user.is_superuser or self.user.is_system_auditor:
return qs.all() return qs.all()

View File

@@ -126,4 +126,5 @@ activity_stream_registrar.connect(Notification)
activity_stream_registrar.connect(Label) activity_stream_registrar.connect(Label)
activity_stream_registrar.connect(User) activity_stream_registrar.connect(User)
activity_stream_registrar.connect(WorkflowJobTemplate) activity_stream_registrar.connect(WorkflowJobTemplate)
activity_stream_registrar.connect(WorkflowJobTemplateNode)
activity_stream_registrar.connect(WorkflowJob) activity_stream_registrar.connect(WorkflowJob)

View File

@@ -127,10 +127,10 @@ class SurveyJobTemplateMixin(models.Model):
# Overwrite with job template extra vars with survey default vars # Overwrite with job template extra vars with survey default vars
if self.survey_enabled and 'spec' in self.survey_spec: if self.survey_enabled and 'spec' in self.survey_spec:
for survey_element in self.survey_spec.get("spec", []): for survey_element in self.survey_spec.get("spec", []):
default = survey_element['default'] default = survey_element.get('default')
variable_key = survey_element['variable'] variable_key = survey_element.get('variable')
if survey_element.get('type') == 'password': if survey_element.get('type') == 'password':
if variable_key in kwargs_extra_vars: if variable_key in kwargs_extra_vars and default:
kw_value = kwargs_extra_vars[variable_key] kw_value = kwargs_extra_vars[variable_key]
if kw_value.startswith('$encrypted$') and kw_value != default: if kw_value.startswith('$encrypted$') and kw_value != default:
kwargs_extra_vars[variable_key] = default kwargs_extra_vars[variable_key] = default

View File

@@ -10,9 +10,9 @@ import re
# Django # Django
from django.db import models, transaction, connection from django.db import models, transaction, connection
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.utils.translation import ugettext_lazy as _
# AWX # AWX
@@ -33,29 +33,29 @@ ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='system_administrator'
ROLE_SINGLETON_SYSTEM_AUDITOR='system_auditor' ROLE_SINGLETON_SYSTEM_AUDITOR='system_auditor'
role_names = { role_names = {
'system_administrator' : 'System Administrator', 'system_administrator' : _('System Administrator'),
'system_auditor' : 'System Auditor', 'system_auditor' : _('System Auditor'),
'adhoc_role' : 'Ad Hoc', 'adhoc_role' : _('Ad Hoc'),
'admin_role' : 'Admin', 'admin_role' : _('Admin'),
'auditor_role' : 'Auditor', 'auditor_role' : _('Auditor'),
'execute_role' : 'Execute', 'execute_role' : _('Execute'),
'member_role' : 'Member', 'member_role' : _('Member'),
'read_role' : 'Read', 'read_role' : _('Read'),
'update_role' : 'Update', 'update_role' : _('Update'),
'use_role' : 'Use', 'use_role' : _('Use'),
} }
role_descriptions = { role_descriptions = {
'system_administrator' : 'Can manage all aspects of the system', 'system_administrator' : _('Can manage all aspects of the system'),
'system_auditor' : 'Can view all settings on the system', 'system_auditor' : _('Can view all settings on the system'),
'adhoc_role' : 'May run ad hoc commands on an inventory', 'adhoc_role' : _('May run ad hoc commands on an inventory'),
'admin_role' : 'Can manage all aspects of the %s', 'admin_role' : _('Can manage all aspects of the %s'),
'auditor_role' : 'Can view all settings for the %s', 'auditor_role' : _('Can view all settings for the %s'),
'execute_role' : 'May run the %s', 'execute_role' : _('May run the %s'),
'member_role' : 'User is a member of the %s', 'member_role' : _('User is a member of the %s'),
'read_role' : 'May view settings for the %s', 'read_role' : _('May view settings for the %s'),
'update_role' : 'May update project or inventory or group using the configured source update system', 'update_role' : _('May update project or inventory or group using the configured source update system'),
'use_role' : 'Can use the %s in a job template', 'use_role' : _('Can use the %s in a job template'),
} }

View File

@@ -721,7 +721,7 @@ class BaseTask(Task):
stdout_handle = self.get_stdout_handle(instance) stdout_handle = self.get_stdout_handle(instance)
if self.should_use_proot(instance, **kwargs): if self.should_use_proot(instance, **kwargs):
if not check_proot_installed(): if not check_proot_installed():
raise RuntimeError('proot is not installed') raise RuntimeError('bubblewrap is not installed')
kwargs['proot_temp_dir'] = build_proot_temp_dir() kwargs['proot_temp_dir'] = build_proot_temp_dir()
args = wrap_args_with_proot(args, cwd, **kwargs) args = wrap_args_with_proot(args, cwd, **kwargs)
safe_args = wrap_args_with_proot(safe_args, cwd, **kwargs) safe_args = wrap_args_with_proot(safe_args, cwd, **kwargs)
@@ -874,7 +874,7 @@ class RunJob(BaseTask):
cp_dir = os.path.join(kwargs['private_data_dir'], 'cp') cp_dir = os.path.join(kwargs['private_data_dir'], 'cp')
if not os.path.exists(cp_dir): if not os.path.exists(cp_dir):
os.mkdir(cp_dir, 0700) os.mkdir(cp_dir, 0700)
env['ANSIBLE_SSH_CONTROL_PATH'] = os.path.join(cp_dir, 'ansible-ssh-%%h-%%p-%%r') env['ANSIBLE_SSH_CONTROL_PATH'] = os.path.join(cp_dir, '%%h%%p%%r')
# Allow the inventory script to include host variables inline via ['_meta']['hostvars']. # Allow the inventory script to include host variables inline via ['_meta']['hostvars'].
env['INVENTORY_HOSTVARS'] = str(True) env['INVENTORY_HOSTVARS'] = str(True)
@@ -1609,7 +1609,6 @@ class RunInventoryUpdate(BaseTask):
if inventory_update.overwrite_vars: if inventory_update.overwrite_vars:
args.append('--overwrite-vars') args.append('--overwrite-vars')
args.append('--source') args.append('--source')
# If this is a cloud-based inventory (e.g. from AWS, Rackspace, etc.) # If this is a cloud-based inventory (e.g. from AWS, Rackspace, etc.)
# then we need to set some extra flags based on settings in # then we need to set some extra flags based on settings in
# Tower. # Tower.
@@ -1665,10 +1664,7 @@ class RunInventoryUpdate(BaseTask):
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
args.append(runpath) args.append(runpath)
args.append("--custom") args.append("--custom")
# try: self.custom_dir_path.append(runpath)
# shutil.rmtree(runpath, True)
# except OSError:
# pass
verbosity = getattr(settings, 'INVENTORY_UPDATE_VERBOSITY', 1) verbosity = getattr(settings, 'INVENTORY_UPDATE_VERBOSITY', 1)
args.append('-v%d' % verbosity) args.append('-v%d' % verbosity)
if settings.DEBUG: if settings.DEBUG:
@@ -1690,6 +1686,19 @@ class RunInventoryUpdate(BaseTask):
def get_idle_timeout(self): def get_idle_timeout(self):
return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None) return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None)
def pre_run_hook(self, instance, **kwargs):
self.custom_dir_path = []
def post_run_hook(self, instance, status, **kwargs):
print("In post run hook")
if self.custom_dir_path:
for p in self.custom_dir_path:
try:
shutil.rmtree(p, True)
except OSError:
pass
class RunAdHocCommand(BaseTask): class RunAdHocCommand(BaseTask):
''' '''

View File

@@ -1,9 +1,12 @@
import mock import mock
import pytest import pytest
from collections import namedtuple
from awx.api.views import ( from awx.api.views import (
ApiV1RootView, ApiV1RootView,
JobTemplateLabelList, JobTemplateLabelList,
JobTemplateSurveySpec,
) )
@@ -65,3 +68,16 @@ class TestJobTemplateLabelList:
super(JobTemplateLabelList, view).unattach(mock_request, None, None) super(JobTemplateLabelList, view).unattach(mock_request, None, None)
assert mixin_unattach.called_with(mock_request, None, None) assert mixin_unattach.called_with(mock_request, None, None)
class TestJobTemplateSurveySpec(object):
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
def test_get_password_type(self, mocker, mock_response_new):
JobTemplate = namedtuple('JobTemplate', 'survey_spec')
obj = JobTemplate(survey_spec={'spec':[{'type': 'password', 'default': 'my_default'}]})
with mocker.patch.object(JobTemplateSurveySpec, 'get_object', return_value=obj):
view = JobTemplateSurveySpec()
response = view.get(mocker.MagicMock())
assert response == mock_response_new
# which there was a better way to do this!
assert response.call_args[0][1]['spec'][0]['default'] == '$encrypted$'

View File

@@ -84,3 +84,18 @@ def test_job_template_survey_variable_validation(job_template_factory):
} }
obj.survey_enabled = True obj.survey_enabled = True
assert obj.survey_variable_validation({"a": 5}) == ["Value 5 for 'a' expected to be a string."] assert obj.survey_variable_validation({"a": 5}) == ["Value 5 for 'a' expected to be a string."]
def test_job_template_survey_mixin(job_template_factory):
objects = job_template_factory(
'survey_mixin_test',
organization='org1',
inventory='inventory1',
credential='cred1',
persisted=False,
)
obj = objects.job_template
obj.survey_enabled = True
obj.survey_spec = {'spec': [{'default':'my_default', 'type':'password', 'variable':'my_variable'}]}
kwargs = obj._update_unified_job_kwargs(extra_vars={'my_variable':'$encrypted$'})
assert kwargs['extra_vars'] == '{"my_variable": "my_default"}'

View File

@@ -1,12 +1,13 @@
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import controller from './about.controller'; import controller from './about.controller';
import { N_ } from '../i18n';
export default { export default {
name: 'setup.about', name: 'setup.about',
route: '/about', route: '/about',
controller: controller, controller: controller,
ncyBreadcrumb: { ncyBreadcrumb: {
label: "ABOUT" label: N_("ABOUT")
}, },
onExit: function(){ onExit: function(){
// hacky way to handle user browsing away via URL bar // hacky way to handle user browsing away via URL bar

View File

@@ -23,10 +23,10 @@
<span class="AddPermissions-directionNumber"> <span class="AddPermissions-directionNumber">
1 1
</span> </span>
<div ng-hide='withoutTeamPermissions'> <div ng-hide='withoutTeamPermissions' translate>
Please select Users / Teams from the lists below. Please select Users / Teams from the lists below.
</div> </div>
<div ng-show='withoutTeamPermissions'> <div ng-show='withoutTeamPermissions' translate>
Please select Users from the list below. Please select Users from the list below.
</div> </div>
</div> </div>
@@ -34,12 +34,12 @@
<div class="Form-tabHolder" ng-hide='withoutTeamPermissions'> <div class="Form-tabHolder" ng-hide='withoutTeamPermissions'>
<div id="users_tab" class="Form-tab" <div id="users_tab" class="Form-tab"
ng-click="toggleFormTabs('users')" ng-click="toggleFormTabs('users')"
ng-class="{'is-selected': usersSelected }"> ng-class="{'is-selected': usersSelected }" translate>
Users Users
</div> </div>
<div id="teams_tab" class="Form-tab" <div id="teams_tab" class="Form-tab"
ng-click="toggleFormTabs('teams')" ng-click="toggleFormTabs('teams')"
ng-class="{'is-selected': teamsSelected }"> ng-class="{'is-selected': teamsSelected }" translate>
Teams Teams
</div> </div>
</div> </div>
@@ -59,7 +59,7 @@
<span class="AddPermissions-directionNumber"> <span class="AddPermissions-directionNumber">
2 2
</span> </span>
Please assign roles to the selected users/teams <translate>Please assign roles to the selected users/teams</translate>
<div class="AddPermissions-keyToggle btn" <div class="AddPermissions-keyToggle btn"
ng-class="{'is-active': showKeyPane}" ng-class="{'is-active': showKeyPane}"
ng-click="toggleKeyPane()"> ng-click="toggleKeyPane()">

View File

@@ -26,7 +26,7 @@
<span class="AddPermissions-directionNumber"> <span class="AddPermissions-directionNumber">
1 1
</span> </span>
<div> <div translate>
Please select resources from the lists below. Please select resources from the lists below.
</div> </div>
</div> </div>
@@ -34,30 +34,30 @@
<div class="Form-tabHolder"> <div class="Form-tabHolder">
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('job_templates')" ng-click="selectTab('job_templates')"
ng-class="{'is-selected': tab.job_templates }"> ng-class="{'is-selected': tab.job_templates }" translate>
Job Templates Job Templates
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('workflow_templates')" ng-click="selectTab('workflow_templates')"
ng-class="{'is-selected': tab.workflow_templates}" ng-class="{'is-selected': tab.workflow_templates}"
> translate>
Workflow Templates Workflow Templates
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('projects')" ng-click="selectTab('projects')"
ng-class="{'is-selected': tab.projects }"> ng-class="{'is-selected': tab.projects }" translate>
Projects Projects
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('inventories')" ng-click="selectTab('inventories')"
ng-class="{'is-selected': tab.inventories}" ng-class="{'is-selected': tab.inventories}"
> translate>
Inventories Inventories
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('credentials')" ng-click="selectTab('credentials')"
ng-class="{'is-selected': tab.credentials}" ng-class="{'is-selected': tab.credentials}"
> translate>
Credentials Credentials
</div> </div>
</div> </div>
@@ -86,10 +86,10 @@
<span class="AddPermissions-directionNumber"> <span class="AddPermissions-directionNumber">
2 2
</span> </span>
Please assign roles to the selected resources <translate>Please assign roles to the selected resources</translate>
<div class="AddPermissions-keyToggle btn" <div class="AddPermissions-keyToggle btn"
ng-class="{'is-active': showKeyPane}" ng-class="{'is-active': showKeyPane}"
ng-click="toggleKeyPane()"> ng-click="toggleKeyPane()" translate>
Key Key
</div> </div>
</div> </div>
@@ -97,34 +97,34 @@
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('job_templates')" ng-click="selectTab('job_templates')"
ng-class="{'is-selected': tab.job_templates }" ng-class="{'is-selected': tab.job_templates }"
ng-show="showSection2Tab('job_templates')"> ng-show="showSection2Tab('job_templates')" translate>
Job Templates Job Templates
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('workflow_templates')" ng-click="selectTab('workflow_templates')"
ng-class="{'is-selected': tab.workflow_templates }" ng-class="{'is-selected': tab.workflow_templates }"
ng-show="showSection2Tab('workflow_templates')"> ng-show="showSection2Tab('workflow_templates')" translate>
Workflow Templates Workflow Templates
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('projects')" ng-click="selectTab('projects')"
ng-class="{'is-selected': tab.projects}" ng-class="{'is-selected': tab.projects}"
ng-show="showSection2Tab('projects')" ng-show="showSection2Tab('projects')"
> translate>
Projects Projects
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('inventories')" ng-click="selectTab('inventories')"
ng-class="{'is-selected': tab.inventories}" ng-class="{'is-selected': tab.inventories}"
ng-show="showSection2Tab('inventories')" ng-show="showSection2Tab('inventories')"
> translate>
Inventories Inventories
</div> </div>
<div class="Form-tab" <div class="Form-tab"
ng-click="selectTab('credentials')" ng-click="selectTab('credentials')"
ng-class="{'is-selected': tab.credentials}" ng-class="{'is-selected': tab.credentials}"
ng-show="showSection2Tab('credentials')" ng-show="showSection2Tab('credentials')"
> translate>
Credentials Credentials
</div> </div>
</div> </div>
@@ -170,13 +170,13 @@
<div class="buttons Form-buttons AddPermissions-buttons"> <div class="buttons Form-buttons AddPermissions-buttons">
<button type="button" <button type="button"
class="btn btn-sm Form-cancelButton" class="btn btn-sm Form-cancelButton"
ng-click="closeModal()"> ng-click="closeModal()" translate>
Cancel Cancel
</button> </button>
<button type="button" <button type="button"
class="btn btn-sm Form-saveButton" class="btn btn-sm Form-saveButton"
ng-click="saveForm()" ng-click="saveForm()"
ng-disabled="!saveEnabled()"> ng-disabled="!saveEnabled()" translate>
Save Save
</button> </button>
</div> </div>

View File

@@ -5,7 +5,7 @@
*************************************************/ *************************************************/
export default function() { export default ['i18n', function(i18n) {
return { return {
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12', searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
name: 'teams', name: 'teams',
@@ -15,19 +15,20 @@
multiSelectExtended: true, multiSelectExtended: true,
index: false, index: false,
hover: true, hover: true,
emptyListText : 'No Teams exist', emptyListText : i18n._('No Teams exist'),
fields: { fields: {
name: { name: {
key: true, key: true,
label: 'name' label: i18n._('name')
}, },
organization: { organization: {
label: 'organization', label: i18n._('organization'),
ngBind: 'team.summary_fields.organization.name', ngBind: 'team.summary_fields.organization.name',
sourceModel: 'organization', sourceModel: 'organization',
sourceField: 'name' sourceField: 'name',
searchable: true
} }
} }
}; };
} }];

View File

@@ -5,7 +5,7 @@
*************************************************/ *************************************************/
export default function() { export default ['i18n', function(i18n) {
return { return {
name: 'users', name: 'users',
iterator: 'user', iterator: 'user',
@@ -21,22 +21,22 @@
multiSelectExtended: true, multiSelectExtended: true,
index: false, index: false,
hover: true, hover: true,
emptyListText : 'No Users exist', emptyListText : i18n._('No Users exist'),
fields: { fields: {
first_name: { first_name: {
label: 'First Name', label: i18n._('First Name'),
columnClass: 'col-md-3 col-sm-3 hidden-xs' columnClass: 'col-md-3 col-sm-3 hidden-xs'
}, },
last_name: { last_name: {
label: 'Last Name', label: i18n._('Last Name'),
columnClass: 'col-md-3 col-sm-3 hidden-xs' columnClass: 'col-md-3 col-sm-3 hidden-xs'
}, },
username: { username: {
key: true, key: true,
label: 'Username', label: i18n._('Username'),
columnClass: 'col-md-5 col-sm-5 col-xs-11' columnClass: 'col-md-5 col-sm-5 col-xs-11'
}, },
}, },
}; };
} }];

View File

@@ -8,7 +8,8 @@
export default export default
[ [
'CreateSelect2', 'CreateSelect2',
function(CreateSelect2) { 'i18n',
function(CreateSelect2, i18n) {
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
@@ -21,7 +22,7 @@ export default
CreateSelect2({ CreateSelect2({
element: '.roleSelect2', element: '.roleSelect2',
multiple: true, multiple: true,
placeholder: 'Select roles' placeholder: i18n._('Select roles')
}); });
} }
}; };

View File

@@ -4,6 +4,8 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
import { N_ } from '../i18n';
export default { export default {
name: 'activityStream', name: 'activityStream',
route: '/activity_stream?target&id', route: '/activity_stream?target&id',
@@ -22,7 +24,7 @@ export default {
} }
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
label: "ACTIVITY STREAM" label: N_("ACTIVITY STREAM")
}, },
onExit: function() { onExit: function() {
$('#stream-detail-modal').modal('hide'); $('#stream-detail-modal').modal('hide');

View File

@@ -9,15 +9,15 @@
</div> </div>
<div class="Modal-body" id="detail-body"> <div class="Modal-body" id="detail-body">
<div ng-show="user" class="StreamDetail-inlineRow"> <div ng-show="user" class="StreamDetail-inlineRow">
<div class="StreamDetail-rowTitle StreamDetail-inlineRowTitle">INITIATED BY</div> <div class="StreamDetail-rowTitle StreamDetail-inlineRowTitle" translate>INITIATED BY</div>
<div class="StreamDetail-inlineRowData" ng-bind="user"></div> <div class="StreamDetail-inlineRowData" ng-bind="user"></div>
</div> </div>
<div ng-show="operation" class="StreamDetail-inlineRow"> <div ng-show="operation" class="StreamDetail-inlineRow">
<div class="StreamDetail-rowTitle StreamDetail-inlineRowTitle">ACTION</div> <div class="StreamDetail-rowTitle StreamDetail-inlineRowTitle" translate>ACTION</div>
<div class="StreamDetail-inlineRowData StreamDetail-actions" ng-bind-html="operation"></div> <div class="StreamDetail-inlineRowData StreamDetail-actions" ng-bind-html="operation"></div>
</div> </div>
<div ng-show="changes"> <div ng-show="changes">
<div class="StreamDetail-rowTitle StreamDetail-changesRowTitle">CHANGES</div> <div class="StreamDetail-rowTitle StreamDetail-changesRowTitle" translate>CHANGES</div>
<pre class="StreamDetail-changes">{{ changes | json : spacing}}</pre> <pre class="StreamDetail-changes">{{ changes | json : spacing}}</pre>
</div> </div>
</div> </div>

View File

@@ -4,7 +4,7 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export default ['templateUrl', function(templateUrl) { export default ['templateUrl', 'i18n', function(templateUrl, i18n) {
return { return {
restrict: 'E', restrict: 'E',
scope: true, scope: true,
@@ -15,18 +15,18 @@ export default ['templateUrl', function(templateUrl) {
$scope.streamTarget = ($state.params && $state.params.target) ? $state.params.target : 'dashboard'; $scope.streamTarget = ($state.params && $state.params.target) ? $state.params.target : 'dashboard';
$scope.options = [ $scope.options = [
{label: 'All Activity', value: 'dashboard'}, {label: i18n._('All Activity'), value: 'dashboard'},
{label: 'Credentials', value: 'credential'}, {label: i18n._('Credentials'), value: 'credential'},
{label: 'Hosts', value: 'host'}, {label: i18n._('Hosts'), value: 'host'},
{label: 'Inventories', value: 'inventory'}, {label: i18n._('Inventories'), value: 'inventory'},
{label: 'Inventory Scripts', value: 'inventory_script'}, {label: i18n._('Inventory Scripts'), value: 'inventory_script'},
{label: 'Jobs', value: 'job'}, {label: i18n._('Jobs'), value: 'job'},
{label: 'Organizations', value: 'organization'}, {label: i18n._('Organizations'), value: 'organization'},
{label: 'Projects', value: 'project'}, {label: i18n._('Projects'), value: 'project'},
{label: 'Schedules', value: 'schedule'}, {label: i18n._('Schedules'), value: 'schedule'},
{label: 'Teams', value: 'team'}, {label: i18n._('Teams'), value: 'team'},
{label: 'Templates', value: 'template'}, {label: i18n._('Templates'), value: 'template'},
{label: 'Users', value: 'user'} {label: i18n._('Users'), value: 'user'}
]; ];
CreateSelect2({ CreateSelect2({

View File

@@ -99,6 +99,8 @@ var tower = angular.module('Tower', [
require('angular-tz-extensions'), require('angular-tz-extensions'),
require('lr-infinite-scroll'), require('lr-infinite-scroll'),
require('ng-toast'), require('ng-toast'),
'gettext',
'I18N',
uiRouter, uiRouter,
'ui.router.state.events', 'ui.router.state.events',
@@ -201,8 +203,6 @@ var tower = angular.module('Tower', [
scheduler.name, scheduler.name,
'ApiModelHelper', 'ApiModelHelper',
'ActivityStreamHelper', 'ActivityStreamHelper',
'gettext',
'I18N',
'WorkflowFormDefinition', 'WorkflowFormDefinition',
'InventorySourcesListDefinition', 'InventorySourcesListDefinition',
'WorkflowMakerFormDefinition' 'WorkflowMakerFormDefinition'
@@ -290,6 +290,9 @@ var tower = angular.module('Tower', [
"jobs": ["status_changed"] "jobs": ["status_changed"]
} }
} }
},
ncyBreadcrumb: {
label: N_('PROJECTS')
} }
}) })
}); });
@@ -371,12 +374,12 @@ var tower = angular.module('Tower', [
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest', 'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService', 'I18NInit', 'FeaturesService', '$filter', 'SocketService',
function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, $stateParams, function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer, CheckLicense, $location, Authorization, LoadBasePaths, Timer,
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait, ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
$filter, SocketService, I18NInit) { $filter, SocketService) {
$rootScope.$state = $state; $rootScope.$state = $state;
$rootScope.$state.matches = function(stateName) { $rootScope.$state.matches = function(stateName) {
@@ -388,7 +391,6 @@ var tower = angular.module('Tower', [
$log.debug(`$state.defaultErrorHandler: ${error}`); $log.debug(`$state.defaultErrorHandler: ${error}`);
}); });
I18NInit();
$stateExtender.addState({ $stateExtender.addState({
name: 'dashboard', name: 'dashboard',
url: '/home', url: '/home',

View File

@@ -73,6 +73,10 @@
vertical-align: bottom; vertical-align: bottom;
} }
.BreadCrumb-invItem {
max-width: 400px;
}
.BreadCrumb-item + .BreadCrumb-item:before { .BreadCrumb-item + .BreadCrumb-item:before {
content: "/"; content: "/";
padding: 0 5px; padding: 0 5px;

View File

@@ -1,6 +1,6 @@
export default export default
['templateUrl', '$state', 'FeaturesService', 'ProcessErrors','$rootScope', 'Store', 'Empty', '$window', 'BreadCrumbService', ['templateUrl', '$state', 'FeaturesService', 'ProcessErrors','$rootScope', 'Store', 'Empty', '$window', 'BreadCrumbService', 'i18n',
function(templateUrl, $state, FeaturesService, ProcessErrors, $rootScope, Store, Empty, $window, BreadCrumbService) { function(templateUrl, $state, FeaturesService, ProcessErrors, $rootScope, Store, Empty, $window, BreadCrumbService, i18n) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: templateUrl('bread-crumb/bread-crumb'), templateUrl: templateUrl('bread-crumb/bread-crumb'),
@@ -103,7 +103,7 @@ export default
if(features){ if(features){
scope.loadingLicense = false; scope.loadingLicense = false;
scope.activityStreamActive = (toState.name === 'activityStream') ? true : false; scope.activityStreamActive = (toState.name === 'activityStream') ? true : false;
scope.activityStreamTooltip = (toState.name === 'activityStream') ? 'Hide Activity Stream' : 'View Activity Stream'; scope.activityStreamTooltip = (toState.name === 'activityStream') ? i18n._('Hide Activity Stream') : i18n._('View Activity Stream');
scope.showActivityStreamButton = (FeaturesService.featureEnabled('activity_streams') || toState.name ==='activityStream') ? true : false; scope.showActivityStreamButton = (FeaturesService.featureEnabled('activity_streams') || toState.name ==='activityStream') ? true : false;
} }
} }

View File

@@ -1,6 +1,6 @@
<div class="tab-pane Configuration-container" id="configuration_edit"> <div class="tab-pane Configuration-container" id="configuration_edit">
<div class="Form-nav--dropdownContainer"> <div class="Form-nav--dropdownContainer">
<div class="Form-nav--dropdownLabel">Sub Category</div> <div class="Form-nav--dropdownLabel" translate>Sub Category</div>
<div class="Form-nav--dropdown"> <div class="Form-nav--dropdown">
<select <select
id="configure-dropdown-nav" id="configure-dropdown-nav"

View File

@@ -7,15 +7,15 @@
<div class="tab-pane" id="configuration-panel"> <div class="tab-pane" id="configuration-panel">
<div ng-cloak id="htmlTemplate" class="Panel"> <div ng-cloak id="htmlTemplate" class="Panel">
<div class="Form-header"> <div class="Form-header">
<div class="Form-title">Configure Tower</div> <div class="Form-title" translate>Configure Tower</div>
</div> </div>
<div class="row Form-tabRow"> <div class="row Form-tabRow">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="Form-tabHolder"> <div class="Form-tabHolder">
<div class="Form-tab" ng-click="vm.activeTabCheck('auth')" ng-class="{'is-selected': vm.activeTab === 'auth' }">Authentication</div> <div class="Form-tab" ng-click="vm.activeTabCheck('auth')" ng-class="{'is-selected': vm.activeTab === 'auth' }" translate>Authentication</div>
<div class="Form-tab" ng-click="vm.activeTabCheck('jobs')" ng-class="{'is-selected': vm.activeTab === 'jobs' }">Jobs</div> <div class="Form-tab" ng-click="vm.activeTabCheck('jobs')" ng-class="{'is-selected': vm.activeTab === 'jobs' }" translate>Jobs</div>
<div class="Form-tab" ng-click="vm.activeTabCheck('system')" ng-class="{'is-selected': vm.activeTab === 'system' }">System</div> <div class="Form-tab" ng-click="vm.activeTabCheck('system')" ng-class="{'is-selected': vm.activeTab === 'system' }" translate>System</div>
<div class="Form-tab" ng-click="vm.activeTabCheck('ui')" ng-class="{'is-selected': vm.activeTab === 'ui' }">User Interface</div> <div class="Form-tab" ng-click="vm.activeTabCheck('ui')" ng-class="{'is-selected': vm.activeTab === 'ui' }" translate>User Interface</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,6 +12,7 @@
import ConfigurationJobsController from './jobs-form/configuration-jobs.controller'; import ConfigurationJobsController from './jobs-form/configuration-jobs.controller';
import ConfigurationSystemController from './system-form/configuration-system.controller'; import ConfigurationSystemController from './system-form/configuration-system.controller';
import ConfigurationUiController from './ui-form/configuration-ui.controller'; import ConfigurationUiController from './ui-form/configuration-ui.controller';
import { N_ } from '../i18n';
export default { export default {
name: 'configuration', name: 'configuration',
@@ -26,7 +27,7 @@
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'setup', parent: 'setup',
label: "Edit Configuration" label: N_("Edit Configuration")
}, },
controller: ConfigurationController, controller: ConfigurationController,
resolve: { resolve: {

View File

@@ -17,10 +17,6 @@
reset: 'AD_HOC_COMMANDS', reset: 'AD_HOC_COMMANDS',
multiSelect: true multiSelect: true
}, },
STDOUT_MAX_BYTES_DISPLAY: {
type: 'number',
reset: 'STDOUT_MAX_BYTES_DISPLAY'
},
AWX_PROOT_BASE_PATH: { AWX_PROOT_BASE_PATH: {
type: 'text', type: 'text',
reset: 'AWX_PROOT_BASE_PATH', reset: 'AWX_PROOT_BASE_PATH',

View File

@@ -13,7 +13,8 @@
export function CredentialsList($scope, $rootScope, $location, $log, export function CredentialsList($scope, $rootScope, $location, $log,
$stateParams, Rest, Alert, CredentialList, Prompt, ClearScope, $stateParams, Rest, Alert, CredentialList, Prompt, ClearScope,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) { ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
i18n) {
ClearScope(); ClearScope();
@@ -38,31 +39,34 @@ export function CredentialsList($scope, $rootScope, $location, $log,
$scope.$on(`${list.iterator}_options`, function(event, data){ $scope.$on(`${list.iterator}_options`, function(event, data){
$scope.options = data.data.actions.GET; $scope.options = data.data.actions.GET;
console.log($scope.options);
optionsRequestDataProcessing(); optionsRequestDataProcessing();
}); });
$scope.$watchCollection(`${$scope.list.name}`, function() { $scope.$watchCollection(`${$scope.list.name}`, function() {
optionsRequestDataProcessing(); optionsRequestDataProcessing();
} });
);
// iterate over the list and add fields like type label, after the // iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched // OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){ function optionsRequestDataProcessing(){
$scope[list.name].forEach(function(item, item_idx) { if ($scope[list.name] !== undefined) {
var itm = $scope[list.name][item_idx]; $scope[list.name].forEach(function(item, item_idx) {
var itm = $scope[list.name][item_idx];
// Set the item type label // Set the item type label
if (list.fields.kind && $scope.options && if (list.fields.kind && $scope.options &&
$scope.options.hasOwnProperty('kind')) { $scope.options.hasOwnProperty('kind')) {
$scope.options.kind.choices.every(function(choice) { $scope.options.kind.choices.every(function(choice) {
if (choice[0] === item.kind) { if (choice[0] === item.kind) {
itm.kind_label = choice[1]; itm.kind_label = choice[1];
return false; return false;
} }
return true; return true;
}); });
} }
}); });
}
} }
$scope.addCredential = function() { $scope.addCredential = function() {
@@ -97,24 +101,24 @@ export function CredentialsList($scope, $rootScope, $location, $log,
}; };
Prompt({ Prompt({
hdr: 'Delete', hdr: i18n._('Delete'),
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the credential below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>', body: '<div class="Prompt-bodyQuery">' + i18n._('Are you sure you want to delete the credential below?') + '</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
action: action, action: action,
actionText: 'DELETE' actionText: i18n._('DELETE')
}); });
}; };
} }
CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log', CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope', '$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset' 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n'
]; ];
export function CredentialsAdd($scope, $rootScope, $compile, $location, $log, export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors, $stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange, ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange,
OwnerChange, FormSave, $state, CreateSelect2) { OwnerChange, FormSave, $state, CreateSelect2, i18n) {
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view
@@ -154,7 +158,7 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
GenerateForm.applyDefaults(form, $scope); GenerateForm.applyDefaults(form, $scope);
$scope.keyEntered = false; $scope.keyEntered = false;
$scope.permissionsTooltip = 'Please save before assigning permissions'; $scope.permissionsTooltip = i18n._('Please save before assigning permissions');
// determine if the currently logged-in user may share this credential // determine if the currently logged-in user may share this credential
// previous commentary said: "$rootScope.current_user isn't available because a call to the config endpoint hasn't finished resolving yet" // previous commentary said: "$rootScope.current_user isn't available because a call to the config endpoint hasn't finished resolving yet"
@@ -281,7 +285,7 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert', '$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange',
'OwnerChange', 'FormSave', '$state', 'CreateSelect2' 'OwnerChange', 'FormSave', '$state', 'CreateSelect2', 'i18n'
]; ];
export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
@@ -571,10 +575,10 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
}; };
Prompt({ Prompt({
hdr: 'Delete', hdr: i18n._('Delete'),
body: '<div class="Prompt-bodyQuery">Are you sure you want to remove the ' + title + ' below from ' + $scope.name + '?</div><div class="Prompt-bodyTarget">' + name + '</div>', body: '<div class="Prompt-bodyQuery">' + i18n.sprintf(i18n._('Are you sure you want to remove the %s below from %s?'), title, $scope.name) + '</div><div class="Prompt-bodyTarget">' + name + '</div>',
action: action, action: action,
actionText: 'DELETE' actionText: i18n._('DELETE')
}); });
}; };

View File

@@ -47,7 +47,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
}); });
Rest.setUrl(GetBasePath("job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false"); Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template");
Rest.get() Rest.get()
.success(function (data) { .success(function (data) {
$scope.dashboardJobTemplatesListData = data.results; $scope.dashboardJobTemplatesListData = data.results;
@@ -123,7 +123,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log
.error(function (data, status) { .error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status }); ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
}); });
Rest.setUrl(GetBasePath("job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false"); Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template");
Rest.get() Rest.get()
.success(function (data) { .success(function (data) {
data = data.results; data = data.results;

View File

@@ -9,6 +9,7 @@ import form from './dashboard-hosts.form';
import listController from './dashboard-hosts-list.controller'; import listController from './dashboard-hosts-list.controller';
import editController from './dashboard-hosts-edit.controller'; import editController from './dashboard-hosts-edit.controller';
import service from './dashboard-hosts.service'; import service from './dashboard-hosts.service';
import { N_ } from '../../i18n';
export default export default
angular.module('dashboardHosts', []) angular.module('dashboardHosts', [])
@@ -51,7 +52,7 @@ angular.module('dashboardHosts', [])
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'dashboard', parent: 'dashboard',
label: "HOSTS" label: N_("HOSTS")
}, },
}) })
}); });

View File

@@ -3,7 +3,8 @@ export default
[ 'InitiatePlaybookRun', [ 'InitiatePlaybookRun',
'templateUrl', 'templateUrl',
'$state', '$state',
function JobTemplatesList(InitiatePlaybookRun, templateUrl, $state) { 'Alert',
function JobTemplatesList(InitiatePlaybookRun, templateUrl, $state, Alert) {
return { return {
restrict: 'E', restrict: 'E',
link: link, link: link,
@@ -32,7 +33,8 @@ export default
launch_url: job_template.url, launch_url: job_template.url,
edit_url: job_template.url.replace('api/v1', '#'), edit_url: job_template.url.replace('api/v1', '#'),
name: job_template.name, name: job_template.name,
id: job_template.id id: job_template.id,
type: job_template.type
}; }); }; });
scope.snapRows = (list.length < 4); scope.snapRows = (list.length < 4);
@@ -42,8 +44,23 @@ export default
return (status === "successful"); return (status === "successful");
}; };
scope.launchJobTemplate = function(jobTemplateId){ scope.launchJobTemplate = function(template){
InitiatePlaybookRun({ scope: scope, id: jobTemplateId, job_type: 'job_template' }); if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'job_template' });
}
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'workflow_job_template' });
}
else {
// Something went wrong - Let the user know that we're unable to launch because we don't know
// what type of job template this is
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
}
}
else {
Alert('Error: Unable to launch template', 'Template parameter is missing');
}
}; };
scope.editJobTemplate = function (jobTemplateId) { scope.editJobTemplate = function (jobTemplateId) {

View File

@@ -1,7 +1,7 @@
<div class="DashboardList" ng-hide="noJobTemplates"> <div class="DashboardList" ng-hide="noJobTemplates">
<div class="DashboardList-header"> <div class="DashboardList-header">
<h3 class="DashboardList-headerText"> <h3 class="DashboardList-headerText">
<translate>RECENTLY USED JOB TEMPLATES</translate> <translate>RECENTLY USED TEMPLATES</translate>
</h3> </h3>
<a href="/#/templates" class="DashboardList-viewAll"> <a href="/#/templates" class="DashboardList-viewAll">
<translate>VIEW ALL</translate> <translate>VIEW ALL</translate>
@@ -34,7 +34,7 @@
</td> </td>
<td class="List-actionsContainer"> <td class="List-actionsContainer">
<div class="List-actionButtonCell"> <div class="List-actionButtonCell">
<button class="List-actionButton" ng-click="launchJobTemplate(job_template.id)"> <button class="List-actionButton" ng-click="launchJobTemplate(job_template)">
<i class="icon-launch"></i> <i class="icon-launch"></i>
</button> </button>
<button class="List-actionButton" ng-click="editJobTemplate(job_template.id)"> <button class="List-actionButton" ng-click="editJobTemplate(job_template.id)">
@@ -53,7 +53,8 @@
</h3> </h3>
</div> </div>
<div class="DashboardList-container"> <div class="DashboardList-container">
<p class="DashboardList-noJobs">No job templates were recently used.<br /> <p class="DashboardList-noJobs"><translate>No job templates were recently used.</translate><br />
You can create a job template <a href="#/templates/add_job_template">here</a>.</p> <!-- TODO: Seems $sce.trustAsHtml() does not work here. -->
<translate>You can create a job template <a href="#/templates/add_job_template">here</a>.</translate></p>
</div> </div>
</div> </div>

View File

@@ -18,10 +18,11 @@
export default export default
angular.module('ActivityDetailDefinition', []) angular.module('ActivityDetailDefinition', [])
.value('ActivityDetailForm', { .factory('ActivityDetailForm', ['i18n', function(i18n) {
return {
name: 'activity', name: 'activity',
editTitle: 'Activity Detail', editTitle: i18n._('Activity Detail'),
well: false, well: false,
'class': 'horizontal-narrow', 'class': 'horizontal-narrow',
formFieldSize: 'col-lg-10', formFieldSize: 'col-lg-10',
@@ -29,17 +30,17 @@ export default
fields: { fields: {
user: { user: {
label: "Initiated by", label: i18n._("Initiated by"),
type: 'text', type: 'text',
readonly: true readonly: true
}, },
operation: { operation: {
label: 'Action', label: i18n._('Action'),
type: 'text', type: 'text',
readonly: true readonly: true
}, },
changes: { changes: {
label: 'Changes', label: i18n._('Changes'),
type: 'textarea', type: 'textarea',
class: 'Form-textAreaLabel', class: 'Form-textAreaLabel',
ngHide: "!changes || changes =='' || changes == 'null'", ngHide: "!changes || changes =='' || changes == 'null'",
@@ -47,4 +48,4 @@ export default
} }
} }
}); //Form };}]); //Form

View File

@@ -12,73 +12,74 @@
export default export default
angular.module('EventsViewerFormDefinition', []) angular.module('EventsViewerFormDefinition', [])
.value('EventsViewerForm', { .factory('EventsViewerForm', ['i18n', function(i18n) {
return {
fields: { fields: {
host_name: { host_name: {
label: 'Host', label: i18n._('Host'),
section: 'Event' section: i18n._('Event')
}, },
status: { status: {
label: 'Status', labellabel: i18n._('Status'),
section: 'Event' section: i18n._('Event')
}, },
id: { id: {
label: 'ID', labellabel: i18n._('ID'),
section: 'Event' section: i18n._('Event')
}, },
created: { created: {
label: 'Created On', labellabel: i18n._('Created On'),
section: 'Event' section: i18n._('Event')
}, },
role: { role: {
label: 'Role', labellabel: i18n._('Role'),
section: 'Event' section: i18n._('Event')
}, },
play: { play: {
label: 'Play', labellabel: i18n._('Play'),
type: 'text', type: 'text',
section: 'Event' section: i18n._('Event')
}, },
task: { task: {
label: 'Task', labellabel: i18n._('Task'),
section: 'Event' section: i18n._('Event')
}, },
item: { item: {
label: 'Item', labellabel: i18n._('Item'),
section: 'Event' section: i18n._('Event')
}, },
module_name: { module_name: {
label: 'Module', labellabel: i18n._('Module'),
section: 'Event' section: i18n._('Event')
}, },
module_args: { module_args: {
label: 'Arguments', labellabel: i18n._('Arguments'),
section: 'Event' section: i18n._('Event')
}, },
rc: { rc: {
label: 'Return Code', labellabel: i18n._('Return Code'),
section: 'Results' section: i18n._('Results')
}, },
msg: { msg: {
label: 'Message', labellabel: i18n._('Message'),
section: 'Results' section: i18n._('Results')
}, },
results: { results: {
label: 'Results', labellabel: i18n._('Results'),
section: 'Results' section: i18n._('Results')
}, },
start: { start: {
label: 'Start', labellabel: i18n._('Start'),
section: 'Timing' section: i18n._('Timing')
}, },
end: { end: {
label: 'End', labellabel: i18n._('End'),
section: 'Timing' section: i18n._('Timing')
}, },
delta: { delta: {
label: 'Elapsed', labellabel: i18n._('Elapsed'),
section: 'Timing' section: i18n._('Timing')
} }
} }
}); };}]);

View File

@@ -92,7 +92,7 @@ angular.module('InventoryFormDefinition', [])
dataPlacement: 'top', dataPlacement: 'top',
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/', basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/',
type: 'collection', type: 'collection',
title: 'Permissions', title: i18n._('Permissions'),
iterator: 'permission', iterator: 'permission',
index: false, index: false,
open: false, open: false,

View File

@@ -12,49 +12,50 @@
export default export default
angular.module('LogViewerStatusDefinition', []) angular.module('LogViewerStatusDefinition', [])
.value('LogViewerStatusForm', { .factory('LogViewerStatusForm', ['i18n', function(i18n) {
return {
name: 'status', name: 'status',
well: false, well: false,
fields: { fields: {
"name": { "name": {
label: "Name", label: i18n._("Name"),
type: "text", type: "text",
readonly: true, readonly: true,
}, },
"status": { "status": {
label: "Status", label: i18n._("Status"),
type: "text", type: "text",
readonly: true readonly: true
}, },
"license_error": { "license_error": {
label: "License Error", label: i18n._("License Error"),
type: "text", type: "text",
readonly: true readonly: true
}, },
"started": { "started": {
label: "Started", label: i18n._("Started"),
type: "date", type: "date",
"filter": "longDate", "filter": "longDate",
readonly: true readonly: true
}, },
"finished": { "finished": {
label: "Finished", label: i18n._("Finished"),
type: "date", type: "date",
"filter": "longDate", "filter": "longDate",
readonly: true readonly: true
}, },
"elapsed": { "elapsed": {
label: "Elapsed", label: i18n._("Elapsed"),
type: "text", type: "text",
readonly: true readonly: true
}, },
"launch_type": { "launch_type": {
label: "Launch Type", label: i18n._("Launch Type"),
type: "text", type: "text",
readonly: true readonly: true
} }
} }
}); };}]);

View File

@@ -132,18 +132,18 @@ export default
fields: { fields: {
username: { username: {
key: true, key: true,
label: 'User', label: i18n._('User'),
linkBase: 'users', linkBase: 'users',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
}, },
role: { role: {
label: 'Role', label: i18n._('Role'),
type: 'role', type: 'role',
noSort: true, noSort: true,
class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4', class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4',
}, },
team_roles: { team_roles: {
label: 'Team Roles', label: i18n._('Team Roles'),
type: 'team_roles', type: 'team_roles',
noSort: true, noSort: true,
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4', class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4',

View File

@@ -12,45 +12,45 @@
export default export default
angular.module('ActivityStreamHelper', ['Utilities']) angular.module('ActivityStreamHelper', ['Utilities'])
.factory('GetTargetTitle', [ .factory('GetTargetTitle', ['i18n',
function () { function (i18n) {
return function (target) { return function (target) {
var rtnTitle = 'ALL ACTIVITY'; var rtnTitle = i18n._('ALL ACTIVITY');
switch(target) { switch(target) {
case 'project': case 'project':
rtnTitle = 'PROJECTS'; rtnTitle = i18n._('PROJECTS');
break; break;
case 'inventory': case 'inventory':
rtnTitle = 'INVENTORIES'; rtnTitle = i18n._('INVENTORIES');
break; break;
case 'credential': case 'credential':
rtnTitle = 'CREDENTIALS'; rtnTitle = i18n._('CREDENTIALS');
break; break;
case 'user': case 'user':
rtnTitle = 'USERS'; rtnTitle = i18n._('USERS');
break; break;
case 'team': case 'team':
rtnTitle = 'TEAMS'; rtnTitle = i18n._('TEAMS');
break; break;
case 'organization': case 'organization':
rtnTitle = 'ORGANIZATIONS'; rtnTitle = i18n._('ORGANIZATIONS');
break; break;
case 'job': case 'job':
rtnTitle = 'JOBS'; rtnTitle = i18n._('JOBS');
break; break;
case 'inventory_script': case 'inventory_script':
rtnTitle = 'INVENTORY SCRIPTS'; rtnTitle = i18n._('INVENTORY SCRIPTS');
break; break;
case 'schedule': case 'schedule':
rtnTitle = 'SCHEDULES'; rtnTitle = i18n._('SCHEDULES');
break; break;
case 'host': case 'host':
rtnTitle = 'HOSTS'; rtnTitle = i18n._('HOSTS');
break; break;
case 'template': case 'template':
rtnTitle = 'TEMPLATES'; rtnTitle = i18n._('TEMPLATES');
break; break;
} }

View File

@@ -126,9 +126,9 @@ export default
}]) }])
.factory('DeleteJob', ['$state', 'Find', 'GetBasePath', 'Rest', 'Wait', .factory('DeleteJob', ['$state', 'Find', 'GetBasePath', 'Rest', 'Wait',
'ProcessErrors', 'Prompt', 'Alert', '$filter', 'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n',
function($state, Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert, function($state, Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert,
$filter){ $filter, i18n){
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
id = params.id, id = params.id,
@@ -158,11 +158,11 @@ export default
if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') { if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') {
url = job.related.cancel; url = job.related.cancel;
action_label = 'cancel'; action_label = 'cancel';
hdr = 'Cancel'; hdr = i18n._('Cancel');
} else { } else {
url = job.url; url = job.url;
action_label = 'delete'; action_label = 'delete';
hdr = 'Delete'; hdr = i18n._('Delete');
} }
action = function () { action = function () {
@@ -227,8 +227,8 @@ export default
scope.removeCancelJob(); scope.removeCancelJob();
} }
scope.removeCancelJob = scope.$on('CancelJob', function() { scope.removeCancelJob = scope.$on('CancelJob', function() {
var cancelBody = "<div class=\"Prompt-bodyQuery\">Submit the request to cancel?</div>"; var cancelBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Submit the request to cancel?") + "</div>";
var deleteBody = "<div class=\"Prompt-bodyQuery\">Are you sure you want to delete the job below?</div><div class=\"Prompt-bodyTarget\">#" + id + " " + $filter('sanitize')(job.name) + "</div>"; var deleteBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to delete the job below?") + "</div><div class=\"Prompt-bodyTarget\">#" + id + " " + $filter('sanitize')(job.name) + "</div>";
Prompt({ Prompt({
hdr: hdr, hdr: hdr,
body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody, body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody,

View File

@@ -48,35 +48,35 @@ export default
}; };
}]) }])
.factory('GetProjectToolTip', [ function() { .factory('GetProjectToolTip', ['i18n', function(i18n) {
return function(status) { return function(status) {
var result = ''; var result = '';
switch (status) { switch (status) {
case 'n/a': case 'n/a':
case 'ok': case 'ok':
case 'never updated': case 'never updated':
result = 'No SCM updates have run for this project'; result = i18n._('No SCM updates have run for this project');
break; break;
case 'pending': case 'pending':
case 'waiting': case 'waiting':
case 'new': case 'new':
result = 'Queued. Click for details'; result = i18n._('Queued. Click for details');
break; break;
case 'updating': case 'updating':
case 'running': case 'running':
result = 'Running! Click for details'; result = i18n._('Running! Click for details');
break; break;
case 'successful': case 'successful':
result = 'Success! Click for details'; result = i18n._('Success! Click for details');
break; break;
case 'failed': case 'failed':
result = 'Failed. Click for details'; result = i18n._('Failed. Click for details');
break; break;
case 'missing': case 'missing':
result = 'Missing. Click for details'; result = i18n._('Missing. Click for details');
break; break;
case 'canceled': case 'canceled':
result = 'Canceled. Click for details'; result = i18n._('Canceled. Click for details');
break; break;
} }
return result; return result;

View File

@@ -12,6 +12,7 @@ import inventoryManageListRoute from './manage/inventory-manage.route';
import { copyMoveGroupRoute, copyMoveHostRoute } from './manage/copy-move/copy-move.route'; import { copyMoveGroupRoute, copyMoveHostRoute } from './manage/copy-move/copy-move.route';
import adHocRoute from './manage/adhoc/adhoc.route'; import adHocRoute from './manage/adhoc/adhoc.route';
import { templateUrl } from '../shared/template-url/template-url.factory'; import { templateUrl } from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
export default export default
angular.module('inventory', [ angular.module('inventory', [
inventoryAdd.name, inventoryAdd.name,
@@ -55,7 +56,7 @@ angular.module('inventory', [
searchPrefix: 'schedule', searchPrefix: 'schedule',
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'inventoryManage.editGroup({group_id: parentObject.id})', parent: 'inventoryManage.editGroup({group_id: parentObject.id})',
label: 'SCHEDULES' label: N_('SCHEDULES')
}, },
resolve: { resolve: {
Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath', 'groupData', Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath', 'groupData',
@@ -89,7 +90,7 @@ angular.module('inventory', [
'@': { '@': {
templateProvider: function(SchedulesList, generateList, ParentObject) { templateProvider: function(SchedulesList, generateList, ParentObject) {
// include name of parent resource in listTitle // include name of parent resource in listTitle
SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Schedules`; SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
let html = generateList.build({ let html = generateList.build({
list: SchedulesList, list: SchedulesList,
mode: 'edit' mode: 'edit'
@@ -106,7 +107,7 @@ angular.module('inventory', [
name: 'inventoryManage.editGroup.schedules.add', name: 'inventoryManage.editGroup.schedules.add',
url: '/add', url: '/add',
ncyBreadcrumb: { ncyBreadcrumb: {
label: "CREATE SCHEDULE" label: N_("CREATE SCHEDULE")
}, },
views: { views: {
'form': { 'form': {
@@ -286,6 +287,9 @@ angular.module('inventory', [
stateTree = { stateTree = {
name: 'inventories', name: 'inventories',
url: '/inventories', url: '/inventories',
ncyBreadcrumb: {
label: N_("INVENTORIES")
},
lazyLoad: () => generateStateTree() lazyLoad: () => generateStateTree()
}; };

View File

@@ -5,6 +5,7 @@
*************************************************/ *************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory'; import {templateUrl} from '../../../shared/template-url/template-url.factory';
import { N_ } from '../../../i18n';
export default { export default {
url: '/adhoc', url: '/adhoc',
@@ -22,6 +23,6 @@ export default {
} }
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
label: "RUN COMMAND" label: N_("RUN COMMAND")
} }
}; };

View File

@@ -1,22 +1,22 @@
<div class="BreadCrumb InventoryManageBreadCrumbs"> <div class="BreadCrumb InventoryManageBreadCrumbs">
<ol class="BreadCrumb-list"> <ol class="BreadCrumb-list">
<li class="BreadCrumb-item"><a ui-sref="inventories">Inventories</a></li> <li class="BreadCrumb-item"><a ui-sref="inventories">Inventories</a></li>
<li class="BreadCrumb-item"> <li class="BreadCrumb-item BreadCrumb-invItem">
<a href ng-if="currentState !== 'inventoryManage' || groups.length > 0" ng-click="goToInventory()">{{inventory.name}}</a> <a href ng-if="currentState !== 'inventoryManage' || groups.length > 0" ng-click="goToInventory()">{{inventory.name}}</a>
<span ng-if="currentState === 'inventoryManage' && groups.length === 0">{{inventory.name}}</span> <span ng-if="currentState === 'inventoryManage' && groups.length === 0">{{inventory.name}}</span>
</li> </li>
<!-- inside inventoryManage list view (last item is not clickable) --> <!-- inside inventoryManage list view (last item is not clickable) -->
<li ng-repeat="group in groups | limitTo:(groups.length-1) track by $index" class="BreadCrumb-item" ng-if="currentState === 'inventoryManage'"> <li ng-repeat="group in groups | limitTo:(groups.length-1) track by $index" class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState === 'inventoryManage'">
<a href ng-click="goToGroup($index+1)">{{group.name}}</a> <a href ng-click="goToGroup($index+1)">{{group.name}}</a>
</li> </li>
<li ng-hide="groups.length == 0" class="BreadCrumb-item" ng-if="currentState === 'inventoryManage'"> <li ng-hide="groups.length == 0" class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState === 'inventoryManage'">
<span>{{groups[groups.length-1].name}}</span> <span>{{groups[groups.length-1].name}}</span>
</li> </li>
<!-- inside inventoryManage.child like add/edit (last item is clickable)--> <!-- inside inventoryManage.child like add/edit (last item is clickable)-->
<li ng-repeat="group in groups track by $index" class="BreadCrumb-item" ng-if="currentState !== 'inventoryManage'"> <li ng-repeat="group in groups track by $index" class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState !== 'inventoryManage'">
<a href ng-click="goToGroup($index+1)">{{group.name}}</a> <a href ng-click="goToGroup($index+1)">{{group.name}}</a>
</li> </li>
<li class="BreadCrumb-item" ng-if="currentState !== 'inventoryManage'"></li> <li class="BreadCrumb-item BreadCrumb-invItem" ng-if="currentState !== 'inventoryManage'"></li>
<div class="InventoryManageBreadCrumb-ncy" ng-if="!licenseMissing" ncy-breadcrumb></div> <div class="InventoryManageBreadCrumb-ncy" ng-if="!licenseMissing" ncy-breadcrumb></div>
</ol> </ol>
</div> </div>

View File

@@ -4,6 +4,7 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory'; import {templateUrl} from '../../../shared/template-url/template-url.factory';
import { N_ } from '../../../i18n';
import CopyMoveGroupsController from './copy-move-groups.controller'; import CopyMoveGroupsController from './copy-move-groups.controller';
import CopyMoveHostsController from './copy-move-hosts.controller'; import CopyMoveHostsController from './copy-move-hosts.controller';
@@ -25,7 +26,7 @@ var copyMoveGroupRoute = {
} }
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
label: "COPY OR MOVE {{item.name}}" label: N_("COPY OR MOVE") + " {{item.name}}"
}, },
resolve: { resolve: {
Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath', 'group', Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath', 'group',
@@ -61,7 +62,7 @@ var copyMoveHostRoute = {
url: '/copy-move-host/{host_id}', url: '/copy-move-host/{host_id}',
searchPrefix: 'copy', searchPrefix: 'copy',
ncyBreadcrumb: { ncyBreadcrumb: {
label: "COPY OR MOVE {{item.name}}" label: N_("COPY OR MOVE") + " {{item.name}}"
}, },
resolve: { resolve: {
Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath', Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath',

View File

@@ -9,6 +9,7 @@ import inventoryScriptsAdd from './add/main';
import inventoryScriptsEdit from './edit/main'; import inventoryScriptsEdit from './edit/main';
import list from './inventory-scripts.list'; import list from './inventory-scripts.list';
import form from './inventory-scripts.form'; import form from './inventory-scripts.form';
import { N_ } from '../i18n';
export default export default
angular.module('inventoryScripts', [ angular.module('inventoryScripts', [
@@ -62,7 +63,7 @@ angular.module('inventoryScripts', [
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'setup', parent: 'setup',
label: 'INVENTORY SCRIPTS' label: N_('INVENTORY SCRIPTS')
} }
}) })
}); });

View File

@@ -8,10 +8,10 @@
<div class="JobDetail-panelHeader"> <div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer"> <div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessStatus" href="" ng-click="toggleLessStatus()"> <a class="JobDetail-panelHeaderText" ng-show="lessStatus" href="" ng-click="toggleLessStatus()">
RESULTS<i class="JobDetail-expandArrow fa fa-caret-right"></i> <translate>RESULTS</translate><i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a> </a>
<a class="JobDetail-panelHeaderText" ng-show="!lessStatus" href="" ng-click="toggleLessStatus()"> <a class="JobDetail-panelHeaderText" ng-show="!lessStatus" href="" ng-click="toggleLessStatus()">
RESULTS<i class="JobDetail-expandArrow fa fa-caret-down"></i> <translate>RESULTS</translate><i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a> </a>
</div> </div>
<div class="JobDetail-actions"> <div class="JobDetail-actions">
@@ -23,12 +23,12 @@
<div class="form-horizontal JobDetail-resultsDetails" role="form" id="job-status-form"> <div class="form-horizontal JobDetail-resultsDetails" role="form" id="job-status-form">
<div class="form-group JobDetail-resultRow toggle-show"> <div class="form-group JobDetail-resultRow toggle-show">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Status</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Status</label>
<div class="JobDetail-resultRowText"><i class="JobDetail-statusIcon--results fa icon-job-{{ job_status.status }}"></i> {{ job_status.status_label }}</div> <div class="JobDetail-resultRowText"><i class="JobDetail-statusIcon--results fa icon-job-{{ job_status.status }}"></i> {{ job_status.status_label }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.explanation"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.explanation">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 col-xs-12">Explanation</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 col-xs-12" translate>Explanation</label>
<div class="JobDetail-resultRowText col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation" <div class="JobDetail-resultRowText col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation"
ng-show="!previousTaskFailed" ng-bind-html="job_status.explanation"></div> ng-show="!previousTaskFailed" ng-bind-html="job_status.explanation"></div>
<div class="JobDetail-resultRowText col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation" <div class="JobDetail-resultRowText col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation"
@@ -49,118 +49,118 @@
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.traceback"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.traceback">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-12 col-sm-12 col-xs-12">Results Traceback</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-12 col-sm-12 col-xs-12" translate>Results Traceback</label>
<div class="JobDetail-resultRowText col-lg-10 col-md-12 col-sm-12 col-xs-12 job_status_traceback" ng-bind-html="job_status.traceback"></div> <div class="JobDetail-resultRowText col-lg-10 col-md-12 col-sm-12 col-xs-12 job_status_traceback" ng-bind-html="job_status.traceback"></div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_template_name"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_template_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Template</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Template</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href="{{ job_template_url }}" aw-tool-tip="Edit the job template" data-placement="top">{{ job_template_name }}</a> <a href="{{ job_template_url }}" aw-tool-tip="Edit the job template" data-placement="top">{{ job_template_name }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Started</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Started</label>
<div class="JobDetail-resultRowText">{{ job_status.started | longDate }}</div> <div class="JobDetail-resultRowText">{{ job_status.started | longDate }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_type"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_type">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Job Type</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Job Type</label>
<div class="JobDetail-resultRowText">{{ job_type }}</div> <div class="JobDetail-resultRowText">{{ job_type }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Finished</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Finished</label>
<div class="JobDetail-resultRowText">{{ job_status.finished | longDate }}</div> <div class="JobDetail-resultRowText">{{ job_status.finished | longDate }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="created_by"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="created_by">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Launched By</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Launched By</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href="{{ users_url }}" aw-tool-tip="Edit the User" data-placement="top">{{ created_by }}</a> <a href="{{ users_url }}" aw-tool-tip="Edit the User" data-placement="top">{{ created_by }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Elapsed</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Elapsed</label>
<div class="JobDetail-resultRowText">{{ job_status.elapsed }}</div> <div class="JobDetail-resultRowText">{{ job_status.elapsed }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="scheduled_by"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="scheduled_by">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Launched By</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Launched By</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href aw-tool-tip="Edit the Schedule" data-placement="top" ng-click="editSchedule()">{{scheduled_by}}</a> <a href aw-tool-tip="Edit the Schedule" data-placement="top" ng-click="editSchedule()">{{scheduled_by}}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="inventory_name"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="inventory_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Inventory</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Inventory</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href="{{ inventory_url }}" aw-tool-tip="Edit the inventory" data-placement="top">{{ inventory_name }}</a> <a href="{{ inventory_url }}" aw-tool-tip="Edit the inventory" data-placement="top">{{ inventory_name }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="project_name"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="project_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Project</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Project</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href="{{ project_url }}" aw-tool-tip="Edit the project" data-placement="top">{{ project_name }}</a> <a href="{{ project_url }}" aw-tool-tip="Edit the project" data-placement="top">{{ project_name }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.playbook"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job.playbook">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Playbook</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Playbook</label>
<div class="JobDetail-resultRowText">{{ job.playbook }}</div> <div class="JobDetail-resultRowText">{{ job.playbook }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="credential_name"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="credential_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Machine Credential</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Machine Credential</label>
<div class="JobDetail-resultRowText JobDetail-resultRowText"> <div class="JobDetail-resultRowText JobDetail-resultRowText">
<a href="{{ credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ credential_name }}</a> <a href="{{ credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ credential_name }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="cloud_credential_name"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="cloud_credential_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Cloud Credential</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Cloud Credential</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href="{{ cloud_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ cloud_credential_name }}</a> <a href="{{ cloud_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ cloud_credential_name }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="network_credential_name"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="network_credential_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Network Credential</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Network Credential</label>
<div class="JobDetail-resultRowText"> <div class="JobDetail-resultRowText">
<a href="{{ network_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ network_credential_name }}</a> <a href="{{ network_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ network_credential_name }}</a>
</div> </div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.forks"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job.forks">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Forks</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Forks</label>
<div class="JobDetail-resultRowText">{{ job.forks }}</div> <div class="JobDetail-resultRowText">{{ job.forks }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.limit"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job.limit">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Limit</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Limit</label>
<div class="JobDetail-resultRowText">{{ job.limit }}</div> <div class="JobDetail-resultRowText">{{ job.limit }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="verbosity"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="verbosity">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Verbosity</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Verbosity</label>
<div class="JobDetail-resultRowText">{{ verbosity }}</div> <div class="JobDetail-resultRowText">{{ verbosity }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.job_tags"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job.job_tags">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Job Tags</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Job Tags</label>
<div class="JobDetail-resultRowText">{{ job.job_tags }}</div> <div class="JobDetail-resultRowText">{{ job.job_tags }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.skip_tags"> <div class="form-group JobDetail-resultRow toggle-show" ng-show="job.skip_tags">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Skip Tags</label> <label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Skip Tags</label>
<div class="JobDetail-resultRowText">{{ job.skip_tags }}</div> <div class="JobDetail-resultRowText">{{ job.skip_tags }}</div>
</div> </div>
<div class="form-group JobDetail-resultRow JobDetail-resultRow--variables toggle-show" ng-show="variables"> <div class="form-group JobDetail-resultRow JobDetail-resultRow--variables toggle-show" ng-show="variables">
<label class="JobDetail-resultRowLabel JobDetail-extraVarsLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Extra Variables</label> <label class="JobDetail-resultRowLabel JobDetail-extraVarsLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Extra Variables</label>
<textarea rows="6" ng-model="variables" name="variables" class="JobDetail-extraVars" id="pre-formatted-variables"></textarea> <textarea rows="6" ng-model="variables" name="variables" class="JobDetail-extraVars" id="pre-formatted-variables"></textarea>
</div> </div>
@@ -173,10 +173,10 @@
<div class="JobDetail-panelHeader"> <div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer"> <div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessDetail" href="" ng-click="toggleLessDetail()"> <a class="JobDetail-panelHeaderText" ng-show="lessDetail" href="" ng-click="toggleLessDetail()">
DETAILS<i class="JobDetail-expandArrow fa fa-caret-right"></i> <translate>DETAILS</translate><i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a> </a>
<a class="JobDetail-panelHeaderText" ng-show="!lessDetail" href="" ng-click="toggleLessDetail()"> <a class="JobDetail-panelHeaderText" ng-show="!lessDetail" href="" ng-click="toggleLessDetail()">
DETAILS<i class="JobDetail-expandArrow fa fa-caret-down"></i> <translate>DETAILS</translate><i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a> </a>
</div> </div>
</div> </div>
@@ -194,8 +194,8 @@
</div> </div>
<div class="JobDetail-tableToggleContainer form-group"> <div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterPlayStatus"> <div class="btn-group" aw-toggle-button data-after-toggle="filterPlayStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active">All</button> <button class="JobDetail-tableToggle btn btn-xs btn-primary active" translate>All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default">Failed</button> <button class="JobDetail-tableToggle btn btn-xs btn-default" translate>Failed</button>
</div> </div>
</div> </div>
</div> </div>
@@ -205,9 +205,9 @@
<table class="table table-condensed"> <table class="table table-condensed">
<thead> <thead>
<tr> <tr>
<th class="List-tableHeader col-lg-7 col-md-6 col-sm-6 col-xs-4">Plays</th> <th class="List-tableHeader col-lg-7 col-md-6 col-sm-6 col-xs-4" translate>Plays</th>
<th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3">Started</th> <th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Started</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3">Elapsed</th> <th class="List-tableHeader JobDetail-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Elapsed</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@@ -242,7 +242,7 @@
<!-- end of plays section of details--> <!-- end of plays section of details-->
<div id="task-section" class="section JobDetail-tasks" > <div id="task-section" class="section JobDetail-tasks" >
<div class="JobDetail-instructions"><span class="badge">2</span> Please select a task below to view its associated hosts</div> <div class="JobDetail-instructions"><span class="badge">2</span> <translate>Please select a task below to view its associated hosts</translate></div>
<div class="JobDetail-searchHeaderRow"> <div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group"> <div class="JobDetail-searchContainer form-group">
<div class="search-name"> <div class="search-name">
@@ -253,8 +253,8 @@
</div> </div>
<div class="JobDetail-tableToggleContainer form-group"> <div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterTaskStatus"> <div class="btn-group" aw-toggle-button data-after-toggle="filterTaskStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active">All</button> <button class="JobDetail-tableToggle btn btn-xs btn-primary active" translate>All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default">Failed</button> <button class="JobDetail-tableToggle btn btn-xs btn-default" translate>Failed</button>
</div> </div>
</div> </div>
</div> </div>
@@ -263,10 +263,10 @@
<table id="tasks-table-header" class="table table-condensed" ng-show="taskList.length !== 0"> <table id="tasks-table-header" class="table table-condensed" ng-show="taskList.length !== 0">
<thead> <thead>
<tr> <tr>
<th class="List-tableHeader col-lg-3 col-md-3 col-sm-6 col-xs-4">Tasks</th> <th class="List-tableHeader col-lg-3 col-md-3 col-sm-6 col-xs-4" translate>Tasks</th>
<th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3">Started</th> <th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Started</th>
<th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3">Elapsed</th> <th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Elapsed</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-4 col-md-3 hidden-xs hidden-sm">Host Status</th> <th class="List-tableHeader JobDetail-tableHeader col-lg-4 col-md-3 hidden-xs hidden-sm" translate>Host Status</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@@ -304,19 +304,19 @@
<span class="badge missing-hosts">{{ task.missingCount }}</span> <span class="badge missing-hosts">{{ task.missingCount }}</span>
</a> </a>
<div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found." data-placement="top" style="width: 100%;" ng-show="task.status === 'no-matching-hosts'"> <div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found." data-placement="top" style="width: 100%;" ng-show="task.status === 'no-matching-hosts'">
No matching hosts. <translate>No matching hosts.</translate>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
<tr ng-show="taskList.length === 0 && waiting"> <tr ng-show="taskList.length === 0 && waiting">
<td colspan="5" class="col-lg-12 loading-info">Waiting...</td> <td colspan="5" class="col-lg-12 loading-info" translate>Waiting...</td>
</tr> </tr>
<tr ng-show="taskList.length === 0 && tasksLoading && !waiting"> <tr ng-show="taskList.length === 0 && tasksLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info">Loading...</td> <td colspan="5" class="col-lg-12 loading-info" translate>Loading...</td>
</tr> </tr>
<tr ng-show="taskList.length === 0 && !tasksLoading && !waiting"> <tr ng-show="taskList.length === 0 && !tasksLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info">No matching tasks</td> <td colspan="5" class="col-lg-12 loading-info" translate>No matching tasks</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -337,8 +337,8 @@
</div> </div>
<div class="JobDetail-tableToggleContainer form-group"> <div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostStatus"> <div class="btn-group" aw-toggle-button data-after-toggle="filterHostStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active">All</button> <button class="JobDetail-tableToggle btn btn-xs btn-primary active" translate>All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default">Failed</button> <button class="JobDetail-tableToggle btn btn-xs btn-default" translate>Failed</button>
</div> </div>
</div> </div>
</div> </div>
@@ -346,9 +346,9 @@
<table class="table table-condensed" ng-show="results.length !== 0"> <table class="table table-condensed" ng-show="results.length !== 0">
<thead> <thead>
<tr> <tr>
<th class="List-tableHeader col-lg-4 col-md-3 col-sm-3 col-xs-3">Hosts</th> <th class="List-tableHeader col-lg-4 col-md-3 col-sm-3 col-xs-3" translate>Hosts</th>
<th class="List-tableHeader col-lg-3 col-md-4 col-sm-3 col-xs-3">Item</th> <th class="List-tableHeader col-lg-3 col-md-4 col-sm-3 col-xs-3" translate>Item</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-3 col-md-4 col-sm-3 col-xs-3">Message</th> <th class="List-tableHeader JobDetail-tableHeader col-lg-3 col-md-4 col-sm-3 col-xs-3" translate>Message</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@@ -365,13 +365,13 @@
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td> <td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>
</tr> </tr>
<tr ng-show="results.length === 0 && waiting"> <tr ng-show="results.length === 0 && waiting">
<td colspan="5" class="col-lg-12 loading-info">Waiting...</td> <td colspan="5" class="col-lg-12 loading-info" translate>Waiting...</td>
</tr> </tr>
<tr ng-show="results.length === 0 && hostResultsLoading && !waiting"> <tr ng-show="results.length === 0 && hostResultsLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info">Loading...</td> <td colspan="5" class="col-lg-12 loading-info" translate>Loading...</td>
</tr> </tr>
<tr ng-show="results.length === 0 && !hostResultsLoading && !waiting"> <tr ng-show="results.length === 0 && !hostResultsLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info">No matching host events</td> <td colspan="5" class="col-lg-12 loading-info" translate>No matching host events</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -388,10 +388,10 @@
<div class="JobDetail-panelHeader"> <div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer"> <div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()"> <a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-right"></i> <translate>EVENT SUMMARY</translate><i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a> </a>
<a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()"> <a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-down"></i> <translate>EVENT SUMMARY</translate><i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a> </a>
</div> </div>
</div> </div>
@@ -411,13 +411,13 @@
<div class="JobDetail-rightSide"> <div class="JobDetail-rightSide">
<div class="JobDetail-stdoutPanel Panel"> <div class="JobDetail-stdoutPanel Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div> <div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions"> <div class="StandardOut-panelHeaderActions">
<button class="StandardOut-actionButton" aw-tool-tip="Toggle Output" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()"> <button class="StandardOut-actionButton" aw-tool-tip="{{'Toggle Output'|translate}}" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i> <i class="fa fa-arrows-alt"></i>
</button> </button>
<a ng-show="job_status.status === 'failed' || job_status.status === 'successful' || job_status.status === 'canceled'" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}"> <a ng-show="job_status.status === 'failed' || job_status.status === 'successful' || job_status.status === 'canceled'" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top"> <button class="StandardOut-actionButton" aw-tool-tip="{{'Download Output'|translate}}" data-placement="top">
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
</button> </button>
</a> </a>

View File

@@ -1,6 +1,6 @@
export default export default
function LaunchJob(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath, $state, $location, $rootScope) { function LaunchJob(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath, $state, $location, $rootScope, i18n) {
// This factory gathers up all the job launch data and POST's it. // This factory gathers up all the job launch data and POST's it.
@@ -163,8 +163,10 @@ export default
} }
}) })
.error(function(data, status) { .error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', let template_id = scope.job_template_id;
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); template_id = (template_id === undefined) ? "undefined" : i18n.sprintf("%d", template_id);
ProcessErrors(scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed updating job %s with variables. POST returned: %d'), template_id, status) });
}); });
}; };
@@ -182,8 +184,8 @@ export default
buildData(); buildData();
}) })
.error(function (data, status) { .error(function (data, status) {
ProcessErrors(scope, data, status, { hdr: 'Error!', ProcessErrors(scope, data, status, { hdr: i18n._('Error!'),
msg: 'Failed to retrieve job template extra variables.' }); msg: i18n._('Failed to retrieve job template extra variables.') });
}); });
}; };
@@ -209,5 +211,6 @@ LaunchJob.$inject =
'GetBasePath', 'GetBasePath',
'$state', '$state',
'$location', '$location',
'$rootScope' '$rootScope',
'i18n'
]; ];

View File

@@ -5,6 +5,7 @@
*************************************************/ *************************************************/
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
export default { export default {
name: 'license', name: 'license',
@@ -14,7 +15,7 @@ export default {
data: {}, data: {},
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'setup', parent: 'setup',
label: 'LICENSE' label: N_('LICENSE')
}, },
resolve: { resolve: {
features: ['CheckLicense', '$rootScope', features: ['CheckLicense', '$rootScope',

View File

@@ -13,7 +13,7 @@ export default
name: 'jobs', name: 'jobs',
basePath: 'unified_jobs', basePath: 'unified_jobs',
iterator: 'job', iterator: 'job',
editTitle: 'All Jobs', editTitle: i18n._('All Jobs'),
index: false, index: false,
hover: true, hover: true,
well: false, well: false,
@@ -42,13 +42,13 @@ export default
noLink: true noLink: true
}, },
name: { name: {
label: 'Name', label: i18n._('Name'),
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6', columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6',
ngClick: "viewJobDetails(job)", ngClick: "viewJobDetails(job)",
badgePlacement: 'right', badgePlacement: 'right',
badgeCustom: true, badgeCustom: true,
badgeIcon: `<a href="{{ job.workflow_result_link }}" badgeIcon: `<a href="{{ job.workflow_result_link }}"
aw-tool-tip="View workflow results" aw-tool-tip=i18n._("View workflow results")
data-placement="top" data-placement="top"
data-original-title="" title=""> data-original-title="" title="">
<i class="WorkflowBadge" <i class="WorkflowBadge"
@@ -58,14 +58,14 @@ export default
</a>` </a>`
}, },
type: { type: {
label: 'Type', label: i18n._('Type'),
ngBind: 'job.type_label', ngBind: 'job.type_label',
link: false, link: false,
columnClass: "col-lg-2 hidden-md hidden-sm hidden-xs", columnClass: "col-lg-2 hidden-md hidden-sm hidden-xs",
columnShow: "showJobType", columnShow: "showJobType",
}, },
finished: { finished: {
label: 'Finished', label: i18n._('Finished'),
noLink: true, noLink: true,
filter: "longDate", filter: "longDate",
columnClass: "col-lg-2 col-md-3 col-sm-3 hidden-xs", columnClass: "col-lg-2 col-md-3 col-sm-3 hidden-xs",
@@ -73,7 +73,7 @@ export default
desc: true desc: true
}, },
labels: { labels: {
label: 'Labels', label: i18n._('Labels'),
type: 'labels', type: 'labels',
nosort: true, nosort: true,
showDelete: false, showDelete: false,
@@ -91,28 +91,28 @@ export default
"view": { "view": {
mode: "all", mode: "all",
ngClick: "viewJobDetails(job)", ngClick: "viewJobDetails(job)",
awToolTip: "View the job", awToolTip: i18n._("View the job"),
dataPlacement: "top" dataPlacement: "top"
}, },
submit: { submit: {
icon: 'icon-rocket', icon: 'icon-rocket',
mode: 'all', mode: 'all',
ngClick: 'relaunchJob($event, job.id)', ngClick: 'relaunchJob($event, job.id)',
awToolTip: 'Relaunch using the same parameters', awToolTip: i18n._('Relaunch using the same parameters'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: "!(job.type == 'system_job') && job.summary_fields.user_capabilities.start" ngShow: "!(job.type == 'system_job') && job.summary_fields.user_capabilities.start"
}, },
cancel: { cancel: {
mode: 'all', mode: 'all',
ngClick: 'deleteJob(job.id)', ngClick: 'deleteJob(job.id)',
awToolTip: 'Cancel the job', awToolTip: i18n._('Cancel the job'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: "(job.status === 'running'|| job.status === 'waiting' || job.status === 'pending') && job.summary_fields.user_capabilities.start" ngShow: "(job.status === 'running'|| job.status === 'waiting' || job.status === 'pending') && job.summary_fields.user_capabilities.start"
}, },
"delete": { "delete": {
mode: 'all', mode: 'all',
ngClick: 'deleteJob(job.id)', ngClick: 'deleteJob(job.id)',
awToolTip: 'Delete the job', awToolTip: i18n._('Delete the job'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: "(job.status !== 'running' && job.status !== 'waiting' && job.status !== 'pending') && job.summary_fields.user_capabilities.delete" ngShow: "(job.status !== 'running' && job.status !== 'waiting' && job.status !== 'pending') && job.summary_fields.user_capabilities.delete"
} }

View File

@@ -7,11 +7,12 @@
export default export default
angular.module('JobEventsListDefinition', []) angular.module('JobEventsListDefinition', [])
.value('JobEventList', { .factory('JobEventList', ['i18n', function(i18n) {
return {
name: 'jobevents', name: 'jobevents',
iterator: 'jobevent', iterator: 'jobevent',
editTitle: 'Job Events', editTitle: i18n._('Job Events'),
index: false, index: false,
hover: true, hover: true,
"class": "condensed", "class": "condensed",
@@ -27,27 +28,27 @@ export default
//}, //},
events: { events: {
href: '/#/job_events/{{ job_id }}', href: '/#/job_events/{{ job_id }}',
label: 'Events', label: i18n._('Events'),
active: true, active: true,
icon: 'icon-list-ul' icon: 'icon-list-ul'
}, },
hosts: { hosts: {
href: '/#/job_host_summaries/{{ job_id }}', href: '/#/job_host_summaries/{{ job_id }}',
label: 'Host Summary', label: i18n._('Host Summary'),
icon: 'icon-laptop' icon: 'icon-laptop'
} }
}, },
fields: { fields: {
created: { created: {
label: 'Created On', label: i18n._('Created On'),
columnClass: 'col-lg-1 col-md-1 hidden-sm hidden-xs', columnClass: 'col-lg-1 col-md-1 hidden-sm hidden-xs',
key: true, key: true,
nosort: true, nosort: true,
noLink: true noLink: true
}, },
status: { status: {
label: 'Status', label: i18n._('Status'),
showValue: false, showValue: false,
columnClass: 'col-sm-1 col-xs-2 text-center', columnClass: 'col-sm-1 col-xs-2 text-center',
nosort: true, nosort: true,
@@ -61,7 +62,7 @@ export default
badgeNgClick: 'viewJobEvent(jobevent.id)' badgeNgClick: 'viewJobEvent(jobevent.id)'
}, },
event_display: { event_display: {
label: 'Event', label: i18n._('Event'),
hasChildren: true, hasChildren: true,
ngClick: 'toggleChildren(jobevent.id, jobevent.related.children)', ngClick: 'toggleChildren(jobevent.id, jobevent.related.children)',
nosort: true, nosort: true,
@@ -69,7 +70,7 @@ export default
appendHTML: 'jobevent.event_detail' appendHTML: 'jobevent.event_detail'
}, },
host: { host: {
label: 'Host', label: i18n._('Host'),
ngBind: 'jobevent.summary_fields.host.name', ngBind: 'jobevent.summary_fields.host.name',
ngHref: '{{ jobevent.hostLink }}', ngHref: '{{ jobevent.hostLink }}',
nosort: true, nosort: true,
@@ -85,7 +86,7 @@ export default
awToolTip: 'Refresh the page', awToolTip: 'Refresh the page',
ngClick: 'refresh()', ngClick: 'refresh()',
actionClass: 'btn List-buttonDefault', actionClass: 'btn List-buttonDefault',
buttonContent: 'REFRESH' buttonContent: i18n._('REFRESH')
} }
}, },
@@ -94,10 +95,10 @@ export default
columnClass: 'col-sm-1 col-xs-2', columnClass: 'col-sm-1 col-xs-2',
view: { view: {
label: 'View', label: i18n._('View'),
ngClick: 'viewJobEvent(jobevent.id)', ngClick: 'viewJobEvent(jobevent.id)',
awToolTip: 'View event details', awToolTip: i18n._('View event details'),
dataPlacement: 'top' dataPlacement: 'top'
} }
} }
}); };}]);

View File

@@ -7,7 +7,8 @@
export default export default
angular.module('SchedulesListDefinition', []) angular.module('SchedulesListDefinition', [])
.value('SchedulesList', { .factory('StreamList', ['i18n', function(i18n) {
return {
name: 'schedules', name: 'schedules',
iterator: 'schedule', iterator: 'schedule',
@@ -31,22 +32,22 @@ export default
}, },
name: { name: {
key: true, key: true,
label: 'Name', label: i18n._('Name'),
ngClick: "editSchedule(schedule)", ngClick: "editSchedule(schedule)",
columnClass: "col-md-3 col-sm-3 col-xs-6" columnClass: "col-md-3 col-sm-3 col-xs-6"
}, },
dtstart: { dtstart: {
label: 'First Run', label: i18n._('First Run'),
filter: "longDate", filter: "longDate",
columnClass: "List-staticColumn--schedulerTime hidden-sm hidden-xs" columnClass: "List-staticColumn--schedulerTime hidden-sm hidden-xs"
}, },
next_run: { next_run: {
label: 'Next Run', label: i18n._('Next Run'),
filter: "longDate", filter: "longDate",
columnClass: "List-staticColumn--schedulerTime hidden-xs" columnClass: "List-staticColumn--schedulerTime hidden-xs"
}, },
dtend: { dtend: {
label: 'Final Run', label: i18n._('Final Run'),
filter: "longDate", filter: "longDate",
columnClass: "List-staticColumn--schedulerTime hidden-xs" columnClass: "List-staticColumn--schedulerTime hidden-xs"
}, },
@@ -55,45 +56,45 @@ export default
actions: { actions: {
refresh: { refresh: {
mode: 'all', mode: 'all',
awToolTip: "Refresh the page", awToolTip: i18n._("Refresh the page"),
ngClick: "refreshSchedules()", ngClick: "refreshSchedules()",
actionClass: 'btn List-buttonDefault', actionClass: 'btn List-buttonDefault',
ngShow: "socketStatus == 'error'", ngShow: "socketStatus == 'error'",
buttonContent: 'REFRESH' buttonContent: i18n._('REFRESH')
}, },
add: { add: {
mode: 'all', mode: 'all',
ngClick: 'addSchedule()', ngClick: 'addSchedule()',
awToolTip: 'Add a new schedule', awToolTip: i18n._('Add a new schedule'),
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD', buttonContent: '&#43; ' + i18n._('ADD'),
ngShow: 'canAdd' ngShow: 'canAdd'
} }
}, },
fieldActions: { fieldActions: {
edit: { edit: {
label: 'Edit', label: i18n._('Edit'),
ngClick: "editSchedule(schedule)", ngClick: "editSchedule(schedule)",
icon: 'icon-edit', icon: 'icon-edit',
awToolTip: 'Edit schedule', awToolTip: i18n._('Edit schedule'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: 'schedule.summary_fields.user_capabilities.edit' ngShow: 'schedule.summary_fields.user_capabilities.edit'
}, },
view: { view: {
label: 'View', label: i18n._('View'),
ngClick: "editSchedule(schedule)", ngClick: "editSchedule(schedule)",
awToolTip: 'View schedule', awToolTip: i18n._('View schedule'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: '!schedule.summary_fields.user_capabilities.edit' ngShow: '!schedule.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
label: 'Delete', label: i18n._('Delete'),
ngClick: "deleteSchedule(schedule.id)", ngClick: "deleteSchedule(schedule.id)",
icon: 'icon-trash', icon: 'icon-trash',
awToolTip: 'Delete schedule', awToolTip: i18n._('Delete schedule'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: 'schedule.summary_fields.user_capabilities.delete' ngShow: 'schedule.summary_fields.user_capabilities.delete'
} }
} }
}); };}]);

View File

@@ -7,15 +7,16 @@
export default export default
angular.module('StreamListDefinition', []) angular.module('StreamListDefinition', [])
.value('StreamList', { .factory('StreamList', ['i18n', function(i18n) {
return {
name: 'activities', name: 'activities',
iterator: 'activity', iterator: 'activity',
basePath: 'activity_stream', basePath: 'activity_stream',
editTitle: 'Activity Stream', editTitle: i18n._('Activity Stream'),
listTitle: 'Activity Stream<span ng-show="streamSubTitle"><div class="List-titleLockup"></div>{{streamSubTitle}}<span>', listTitle: i18n._('Activity Stream') + '<span ng-show="streamSubTitle"><div class="List-titleLockup"></div>{{streamSubTitle}}<span>',
listTitleBadge: false, listTitleBadge: false,
emptyListText: 'There are no events to display at this time', emptyListText: i18n._('There are no events to display at this time'),
selectInstructions: '', selectInstructions: '',
index: false, index: false,
hover: true, hover: true,
@@ -24,7 +25,7 @@ export default
fields: { fields: {
timestamp: { timestamp: {
label: 'Time', label: i18n._('Time'),
key: true, key: true,
desc: true, desc: true,
noLink: true, noLink: true,
@@ -32,14 +33,14 @@ export default
columnClass: 'col-lg-3 col-md-2 col-sm-3 col-xs-3' columnClass: 'col-lg-3 col-md-2 col-sm-3 col-xs-3'
}, },
user: { user: {
label: 'Initiated by', label: i18n._('Initiated by'),
ngBindHtml: 'activity.user', // @todo punch monkey ngBindHtml: 'activity.user', // @todo punch monkey
sourceModel: 'actor', sourceModel: 'actor',
sourceField: 'username', sourceField: 'username',
columnClass: 'col-lg-3 col-md-3 col-sm-3 col-xs-3' columnClass: 'col-lg-3 col-md-3 col-sm-3 col-xs-3'
}, },
description: { description: {
label: 'Event', label: i18n._('Event'),
ngBindHtml: 'activity.description', // @todo punch monkey ngBindHtml: 'activity.description', // @todo punch monkey
nosort: true, nosort: true,
columnClass: 'ActivityStream-eventColumnHeader col-lg-5 col-md-6 col-sm-4 col-xs-4' columnClass: 'ActivityStream-eventColumnHeader col-lg-5 col-md-6 col-sm-4 col-xs-4'
@@ -50,10 +51,10 @@ export default
refresh: { refresh: {
mode: 'all', mode: 'all',
id: 'activity-stream-refresh-btn', id: 'activity-stream-refresh-btn',
awToolTip: "Refresh the page", awToolTip: i18n._("Refresh the page"),
ngClick: "refreshStream()", ngClick: "refreshStream()",
actionClass: 'btn List-buttonDefault ActivityStream-refreshButton', actionClass: 'btn List-buttonDefault ActivityStream-refreshButton',
buttonContent: 'REFRESH' buttonContent: i18n._('REFRESH')
} }
}, },
@@ -62,13 +63,13 @@ export default
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2', columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2',
view: { view: {
label: 'View', label: i18n._('View'),
ngClick: "showDetail(activity.id)", ngClick: "showDetail(activity.id)",
icon: 'fa-zoom-in', icon: 'fa-zoom-in',
"class": 'btn-default btn-xs', "class": 'btn-default btn-xs',
awToolTip: 'View event details', awToolTip: i18n._('View event details'),
dataPlacement: 'top' dataPlacement: 'top'
} }
} }
}); };}]);

View File

@@ -23,9 +23,9 @@
*/ */
export default export default
['$rootScope', '$cookieStore', 'CreateDialog', 'Authorization', ['$rootScope', '$cookieStore', 'CreateDialog', 'Authorization',
'Store', '$interval', '$state', '$q', 'Store', '$interval', '$state', '$q', 'i18n',
function ($rootScope, $cookieStore, CreateDialog, Authorization, function ($rootScope, $cookieStore, CreateDialog, Authorization,
Store, $interval, $state, $q) { Store, $interval, $state, $q, i18n) {
return { return {
sessionTime: null, sessionTime: null,
@@ -154,7 +154,7 @@ export default
}); });
CreateDialog({ CreateDialog({
id: 'idle-modal' , id: 'idle-modal' ,
title: "Idle Session", title: i18n._("Idle Session"),
scope: $rootScope, scope: $rootScope,
buttons: buttons, buttons: buttons,
width: 470, width: 470,

View File

@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
</form> </form>
<div id="login_modal_notice" class="LoginModalNotice" ng-if="customLoginInfoPresent"><div class="LoginModalNotice-title">NOTICE</div>{{ customLoginInfo | sanitize }}</div> <div id="login_modal_notice" class="LoginModalNotice" ng-if="customLoginInfoPresent"><div class="LoginModalNotice-title" translate>NOTICE</div>{{ customLoginInfo | sanitize }}</div>
</div> </div>
<div class="LoginModal-footer"> <div class="LoginModal-footer">
<div class="LoginModal-footerBlock"> <div class="LoginModal-footerBlock">

View File

@@ -10,11 +10,11 @@ export default
[ 'Wait', '$compile', 'CreateDialog', 'GetBasePath' , [ 'Wait', '$compile', 'CreateDialog', 'GetBasePath' ,
'SchedulesList', 'Rest' , 'SchedulesList', 'Rest' ,
'ProcessErrors', 'managementJobsListObject', '$rootScope', '$state', 'ProcessErrors', 'managementJobsListObject', '$rootScope', '$state',
'$scope', 'CreateSelect2', '$scope', 'CreateSelect2', 'i18n',
function( Wait, $compile, CreateDialog, GetBasePath, function( Wait, $compile, CreateDialog, GetBasePath,
SchedulesList, Rest, ProcessErrors, SchedulesList, Rest, ProcessErrors,
managementJobsListObject, $rootScope, $state, $scope, managementJobsListObject, $rootScope, $state, $scope,
CreateSelect2) { CreateSelect2, i18n) {
var defaultUrl = GetBasePath('system_job_templates') + "?order_by=name"; var defaultUrl = GetBasePath('system_job_templates') + "?order_by=name";
@@ -26,8 +26,8 @@ export default
Wait('stop'); Wait('stop');
}) })
.error(function(data, status){ .error(function(data, status){
ProcessErrors($scope, data, status, null, {hdr: 'Error!', ProcessErrors($scope, data, status, null, {hdr: i18n._('Error!'),
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); msg: i18n.sprintf(i18n._('Call to %s failed. Return status: %d'), (defaultUrl === undefined) ? "undefined" : defaultUrl, status )});
}); });
}; };
getManagementJobs(); getManagementJobs();
@@ -145,8 +145,10 @@ export default
$state.go('managementJobStdout', {id: data.system_job}, {reload:true}); $state.go('managementJobStdout', {id: data.system_job}, {reload:true});
}) })
.error(function(data, status) { .error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', let template_id = scope.job_template_id;
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); template_id = (template_id === undefined) ? "undefined" : i18n.sprintf("%d", template_id);
ProcessErrors(scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed updating job %s with variables. POST returned: %d'), template_id, status) });
}); });
}, },
"class": "btn btn-primary", "class": "btn btn-primary",
@@ -233,8 +235,10 @@ export default
$state.go('managementJobStdout', {id: data.system_job}, {reload:true}); $state.go('managementJobStdout', {id: data.system_job}, {reload:true});
}) })
.error(function(data, status) { .error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', let template_id = scope.job_template_id;
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); template_id = (template_id === undefined) ? "undefined" : i18n.sprintf("%d", template_id);
ProcessErrors(scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed updating job %s with variables. POST returned: %d'), template_id, status) });
}); });
}, },
"class": "btn btn-primary", "class": "btn btn-primary",

View File

@@ -4,6 +4,8 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
import { N_ } from '../../i18n';
export default { export default {
name: 'managementJobsList.notifications', name: 'managementJobsList.notifications',
route: '/:management_id/notifications', route: '/:management_id/notifications',
@@ -16,7 +18,7 @@ export default {
controller: 'managementJobsNotificationsController', controller: 'managementJobsNotificationsController',
templateProvider: function(NotificationsList, generateList, ParentObject) { templateProvider: function(NotificationsList, generateList, ParentObject) {
// include name of parent resource in listTitle // include name of parent resource in listTitle
NotificationsList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Notifications`; NotificationsList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Notifications');
let html = generateList.build({ let html = generateList.build({
list: NotificationsList, list: NotificationsList,
mode: 'edit' mode: 'edit'
@@ -41,6 +43,6 @@ export default {
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'managementJobsList', parent: 'managementJobsList',
label: 'NOTIFICATIONS' label: N_('NOTIFICATIONS')
} }
}; };

View File

@@ -9,6 +9,7 @@ import { templateUrl } from '../../shared/template-url/template-url.factory';
import controller from '../../scheduler/schedulerList.controller'; import controller from '../../scheduler/schedulerList.controller';
import addController from '../../scheduler/schedulerAdd.controller'; import addController from '../../scheduler/schedulerAdd.controller';
import editController from '../../scheduler/schedulerEdit.controller'; import editController from '../../scheduler/schedulerEdit.controller';
import { N_ } from '../../i18n';
export default export default
angular.module('managementJobScheduler', []) angular.module('managementJobScheduler', [])
@@ -22,13 +23,13 @@ angular.module('managementJobScheduler', [])
route: '/management_jobs/:id/schedules', route: '/management_jobs/:id/schedules',
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'managementJobsList', parent: 'managementJobsList',
label: 'SCHEDULES' label: N_('SCHEDULES')
}, },
views: { views: {
'@': { '@': {
templateProvider: function(SchedulesList, generateList, ParentObject) { templateProvider: function(SchedulesList, generateList, ParentObject) {
// include name of parent resource in listTitle // include name of parent resource in listTitle
SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Schedules`; SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
let html = generateList.build({ let html = generateList.build({
list: SchedulesList, list: SchedulesList,
mode: 'edit' mode: 'edit'
@@ -70,7 +71,7 @@ angular.module('managementJobScheduler', [])
route: '/add', route: '/add',
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'managementJobSchedules', parent: 'managementJobSchedules',
label: 'CREATE SCHEDULED JOB' label: N_('CREATE SCHEDULED JOB')
}, },
views: { views: {
'form': { 'form': {
@@ -84,7 +85,7 @@ angular.module('managementJobScheduler', [])
route: '/edit/:schedule_id', route: '/edit/:schedule_id',
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'managementJobSchedules', parent: 'managementJobSchedules',
label: 'EDIT SCHEDULED JOB' label: N_('EDIT SCHEDULED JOB')
}, },
views: { views: {
'form': { 'form': {

View File

@@ -18,7 +18,7 @@
<div class="form-group SchedulerForm-formGroup"> <div class="form-group SchedulerForm-formGroup">
<label class="Form-inputLabel"> <label class="Form-inputLabel">
<span class="red-text">*</span> <span class="red-text">*</span>
Name <span translate>Name</span>
</label> </label>
<input <input
ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
@@ -31,14 +31,14 @@
ng-model="schedulerName" required ng-model="schedulerName" required
placeholder="Schedule name"> placeholder="Schedule name">
<div class="error" <div class="error"
ng-show="scheduler_form.$dirty && scheduler_form.schedulerName.$error.required"> ng-show="scheduler_form.$dirty && scheduler_form.schedulerName.$error.required" translate>
A schedule name is required. A schedule name is required.
</div> </div>
</div> </div>
<div class="form-group SchedulerForm-formGroup"> <div class="form-group SchedulerForm-formGroup">
<label class="Form-inputLabel"> <label class="Form-inputLabel">
<span class="red-text">*</span> <span class="red-text">*</span>
Start Date <span translate>Start Date</span>
</label> </label>
<div class="input-group Form-inputGroup SchedulerForm-inputGroup--date"> <div class="input-group Form-inputGroup SchedulerForm-inputGroup--date">
<scheduler-date-picker date="schedulerStartDt" <scheduler-date-picker date="schedulerStartDt"
@@ -53,7 +53,7 @@
<div class="form-group SchedulerForm-formGroup"> <div class="form-group SchedulerForm-formGroup">
<label class="Form-inputLabel"> <label class="Form-inputLabel">
<span class="red-text">*</span> <span class="red-text">*</span>
Start Time <span translate>Start Time</span>
<span class="fmt-help" <span class="fmt-help"
ng-show="schedulerShowTimeZone"> ng-show="schedulerShowTimeZone">
(HH24:MM:SS) (HH24:MM:SS)
@@ -122,7 +122,7 @@
</div> --> </div> -->
</div> </div>
<div class="error" <div class="error"
ng-show="scheduler_startTime_error"> ng-show="scheduler_startTime_error" translate>
The time must be in HH24:MM:SS format. The time must be in HH24:MM:SS format.
</div> </div>
</div> </div>
@@ -130,7 +130,7 @@
ng-show="schedulerShowTimeZone"> ng-show="schedulerShowTimeZone">
<label class="Form-inputLabel"> <label class="Form-inputLabel">
<span class="red-text">*</span> <span class="red-text">*</span>
Local Time Zone <span translate>Local Time Zone</span>
</label> </label>
<select <select
ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
@@ -146,7 +146,7 @@
<div class="form-group SchedulerForm-formGroup"> <div class="form-group SchedulerForm-formGroup">
<label class="Form-inputLabel"> <label class="Form-inputLabel">
<span class="red-text">*</span> <span class="red-text">*</span>
Repeat frequency <span translate>Repeat frequency</span>
</label> </label>
<select name="schedulerFrequency" <select name="schedulerFrequency"
ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
@@ -163,14 +163,13 @@
</div> </div>
<div class="form-group SchedulerForm-formGroup" ng-if="cleanupJob && !isFactCleanup"> <div class="form-group SchedulerForm-formGroup" ng-if="cleanupJob && !isFactCleanup">
<label class="Form-inputLabel"><span class="red-text">*</span> Days of data to keep</label> <label class="Form-inputLabel"><span class="red-text">*</span> Days of data to keep</label>
<input type="number" sch-spinner="scheduler_form" class="scheduler-time-spinner SchedulerTime-input SpinnerInput RepeatFrequencyOptions-number" name="schedulerPurgeDays" id="schedulerPurgeDays" min="1" aw-spinner="schedulerPurgeDays" ng-model="schedulerPurgeDays" required placeholder="Days of data to keep" <input type="number" sch-spinner="scheduler_form" class="scheduler-time-spinner SchedulerTime-input SpinnerInput RepeatFrequencyOptions-number" name="schedulerPurgeDays" id="schedulerPurgeDays" min="1" aw-spinner="schedulerPurgeDays" ng-model="schedulerPurgeDays" required placeholder="Days of data to keep" ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)">
ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"> <div class="error" ng-show="scheduler_form.schedulerPurgeDays.$dirty && scheduler_form.schedulerPurgeDays.$error.required" translate>A value is required.</div>
<div class="error" ng-show="scheduler_form.schedulerPurgeDays.$dirty && scheduler_form.schedulerPurgeDays.$error.required">A value is required.</div> <div class="error" ng-show="scheduler_form.schedulerPurgeDays.$error.number" translate>This is not a valid number.</div>
<div class="error" ng-show="scheduler_form.schedulerPurgeDays.$error.number">This is not a valid number.</div>
</div> </div>
<div class="RepeatFrequencyOptions-label" <div class="RepeatFrequencyOptions-label"
ng-show="schedulerFrequency.value && schedulerFrequency.value !== 'none'"> ng-show="schedulerFrequency.value && schedulerFrequency.value !== 'none'">
Frequency Details</div> <span translate>Frequency Details</span></div>
<div class="RepeatFrequencyOptions Form" <div class="RepeatFrequencyOptions Form"
ng-show="schedulerFrequency.value && schedulerFrequency.value !== 'none'"> ng-show="schedulerFrequency.value && schedulerFrequency.value !== 'none'">
<div class="form-group <div class="form-group

View File

@@ -15,6 +15,7 @@ import notificationsList from './notifications.list';
import toggleNotification from './shared/toggle-notification.factory'; import toggleNotification from './shared/toggle-notification.factory';
import notificationsListInit from './shared/notification-list-init.factory'; import notificationsListInit from './shared/notification-list-init.factory';
import typeChange from './shared/type-change.service'; import typeChange from './shared/type-change.service';
import { N_ } from '../i18n';
export default export default
angular.module('notifications', [ angular.module('notifications', [
@@ -39,7 +40,7 @@ angular.module('notifications', [
url: '/notification_templates', url: '/notification_templates',
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'setup', parent: 'setup',
label: "NOTIFICATIONS" label: N_("NOTIFICATIONS")
}, },
lazyLoad: () => stateDefinitions.generateTree({ lazyLoad: () => stateDefinitions.generateTree({
parent: 'notifications', // top-most node in the generated tree parent: 'notifications', // top-most node in the generated tree
@@ -84,7 +85,7 @@ angular.module('notifications', [
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'setup', parent: 'setup',
name: 'NOTIFICATIONS' label: N_('NOTIFICATIONS')
} }
}) })
}); });

View File

@@ -7,10 +7,12 @@
export default ['$rootScope', '$scope', 'Wait', 'generateList', 'NotificationTemplatesList', export default ['$rootScope', '$scope', 'Wait', 'generateList', 'NotificationTemplatesList',
'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', 'GetChoices', 'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', 'GetChoices',
'Empty', 'Find', 'ngToast', '$compile', '$filter', 'Dataset', 'rbacUiControlService', 'Empty', 'Find', 'ngToast', '$compile', '$filter', 'Dataset', 'rbacUiControlService',
'i18n',
function( function(
$rootScope, $scope, Wait, GenerateList, NotificationTemplatesList, $rootScope, $scope, Wait, GenerateList, NotificationTemplatesList,
GetBasePath, Rest, ProcessErrors, Prompt, $state, GetChoices, GetBasePath, Rest, ProcessErrors, Prompt, $state, GetChoices,
Empty, Find, ngToast, $compile, $filter, Dataset, rbacUiControlService) { Empty, Find, ngToast, $compile, $filter, Dataset, rbacUiControlService,
i18n) {
var defaultUrl = GetBasePath('notification_templates'), var defaultUrl = GetBasePath('notification_templates'),
list = NotificationTemplatesList; list = NotificationTemplatesList;
@@ -68,8 +70,8 @@
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n"; html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n"; html += "<thead>\n";
html += "<tr>"; html += "<tr>";
html += "<th>Status</th>"; html += "<th>" + i18n._("Status") + "</th>";
html += "<th>Time</th>"; html += "<th>" + i18n._("Time") + "</th>";
html += "</tr>\n"; html += "</tr>\n";
html += "</thead>\n"; html += "</thead>\n";
html += "<tbody>\n"; html += "<tbody>\n";
@@ -83,7 +85,7 @@
html += "</tbody>\n"; html += "</tbody>\n";
html += "</table>\n"; html += "</table>\n";
} else { } else {
html = "<p>No recent notifications.</p>\n"; html = "<p>" + i18n._("No recent notifications.") + "</p>\n";
} }
notification_template.template_status_html = html; notification_template.template_status_html = html;
} }
@@ -110,7 +112,7 @@
}) })
.catch(function() { .catch(function() {
ngToast.danger({ ngToast.danger({
content: `<i class="fa fa-check-circle Toast-successIcon"></i> <b>${name}:</b> Notification Failed.`, content: `<i class="fa fa-check-circle Toast-successIcon"></i> <b>${name}:</b> ` + i18n._('Notification Failed.'),
}); });
}); });
@@ -179,12 +181,12 @@
}); });
}); });
}; };
var bodyHtml = '<div class="Prompt-bodyQuery">Are you sure you want to delete the notification template below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>'; var bodyHtml = '<div class="Prompt-bodyQuery">' + i18n._('Are you sure you want to delete the notification template below?') + '</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>';
Prompt({ Prompt({
hdr: 'Delete', hdr: i18n._('Delete'),
body: bodyHtml, body: bodyHtml,
action: action, action: action,
actionText: 'DELETE' actionText: i18n._('DELETE')
}); });
}; };
} }

View File

@@ -16,6 +16,9 @@ export default ['i18n', function(i18n) {
addTitle: i18n._('New Notification Template'), addTitle: i18n._('New Notification Template'),
editTitle: '{{ name }}', editTitle: '{{ name }}',
name: 'notification_template', name: 'notification_template',
// I18N for "CREATE NOTIFICATION_TEMPLATE"
// on /#/notification_templates/add
breadcrumbName: i18n._('NOTIFICATION TEMPLATE'),
stateTree: 'notifications', stateTree: 'notifications',
basePath: 'notification_templates', basePath: 'notification_templates',
showActions: true, showActions: true,
@@ -389,19 +392,19 @@ export default ['i18n', function(i18n) {
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
email_options: { email_options: {
label: 'Options', label: i18n._('Options'),
type: 'radio_group', type: 'radio_group',
subForm: 'typeSubForm', subForm: 'typeSubForm',
ngShow: "notification_type.value == 'email'", ngShow: "notification_type.value == 'email'",
ngChange: "emailOptionsChange()", ngChange: "emailOptionsChange()",
options: [{ options: [{
value: 'use_tls', value: 'use_tls',
label: 'Use TLS', label: i18n._('Use TLS'),
ngShow: "notification_type.value == 'email' ", ngShow: "notification_type.value == 'email' ",
labelClass: 'NotificationsForm-radioButtons' labelClass: 'NotificationsForm-radioButtons'
}, { }, {
value: 'use_ssl', value: 'use_ssl',
label: 'Use SSL', label: i18n._('Use SSL'),
ngShow: "notification_type.value == 'email'", ngShow: "notification_type.value == 'email'",
labelClass: 'NotificationsForm-radioButtons' labelClass: 'NotificationsForm-radioButtons'
}] }]

View File

@@ -23,7 +23,7 @@ export default ['i18n', function(i18n){
nosort: true, nosort: true,
icon: 'icon-job-{{ notification_template.status }}', icon: 'icon-job-{{ notification_template.status }}',
awPopOver: '{{ notification_template.template_status_html }}', awPopOver: '{{ notification_template.template_status_html }}',
dataTitle: "Recent Notifications", dataTitle: i18n._("Recent Notifications"),
dataPlacement: 'right', dataPlacement: 'right',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus' columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus'
}, },

View File

@@ -5,7 +5,7 @@
<div class="AddUsers-header"> <div class="AddUsers-header">
<div class="List-header"> <div class="List-header">
<div class="List-title"> <div class="List-title">
<div class="List-titleText ng-binding">{{ $parent.organization_name }}<div class="List-titleLockup"></div>Add {{ addType | capitalize}} <div class="List-titleText ng-binding">{{ $parent.organization_name }}<div class="List-titleLockup"></div><span translate>Add</span> {{ addType | capitalize}}
</div> </div>
</div> </div>
<div class="Form-exitHolder"> <div class="Form-exitHolder">

View File

@@ -10,6 +10,7 @@ import OrganizationsJobTemplates from './controllers/organizations-job-templates
import OrganizationsProjects from './controllers/organizations-projects.controller'; import OrganizationsProjects from './controllers/organizations-projects.controller';
import OrganizationsTeams from './controllers/organizations-teams.controller'; import OrganizationsTeams from './controllers/organizations-teams.controller';
import OrganizationsUsers from './controllers/organizations-users.controller'; import OrganizationsUsers from './controllers/organizations-users.controller';
import { N_ } from '../../i18n';
export default [{ export default [{
name: 'organizations.users', name: 'organizations.users',
@@ -55,7 +56,7 @@ export default [{
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: "organizations.edit", parent: "organizations.edit",
label: "USERS" label: N_("USERS")
}, },
data: { data: {
@@ -128,7 +129,7 @@ export default [{
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: "organizations.edit", parent: "organizations.edit",
label: "TEAMS" label: N_("TEAMS")
}, },
resolve: { resolve: {
features: ['FeaturesService', function(FeaturesService) { features: ['FeaturesService', function(FeaturesService) {
@@ -174,7 +175,7 @@ export default [{
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: "organizations.edit", parent: "organizations.edit",
label: "INVENTORIES" label: N_("INVENTORIES")
}, },
resolve: { resolve: {
features: ['FeaturesService', function(FeaturesService) { features: ['FeaturesService', function(FeaturesService) {
@@ -225,7 +226,7 @@ export default [{
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: "organizations.edit", parent: "organizations.edit",
label: "PROJECTS" label: N_("PROJECTS")
}, },
resolve: { resolve: {
features: ['FeaturesService', function(FeaturesService) { features: ['FeaturesService', function(FeaturesService) {
@@ -283,7 +284,7 @@ export default [{
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: "organizations.edit", parent: "organizations.edit",
label: "JOB TEMPLATES" label: N_("JOB TEMPLATES")
}, },
resolve: { resolve: {
features: ['FeaturesService', function(FeaturesService) { features: ['FeaturesService', function(FeaturesService) {
@@ -359,7 +360,7 @@ export default [{
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: "organizations.edit", parent: "organizations.edit",
label: "ADMINS" label: N_("ADMINS")
}, },
resolve: { resolve: {
features: ['FeaturesService', function(FeaturesService) { features: ['FeaturesService', function(FeaturesService) {

View File

@@ -7,10 +7,10 @@
export default ['$stateParams', '$scope', '$rootScope', '$location', export default ['$stateParams', '$scope', '$rootScope', '$location',
'$log', '$compile', 'Rest', 'OrganizationList', 'Alert', 'Prompt', 'ClearScope', '$log', '$compile', 'Rest', 'OrganizationList', 'Alert', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'rbacUiControlService', '$filter', 'Dataset', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'rbacUiControlService', '$filter', 'Dataset', 'i18n',
function($stateParams, $scope, $rootScope, $location, function($stateParams, $scope, $rootScope, $location,
$log, $compile, Rest, OrganizationList, Alert, Prompt, ClearScope, $log, $compile, Rest, OrganizationList, Alert, Prompt, ClearScope,
ProcessErrors, GetBasePath, Wait, $state, rbacUiControlService, $filter, Dataset) { ProcessErrors, GetBasePath, Wait, $state, rbacUiControlService, $filter, Dataset, i18n) {
ClearScope(); ClearScope();
@@ -162,10 +162,10 @@ export default ['$stateParams', '$scope', '$rootScope', '$location',
}; };
Prompt({ Prompt({
hdr: 'Delete', hdr: i18n._('Delete'),
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the organization below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>', body: '<div class="Prompt-bodyQuery">' + i18n._('Are you sure you want to delete the organization below?') + '</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
action: action, action: action,
actionText: 'DELETE' actionText: i18n._('DELETE')
}); });
}; };
} }

View File

@@ -10,6 +10,7 @@ import OrganizationsAdd from './add/organizations-add.controller';
import OrganizationsEdit from './edit/organizations-edit.controller'; import OrganizationsEdit from './edit/organizations-edit.controller';
import organizationsLinkout from './linkout/main'; import organizationsLinkout from './linkout/main';
import OrganizationsLinkoutStates from './linkout/organizations-linkout.route'; import OrganizationsLinkoutStates from './linkout/organizations-linkout.route';
import { N_ } from '../i18n';
export default export default
@@ -48,7 +49,7 @@ angular.module('Organizations', [
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'setup', parent: 'setup',
label: 'ORGANIZATIONS' label: N_('ORGANIZATIONS')
}, },
// concat manually-defined state definitions with generated defintions // concat manually-defined state definitions with generated defintions
}).then((generated) => { }).then((generated) => {

View File

@@ -1,12 +1,12 @@
<div id="logviewer-modal-dialog" style="display: none;"> <div id="logviewer-modal-dialog" style="display: none;">
<ul id="logview-tabs" class="nav nav-tabs"> <ul id="logview-tabs" class="nav nav-tabs">
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'logview-tabs')">Status</a></li> <li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'logview-tabs')" translate>Status</a></li>
<li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'logview-tabs')">Standard Out</a></li> <li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'logview-tabs')" translate>Standard Out</a></li>
<li><a href="#traceback" id="traceback-link" data-toggle="tab" ng-click="toggleTab($event, 'traceback-link', 'logview-tabs')">Traceback</a></li> <li><a href="#traceback" id="traceback-link" data-toggle="tab" ng-click="toggleTab($event, 'traceback-link', 'logview-tabs')" translate>Traceback</a></li>
<li><a href="#options" id="options-link" data-toggle="tab" ng-click="toggleTab($event, 'options-link', 'logview-tabs')">Options</a></li> <li><a href="#options" id="options-link" data-toggle="tab" ng-click="toggleTab($event, 'options-link', 'logview-tabs')" translate>Options</a></li>
<li><a href="#variables" id="variables-link" data-toggle="tab" ng-click="toggleTab($event, 'variable-link', 'logview-tabs')">Extra Variables</a></li> <li><a href="#variables" id="variables-link" data-toggle="tab" ng-click="toggleTab($event, 'variable-link', 'logview-tabs')" translate>Extra Variables</a></li>
<li><a href="#source-variables" id="source-variables-link" data-toggle="tab" ng-click="toggleTab($event, 'source-variable-link', 'logview-tabs')">Source Vars</a></li> <li><a href="#source-variables" id="source-variables-link" data-toggle="tab" ng-click="toggleTab($event, 'source-variable-link', 'logview-tabs')" translate>Source Vars</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="status"> <div class="tab-pane active" id="status">

View File

@@ -1,6 +1,7 @@
import { templateUrl } from '../shared/template-url/template-url.factory'; import { templateUrl } from '../shared/template-url/template-url.factory';
import { PortalModeJobTemplatesController } from './portal-mode-job-templates.controller'; import { PortalModeJobTemplatesController } from './portal-mode-job-templates.controller';
import { PortalModeJobsController } from './portal-mode-jobs.controller'; import { PortalModeJobsController } from './portal-mode-jobs.controller';
import { N_ } from '../i18n';
// Using multiple named views requires a parent layout // Using multiple named views requires a parent layout
// https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views // https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
@@ -8,7 +9,7 @@ export default {
name: 'portalMode', name: 'portalMode',
url: '/portal?{group_search:queryset}{host_search:queryset}', url: '/portal?{group_search:queryset}{host_search:queryset}',
ncyBreadcrumb: { ncyBreadcrumb: {
label: 'MY VIEW' label: N_('MY VIEW')
}, },
params: { params: {
job_search: { job_search: {

View File

@@ -9,6 +9,7 @@ import addController from './schedulerAdd.controller';
import editController from './schedulerEdit.controller'; import editController from './schedulerEdit.controller';
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import schedulerDatePicker from './schedulerDatePicker.directive'; import schedulerDatePicker from './schedulerDatePicker.directive';
import { N_ } from '../i18n';
export default export default
angular.module('scheduler', []) angular.module('scheduler', [])
@@ -32,7 +33,7 @@ export default
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'templates.editJobTemplate({job_template_id: parentObject.id})', parent: 'templates.editJobTemplate({job_template_id: parentObject.id})',
label: 'SCHEDULES' label: N_('SCHEDULES')
}, },
resolve: { resolve: {
Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath', Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath',
@@ -63,7 +64,7 @@ export default
'@': { '@': {
templateProvider: function(SchedulesList, generateList, ParentObject){ templateProvider: function(SchedulesList, generateList, ParentObject){
// include name of parent resource in listTitle // include name of parent resource in listTitle
SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Schedules`; SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
let html = generateList.build({ let html = generateList.build({
list: SchedulesList, list: SchedulesList,
mode: 'edit' mode: 'edit'
@@ -86,7 +87,7 @@ export default
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'jobTemplateSchedules', parent: 'jobTemplateSchedules',
label: 'CREATE SCHEDULE' label: N_('CREATE SCHEDULE')
} }
}); });
$stateExtender.addState({ $stateExtender.addState({
@@ -118,7 +119,7 @@ export default
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'templates.editWorkflowJobTemplate({workflow_job_template_id: parentObject.id})', parent: 'templates.editWorkflowJobTemplate({workflow_job_template_id: parentObject.id})',
label: 'SCHEDULES' label: N_('SCHEDULES')
}, },
resolve: { resolve: {
Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath', Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath',
@@ -149,7 +150,7 @@ export default
'@': { '@': {
templateProvider: function(SchedulesList, generateList, ParentObject){ templateProvider: function(SchedulesList, generateList, ParentObject){
// include name of parent resource in listTitle // include name of parent resource in listTitle
SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Schedules`; SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
let html = generateList.build({ let html = generateList.build({
list: SchedulesList, list: SchedulesList,
mode: 'edit' mode: 'edit'
@@ -172,7 +173,7 @@ export default
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'workflowJobTemplateSchedules', parent: 'workflowJobTemplateSchedules',
label: 'CREATE SCHEDULE' label: N_('CREATE SCHEDULE')
} }
}); });
$stateExtender.addState({ $stateExtender.addState({
@@ -201,7 +202,7 @@ export default
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'projects.edit({project_id: parentObject.id})', parent: 'projects.edit({project_id: parentObject.id})',
label: 'SCHEDULES' label: N_('SCHEDULES')
}, },
resolve: { resolve: {
Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath', Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath',
@@ -232,7 +233,7 @@ export default
'@': { '@': {
templateProvider: function(SchedulesList, generateList, ParentObject){ templateProvider: function(SchedulesList, generateList, ParentObject){
// include name of parent resource in listTitle // include name of parent resource in listTitle
SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Schedules`; SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
let html = generateList.build({ let html = generateList.build({
list: SchedulesList, list: SchedulesList,
mode: 'edit' mode: 'edit'
@@ -249,7 +250,7 @@ export default
name: 'projectSchedules.add', name: 'projectSchedules.add',
route: '/add', route: '/add',
ncyBreadcrumb: { ncyBreadcrumb: {
label: 'CREATE SCHEDULE' label: N_('CREATE SCHEDULE')
}, },
views: { views: {
'form': { 'form': {
@@ -289,7 +290,7 @@ export default
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
parent: 'jobs', parent: 'jobs',
label: 'SCHEDULED' label: N_('SCHEDULED')
}, },
resolve: { resolve: {
SchedulesList: ['ScheduledJobsList', function(list){ SchedulesList: ['ScheduledJobsList', function(list){

View File

@@ -4,6 +4,9 @@ import icon from '../shared/icon/main';
export default export default
angular.module('setupMenu', angular.module('setupMenu',
[ icon.name]) [ icon.name])
.run(['$stateExtender', function($stateExtender) { .run(['$stateExtender', 'I18NInit',
function($stateExtender, I18NInit) {
I18NInit();
$stateExtender.addState(route); $stateExtender.addState(route);
}]); }]);

View File

@@ -50,8 +50,8 @@
</p> </p>
</a> </a>
<a ui-sref="configuration" class="SetupItem" ng-if="user_is_superuser || user_is_system_auditor"> <a ui-sref="configuration" class="SetupItem" ng-if="user_is_superuser || user_is_system_auditor">
<h4 class="SetupItem-title">Configure Tower</h4> <h4 class="SetupItem-title" translate>Configure Tower</h4>
<p class="SetupItem-description"> <p class="SetupItem-description" translate>
Edit Tower's configuration. Edit Tower's configuration.
</p> </p>
</a> </a>

View File

@@ -1,10 +1,11 @@
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
export default { export default {
name: 'setup', name: 'setup',
route: '/setup', route: '/setup',
ncyBreadcrumb: { ncyBreadcrumb: {
label: "SETTINGS" label: N_("SETTINGS")
}, },
templateUrl: templateUrl('setup-menu/setup-menu'), templateUrl: templateUrl('setup-menu/setup-menu'),
controller: function(orgAdmin, $scope){ controller: function(orgAdmin, $scope){

View File

@@ -678,7 +678,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if(field.reset && !field.disabled) { if(field.reset && !field.disabled) {
var resetValue = "'" + field.reset+ "'"; var resetValue = "'" + field.reset+ "'";
html+= `<a class="Form-resetValue" ng-click="resetValue(${resetValue})">Reset</a>`; html+= `<a class="Form-resetValue" ng-click="resetValue(${resetValue})">` + i18n._("Reset") + `</a>`;
} }
html += "\n\t</label>\n"; html += "\n\t</label>\n";
@@ -1870,9 +1870,9 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
<div <div
class="row" class="row"
ng-show="${itm}.length === 0 && !(searchTags | isEmpty)"> ng-show="${itm}.length === 0 && !(searchTags | isEmpty)">
<div class="col-lg-12 List-searchNoResults"> <div class="col-lg-12 List-searchNoResults">`;
No records matched your search. html += i18n._('No records matched your search.');
</div> html += `</div>
</div> </div>
`; `;

View File

@@ -30,15 +30,15 @@
</a> </a>
</li> </li>
</ul> </ul>
<span class="Paginate-pager--pageof">Page <span class="Paginate-pager--pageof" translate>Page
<span id="current-page">{{current()}}</span> of <span id="current-page">{{current()}}</span> of
<span id="total-pages">{{last()}}</span> <span id="total-pages">{{last()}}</span>
</span> </span>
</div> </div>
<div class="Paginate-total page-label" ng-hide="dataCount === 0"> <div class="Paginate-total page-label" ng-hide="dataCount === 0">
<span>ITEMS&nbsp; <span translate>ITEMS&nbsp;
<span>{{dataRange}}</span> <span>{{dataRange}}</span>
<span>of {{dataCount()}}</span> <span translate>of {{dataCount()}}</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', 'QuerySet', 'SmartSearchService', export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', 'QuerySet', 'SmartSearchService', 'i18n',
function($stateParams, $scope, $state, QuerySet, GetBasePath, qs, SmartSearchService) { function($stateParams, $scope, $state, QuerySet, GetBasePath, qs, SmartSearchService, i18n) {
let path, relations, let path, relations,
defaults, defaults,
@@ -35,6 +35,7 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', '
$scope.options = data.options.data; $scope.options = data.options.data;
$scope.$emit(`${$scope.list.iterator}_options`, data.options); $scope.$emit(`${$scope.list.iterator}_options`, data.options);
}); });
$scope.searchPlaceholder = $scope.disableSearch ? i18n._('Cannot search running job') : i18n._('Search');
function compareParams(a, b) { function compareParams(a, b) {
for (let key in a) { for (let key in a) {
@@ -73,6 +74,15 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', '
}); });
$scope.$on('$destroy', stateChangeSuccessListener); $scope.$on('$destroy', stateChangeSuccessListener);
$scope.$watch('disableSearch', function(disableSearch){
if(disableSearch) {
$scope.searchPlaceholder = i18n._('Cannot search running job');
}
else {
$scope.searchPlaceholder = i18n._('Search');
}
});
} }
// Removes state definition defaults and pagination terms // Removes state definition defaults and pagination terms

View File

@@ -4,14 +4,14 @@
<div class="SmartSearch-searchTermContainer"> <div class="SmartSearch-searchTermContainer">
<!-- string search input --> <!-- string search input -->
<form name="smartSearch" class="SmartSearch-form" aw-enter-key="add(searchTerm)" novalidate> <form name="smartSearch" class="SmartSearch-form" aw-enter-key="add(searchTerm)" novalidate>
<input class="SmartSearch-input" ng-model="searchTerm" placeholder="{{disableSearch ? 'Cannot search running job' : 'Search'}}" <input class="SmartSearch-input" ng-model="searchTerm" placeholder="{{searchPlaceholder}}"
ng-disabled="disableSearch"> ng-disabled="disableSearch">
</form> </form>
<div type="submit" class="SmartSearch-searchButton" ng-disabled="!searchTerm" ng-click="add(searchTerm)"> <div type="submit" class="SmartSearch-searchButton" ng-disabled="!searchTerm" ng-click="add(searchTerm)">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</div> </div>
</div> </div>
<div class="SmartSearch-keyToggle btn" ng-class="{'is-active': showKeyPane}" ng-click="toggleKeyPane()"> <div class="SmartSearch-keyToggle btn" ng-class="{'is-active': showKeyPane}" ng-click="toggleKeyPane()" translate>
Key Key
</div> </div>
</div> </div>
@@ -28,7 +28,7 @@
<span class="SmartSearch-name">{{tag}}</span> <span class="SmartSearch-name">{{tag}}</span>
</div> </div>
</div> </div>
<a href class="SmartSearch-clearAll" ng-click="clearAll()" ng-show="!(searchTags | isEmpty)">CLEAR ALL</a> <a href class="SmartSearch-clearAll" ng-click="clearAll()" ng-show="!(searchTags | isEmpty)" translate>CLEAR ALL</a>
</div> </div>
</div> </div>
</div> </div>
@@ -37,7 +37,7 @@
<div class="SmartSearch-keyRow"> <div class="SmartSearch-keyRow">
<div class="SmartSearch-examples"> <div class="SmartSearch-examples">
<div class="SmartSearch-examples--title"> <div class="SmartSearch-examples--title">
<b>EXAMPLES:</b> <b translate>EXAMPLES:</b>
</div> </div>
<div class="SmartSearch-examples--search">name:foo</div> <div class="SmartSearch-examples--search">name:foo</div>
<div class="SmartSearch-examples--search">organization.name:Default</div> <div class="SmartSearch-examples--search">organization.name:Default</div>
@@ -45,13 +45,13 @@
</div> </div>
</div> </div>
<div class="SmartSearch-keyRow"> <div class="SmartSearch-keyRow">
<b>FIELDS:</b> <span ng-repeat="(key,value) in model.base">{{ key }}<span ng-if="!$last">, </span></span> <b translate>FIELDS:</b> <span ng-repeat="(key,value) in model.base">{{ key }}<span ng-if="!$last">, </span></span>
</div> </div>
<div class="SmartSearch-keyRow"> <div class="SmartSearch-keyRow">
<b>RELATED FIELDS:</b> <span ng-repeat="relation in model.related">{{ relation }}<span ng-if="!$last">, </span></span> <b translate>RELATED FIELDS:</b> <span ng-repeat="relation in model.related">{{ relation }}<span ng-if="!$last">, </span></span>
</div> </div>
<div class="SmartSearch-keyRow"> <div class="SmartSearch-keyRow">
<b>ADDITIONAL INFORMATION:</b> <span>For additional information on advanced search search syntax please see the Ansible Tower documentation.</span> <b translate>ADDITIONAL INFORMATION:</b> <span translate>For additional information on advanced search search syntax please see the Ansible Tower documentation.</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -9,7 +9,7 @@
* generateLookupNodes - Attaches to a form node. Builds an abstract '*.lookup' node with field-specific 'lookup.*' children e.g. {name: 'projects.add.lookup.organizations', ...} * generateLookupNodes - Attaches to a form node. Builds an abstract '*.lookup' node with field-specific 'lookup.*' children e.g. {name: 'projects.add.lookup.organizations', ...}
*/ */
export default ['$injector', '$stateExtender', '$log', function($injector, $stateExtender, $log) { export default ['$injector', '$stateExtender', '$log', 'i18n', function($injector, $stateExtender, $log, i18n) {
return { return {
/** /**
* @ngdoc method * @ngdoc method
@@ -150,7 +150,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
url: url, url: url,
ncyBreadcrumb: { ncyBreadcrumb: {
[params.parent ? 'parent' : null]: `${params.parent}`, [params.parent ? 'parent' : null]: `${params.parent}`,
label: `CREATE ${form.breadcrumbName || form.name}` label: i18n.sprintf(i18n._("CREATE %s"), i18n._(`${form.breadcrumbName || form.name}`))
}, },
views: { views: {
'form': { 'form': {
@@ -274,7 +274,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
}, },
views: { views: {
[`modal@${formStateDefinition.name}`]: { [`modal@${formStateDefinition.name}`]: {
template: `<add-rbac-user-team resolve="$resolve" title="Add Permissions"></add-rbac-user-team>` template: `<add-rbac-user-team resolve="$resolve" title="` + i18n._('Add Permissions') + `"></add-rbac-user-team>`
} }
}, },
resolve: { resolve: {
@@ -339,7 +339,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
}, },
views: { views: {
[`modal@${formStateDefinition.name}`]: { [`modal@${formStateDefinition.name}`]: {
template: `<add-rbac-resource users-dataset="$resolve.usersDataset" teams-dataset="$resolve.teamsDataset" selected="allSelected" resource-data="$resolve.resourceData" title="Add Users / Teams"></add-rbac-resource>` template: `<add-rbac-resource users-dataset="$resolve.usersDataset" teams-dataset="$resolve.teamsDataset" selected="allSelected" resource-data="$resolve.resourceData" title="` + i18n._('Add Users') + ' / ' + i18n._('Teams') + `"></add-rbac-resource>`
} }
}, },
resolve: { resolve: {
@@ -492,7 +492,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
// } // }
return state; return state;
} }
function buildRbacUserDirective() { function buildRbacUserDirective() {
let states = []; let states = [];
@@ -508,7 +508,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
}, },
views: { views: {
[`modal@${formStateDefinition.name}`]: { [`modal@${formStateDefinition.name}`]: {
template: `<add-rbac-resource users-dataset="$resolve.usersDataset" selected="allSelected" resource-data="$resolve.resourceData" without-team-permissions="true" title="Add Users"></add-rbac-resource>` template: `<add-rbac-resource users-dataset="$resolve.usersDataset" selected="allSelected" resource-data="$resolve.resourceData" without-team-permissions="true" title="` + i18n._('Add Users') + `"></add-rbac-resource>`
} }
}, },
resolve: { resolve: {

View File

@@ -26,6 +26,10 @@
padding: 0px; padding: 0px;
} }
.SmartStatus--failed:before {
content: "\f06a";
}
.SmartStatus--running{ .SmartStatus--running{
color: @default-icon; color: @default-icon;
margin-top: 10px; margin-top: 10px;

View File

@@ -4,7 +4,7 @@
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen"> <div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
<div class="Panel"> <div class="Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText"> <div class="StandardOut-panelHeaderText" translate>
RESULTS RESULTS
</div> </div>
<div class="StandardOut-actions"> <div class="StandardOut-actions">
@@ -16,12 +16,12 @@
<div class="StandardOut-details"> <div class="StandardOut-details">
<div class="StandardOut-detailsRow" ng-show="job.module_name"> <div class="StandardOut-detailsRow" ng-show="job.module_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Name</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Name</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.module_name }}</div> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.module_name }}</div>
</div> </div>
<div class="StandardOut-detailsRow"> <div class="StandardOut-detailsRow">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STATUS</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STATUS</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<i class="fa icon-job-{{ job.status }}"></i> <i class="fa icon-job-{{ job.status }}"></i>
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span> <span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
@@ -29,54 +29,54 @@
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.started"> <div class="StandardOut-detailsRow" ng-show="job.started">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STARTED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STARTED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.started | longDate }} {{ job.started | longDate }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.finished"> <div class="StandardOut-detailsRow" ng-show="job.finished">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">FINISHED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>FINISHED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.finished | longDate }} {{ job.finished | longDate }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.finished"> <div class="StandardOut-detailsRow" ng-show="job.finished">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">ELAPSED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>ELAPSED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.elapsed }} seconds {{ job.elapsed }} seconds
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.module_args"> <div class="StandardOut-detailsRow" ng-show="job.module_args">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Module Args</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Module Args</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.module_args }}</div> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.module_args }}</div>
</div> </div>
<div class="StandardOut-detailsRow"> <div class="StandardOut-detailsRow">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Inventory</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Inventory</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a href="{{ inventory_url }}" <a href="{{ inventory_url }}"
aw-tool-tip="The inventory this command ran on." aw-tool-tip="{{'The inventory this command ran on.'|translate}}"
data-placement="top">{{ inventory_name }}</a> data-placement="top">{{ inventory_name }}</a>
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="credential_name"> <div class="StandardOut-detailsRow" ng-show="credential_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Credential</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Credential</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a href="{{ credential_url }}" <a href="{{ credential_url }}"
aw-tool-tip="The credential used to run this command." aw-tool-tip="{{'The credential used to run this command.'|translate}}"
data-placement="top">{{ credential_name }}</a> data-placement="top">{{ credential_name }}</a>
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="created_by"> <div class="StandardOut-detailsRow" ng-show="created_by">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Launched By</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Launched By</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a href="/#/users/{{ created_by.id }}" <a href="/#/users/{{ created_by.id }}"
aw-tool-tip="The user who ran this command." aw-tool-tip="{{'The user who ran this command.'|translate}}"
data-placement="top">{{ created_by.username }}</a> data-placement="top">{{ created_by.username }}</a>
</div> </div>
</div> </div>
@@ -84,19 +84,19 @@
<!-- since zero is a falsy value, you need ng-show such that <!-- since zero is a falsy value, you need ng-show such that
the number is >= 0 --> the number is >= 0 -->
<div class="StandardOut-detailsRow" ng-show="forks >= 0"> <div class="StandardOut-detailsRow" ng-show="forks >= 0">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Forks</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Forks</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ forks }}</div> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ forks }}</div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="limit"> <div class="StandardOut-detailsRow" ng-show="limit">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Limit</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Limit</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ limit }}</div> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ limit }}</div>
</div> </div>
<!-- since zero is a falsy value, you need ng-show such that <!-- since zero is a falsy value, you need ng-show such that
the number is >= 0 --> the number is >= 0 -->
<div class="StandardOut-detailsRow" ng-show="verbosity >= 0"> <div class="StandardOut-detailsRow" ng-show="verbosity >= 0">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">Verbosity</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Verbosity</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ verbosity }}</div> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ verbosity }}</div>
</div> </div>
</div> </div>
@@ -105,13 +105,13 @@
<div class="StandardOut-rightPanel"> <div class="StandardOut-rightPanel">
<div class="Panel"> <div class="Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div> <div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions"> <div class="StandardOut-panelHeaderActions">
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()"> <button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i> <i class="fa fa-arrows-alt"></i>
</button> </button>
<a href="/api/v1/ad_hoc_commands/{{ job.id }}/stdout?format=txt_download"> <a href="/api/v1/ad_hoc_commands/{{ job.id }}/stdout?format=txt_download">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top"> <button class="StandardOut-actionButton" aw-tool-tip="{{'Download Output'|translate}}" data-placement="top">
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
</button> </button>
</a> </a>

View File

@@ -4,7 +4,7 @@
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen"> <div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
<div class="Panel"> <div class="Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText"> <div class="StandardOut-panelHeaderText" translate>
RESULTS RESULTS
</div> </div>
<div class="StandardOut-actions"> <div class="StandardOut-actions">
@@ -16,13 +16,13 @@
<div class="StandardOut-details"> <div class="StandardOut-details">
<div class="StandardOut-detailsRow" ng-show="inventory_source_name"> <div class="StandardOut-detailsRow" ng-show="inventory_source_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">NAME</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>NAME</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a href="{{inv_manage_group_link}}"> <a href="{{inv_manage_group_link}}">
{{ inventory_source_name }} {{ inventory_source_name }}
</a> </a>
<a href="{{ workflow_result_link }}" <a href="{{ workflow_result_link }}"
aw-tool-tip="View workflow results" aw-tool-tip="{{'View workflow results'|translate}}"
data-placement="top" data-placement="top"
data-original-title="" title=""> data-original-title="" title="">
<i class="WorkflowBadge" <i class="WorkflowBadge"
@@ -34,7 +34,7 @@
</div> </div>
<div class="StandardOut-detailsRow"> <div class="StandardOut-detailsRow">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STATUS</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STATUS</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<i class="fa icon-job-{{ job.status }}"></i> <i class="fa icon-job-{{ job.status }}"></i>
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span> <span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
@@ -42,42 +42,42 @@
</div> </div>
<div class="StandardOut-detailsRow" ng-show="{{job.license_error !== null}}"> <div class="StandardOut-detailsRow" ng-show="{{job.license_error !== null}}">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">LICENSE ERROR</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>LICENSE ERROR</div>
<div class="StandardOut-detailsContent StandardOut--capitalize"> <div class="StandardOut-detailsContent StandardOut--capitalize">
{{ job.license_error }} {{ job.license_error }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.started"> <div class="StandardOut-detailsRow" ng-show="job.started">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STARTED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STARTED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.started | longDate }} {{ job.started | longDate }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.finished"> <div class="StandardOut-detailsRow" ng-show="job.finished">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">FINISHED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>FINISHED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.finished | longDate }} {{ job.finished | longDate }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.finished"> <div class="StandardOut-detailsRow" ng-show="job.finished">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">ELAPSED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>ELAPSED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.elapsed }} seconds {{ job.elapsed }} seconds
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.launch_type"> <div class="StandardOut-detailsRow" ng-show="job.launch_type">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">LAUNCH TYPE</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>LAUNCH TYPE</div>
<div class="StandardOut-detailsContent StandardOut--capitalize"> <div class="StandardOut-detailsContent StandardOut--capitalize">
{{ job.launch_type }} {{ job.launch_type }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="credential_name"> <div class="StandardOut-detailsRow" ng-show="credential_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">CREDENTIAL</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>CREDENTIAL</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a ui-sref="credentials.edit({credential_id: credential})"> <a ui-sref="credentials.edit({credential_id: credential})">
{{ credential_name }} {{ credential_name }}
@@ -86,7 +86,7 @@
</div> </div>
<div class="StandardOut-detailsRow" ng-show="inventory_source_name"> <div class="StandardOut-detailsRow" ng-show="inventory_source_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">GROUP</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>GROUP</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a href="{{inv_manage_group_link}}"> <a href="{{inv_manage_group_link}}">
{{ inventory_source_name }} {{ inventory_source_name }}
@@ -95,28 +95,28 @@
</div> </div>
<div class="StandardOut-detailsRow" ng-show="source"> <div class="StandardOut-detailsRow" ng-show="source">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">SOURCE</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>SOURCE</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ source }} {{ source }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="source_regions"> <div class="StandardOut-detailsRow" ng-show="source_regions">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">REGIONS</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>REGIONS</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ source_regions }} {{ source_regions }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="{{ job.overwrite !== null }}"> <div class="StandardOut-detailsRow" ng-show="{{ job.overwrite !== null }}">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">OVERWRITE</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>OVERWRITE</div>
<div class="StandardOut-detailsContent StandardOut--capitalize"> <div class="StandardOut-detailsContent StandardOut--capitalize">
{{ job.overwrite }} {{ job.overwrite }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="{{ job.overwrite_vars !== null }}"> <div class="StandardOut-detailsRow" ng-show="{{ job.overwrite_vars !== null }}">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">OVERWRITE VARS</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>OVERWRITE VARS</div>
<div class="StandardOut-detailsContent StandardOut--capitalize"> <div class="StandardOut-detailsContent StandardOut--capitalize">
{{ job.overwrite_vars }} {{ job.overwrite_vars }}
</div> </div>
@@ -128,13 +128,13 @@
<div class="StandardOut-rightPanel"> <div class="StandardOut-rightPanel">
<div class="Panel"> <div class="Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div> <div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions"> <div class="StandardOut-panelHeaderActions">
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()"> <button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i> <i class="fa fa-arrows-alt"></i>
</button> </button>
<a href="/api/v1/inventory_updates/{{ job.id }}/stdout?format=txt_download"> <a href="/api/v1/inventory_updates/{{ job.id }}/stdout?format=txt_download">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top"> <button class="StandardOut-actionButton" aw-tool-tip="{{'Download Output'|translate}}" data-placement="top">
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
</button> </button>
</a> </a>

View File

@@ -4,25 +4,25 @@
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen"> <div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
<div class="Panel"> <div class="Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText"> <div class="StandardOut-panelHeaderText" translate>
RESULTS RESULTS
</div> </div>
<div class="StandardOut-actions"> <div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button> <button id="relaunch-job-button" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="{{'Relaunch using the same parameters'|translate}}" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status == 'running' || job.status=='pending' " aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button> <button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status == 'running' || job.status=='pending' " aw-tool-tip="{{'Cancel'|translate}}" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status == 'running' || job.status == 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button> <button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status == 'running' || job.status == 'pending' " aw-tool-tip="{{'Delete'|translate}}" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div> </div>
</div> </div>
<div class="StandardOut-details"> <div class="StandardOut-details">
<div class="StandardOut-detailsRow" ng-show="project_name"> <div class="StandardOut-detailsRow" ng-show="project_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">NAME</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>NAME</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a ui-sref="projects.edit({id: job.project})"> <a ui-sref="projects.edit({id: job.project})">
{{ project_name }} {{ project_name }}
</a> </a>
<a href="{{ workflow_result_link }}" <a href="{{ workflow_result_link }}"
aw-tool-tip="View workflow results" aw-tool-tip="{{'View workflow results'|translate}}"
data-placement="top" data-placement="top"
data-original-title="" title=""> data-original-title="" title="">
<i class="WorkflowBadge" <i class="WorkflowBadge"
@@ -34,7 +34,7 @@
</div> </div>
<div class="StandardOut-detailsRow"> <div class="StandardOut-detailsRow">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STATUS</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STATUS</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<i class="fa icon-job-{{ job.status }}"></i> <i class="fa icon-job-{{ job.status }}"></i>
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span> <span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
@@ -42,35 +42,35 @@
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.started"> <div class="StandardOut-detailsRow" ng-show="job.started">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STARTED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STARTED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.started | longDate }} {{ job.started | longDate }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.finished"> <div class="StandardOut-detailsRow" ng-show="job.finished">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">FINISHED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>FINISHED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.finished | longDate }} {{ job.finished | longDate }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.finished"> <div class="StandardOut-detailsRow" ng-show="job.finished">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">ELAPSED</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>ELAPSED</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
{{ job.elapsed }} seconds {{ job.elapsed }} seconds
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="job.launch_type"> <div class="StandardOut-detailsRow" ng-show="job.launch_type">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">LAUNCH TYPE</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>LAUNCH TYPE</div>
<div class="StandardOut-detailsContent StandardOut--capitalize"> <div class="StandardOut-detailsContent StandardOut--capitalize">
{{ job.launch_type }} {{ job.launch_type }}
</div> </div>
</div> </div>
<div class="StandardOut-detailsRow" ng-show="project_name"> <div class="StandardOut-detailsRow" ng-show="project_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">PROJECT</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>PROJECT</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a ui-sref="projects.edit({id: job.project})"> <a ui-sref="projects.edit({id: job.project})">
{{ project_name }} {{ project_name }}
@@ -79,7 +79,7 @@
</div> </div>
<div class="StandardOut-detailsRow" ng-show="credential_name"> <div class="StandardOut-detailsRow" ng-show="credential_name">
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">CREDENTIAL</div> <div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>CREDENTIAL</div>
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8"> <div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
<a ui-sref="credentials.edit({credential_id: credential})"> <a ui-sref="credentials.edit({credential_id: credential})">
{{ credential_name }} {{ credential_name }}
@@ -93,13 +93,13 @@
<div class="StandardOut-rightPanel"> <div class="StandardOut-rightPanel">
<div class="Panel"> <div class="Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div> <div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions"> <div class="StandardOut-panelHeaderActions">
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()"> <button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i> <i class="fa fa-arrows-alt"></i>
</button> </button>
<a href="/api/v1/project_updates/{{ job.id }}/stdout?format=txt_download"> <a href="/api/v1/project_updates/{{ job.id }}/stdout?format=txt_download">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top"> <button class="StandardOut-actionButton" aw-tool-tip="{{'Download Output'|translate}}" data-placement="top">
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
</button> </button>
</a> </a>

View File

@@ -5,6 +5,7 @@
*************************************************/ *************************************************/
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
export default { export default {
name: 'systemTracking', name: 'systemTracking',
@@ -14,7 +15,7 @@ export default {
params: {hosts: null, inventory: null}, params: {hosts: null, inventory: null},
reloadOnSearch: false, reloadOnSearch: false,
ncyBreadcrumb: { ncyBreadcrumb: {
label: "SYSTEM TRACKING" label: N_("SYSTEM TRACKING")
}, },
resolve: { resolve: {
moduleOptions: moduleOptions:

View File

@@ -9,13 +9,13 @@
'$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', '$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait',
'Empty', 'ToJSON', 'CallbackHelpInit', 'Prompt', 'GetChoices', '$state', 'Empty', 'ToJSON', 'CallbackHelpInit', 'Prompt', 'GetChoices', '$state',
'CreateSelect2', '$q', 'CreateSelect2', '$q', 'i18n',
function( function(
$filter, $scope, $rootScope, $compile, $filter, $scope, $rootScope, $compile,
$location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
ProcessErrors, ClearScope, GetBasePath, md5Setup, ParseTypeChange, Wait, ProcessErrors, ClearScope, GetBasePath, md5Setup, ParseTypeChange, Wait,
Empty, ToJSON, CallbackHelpInit, Prompt, GetChoices, Empty, ToJSON, CallbackHelpInit, Prompt, GetChoices,
$state, CreateSelect2, $q $state, CreateSelect2, $q, i18n
) { ) {
Rest.setUrl(GetBasePath('job_templates')); Rest.setUrl(GetBasePath('job_templates'));
@@ -23,7 +23,7 @@
.success(function(data) { .success(function(data) {
if (!data.actions.POST) { if (!data.actions.POST) {
$state.go("^"); $state.go("^");
Alert('Permission Error', 'You do not have permission to add a job template.', 'alert-info'); Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a job template.'), 'alert-info');
} }
}); });

View File

@@ -77,7 +77,7 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest',
// attempts to transition the state and they were squashing each other. // attempts to transition the state and they were squashing each other.
let path = GetBasePath(list.basePath) || GetBasePath(list.name); let path = GetBasePath(list.basePath) || GetBasePath(list.name);
qs.search(path, $stateParams[`${list.iterator}_search`]) qs.search(path, $state.params[`${list.iterator}_search`])
.then(function(searchResponse) { .then(function(searchResponse) {
$scope[`${list.iterator}_dataset`] = searchResponse.data; $scope[`${list.iterator}_dataset`] = searchResponse.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results; $scope[list.name] = $scope[`${list.iterator}_dataset`].results;

View File

@@ -4,11 +4,13 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
import { N_ } from '../../i18n';
export default { export default {
name: 'templates', name: 'templates',
route: '/templates', route: '/templates',
ncyBreadcrumb: { ncyBreadcrumb: {
label: "TEMPLATES" label: N_("TEMPLATES")
}, },
data: { data: {
activityStream: true, activityStream: true,

View File

@@ -92,8 +92,8 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
} }
]) ])
.factory('BuildDescription', ['BuildAnchor', '$log', .factory('BuildDescription', ['BuildAnchor', '$log', 'i18n',
function (BuildAnchor, $log) { function (BuildAnchor, $log, i18n) {
return function (activity) { return function (activity) {
var pastTense = function(operation){ var pastTense = function(operation){
@@ -212,7 +212,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
} }
catch(err){ catch(err){
$log.debug(err); $log.debug(err);
activity.description = 'Event summary not available'; activity.description = i18n._('Event summary not available');
} }
}; };
} }
@@ -258,10 +258,10 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
.factory('Stream', ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath', .factory('Stream', ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath',
'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription', 'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription',
'ShowDetail', 'ShowDetail', 'i18n',
function ($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors, function ($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors,
Wait, StreamList, GenerateList, FormatDate, Wait, StreamList, GenerateList, FormatDate,
BuildDescription, ShowDetail) { BuildDescription, ShowDetail, i18n) {
return function (params) { return function (params) {
var list = _.cloneDeep(StreamList), var list = _.cloneDeep(StreamList),
@@ -297,7 +297,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
if ($state.params.target === 'credential') { if ($state.params.target === 'credential') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Credential', label: i18n._('Credential'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'credential', sourceModel: 'credential',
@@ -305,7 +305,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'host') { } else if ($state.params.target === 'host') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Host', label: i18n._('Host'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'host', sourceModel: 'host',
@@ -313,7 +313,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'inventory') { } else if ($state.params.target === 'inventory') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Inventory', label: i18n._('Inventory'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'inventory', sourceModel: 'inventory',
@@ -321,7 +321,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'inventory_script') { } else if ($state.params.target === 'inventory_script') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Inventory Script', label: i18n._('Inventory Script'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'custom_inventory_script', sourceModel: 'custom_inventory_script',
@@ -329,7 +329,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'job_template') { } else if ($state.params.target === 'job_template') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Job Template', label: i18n._('Job Template'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'job_template', sourceModel: 'job_template',
@@ -337,7 +337,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'job') { } else if ($state.params.target === 'job') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Job', label: i18n._('Job'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'job', sourceModel: 'job',
@@ -345,7 +345,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'organization') { } else if ($state.params.target === 'organization') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Organization', label: i18n._('Organization'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'organization', sourceModel: 'organization',
@@ -353,7 +353,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'project') { } else if ($state.params.target === 'project') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Project', label: i18n._('Project'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'project', sourceModel: 'project',
@@ -361,7 +361,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'schedule') { } else if ($state.params.target === 'schedule') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Schedule', label: i18n._('Schedule'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'schedule', sourceModel: 'schedule',
@@ -369,7 +369,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'team') { } else if ($state.params.target === 'team') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'Team', label: i18n._('Team'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'team', sourceModel: 'team',
@@ -377,7 +377,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
}; };
} else if ($state.params.target === 'user') { } else if ($state.params.target === 'user') {
list.fields.customSearchField = { list.fields.customSearchField = {
label: 'User', label: i18n._('User'),
searchType: 'text', searchType: 'text',
searchOnly: 'true', searchOnly: 'true',
sourceModel: 'user', sourceModel: 'user',

View File

@@ -26,7 +26,7 @@
data-placement="top" data-placement="top"
mode="all" mode="all"
ng-click="relaunchJob()" ng-click="relaunchJob()"
aw-tool-tip="Relaunch using the same parameters" aw-tool-tip="{{'Relaunch using the same parameters'|translate}}"
data-original-title="" data-original-title=""
title=""> title="">
<i class="icon-launch"></i> <i class="icon-launch"></i>
@@ -39,7 +39,7 @@
ng-click="deleteJob()" ng-click="deleteJob()"
ng-show="workflow_status.status == 'running' || ng-show="workflow_status.status == 'running' ||
job_status.status=='pending' " job_status.status=='pending' "
aw-tool-tip="Cancel" aw-tool-tip="{{'Cancel'|translate}}"
data-original-title="" title=""> data-original-title="" title="">
<i class="fa fa-minus-circle"></i> <i class="fa fa-minus-circle"></i>
</button> </button>
@@ -51,7 +51,7 @@
ng-click="deleteJob()" ng-click="deleteJob()"
ng-hide="job_status.status == 'running' || ng-hide="job_status.status == 'running' ||
job_status.status == 'pending' " job_status.status == 'pending' "
aw-tool-tip="Delete" aw-tool-tip="{{'Delete'|translate}}"
data-original-title="" data-original-title=""
title=""> title="">
<i class="fa fa-trash-o"></i> <i class="fa fa-trash-o"></i>
@@ -106,7 +106,7 @@
</label> </label>
<div class="WorkflowResults-resultRowText"> <div class="WorkflowResults-resultRowText">
<a href="{{ created_by_link }}" <a href="{{ created_by_link }}"
aw-tool-tip="Edit the User" aw-tool-tip="{{'Edit the User'|translate}}"
data-placement="top"> data-placement="top">
{{ workflow.summary_fields.created_by.username }} {{ workflow.summary_fields.created_by.username }}
</a> </a>
@@ -122,7 +122,7 @@
WorkflowResults-resultRowLabel--fullWidth"> WorkflowResults-resultRowLabel--fullWidth">
Extra Variables Extra Variables
<i class="WorkflowResults-extraVarsHelp fa fa-question-circle" <i class="WorkflowResults-extraVarsHelp fa fa-question-circle"
aw-tool-tip="Read only view of extra variables added to the workflow." aw-tool-tip="{{'Read only view of extra variables added to the workflow.'|translate}}"
data-placement="top"> data-placement="top">
</i> </i>
</label> </label>

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ msgid ""
" put the username and key in the URL. If using Bitbucket and SSH, do not " " put the username and key in the URL. If using Bitbucket and SSH, do not "
"supply your Bitbucket username." "supply your Bitbucket username."
msgstr "" msgstr ""
"%Remarque :%s Mercurial ne prend pas en charge l'authentification par mot de" "%sRemarque%s : Mercurial ne prend pas en charge l'authentification par mot de"
" passe pour SSH. N'entrez ni le nom d'utilisateur, ni la clé dans l'URL. Si " " passe pour SSH. N'entrez ni le nom d'utilisateur, ni la clé dans l'URL. Si "
"vous utilisez Bitbucket et SSH, ne saisissez pas votre nom d'utilisateur " "vous utilisez Bitbucket et SSH, ne saisissez pas votre nom d'utilisateur "
"Bitbucket." "Bitbucket."
@@ -37,7 +37,7 @@ msgid ""
"Bitbucket do not support password authentication when using SSH. GIT read " "Bitbucket do not support password authentication when using SSH. GIT read "
"only protocol (git://) does not use username or password information." "only protocol (git://) does not use username or password information."
msgstr "" msgstr ""
"%Remarque :%s Si vous utilisez le protocole SSH pour GitHub ou Bitbucket, " "%sRemarque%s : Si vous utilisez le protocole SSH pour GitHub ou Bitbucket, "
"entrez uniquement une clé SSH sans nom d'utilisateur (autre que git). De " "entrez uniquement une clé SSH sans nom d'utilisateur (autre que git). De "
"plus, GitHub et Bitbucket ne prennent pas en charge l'authentification par " "plus, GitHub et Bitbucket ne prennent pas en charge l'authentification par "
"mot de passe lorsque SSH est utilisé. Le protocole GIT en lecture seule " "mot de passe lorsque SSH est utilisé. Le protocole GIT en lecture seule "

View File

@@ -239,7 +239,7 @@ Repositories
The nightly repositories are hosted on the AnsibleWorks Jenkins server, and can The nightly repositories are hosted on the AnsibleWorks Jenkins server, and can
be found at the following location: be found at the following location:
http://jenkins.testing.ansible.com/ansible-tower_nightlies_RTYUIOPOIUYTYU/devel http://jenkins.testing.ansible.com/ansible-tower_nightlies_f8b8c5588b2505970227a7b0900ef69040ad5a00/devel
There are several sub-folders, including `deb/`, `rpm/`, `docs/` and `setup/` There are several sub-folders, including `deb/`, `rpm/`, `docs/` and `setup/`
@@ -260,7 +260,7 @@ The `setup/` folder contains the Ansible Tower setup playbook tar.gz file.
These nightly repositories can be used by the Ansible Tower setup playbook by These nightly repositories can be used by the Ansible Tower setup playbook by
running the `setup.sh` shell script with the following option: running the `setup.sh` shell script with the following option:
./setup.sh -e "aw_repo_url=http://jenkins.testing.ansible.com/ansible-tower_nightlies_RTYUIOPOIUYTYU/devel gpgcheck=0" ./setup.sh -e "aw_repo_url=http://jenkins.testing.ansible.com/ansible-tower_nightlies_f8b8c5588b2505970227a7b0900ef69040ad5a00/devel gpgcheck=0"
### Official Releases ### ### Official Releases ###

View File

@@ -9,11 +9,13 @@ services:
RABBITMQ_USER: guest RABBITMQ_USER: guest
RABBITMQ_PASS: guest RABBITMQ_PASS: guest
RABBITMQ_VHOST: / RABBITMQ_VHOST: /
CELERY_RDB_HOST: 0.0.0.0
ports: ports:
- "8080:8080" - "8080:8080"
- "5555:5555" - "5555:5555"
- "8013:8013" - "8013:8013"
- "8043:8043" - "8043:8043"
- "6899-6999:6899-6999" # default port range for celery.contrib.rdb
links: links:
- postgres - postgres
- memcached - memcached

View File

@@ -8,7 +8,7 @@ services:
image: gcr.io/ansible-tower-engineering/unit-test-runner:latest image: gcr.io/ansible-tower-engineering/unit-test-runner:latest
environment: environment:
SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl" SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl"
TEST_DIRS: awx/main/tests/functional awx/main/tests/unit TEST_DIRS: awx/main/tests/functional awx/main/tests/unit awx/conf/tests
command: ["make test"] command: ["make test"]
volumes: volumes:
- ../../../:/tower_devel - ../../../:/tower_devel

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