Merge branch 'devel' into addOrgToPermModal

This commit is contained in:
jlmitch5 2017-03-13 15:35:56 -04:00 committed by GitHub
commit f37765c2e9
258 changed files with 6431 additions and 13747 deletions

View File

@ -215,6 +215,7 @@ clean-bundle:
clean-ui:
rm -rf awx/ui/static/
rm -rf awx/ui/node_modules/
rm -rf awx/ui/coverage/
rm -f $(UI_DEPS_FLAG_FILE)
rm -f $(UI_RELEASE_FLAG_FILE)
@ -608,7 +609,7 @@ ui-test-ci: $(UI_DEPS_FLAG_FILE)
testjs_ci:
echo "Update UI unittests later" #ui-test-ci
jshint:
jshint: $(UI_DEPS_FLAG_FILE)
$(NPM_BIN) run --prefix awx/ui jshint
ui-test-saucelabs: $(UI_DEPS_FLAG_FILE)

View File

@ -5,7 +5,7 @@ import os
import sys
import warnings
__version__ = '3.1.2'
__version__ = '3.2.0'
__all__ = ['__version__']

View File

@ -149,6 +149,11 @@ class FieldLookupBackend(BaseFilterBackend):
return field.to_python(value)
def value_to_python(self, model, lookup, value):
try:
lookup = lookup.encode("ascii")
except UnicodeEncodeError:
raise ValueError("%r is not an allowed field name. Must be ascii encodable." % lookup)
field, new_lookup = self.get_field_from_lookup(model, lookup)
# Type names are stored without underscores internally, but are presented and

View File

@ -179,7 +179,8 @@ class Metadata(metadata.SimpleMetadata):
# Add version number in which view was added to Tower.
added_in_version = '1.2'
for version in ('3.1.0', '3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
for version in ('3.2.0', '3.1.0', '3.0.0', '2.4.0', '2.3.0', '2.2.0',
'2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
if getattr(view, 'new_in_%s' % version.replace('.', ''), False):
added_in_version = version
break

View File

@ -26,6 +26,9 @@ class JSONParser(parsers.JSONParser):
try:
data = stream.read().decode(encoding)
return json.loads(data, object_pairs_hook=OrderedDict)
obj = json.loads(data, object_pairs_hook=OrderedDict)
if not isinstance(obj, dict):
raise ParseError(_('JSON parse error - not a JSON object'))
return obj
except ValueError as exc:
raise ParseError(_('JSON parse error - %s') % six.text_type(exc))

View File

@ -16,7 +16,8 @@ from awx.main.utils import get_object_or_400
logger = logging.getLogger('awx.api.permissions')
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
'TaskPermission', 'ProjectUpdatePermission', 'UserPermission',]
'TaskPermission', 'ProjectUpdatePermission', 'UserPermission',
'IsSuperUser']
class ModelAccessPermission(permissions.BasePermission):
@ -208,3 +209,10 @@ class UserPermission(ModelAccessPermission):
raise PermissionDenied()
class IsSuperUser(permissions.BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
return request.user and request.user.is_superuser

View File

@ -12,4 +12,5 @@ urlpatterns = patterns(
'awx.conf.views',
url(r'^$', 'setting_category_list'),
url(r'^(?P<category_slug>[a-z0-9-]+)/$', 'setting_singleton_detail'),
url(r'^logging/test/$', 'setting_logging_test'),
)

View File

@ -19,7 +19,9 @@ from rest_framework import status
# Tower
from awx.api.generics import * # noqa
from awx.api.permissions import IsSuperUser
from awx.main.utils import * # noqa
from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException
from awx.conf.license import get_licensed_features
from awx.conf.models import Setting
from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer
@ -130,6 +132,32 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
settings.TOWER_URL_BASE = url
class SettingLoggingTest(GenericAPIView):
view_name = _('Logging Connectivity Test')
model = Setting
serializer_class = SettingSingletonSerializer
permission_classes = (IsSuperUser,)
filter_backends = []
new_in_320 = True
def post(self, request, *args, **kwargs):
defaults = dict()
for key in settings_registry.get_registered_settings(category_slug='logging'):
try:
defaults[key] = settings_registry.get_setting_field(key).get_default()
except serializers.SkipField:
defaults[key] = None
obj = type('Settings', (object,), defaults)()
serializer = self.get_serializer(obj, data=request.data)
serializer.is_valid(raise_exception=True)
try:
BaseHTTPSHandler.perform_test(serializer.validated_data)
except LoggingConnectivityException as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status=status.HTTP_200_OK)
# Create view functions for all of the class-based views to simplify inclusion
# in URL patterns and reverse URL lookups, converting CamelCase names to
# lowercase_with_underscore (e.g. MyView.as_view() becomes my_view).

View File

@ -94,6 +94,23 @@ def test_edit_playbook(patch, job_template_factory, alice):
}, alice, expect=403)
@pytest.mark.django_db
@pytest.mark.parametrize('json_body',
["abc", True, False, "{\"name\": \"test\"}", 100, .5])
def test_invalid_json_body(patch, job_template_factory, alice, json_body):
objs = job_template_factory('jt', organization='org1')
objs.job_template.admin_role.members.add(alice)
resp = patch(
reverse('api:job_template_detail', args=(objs.job_template.id,)),
json_body,
alice,
expect=400
)
assert resp.data['detail'] == (
u'JSON parse error - not a JSON object'
)
@pytest.mark.django_db
def test_edit_nonsenstive(patch, job_template_factory, alice):
objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred')

View File

@ -2,14 +2,19 @@
# All Rights Reserved.
# Python
from collections import OrderedDict
import pytest
import os
# Mock
import mock
# Django
from django.core.urlresolvers import reverse
# AWX
from awx.conf.models import Setting
from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException
TEST_GIF_LOGO = 'data:image/gif;base64,R0lGODlhIQAjAPIAAP//////AP8AAMzMAJmZADNmAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAHACwAAAAAIQAjAAADo3i63P4wykmrvTjrzZsxXfR94WMQBFh6RECuixHMLyzPQ13ewZCvow9OpzEAjIBj79cJJmU+FceIVEZ3QRozxBttmyOBwPBtisdX4Bha3oxmS+llFIPHQXQKkiSEXz9PeklHBzx3hYNyEHt4fmmAhHp8Nz45KgV5FgWFOFEGmwWbGqEfniChohmoQZ+oqRiZDZhEgk81I4mwg4EKVbxzrDHBEAkAIfkECQoABwAsAAAAACEAIwAAA6V4utz+MMpJq724GpP15p1kEAQYQmOwnWjgrmxjuMEAx8rsDjZ+fJvdLWQAFAHGWo8FRM54JqIRmYTigDrDMqZTbbbMj0CgjTLHZKvPQH6CTx+a2vKR0XbbOsoZ7SphG057gjl+c0dGgzeGNiaBiSgbBQUHBV08NpOVlkMSk0FKjZuURHiiOJxQnSGfQJuoEKREejK0dFRGjoiQt7iOuLx0rgxYEQkAIfkECQoABwAsAAAAACEAIwAAA7h4utxnxslJDSGR6nrz/owxYB64QUEwlGaVqlB7vrAJscsd3Lhy+wBArGEICo3DUFH4QDqK0GMy51xOgcGlEAfJ+iAFie62chR+jYKaSAuQGOqwJp7jGQRDuol+F/jxZWsyCmoQfwYwgoM5Oyg1i2w0A2WQIW2TPYOIkleQmy+UlYygoaIPnJmapKmqKiusMmSdpjxypnALtrcHioq3ury7hGm3dnVosVpMWFmwREZbddDOSsjVswcJACH5BAkKAAcALAAAAAAhACMAAAOxeLrc/jDKSZUxNS9DCNYV54HURQwfGRlDEFwqdLVuGjOsW9/Odb0wnsUAKBKNwsMFQGwyNUHckVl8bqI4o43lA26PNkv1S9DtNuOeVirw+aTI3qWAQwnud1vhLSnQLS0GeFF+GoVKNF0fh4Z+LDQ6Bn5/MTNmL0mAl2E3j2aclTmRmYCQoKEDiaRDKFhJez6UmbKyQowHtzy1uEl8DLCnEktrQ2PBD1NxSlXKIW5hz6cJACH5BAkKAAcALAAAAAAhACMAAAOkeLrc/jDKSau9OOvNlTFd9H3hYxAEWDJfkK5LGwTq+g0zDR/GgM+10A04Cm56OANgqTRmkDTmSOiLMgFOTM9AnFJHuexzYBAIijZf2SweJ8ttbbXLmd5+wBiJosSCoGF/fXEeS1g8gHl9hxODKkh4gkwVIwUekESIhA4FlgV3PyCWG52WI2oGnR2lnUWpqhqVEF4Xi7QjhpsshpOFvLosrnpoEAkAIfkECQoABwAsAAAAACEAIwAAA6l4utz+MMpJq71YGpPr3t1kEAQXQltQnk8aBCa7bMMLy4wx1G8s072PL6SrGQDI4zBThCU/v50zCVhidIYgNPqxWZkDg0AgxB2K4vEXbBSvr1JtZ3uOext0x7FqovF6OXtfe1UzdjAxhINPM013ChtJER8FBQeVRX8GlpggFZWWfjwblTiigGZnfqRmpUKbljKxDrNMeY2eF4R8jUiSur6/Z8GFV2WBtwwJACH5BAkKAAcALAAAAAAhACMAAAO6eLrcZi3KyQwhkGpq8f6ONWQgaAxB8JTfg6YkO50pzD5xhaurhCsGAKCnEw6NucNDCAkyI8ugdAhFKpnJJdMaeiofBejowUseCr9GYa0j1GyMdVgjBxoEuPSZXWKf7gKBeHtzMms0gHgGfDIVLztmjScvNZEyk28qjT40b5aXlHCbDgOhnzedoqOOlKeopaqrCy56sgtotbYKhYW6e7e9tsHBssO6eSTIm1peV0iuFUZDyU7NJnmcuQsJACH5BAkKAAcALAAAAAAhACMAAAOteLrc/jDKSZsxNS9DCNYV54Hh4H0kdAXBgKaOwbYX/Miza1vrVe8KA2AoJL5gwiQgeZz4GMXlcHl8xozQ3kW3KTajL9zsBJ1+sV2fQfALem+XAlRApxu4ioI1UpC76zJ4fRqDBzI+LFyFhH1iiS59fkgziW07jjRAG5QDeECOLk2Tj6KjnZafW6hAej6Smgevr6yysza2tiCuMasUF2Yov2gZUUQbU8YaaqjLpQkAOw=='
TEST_PNG_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAjCAYAAAAaLGNkAAAAAXNSR0IB2cksfwAAAdVpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpDb21wcmVzc2lvbj4xPC90aWZmOkNvbXByZXNzaW9uPgogICAgICAgICA8dGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPjI8L3RpZmY6UGhvdG9tZXRyaWNJbnRlcnByZXRhdGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cjl0tmoAAAHVSURBVFgJ7VZRsoMgDNTOu5E9U+/Ud6Z6JssGNg2oNKD90xkHCNnNkgTbYbieKwNXBn6bgSXQ4+16xi5UDiqDN3Pecr6+1fM5DHh7n1NEIPjjoRLKzOjG3qQ5dRtEy2LCjh/Gz2wDZE2nZYKkrxdn/kY9XQQkGCGqqDY5IgJFkEKgBCzDNGXhTKEye7boFRH6IPJj5EshiNCSjV4R4eSx7zhmR2tcdIuwmWiMeao7e0JHViZEWUI5aP8a9O+rx74D6sGEiJftiX3YeueIiFXg2KrhpqzjVC3dPZFYJZ7NOwwtNwM8R0UkLfH0sT5qck+OlkMq0BucKr0iWG7gpAQksD9esM1z3Lnf6SHjLh67nnKEGxC/iomWhByTeXOQJGHHcKxwHhHKnt1HIdYtmexkIb/HOURWTSJqn2gKMDG0bDUc/D0iAseovxUBoylmQCug6IVhSv+4DIeKI94jAr4AjiSEgQ25JYB+YWT9BZ94AM8erwgFkRifaArA6U0G5KT0m//z26REZuK9okgrT6VwE1jTHjbVzyNAyRwTEPOtuiex9FVBNZCkruaA4PZqFp1u8Rpww9/6rcK5y0EkAxRiZJt79PWOVYWGRE9pbJhavMengMflGyumk0akMsQnAAAAAElFTkSuQmCC'
@ -183,3 +188,58 @@ def test_ui_settings(get, put, patch, delete, admin, enterprise_license):
response = get(url, user=admin, expect=200)
assert not response.data['CUSTOM_LOGO']
assert not response.data['CUSTOM_LOGIN_INFO']
@pytest.mark.django_db
def test_logging_aggregrator_connection_test_requires_superuser(get, post, alice):
url = reverse('api:setting_logging_test')
post(url, {}, user=alice, expect=403)
@pytest.mark.parametrize('key', [
'LOG_AGGREGATOR_TYPE',
'LOG_AGGREGATOR_HOST',
'LOG_AGGREGATOR_PORT',
])
@pytest.mark.django_db
def test_logging_aggregrator_connection_test_bad_request(get, post, admin, key):
url = reverse('api:setting_logging_test')
resp = post(url, {}, user=admin, expect=400)
assert 'This field is required.' in resp.data.get(key, [])
@pytest.mark.django_db
def test_logging_aggregrator_connection_test_valid(mocker, get, post, admin):
with mock.patch.object(BaseHTTPSHandler, 'perform_test') as perform_test:
url = reverse('api:setting_logging_test')
post(url, {
'LOG_AGGREGATOR_TYPE': 'logstash',
'LOG_AGGREGATOR_HOST': 'localhost',
'LOG_AGGREGATOR_PORT': 8080,
'LOG_AGGREGATOR_USERNAME': 'logger',
'LOG_AGGREGATOR_PASSWORD': 'mcstash'
}, user=admin, expect=200)
perform_test.assert_called_with(OrderedDict([
('LOG_AGGREGATOR_HOST', u'localhost'),
('LOG_AGGREGATOR_PORT', 8080),
('LOG_AGGREGATOR_TYPE', 'logstash'),
('LOG_AGGREGATOR_USERNAME', 'logger'),
('LOG_AGGREGATOR_PASSWORD', 'mcstash'),
('LOG_AGGREGATOR_LOGGERS', ['awx', 'activity_stream', 'job_events', 'system_tracking']),
('LOG_AGGREGATOR_INDIVIDUAL_FACTS', False),
('LOG_AGGREGATOR_ENABLED', False),
('LOG_AGGREGATOR_TOWER_UUID', '')
]))
@pytest.mark.django_db
def test_logging_aggregrator_connection_test_invalid(mocker, get, post, admin):
with mock.patch.object(BaseHTTPSHandler, 'perform_test') as perform_test:
perform_test.side_effect = LoggingConnectivityException('404: Not Found')
url = reverse('api:setting_logging_test')
resp = post(url, {
'LOG_AGGREGATOR_TYPE': 'logstash',
'LOG_AGGREGATOR_HOST': 'localhost',
'LOG_AGGREGATOR_PORT': 8080
}, user=admin, expect=500)
assert resp.data == {'error': '404: Not Found'}

View File

@ -15,14 +15,21 @@ def test_env_matches_requirements_txt():
return False
return True
def skip_line(line):
return (
line == '' or line.strip().startswith('#') or
line.strip().startswith('git') or line.startswith('-e') or
'## The following requirements were added by pip freeze' in line
)
base_dir = settings.BASE_DIR
requirements_path = os.path.join(base_dir, '../', 'requirements/requirements.txt')
reqs_actual = []
xs = freeze.freeze(local_only=True)
for x in xs:
if '## The following requirements were added by pip freeze' in x:
break
if skip_line(x):
continue
x = x.lower()
(pkg_name, pkg_version) = x.split('==')
reqs_actual.append([pkg_name, pkg_version])
@ -33,11 +40,7 @@ def test_env_matches_requirements_txt():
line = line.partition('#')[0]
line = line.rstrip().lower()
# TODO: process git requiremenst and use egg
if line == '':
continue
if line.strip().startswith('#') or line.strip().startswith('git'):
continue
if line.startswith('-e'):
if skip_line(line):
continue
'''

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import pytest
from rest_framework.exceptions import PermissionDenied
@ -24,6 +26,14 @@ def test_valid_in(valid_value):
assert 'foo' in value
def test_invalid_field():
invalid_field = u"ヽヾ"
field_lookup = FieldLookupBackend()
with pytest.raises(ValueError) as excinfo:
field_lookup.value_to_python(WorkflowJobTemplate, invalid_field, 'foo')
assert 'is not an allowed field name. Must be ascii encodable.' in excinfo.value.message
@pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in'])
@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS)
def test_filter_on_password_field(password_field, lookup_suffix):

View File

@ -1,13 +1,15 @@
import base64
import json
import logging
from uuid import uuid4
from django.conf import LazySettings
import pytest
import requests
from requests_futures.sessions import FuturesSession
from awx.main.utils.handlers import BaseHTTPSHandler as HTTPSHandler, PARAM_NAMES
from awx.main.utils.handlers import (BaseHTTPSHandler as HTTPSHandler,
PARAM_NAMES, LoggingConnectivityException)
from awx.main.utils.formatters import LogstashFormatter
@ -25,19 +27,21 @@ def dummy_log_record():
@pytest.fixture()
def ok200_adapter():
class OK200Adapter(requests.adapters.HTTPAdapter):
def http_adapter():
class FakeHTTPAdapter(requests.adapters.HTTPAdapter):
requests = []
status = 200
reason = None
def send(self, request, **kwargs):
self.requests.append(request)
resp = requests.models.Response()
resp.status_code = 200
resp.raw = '200 OK'
resp.status_code = self.status
resp.reason = self.reason
resp.request = request
return resp
return OK200Adapter()
return FakeHTTPAdapter()
def test_https_logging_handler_requests_sync_implementation():
@ -73,6 +77,42 @@ def test_https_logging_handler_from_django_settings(param, django_settings_name)
assert hasattr(handler, param) and getattr(handler, param) == 'EXAMPLE'
@pytest.mark.parametrize(
'status, reason, exc',
[(200, '200 OK', None), (404, 'Not Found', LoggingConnectivityException)]
)
def test_https_logging_handler_connectivity_test(http_adapter, status, reason, exc):
http_adapter.status = status
http_adapter.reason = reason
settings = LazySettings()
settings.configure(**{
'LOG_AGGREGATOR_HOST': 'example.org',
'LOG_AGGREGATOR_PORT': 8080,
'LOG_AGGREGATOR_TYPE': 'logstash',
'LOG_AGGREGATOR_USERNAME': 'user',
'LOG_AGGREGATOR_PASSWORD': 'password',
'LOG_AGGREGATOR_LOGGERS': ['awx', 'activity_stream', 'job_events', 'system_tracking'],
'CLUSTER_HOST_ID': '',
'LOG_AGGREGATOR_TOWER_UUID': str(uuid4())
})
class FakeHTTPSHandler(HTTPSHandler):
def __init__(self, *args, **kwargs):
super(FakeHTTPSHandler, self).__init__(*args, **kwargs)
self.session.mount('http://', http_adapter)
def emit(self, record):
return super(FakeHTTPSHandler, self).emit(record)
if exc:
with pytest.raises(exc) as e:
FakeHTTPSHandler.perform_test(settings)
assert str(e).endswith('%s: %s' % (status, reason))
else:
assert FakeHTTPSHandler.perform_test(settings) is None
def test_https_logging_handler_logstash_auth_info():
handler = HTTPSHandler(message_type='logstash', username='bob', password='ansible')
handler.add_auth_information()
@ -120,19 +160,19 @@ def test_https_logging_handler_skip_log(params, logger_name, expected):
('splunk', False),
('splunk', True),
])
def test_https_logging_handler_emit(ok200_adapter, dummy_log_record,
def test_https_logging_handler_emit(http_adapter, dummy_log_record,
message_type, async):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
message_type=message_type,
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'],
async=async)
handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter)
handler.session.mount('http://', http_adapter)
async_futures = handler.emit(dummy_log_record)
[future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 1
request = ok200_adapter.requests[0]
assert len(http_adapter.requests) == 1
request = http_adapter.requests[0]
assert request.url == 'http://127.0.0.1/'
assert request.method == 'POST'
body = json.loads(request.body)
@ -152,7 +192,7 @@ def test_https_logging_handler_emit(ok200_adapter, dummy_log_record,
@pytest.mark.parametrize('async', (True, False))
def test_https_logging_handler_emit_logstash_with_creds(ok200_adapter,
def test_https_logging_handler_emit_logstash_with_creds(http_adapter,
dummy_log_record, async):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
username='user', password='pass',
@ -160,38 +200,38 @@ def test_https_logging_handler_emit_logstash_with_creds(ok200_adapter,
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'],
async=async)
handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter)
handler.session.mount('http://', http_adapter)
async_futures = handler.emit(dummy_log_record)
[future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 1
request = ok200_adapter.requests[0]
assert len(http_adapter.requests) == 1
request = http_adapter.requests[0]
assert request.headers['Authorization'] == 'Basic %s' % base64.b64encode("user:pass")
@pytest.mark.parametrize('async', (True, False))
def test_https_logging_handler_emit_splunk_with_creds(ok200_adapter,
def test_https_logging_handler_emit_splunk_with_creds(http_adapter,
dummy_log_record, async):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
password='pass', message_type='splunk',
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'],
async=async)
handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter)
handler.session.mount('http://', http_adapter)
async_futures = handler.emit(dummy_log_record)
[future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 1
request = ok200_adapter.requests[0]
assert len(http_adapter.requests) == 1
request = http_adapter.requests[0]
assert request.headers['Authorization'] == 'Splunk pass'
def test_https_logging_handler_emit_one_record_per_fact(ok200_adapter):
def test_https_logging_handler_emit_one_record_per_fact(http_adapter):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
message_type='logstash', indv_facts=True,
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'])
handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter)
handler.session.mount('http://', http_adapter)
record = logging.LogRecord(
'awx.analytics.system_tracking', # logger name
20, # loglevel INFO
@ -212,8 +252,8 @@ def test_https_logging_handler_emit_one_record_per_fact(ok200_adapter):
async_futures = handler.emit(record)
[future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 2
requests = sorted(ok200_adapter.requests, key=lambda request: json.loads(request.body)['version'])
assert len(http_adapter.requests) == 2
requests = sorted(http_adapter.requests, key=lambda request: json.loads(request.body)['version'])
request = requests[0]
assert request.url == 'http://127.0.0.1/'

View File

@ -5,6 +5,7 @@
import logging
import json
import requests
from requests.exceptions import RequestException
from copy import copy
# loggly
@ -40,6 +41,10 @@ def unused_callback(sess, resp):
pass
class LoggingConnectivityException(Exception):
pass
class HTTPSNullHandler(logging.NullHandler):
"Placeholder null handler to allow loading without database access"
@ -66,6 +71,31 @@ class BaseHTTPSHandler(logging.Handler):
kwargs[param] = getattr(settings, django_setting_name, None)
return cls(*args, **kwargs)
@classmethod
def perform_test(cls, settings):
"""
Tests logging connectivity for the current logging settings.
@raises LoggingConnectivityException
"""
handler = cls.from_django_settings(settings, async=True)
handler.enabled_flag = True
handler.setFormatter(LogstashFormatter(settings_module=settings))
logger = logging.getLogger(__file__)
fn, lno, func = logger.findCaller()
record = logger.makeRecord('awx', 10, fn, lno,
'Ansible Tower Connection Test', tuple(),
None, func)
futures = handler.emit(record)
for future in futures:
try:
resp = future.result()
if not resp.ok:
raise LoggingConnectivityException(
': '.join([str(resp.status_code), resp.reason or ''])
)
except RequestException as e:
raise LoggingConnectivityException(str(e))
def get_full_message(self, record):
if record.exc_info:
return '\n'.join(traceback.format_exception(*record.exc_info))

View File

@ -1671,7 +1671,7 @@ tr td button i {
.modal-body {
min-height: 120px;
padding: 20px 0;
.alert {
padding: 10px;
margin: 0;
@ -1984,10 +1984,6 @@ tr td button i {
width: 73px;
}
.JobDetails-status {
margin-bottom: 12px;
}
.red-text {
color: @red;
}

View File

@ -6,10 +6,10 @@
/* jshint unused: vars */
export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList',
'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit',
'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath',
'OrganizationList',
function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList,
InventoryList, CredentialList, $compile, generateList, GetBasePath, SelectionInit,
InventoryList, CredentialList, $compile, generateList, GetBasePath,
OrganizationList) {
return {
restrict: 'E',

View File

@ -0,0 +1,78 @@
export default
function BuildAnchor($log, $filter) {
// Returns a full <a href=''>resource_name</a> HTML string if link can be derived from supplied context
// returns name of resource if activity stream object doesn't contain enough data to build a UI url
// arguments are: a summary_field object, a resource type, an activity stream object
return function (obj, resource, activity) {
var url = '/#/';
// try/except pattern asserts that:
// if we encounter a case where a UI url can't or shouldn't be generated, just supply the name of the resource
try {
// catch-all case to avoid generating urls if a resource has been deleted
// if a resource still exists, it'll be serialized in the activity's summary_fields
if (!activity.summary_fields[resource]){
throw {name : 'ResourceDeleted', message: 'The referenced resource no longer exists'};
}
switch (resource) {
case 'custom_inventory_script':
url += 'inventory_scripts/' + obj.id + '/';
break;
case 'group':
if (activity.operation === 'create' || activity.operation === 'delete'){
// the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey'
var inventory_id = _.last(activity.changes.inventory.split('-'));
url += 'inventories/' + inventory_id + '/manage?group=' + activity.changes.id;
}
else {
url += 'inventories/' + activity.summary_fields.inventory[0].id + '/manage?group=' + (activity.changes.id || activity.changes.object1_pk);
}
break;
case 'host':
url += 'home/hosts/' + obj.id;
break;
case 'job':
url += 'jobs/' + obj.id;
break;
case 'inventory':
url += 'inventories/' + obj.id + '/';
break;
case 'schedule':
// schedule urls depend on the resource they're associated with
if (activity.summary_fields.job_template){
url += 'job_templates/' + activity.summary_fields.job_template.id + '/schedules/' + obj.id;
}
else if (activity.summary_fields.project){
url += 'projects/' + activity.summary_fields.project.id + '/schedules/' + obj.id;
}
else if (activity.summary_fields.system_job_template){
url += 'management_jobs/' + activity.summary_fields.system_job_template.id + '/schedules/edit/' + obj.id;
}
// urls for inventory sync schedules currently depend on having an inventory id and group id
else {
throw {name : 'NotImplementedError', message : 'activity.summary_fields to build this url not implemented yet'};
}
break;
case 'notification_template':
url += `notification_templates/${obj.id}`;
break;
case 'role':
throw {name : 'NotImplementedError', message : 'role object management is not consolidated to a single UI view'};
case 'job_template':
url += `templates/job_template/${obj.id}`;
break;
case 'workflow_job_template':
url += `templates/workflow_job_template/${obj.id}`;
break;
default:
url += resource + 's/' + obj.id + '/';
}
return ' <a href=\"' + url + '\"> ' + $filter('sanitize')(obj.name || obj.username) + ' </a> ';
}
catch(err){
$log.debug(err);
return ' ' + $filter('sanitize')(obj.name || obj.username || '') + ' ';
}
};
}
BuildAnchor.$inject = ['$log', '$filter'];

View File

@ -0,0 +1,126 @@
export default
function BuildDescription(BuildAnchor, $log, i18n) {
return function (activity) {
var pastTense = function(operation){
return (/e$/.test(activity.operation)) ? operation + 'd ' : operation + 'ed ';
};
// convenience method to see if dis+association operation involves 2 groups
// the group cases are slightly different because groups can be dis+associated into each other
var isGroupRelationship = function(activity){
return activity.object1 === 'group' && activity.object2 === 'group' && activity.summary_fields.group.length > 1;
};
// Activity stream objects will outlive the resources they reference
// in that case, summary_fields will not be available - show generic error text instead
try {
activity.description = pastTense(activity.operation);
switch(activity.object_association){
// explicit role dis+associations
case 'role':
// object1 field is resource targeted by the dis+association
// object2 field is the resource the role is inherited from
// summary_field.role[0] contains ref info about the role
switch(activity.operation){
// expected outcome: "disassociated <object2> role_name from <object1>"
case 'disassociate':
if (isGroupRelationship(activity)){
activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field +
' from ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
}
else{
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
}
break;
// expected outcome: "associated <object2> role_name to <object1>"
case 'associate':
if (isGroupRelationship(activity)){
activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field +
' to ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
}
else{
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
}
break;
}
break;
// inherited role dis+associations (logic identical to case 'role')
case 'parents':
// object1 field is resource targeted by the dis+association
// object2 field is the resource the role is inherited from
// summary_field.role[0] contains ref info about the role
switch(activity.operation){
// expected outcome: "disassociated <object2> role_name from <object1>"
case 'disassociate':
if (isGroupRelationship(activity)){
activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) +
'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
}
else{
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
}
break;
// expected outcome: "associated <object2> role_name to <object1>"
case 'associate':
if (isGroupRelationship(activity)){
activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) +
'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity);
}
else{
activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field +
' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
}
break;
}
break;
// CRUD operations / resource on resource dis+associations
default:
switch(activity.operation){
// expected outcome: "disassociated <object2> from <object1>"
case 'disassociate' :
if (isGroupRelationship(activity)){
activity.description += activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) +
'from ' + activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity);
}
else {
activity.description += activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) +
'from ' + activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
}
break;
// expected outcome "associated <object2> to <object1>"
case 'associate':
// groups are the only resource that can be associated/disassociated into each other
if (isGroupRelationship(activity)){
activity.description += activity.object1 + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity) +
'to ' + activity.object2 + BuildAnchor(activity.summary_fields.group[1], activity.object2, activity);
}
else {
activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity) +
'to ' + activity.object2 + BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity);
}
break;
case 'delete':
activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity);
break;
// expected outcome: "operation <object1>"
case 'update':
activity.description += activity.object1 + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity);
break;
case 'create':
activity.description += activity.object1 + BuildAnchor(activity.changes, activity.object1, activity);
break;
}
break;
}
}
catch(err){
$log.debug(err);
activity.description = i18n._('Event summary not available');
}
};
}
BuildDescription.$inject = ['BuildAnchor', '$log', 'i18n'];

View File

@ -0,0 +1,39 @@
export default
function ShowDetail($filter, $rootScope, Rest, Alert, GenerateForm, ProcessErrors, GetBasePath, FormatDate, ActivityDetailForm, Empty, Find) {
return function (params, scope) {
var activity_id = params.activity_id,
activity = Find({ list: params.scope.activities, key: 'id', val: activity_id }),
element;
if (activity) {
// Grab our element out of the dom
element = angular.element(document.getElementById('stream-detail-modal'));
// Grab the modal's scope so that we can set a few variables
scope = element.scope();
scope.changes = activity.changes;
scope.user = ((activity.summary_fields.actor) ? activity.summary_fields.actor.username : 'system') +
' on ' + $filter('longDate')(activity.timestamp);
scope.operation = activity.description;
scope.header = "Event " + activity.id;
// Open the modal
$('#stream-detail-modal').modal({
show: true,
backdrop: 'static',
keyboard: true
});
if (!scope.$$phase) {
scope.$digest();
}
}
};
}
ShowDetail.$inject = ['$filter', '$rootScope', 'Rest', 'Alert', 'GenerateForm', 'ProcessErrors', 'GetBasePath', 'FormatDate',
'ActivityDetailForm', 'Empty', 'Find'];

View File

@ -0,0 +1,54 @@
export default
function Stream($rootScope, $location, $state, Rest, GetBasePath, ProcessErrors,
Wait, StreamList, GenerateList, FormatDate,
BuildDescription, ShowDetail) {
return function (params) {
var scope = params.scope;
$rootScope.flashMessage = null;
// descriptive title describing what AS is showing
scope.streamTitle = (params && params.title) ? params.title : null;
scope.refreshStream = function () {
$state.go('.', null, {reload: true});
};
scope.showDetail = function (id) {
ShowDetail({
scope: scope,
activity_id: id
});
};
if(scope.activities && scope.activities.length > 0) {
buildUserAndDescription();
}
scope.$watch('activities', function(){
// Watch for future update to scope.activities (like page change, column sort, search, etc)
buildUserAndDescription();
});
function buildUserAndDescription(){
scope.activities.forEach(function(activity, i) {
// build activity.user
if (scope.activities[i].summary_fields.actor) {
scope.activities[i].user = "<a href=\"/#/users/" + scope.activities[i].summary_fields.actor.id + "\">" +
scope.activities[i].summary_fields.actor.username + "</a>";
} else {
scope.activities[i].user = 'system';
}
// build description column / action text
BuildDescription(scope.activities[i]);
});
}
};
}
Stream.$inject = ['$rootScope', '$location', '$state', 'Rest', 'GetBasePath',
'ProcessErrors', 'Wait', 'StreamList', 'generateList', 'FormatDate', 'BuildDescription',
'ShowDetail'];

View File

@ -0,0 +1,51 @@
export default
function GetTargetTitle(i18n) {
return function (target) {
var rtnTitle = i18n._('ALL ACTIVITY');
switch(target) {
case 'project':
rtnTitle = i18n._('PROJECTS');
break;
case 'inventory':
rtnTitle = i18n._('INVENTORIES');
break;
case 'credential':
rtnTitle = i18n._('CREDENTIALS');
break;
case 'user':
rtnTitle = i18n._('USERS');
break;
case 'team':
rtnTitle = i18n._('TEAMS');
break;
case 'notification_template':
rtnTitle = i18n._('NOTIFICATION TEMPLATES');
break;
case 'organization':
rtnTitle = i18n._('ORGANIZATIONS');
break;
case 'job':
rtnTitle = i18n._('JOBS');
break;
case 'custom_inventory_script':
rtnTitle = i18n._('INVENTORY SCRIPTS');
break;
case 'schedule':
rtnTitle = i18n._('SCHEDULES');
break;
case 'host':
rtnTitle = i18n._('HOSTS');
break;
case 'template':
rtnTitle = i18n._('TEMPLATES');
break;
}
return rtnTitle;
};
}
GetTargetTitle.$inject = ['i18n'];

View File

@ -6,14 +6,24 @@
import activityStreamRoute from './activitystream.route';
import activityStreamController from './activitystream.controller';
import streamDropdownNav from './streamDropdownNav/stream-dropdown-nav.directive';
import streamDetailModal from './streamDetailModal/main';
import BuildAnchor from './factories/build-anchor.factory';
import BuildDescription from './factories/build-description.factory';
import ShowDetail from './factories/show-detail.factory';
import Stream from './factories/stream.factory';
import GetTargetTitle from './get-target-title.factory';
import ModelToBasePathKey from './model-to-base-path-key.factory';
export default angular.module('activityStream', [streamDetailModal.name])
.controller('activityStreamController', activityStreamController)
.directive('streamDropdownNav', streamDropdownNav)
.factory('BuildAnchor', BuildAnchor)
.factory('BuildDescription', BuildDescription)
.factory('ShowDetail', ShowDetail)
.factory('Stream', Stream)
.factory('GetTargetTitle', GetTargetTitle)
.factory('ModelToBasePathKey', ModelToBasePathKey)
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(activityStreamRoute);
}]);

View File

@ -0,0 +1,59 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ApiModel
* @description Helper functions to convert singular/plural versions of our models to the opposite
*/
export default
function ModelToBasePathKey() {
return function(model) {
// This function takes in the singular model string and returns the key needed
// to get the base path from $rootScope/local storage.
var basePathKey;
switch(model) {
case 'project':
basePathKey = 'projects';
break;
case 'inventory':
basePathKey = 'inventory';
break;
case 'job_template':
basePathKey = 'job_templates';
break;
case 'credential':
basePathKey = 'credentials';
break;
case 'user':
basePathKey = 'users';
break;
case 'team':
basePathKey = 'teams';
break;
case 'notification_template':
basePathKey = 'notification_templates';
break;
case 'organization':
basePathKey = 'organizations';
break;
case 'management_job':
basePathKey = 'management_jobs';
break;
case 'custom_inventory_script':
basePathKey = 'inventory_scripts';
break;
case 'workflow_job_template':
basePathKey = 'workflow_job_templates';
break;
}
return basePathKey;
};
}

View File

@ -37,20 +37,14 @@ if ($basePath) {
}
// Modules
import './helpers';
import './forms';
import './lists';
import './widgets';
import './filters';
import { Home } from './controllers/Home';
import { SocketsController } from './controllers/Sockets';
import { CredentialsAdd, CredentialsEdit, CredentialsList } from './controllers/Credentials';
import portalMode from './portal-mode/main';
import systemTracking from './system-tracking/main';
import inventories from './inventories/main';
import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/main';
import managementJobs from './management-jobs/main';
import jobDetail from './job-detail/main';
import workflowResults from './workflow-results/main';
import jobResults from './job-results/main';
import jobSubmission from './job-submission/main';
@ -62,30 +56,21 @@ import mainMenu from './main-menu/main';
import breadCrumb from './bread-crumb/main';
import browserData from './browser-data/main';
import configuration from './configuration/main';
import dashboard from './dashboard/main';
import moment from './shared/moment/main';
import home from './home/main';
import login from './login/main';
import activityStream from './activity-stream/main';
import standardOut from './standard-out/main';
import Templates from './templates/main';
import credentials from './credentials/main';
import jobs from './jobs/main';
import { ProjectsList, ProjectsAdd, ProjectsEdit } from './controllers/Projects';
import { UsersList, UsersAdd, UsersEdit } from './controllers/Users';
import { TeamsList, TeamsAdd, TeamsEdit } from './controllers/Teams';
import teams from './teams/main';
import users from './users/main';
import projects from './projects/main';
import RestServices from './rest/main';
import access from './access/main';
import './shared/Modal';
import './shared/prompt-dialog';
import './shared/directives';
import './shared/filters';
import './shared/features/main';
import config from './shared/config/main';
import './login/authenticationServices/pendo/ng-pendo';
import footer from './footer/main';
import scheduler from './scheduler/main';
import { N_ } from './i18n';
var tower = angular.module('Tower', [
// how to add CommonJS / AMD third-party dependencies:
@ -97,6 +82,7 @@ var tower = angular.module('Tower', [
require('angular-sanitize'),
require('angular-scheduler').name,
require('angular-tz-extensions'),
require('angular-md5'),
require('lr-infinite-scroll'),
require('ng-toast'),
'gettext',
@ -119,12 +105,10 @@ var tower = angular.module('Tower', [
setupMenu.name,
mainMenu.name,
breadCrumb.name,
dashboard.name,
moment.name,
home.name,
login.name,
activityStream.name,
footer.name,
jobDetail.name,
workflowResults.name,
jobResults.name,
jobSubmission.name,
@ -132,9 +116,11 @@ var tower = angular.module('Tower', [
standardOut.name,
Templates.name,
portalMode.name,
config.name,
credentials.name,
jobs.name,
teams.name,
users.name,
projects.name,
//'templates',
'Utilities',
'OrganizationFormDefinition',
@ -142,68 +128,44 @@ var tower = angular.module('Tower', [
'OrganizationListDefinition',
'templates',
'UserListDefinition',
'UserHelper',
'PromptDialog',
'AWDirectives',
'InventoriesListDefinition',
'InventoryFormDefinition',
'InventoryHelper',
'InventoryGroupsDefinition',
'InventoryHostsDefinition',
'HostsHelper',
'AWFilters',
'HostFormDefinition',
'HostListDefinition',
'GroupFormDefinition',
'GroupListDefinition',
'GroupsHelper',
'TeamsListDefinition',
'TeamFormDefinition',
'TeamHelper',
'CredentialsListDefinition',
'CredentialFormDefinition',
'TemplatesListDefinition',
'PortalJobTemplatesListDefinition',
'JobTemplateFormDefinition',
'JobTemplatesHelper',
'JobSubmissionHelper',
'ProjectsListDefinition',
'ProjectFormDefinition',
'ProjectStatusDefinition',
'ProjectsHelper',
'CompletedJobsDefinition',
'AllJobsDefinition',
'JobSummaryDefinition',
'ParseHelper',
'ChildrenHelper',
'ProjectPathHelper',
'md5Helper',
'SelectionHelper',
'HostGroupsFormDefinition',
'StreamWidget',
'JobsHelper',
'CredentialsHelper',
'StreamListDefinition',
'ActivityDetailDefinition',
'VariablesHelper',
'SchedulesListDefinition',
'ScheduledJobsDefinition',
//'Timezones',
'SchedulesHelper',
'JobsListDefinition',
'LogViewerStatusDefinition',
'StandardOutHelper',
'LogViewerOptionsDefinition',
'JobDetailHelper',
'lrInfiniteScroll',
'LoadConfigHelper',
'PortalJobsListDefinition',
'features',
'longDateFilter',
'pendolytics',
scheduler.name,
'ApiModelHelper',
'ActivityStreamHelper',
'WorkflowFormDefinition',
'InventorySourcesListDefinition',
'WorkflowMakerFormDefinition'
@ -227,10 +189,9 @@ var tower = angular.module('Tower', [
});
}])
.config(['$urlRouterProvider', '$breadcrumbProvider', 'QuerySetProvider',
'$urlMatcherFactoryProvider', 'stateDefinitionsProvider', '$stateProvider',
'$urlMatcherFactoryProvider',
function($urlRouterProvider, $breadcrumbProvider, QuerySet,
$urlMatcherFactoryProvider, stateDefinitionsProvider, $stateProvider) {
let stateDefinitions = stateDefinitionsProvider.$get();
$urlMatcherFactoryProvider) {
$urlMatcherFactoryProvider.strictMode(false);
$breadcrumbProvider.setOptions({
templateUrl: urlPrefix + 'partials/breadcrumb.html'
@ -266,117 +227,14 @@ var tower = angular.module('Tower', [
// $stateProvider.stateRegistry.onStatesChanged((event, states) =>{
// console.log(event, states)
// })
// lazily generate a tree of substates which will replace this node in ui-router's stateRegistry
// see: stateDefinition.factory for usage documentation
$stateProvider.state({
name: 'projects',
url: '/projects',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'projects', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'ProjectList',
form: 'ProjectsForm',
controllers: {
list: ProjectsList, // DI strings or objects
add: ProjectsAdd,
edit: ProjectsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'project',
socket: {
"groups": {
"jobs": ["status_changed"]
}
}
},
ncyBreadcrumb: {
label: N_('PROJECTS')
}
})
});
$stateProvider.state({
name: 'credentials',
url: '/credentials',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'credentials',
modes: ['add', 'edit'],
list: 'CredentialList',
form: 'CredentialForm',
controllers: {
list: CredentialsList,
add: CredentialsAdd,
edit: CredentialsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'credential'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_('CREDENTIALS')
}
})
});
$stateProvider.state({
name: 'teams',
url: '/teams',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'teams',
modes: ['add', 'edit'],
list: 'TeamList',
form: 'TeamForm',
controllers: {
list: TeamsList,
add: TeamsAdd,
edit: TeamsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'team'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_('TEAMS')
}
})
});
$stateProvider.state({
name: 'users',
url: '/users',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'users',
modes: ['add', 'edit'],
list: 'UserList',
form: 'UserForm',
controllers: {
list: UsersList,
add: UsersAdd,
edit: UsersEdit
},
data: {
activityStream: true,
activityStreamTarget: 'user'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_('USERS')
}
})
});
}
])
.run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log', '$stateParams',
.run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams',
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService',
function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, $stateParams,
function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
@ -392,67 +250,13 @@ var tower = angular.module('Tower', [
$log.debug(`$state.defaultErrorHandler: ${error}`);
});
$stateExtender.addState({
name: 'dashboard',
url: '/home',
templateUrl: urlPrefix + 'partials/home.html',
controller: Home,
params: { licenseMissing: null },
data: {
activityStream: true,
refreshButton: true,
socket: {
"groups": {
"jobs": ["status_changed"]
}
},
},
ncyBreadcrumb: {
label: N_("DASHBOARD")
},
resolve: {
graphData: ['$q', 'jobStatusGraphData', '$rootScope',
function($q, jobStatusGraphData, $rootScope) {
return $rootScope.featuresConfigured.promise.then(function() {
return $q.all({
jobStatus: jobStatusGraphData.get("month", "all"),
});
});
}
]
}
});
$rootScope.refresh = function() {
$state.go('.', null, {reload: true});
};
$stateExtender.addState({
name: 'userCredentials',
url: '/users/:user_id/credentials',
templateUrl: urlPrefix + 'partials/users.html',
controller: CredentialsList
});
$stateExtender.addState({
name: 'userCredentialAdd',
url: '/users/:user_id/credentials/add',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsAdd
});
$stateExtender.addState({
name: 'teamUserCredentialEdit',
url: '/teams/:user_id/credentials/:credential_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsEdit
});
$stateExtender.addState({
name: 'sockets',
url: '/sockets',
templateUrl: urlPrefix + 'partials/sockets.html',
controller: SocketsController,
ncyBreadcrumb: {
label: N_('SOCKETS')
}
});
$rootScope.refreshJobs = function(){
$state.go('.', null, {reload: true});
};
function activateTab() {
// Make the correct tab active
@ -511,19 +315,19 @@ var tower = angular.module('Tower', [
$rootScope.$on("$stateChangeStart", function (event, next) {
// Remove any lingering intervals
// except on jobDetails.* states
var jobDetailStates = [
'jobDetail',
'jobDetail.host-summary',
'jobDetail.host-event.details',
'jobDetail.host-event.json',
'jobDetail.host-events',
'jobDetail.host-event.stdout'
// except on jobResults.* states
var jobResultStates = [
'jobResult',
'jobResult.host-summary',
'jobResult.host-event.details',
'jobResult.host-event.json',
'jobResult.host-events',
'jobResult.host-event.stdout'
];
if ($rootScope.jobDetailInterval && !_.includes(jobDetailStates, next.name) ) {
window.clearInterval($rootScope.jobDetailInterval);
if ($rootScope.jobResultInterval && !_.includes(jobResultStates, next.name) ) {
window.clearInterval($rootScope.jobResultInterval);
}
if ($rootScope.jobStdOutInterval && !_.includes(jobDetailStates, next.name) ) {
if ($rootScope.jobStdOutInterval && !_.includes(jobResultStates, next.name) ) {
window.clearInterval($rootScope.jobStdOutInterval);
}
@ -532,7 +336,7 @@ var tower = angular.module('Tower', [
// capture most recent URL, excluding login/logout
$rootScope.lastPath = $location.path();
$rootScope.enteredPath = $location.path();
$cookieStore.put('lastPath', $location.path());
$cookies.put('lastPath', $location.path());
}
if (Authorization.isUserLoggedIn() === false) {
@ -598,7 +402,7 @@ var tower = angular.module('Tower', [
// User not authenticated, redirect to login page
$location.path('/login');
} else {
var lastUser = $cookieStore.get('current_user'),
var lastUser = $cookies.getObject('current_user'),
timestammp = Store('sessionTime');
if (lastUser && lastUser.id && timestammp && timestammp[lastUser.id] && timestammp[lastUser.id].loggedIn) {
var stime = timestammp[lastUser.id].time,

View File

@ -1,630 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Credentials
* @description This controller's for the credentials page
*/
export function CredentialsList($scope, $rootScope, $location, $log,
$stateParams, Rest, Alert, CredentialList, Prompt, ClearScope,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
i18n) {
ClearScope();
var list = CredentialList,
defaultUrl = GetBasePath('credentials');
init();
function init() {
rbacUiControlService.canAdd('credentials')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.selected = [];
}
$scope.$on(`${list.iterator}_options`, function(event, data){
$scope.options = data.data.actions.GET;
optionsRequestDataProcessing();
});
$scope.$watchCollection(`${$scope.list.name}`, function() {
optionsRequestDataProcessing();
});
// iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
if ($scope[list.name] !== undefined) {
$scope[list.name].forEach(function(item, item_idx) {
var itm = $scope[list.name][item_idx];
// Set the item type label
if (list.fields.kind && $scope.options &&
$scope.options.hasOwnProperty('kind')) {
$scope.options.kind.choices.forEach(function(choice) {
if (choice[0] === item.kind) {
itm.kind_label = choice[1];
}
});
}
});
}
}
$scope.addCredential = function() {
$state.go('credentials.add');
};
$scope.editCredential = function(id) {
$state.go('credentials.edit', { credential_id: id });
};
$scope.deleteCredential = function(id, name) {
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function() {
if (parseInt($state.params.credential_id) === id) {
$state.go("^", null, { reload: true });
} else {
$state.go('.', null, {reload: true});
}
Wait('stop');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
});
});
};
Prompt({
hdr: i18n._('Delete'),
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,
actionText: i18n._('DELETE')
});
};
}
CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n'
];
export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange,
OwnerChange, FormSave, $state, CreateSelect2, i18n) {
ClearScope();
// Inject dynamic view
var form = CredentialForm,
defaultUrl = GetBasePath('credentials'),
url;
init();
function init() {
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
CreateSelect2({
element: '#credential_become_method',
multiple: false
});
CreateSelect2({
element: '#credential_kind',
multiple: false
});
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$scope.keyEntered = false;
$scope.permissionsTooltip = i18n._('Please save before assigning permissions');
// 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"
// I'm 99% sure this state's will never resolve block will be rejected if setup surrounding config endpoint hasn't completed
if ($rootScope.current_user && $rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
}).error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
}
if (!Empty($stateParams.user_id)) {
// Get the username based on incoming route
$scope.owner = 'user';
$scope.user = $stateParams.user_id;
OwnerChange({ scope: $scope });
url = GetBasePath('users') + $stateParams.user_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
$scope.user_username = data.username;
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user. GET status: ' + status });
});
} else if (!Empty($stateParams.team_id)) {
// Get the username based on incoming route
$scope.owner = 'team';
$scope.team = $stateParams.team_id;
OwnerChange({ scope: $scope });
url = GetBasePath('teams') + $stateParams.team_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
$scope.team_name = data.name;
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve team. GET status: ' + status });
});
} else {
// default type of owner to a user
$scope.owner = 'user';
OwnerChange({ scope: $scope });
}
$scope.$watch("ssh_key_data", function(val) {
if (val === "" || val === null || val === undefined) {
$scope.keyEntered = false;
$scope.ssh_key_unlock_ask = false;
$scope.ssh_key_unlock = "";
} else {
$scope.keyEntered = true;
}
});
// Handle Kind change
$scope.kindChange = function() {
KindChange({ scope: $scope, form: form, reset: true });
};
$scope.becomeMethodChange = function() {
BecomeMethodChange({ scope: $scope });
};
// Save
$scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) {
FormSave({ scope: $scope, mode: 'add' });
}
};
$scope.formCancel = function() {
$state.go('credentials');
};
// Password change
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
// Respond to 'Ask at runtime?' checkbox
$scope.ask = function(fld, associated) {
if ($scope[fld + '_ask']) {
$scope[fld] = 'ASK';
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
} else {
$scope[fld] = '';
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
}
};
// Click clear button
$scope.clear = function(fld, associated) {
$scope[fld] = '';
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
$scope[form.name + '_form'].$setDirty();
};
}
CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange',
'OwnerChange', 'FormSave', '$state', 'CreateSelect2', 'i18n'
];
export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt,
GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, FormSave, Wait,
$state, CreateSelect2, Authorization, i18n) {
ClearScope();
var defaultUrl = GetBasePath('credentials'),
form = CredentialForm,
base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.credential_id;
init();
function init() {
$scope.id = id;
$scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.canShareCredential = false;
Wait('start');
if (!$rootScope.current_user) {
Authorization.restoreUserInfo();
}
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options',
callback: 'choicesReadyCredential'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
if ($rootScope.current_user && $rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
Wait('stop');
}).error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
$scope.$watch('organization', function(val) {
if (val === undefined) {
$scope.permissionsTooltip = i18n._('Populate the organization field in the form below in order to set permissions.');
} else {
$scope.permissionsTooltip = '';
}
});
OwnerChange({ scope: $scope });
$scope.$watch("ssh_key_data", function(val) {
if (val === "" || val === null || val === undefined) {
$scope.keyEntered = false;
$scope.ssh_key_unlock_ask = false;
$scope.ssh_key_unlock = "";
} else {
$scope.keyEntered = true;
}
});
}
function setAskCheckboxes() {
var fld, i;
for (fld in form.fields) {
if (form.fields[fld].type === 'sensitive' && $scope[fld] === 'ASK') {
// turn on 'ask' checkbox for password fields with value of 'ASK'
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
$("#" + fld + "-clear-btn").attr("disabled", "disabled");
$scope[fld + '_ask'] = true;
} else {
$scope[fld + '_ask'] = false;
$("#" + fld + "-clear-btn").removeAttr("disabled");
}
master[fld + '_ask'] = $scope[fld + '_ask'];
}
// Set kind field to the correct option
for (i = 0; i < $scope.credential_kind_options.length; i++) {
if ($scope.kind === $scope.credential_kind_options[i].value) {
$scope.kind = $scope.credential_kind_options[i];
break;
}
}
}
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function() {
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl + ':id/');
Rest.get({ params: { id: id } })
.success(function(data) {
if (data && data.summary_fields &&
data.summary_fields.organization &&
data.summary_fields.organization.id) {
$scope.needsRoleList = true;
} else {
$scope.needsRoleList = false;
}
$scope.credential_name = data.name;
var i, fld;
for (fld in form.fields) {
if (data[fld] !== null && data[fld] !== undefined) {
$scope[fld] = data[fld];
master[fld] = $scope[fld];
}
if (form.fields[fld].type === 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
}
if (!Empty($scope.user)) {
$scope.owner = 'user';
} else {
$scope.owner = 'team';
}
master.owner = $scope.owner;
for (i = 0; i < $scope.become_options.length; i++) {
if ($scope.become_options[i].value === data.become_method) {
$scope.become_method = $scope.become_options[i];
break;
}
}
if ($scope.become_method && $scope.become_method.value === "") {
$scope.become_method = null;
}
master.become_method = $scope.become_method;
$scope.$watch('become_method', function(val) {
if (val !== null) {
if (val.value === "") {
$scope.become_username = "";
$scope.become_password = "";
}
}
});
for (i = 0; i < $scope.credential_kind_options.length; i++) {
if ($scope.credential_kind_options[i].value === data.kind) {
$scope.kind = $scope.credential_kind_options[i];
break;
}
}
KindChange({
scope: $scope,
form: form,
reset: false
});
master.kind = $scope.kind;
CreateSelect2({
element: '#credential_become_method',
multiple: false
});
CreateSelect2({
element: '#credential_kind',
multiple: false
});
switch (data.kind) {
case 'aws':
$scope.access_key = data.username;
$scope.secret_key = data.password;
master.access_key = $scope.access_key;
master.secret_key = $scope.secret_key;
break;
case 'ssh':
$scope.ssh_password = data.password;
master.ssh_password = $scope.ssh_password;
break;
case 'rax':
$scope.api_key = data.password;
master.api_key = $scope.api_key;
break;
case 'gce':
$scope.email_address = data.username;
$scope.project = data.project;
break;
case 'azure':
$scope.subscription = data.username;
break;
}
$scope.credential_obj = data;
setAskCheckboxes();
$scope.$emit('credentialLoaded');
Wait('stop');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to retrieve Credential: ' + $stateParams.id + '. GET status: ' + status
});
});
});
// Save changes to the parent
$scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) {
FormSave({ scope: $scope, mode: 'edit' });
}
};
// Handle Owner change
$scope.ownerChange = function() {
OwnerChange({ scope: $scope });
};
// Handle Kind change
$scope.kindChange = function() {
KindChange({ scope: $scope, form: form, reset: true });
};
$scope.becomeMethodChange = function() {
BecomeMethodChange({ scope: $scope });
};
$scope.formCancel = function() {
$state.transitionTo('credentials');
};
// Related set: Add button
$scope.add = function(set) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set + '/add');
};
// Related set: Edit button
$scope.edit = function(set, id) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set + '/' + id);
};
// Related set: Delete button
$scope['delete'] = function(set, itm_id, name, title) {
$rootScope.flashMessage = null;
var action = function() {
var url = defaultUrl + id + '/' + set + '/';
Rest.setUrl(url);
Rest.post({
id: itm_id,
disassociate: 1
})
.success(function() {
$('#prompt-modal').modal('hide');
// @issue: OLD SEARCH
// $scope.search(form.related[set].iterator);
})
.error(function(data, status) {
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. POST returned status: ' + status
});
});
};
Prompt({
hdr: i18n._('Delete'),
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,
actionText: i18n._('DELETE')
});
};
// Password change
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
// Respond to 'Ask at runtime?' checkbox
$scope.ask = function(fld, associated) {
if ($scope[fld + '_ask']) {
$scope[fld] = 'ASK';
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
} else {
$scope[fld] = '';
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
}
};
$scope.clear = function(fld, associated) {
$scope[fld] = '';
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
$scope[form.name + '_form'].$setDirty();
};
}
CredentialsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices',
'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange',
'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n',
];

View File

@ -1,142 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Home
* @description This controller's for the dashboard
*/
/**
* @ngdoc method
* @name controllers.function:Home#Home
* @methodOf controllers.function:Home
* @description this function loads all the widgets on the dashboard.
* dashboardReady (emit) - this is called when the preliminary parts of the dashboard have been loaded, and loads each of the widgets. Note that the
* Host count graph should only be loaded if the user is a super user
*
*/
export function Home($scope, $compile, $stateParams, $rootScope, $location, $log, Wait,
ClearScope, Rest, GetBasePath, ProcessErrors, $window, graphData){
ClearScope('home');
var dataCount = 0;
$scope.$on('ws-jobs', function () {
Rest.setUrl(GetBasePath('dashboard'));
Rest.get()
.success(function (data) {
$scope.dashboardData = data;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard host graph data: ' + status });
});
Rest.setUrl("api/v1/unified_jobs?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job");
Rest.get()
.success(function (data) {
$scope.dashboardJobsListData = data.results;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
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()
.success(function (data) {
$scope.dashboardJobTemplatesListData = data.results;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
});
if ($scope.removeDashboardDataLoadComplete) {
$scope.removeDashboardDataLoadComplete();
}
$scope.removeDashboardDataLoadComplete = $scope.$on('dashboardDataLoadComplete', function () {
dataCount++;
if (dataCount === 3) {
Wait("stop");
dataCount = 0;
}
});
if ($scope.removeDashboardReady) {
$scope.removeDashboardReady();
}
$scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) {
$scope.dashboardCountsData = data;
$scope.graphData = graphData;
$scope.$emit('dashboardDataLoadComplete');
var cleanupJobListener =
$rootScope.$on('DataReceived:JobStatusGraph', function(e, data) {
$scope.graphData.jobStatus = data;
});
$scope.$on('$destroy', function() {
cleanupJobListener();
});
});
if ($scope.removeDashboardJobsListReady) {
$scope.removeDashboardJobsListReady();
}
$scope.removeDashboardJobsListReady = $scope.$on('dashboardJobsListReady', function (e, data) {
$scope.dashboardJobsListData = data;
$scope.$emit('dashboardDataLoadComplete');
});
if ($scope.removeDashboardJobTemplatesListReady) {
$scope.removeDashboardJobTemplatesListReady();
}
$scope.removeDashboardJobTemplatesListReady = $scope.$on('dashboardJobTemplatesListReady', function (e, data) {
$scope.dashboardJobTemplatesListData = data;
$scope.$emit('dashboardDataLoadComplete');
});
$scope.refresh = function () {
Wait('start');
Rest.setUrl(GetBasePath('dashboard'));
Rest.get()
.success(function (data) {
$scope.dashboardData = data;
$scope.$emit('dashboardReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
});
Rest.setUrl("api/v1/unified_jobs?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job");
Rest.get()
.success(function (data) {
data = data.results;
$scope.$emit('dashboardJobsListReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
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()
.success(function (data) {
data = data.results;
$scope.$emit('dashboardJobTemplatesListReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard job templates list: ' + status });
});
};
$scope.refresh();
}
Home.$inject = ['$scope', '$compile', '$stateParams', '$rootScope', '$location', '$log','Wait',
'ClearScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$window', 'graphData'
];

View File

@ -1,356 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:JobEvent
* @description This controller's for the job event page
*/
export function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log, $stateParams, Rest, Alert, JobEventList, GenerateList,
Prompt, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren,
FormatDate, EventView, Wait) {
ClearScope();
var list = JobEventList,
generator = GenerateList;
// @issue: OLD SEARCH
// var defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_events/', //?parent__isnull=1';
// page;
list.base = $location.path();
$scope.job_id = $stateParams.id;
$rootScope.flashMessage = null;
$scope.selected = [];
$scope.expand = true; //on load, automatically expand all nodes
$scope.parentNode = 'parent-event'; // used in ngClass to dynamically set row level class and control
$scope.childNode = 'child-event'; // link color and cursor
if ($scope.removeSetHostLinks) {
$scope.removeSetHostLinks();
}
$scope.removeSetHostLinks = $scope.$on('SetHostLinks', function (e, inventory_id) {
for (var i = 0; i < $scope.jobevents.length; i++) {
if ($scope.jobevents[i].summary_fields.host) {
$scope.jobevents[i].hostLink = "/#/inventories/" + inventory_id;
//encodeURI($scope.jobevents[i].summary_fields.host.name);
}
}
});
function formatJSON(eventData) {
//turn JSON event data into an html form
var i, n, rows, fld, txt,
html = '',
found = false;
if (eventData.res) {
if (typeof eventData.res === 'string') {
n = eventData.res.match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
found = true;
html += "<div class=\"form-group\">\n";
html += "<label>Traceback:</label>\n";
html += "<textarea readonly class=\"form-control nowrap\" rows=\"" + rows + "\">" + eventData.res + "</textarea>\n";
html += "</div>\n";
} else {
for (fld in eventData.res) {
if ((fld === 'msg' || fld === 'stdout' || fld === 'stderr') &&
(eventData.res[fld] !== null && eventData.res[fld] !== '')) {
html += "<div class=\"form-group\">\n";
html += "<label>";
switch (fld) {
case 'msg':
case 'stdout':
html += 'Output:';
break;
case 'stderr':
html += 'Error:';
break;
}
html += "</label>\n";
n = eventData.res[fld].match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
html += "<textarea readonly class=\"form-control nowrap\" rows=\"" + rows + "\">" + eventData.res[fld] + "</textarea>\n";
//html += "<pre>" + eventData.res[fld] + "</pre>\n";
html += "</div>\n";
found = true;
}
if (fld === "results" && Array.isArray(eventData.res[fld]) && eventData.res[fld].length > 0) {
txt = '';
for (i = 0; i < eventData.res[fld].length; i++) {
txt += eventData.res[fld][i];
}
n = txt.match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
if (txt !== '') {
html += "<div class=\"form-group\">\n";
html += "<label>Results:</label>\n";
html += "<textarea readonly class=\"form-control nowrap mono-space\" rows=\"" + rows + "\">" + txt + "</textarea>\n";
//html += "<pre>" + txt + "</pre>\n";
html += "</div>\n";
found = true;
}
}
if (fld === "rc" && eventData.res[fld] !== '') {
html += "<div class=\"form-group\">\n";
html += "<label>Return Code:</label><div class=\"return-code\">" + eventData.res[fld] + "</div>\n";
//html += "<input type=\"text\" class=\"form-control nowrap mono-space\" value=\"" + eventData.res[fld] + "\" readonly >\n";
html += "</div>\n";
found = true;
}
}
}
html = (found) ? "<form class=\"event-form\">\n" + html + "</form>\n" : '';
}
if (eventData.hosts) {
html = "<span class=\"event-detail-host visible-sm\">" + eventData.host + "</span>\n" + html;
} else {
html = (html === '') ? null : html;
}
return html;
}
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
// Initialize the parent levels
generator.inject(list, { mode: 'edit', scope: $scope });
var set = $scope[list.name], i;
for (i = 0; i < set.length; i++) {
set[i].event_display = set[i].event_display.replace(/^\u00a0*/g, '');
if (set[i].event_level < 3) {
set[i].ngicon = 'fa fa-minus-square-o node-toggle';
set[i]['class'] = 'parentNode';
} else {
set[i].ngicon = 'fa fa-square-o node-no-toggle';
set[i]['class'] = 'childNode';
set[i].event_detail = $sce.trustAsHtml(formatJSON(set[i].event_data));
}
set[i].show = true;
set[i].spaces = set[i].event_level * 24;
if ($scope.jobevents[i].failed) {
$scope.jobevents[i].status = 'error';
if (i === set.length - 1) {
$scope.jobevents[i].statusBadgeToolTip = "A failure occurred durring one or more playbook tasks.";
} else if (set[i].event_level < 3) {
$scope.jobevents[i].statusBadgeToolTip = "A failure occurred within the children of this event.";
} else {
$scope.jobevents[i].statusBadgeToolTip = "A failure occurred. Click to view details";
}
} else if ($scope.jobevents[i].changed) {
$scope.jobevents[i].status = 'changed';
if (i === set.length - 1) {
$scope.jobevents[i].statusBadgeToolTip = "A change was completed durring one or more playbook tasks.";
} else if (set[i].event_level < 3) {
$scope.jobevents[i].statusBadgeToolTip = "A change was completed by one or more children of this event.";
} else {
$scope.jobevents[i].statusBadgeToolTip = "A change was completed. Click to view details";
}
} else {
$scope.jobevents[i].status = 'success';
if (i === set.length - 1) {
$scope.jobevents[i].statusBadgeToolTip = "All playbook tasks completed successfully.";
} else if (set[i].event_level < 3) {
$scope.jobevents[i].statusBadgeToolTip = "All the children of this event completed successfully.";
} else {
$scope.jobevents[i].statusBadgeToolTip = "No errors occurred. Click to view details";
}
}
//cDate = new Date(set[i].created);
//set[i].created = FormatDate(cDate);
set[i].created = $filter('longDate')(set[i].created);
}
// Need below lookup to get inventory_id, which is not on event record. Plus, good idea to get status and name
// from job in the event that there are no job event records
Rest.setUrl(GetBasePath('jobs') + $scope.job_id);
Rest.get()
.success(function (data) {
$scope.job_status = data.status;
$scope.job_name = data.summary_fields.job_template.name;
$scope.$emit('SetHostLinks', data.inventory);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job status for job: ' + $scope.job_id + '. GET status: ' + status
});
});
});
// @issue: OLD SEARCH
// SearchInit({
// scope: $scope,
// set: 'jobevents',
// list: list,
// url: defaultUrl
// });
//
// page = ($stateParams.page) ? parseInt($stateParams.page,10) - 1 : null;
//
// PaginateInit({
// scope: $scope,
// list: list,
// url: defaultUrl,
// page: page
// });
//
// // Called from Inventories tab, host failed events link:
// if ($stateParams.host) {
// $scope[list.iterator + 'SearchField'] = 'host';
// $scope[list.iterator + 'SearchValue'] = $stateParams.host;
// $scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label;
// }
//
// $scope.search(list.iterator, $stateParams.page);
$scope.toggle = function (id) {
ToggleChildren({
scope: $scope,
list: list,
id: id
});
};
$scope.viewJobEvent = function (id) {
EventView({
event_id: id
});
};
$scope.refresh = function () {
// @issue: OLD SEARCH
// $scope.jobSearchSpin = true;
$scope.jobLoading = true;
Wait('start');
// @issue: OLD SEARCH
// Refresh({
// scope: $scope,
// set: 'jobevents',
// iterator: 'jobevent',
// url: $scope.current_url
// });
};
}
JobEventsList.$inject = ['$sce', '$filter', '$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'JobEventList',
'generateList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'LookUpInit', 'ToggleChildren', 'FormatDate', 'EventView', 'Wait'
];
export function JobEventsEdit($scope, $rootScope, $compile, $location, $log, $stateParams, JobEventsForm, GenerateForm,
Rest, Alert, ProcessErrors, ClearScope, GetBasePath, FormatDate, EventView, Wait) {
ClearScope();
var form = JobEventsForm,
generator = GenerateForm,
defaultUrl = GetBasePath('base') + 'job_events/' + $stateParams.event_id + '/';
generator.inject(form, { mode: 'edit', related: true, scope: $scope});
generator.reset();
// Retrieve detail record and prepopulate the form
Wait('start');
Rest.setUrl(defaultUrl);
Rest.get()
.success(function (data) {
var cDate, fld, n, rows;
$scope.event_display = data.event_display.replace(/^\u00a0*/g, '');
for (fld in form.fields) {
switch (fld) {
case 'status':
if (data.failed) {
$scope.status = 'error';
} else if (data.changed) {
$scope.status = 'changed';
} else {
$scope.status = 'success';
}
break;
case 'created':
cDate = new Date(data.created);
$scope.created = FormatDate(cDate);
break;
case 'host':
if (data.summary_fields && data.summary_fields.host) {
$scope.host = data.summary_fields.host.name;
}
break;
case 'id':
case 'task':
case 'play':
$scope[fld] = data[fld];
break;
case 'start':
case 'end':
if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) {
cDate = new Date(data.event_data.res[fld]);
$scope[fld] = FormatDate(cDate);
}
break;
case 'msg':
case 'stdout':
case 'stderr':
case 'delta':
case 'rc':
if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) {
$scope[fld] = data.event_data.res[fld];
if (form.fields[fld].type === 'textarea') {
n = data.event_data.res[fld].match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 15) ? 5 : rows;
$('textarea[name="' + fld + '"]').attr('rows', rows);
}
}
break;
case 'module_name':
case 'module_args':
if (data.event_data.res && data.event_data.res.invocation) {
$scope[fld] = data.event_data.res.invocation.fld;
}
break;
}
}
Wait('stop');
})
.error(function (data) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve host: ' + $stateParams.event_id +
'. GET status: ' + status });
});
$scope.navigateBack = function () {
var url = '/jobs/' + $stateParams.job_id + '/job_events';
if ($stateParams.page) {
url += '?page=' + $stateParams.page;
}
$location.url(url);
};
$scope.rawView = function () {
EventView({
"event_id": $scope.id
});
};
}
JobEventsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$stateParams', 'JobEventsForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'FormatDate', 'EventView', 'Wait'
];

View File

@ -1,121 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:JobHosts
* @description This controller's for the job hosts page
*/
export function JobHostSummaryList($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, JobHostList, GenerateList,
Prompt, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
JobStatusToolTip) {
ClearScope();
var list = JobHostList,
// @issue: OLD SEARCH
// defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_host_summaries/',
view = GenerateList,
inventory;
$scope.job_id = $stateParams.id;
$scope.host_id = null;
// After a refresh, populate any needed summary field values on each row
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
// Set status, tooltips, badges icons, etc.
$scope.jobhosts.forEach(function(element, i) {
$scope.jobhosts[i].host_name = ($scope.jobhosts[i].summary_fields.host) ? $scope.jobhosts[i].summary_fields.host.name : '';
$scope.jobhosts[i].status = ($scope.jobhosts[i].failed) ? 'failed' : 'success';
$scope.jobhosts[i].statusBadgeToolTip = JobStatusToolTip($scope.jobhosts[i].status) +
" Click to view details.";
if ($scope.jobhosts[i].summary_fields.host) {
$scope.jobhosts[i].statusLinkTo = '/#/job_events/' + $scope.jobhosts[i].job + '/?host=' +
encodeURI($scope.jobhosts[i].summary_fields.host.name);
}
else {
$scope.jobhosts[i].statusLinkTo = '/#/job_events/' + $scope.jobhosts[i].job;
}
});
for (var i = 0; i < $scope.jobhosts.length; i++) {
$scope.jobhosts[i].hostLinkTo = '/#/inventories/' + inventory + '/?host_name=' +
encodeURI($scope.jobhosts[i].summary_fields.host.name);
}
});
if ($scope.removeJobReady) {
$scope.removeJobReady();
}
$scope.removeJobReady = $scope.$on('JobReady', function() {
view.inject(list, { mode: 'edit', scope: $scope });
// @issue: OLD SEARCH
// SearchInit({
// scope: $scope,
// set: 'jobhosts',
// list: list,
// url: defaultUrl
// });
//
// PaginateInit({
// scope: $scope,
// list: list,
// url: defaultUrl
// });
//
// // Called from Inventories tab, host failed events link:
// if ($stateParams.host_name) {
// $scope[list.iterator + 'SearchField'] = 'host';
// $scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
// $scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label;
// }
// $scope.search(list.iterator);
});
Rest.setUrl(GetBasePath('jobs') + $scope.job_id);
Rest.get()
.success(function (data) {
inventory = data.inventory;
$scope.job_status = data.status;
$scope.$emit('JobReady');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job status for job: ' + $scope.job_id + '. GET status: ' + status
});
});
$scope.showEvents = function (host_name, last_job) {
// When click on !Failed Events link, redirect to latest job/job_events for the host
Rest.setUrl(last_job);
Rest.get()
.success(function (data) {
$location.url('/jobs_events/' + data.id + '/?host=' + encodeURI(host_name));
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to lookup last job: ' + last_job +
'. GET status: ' + status });
});
};
$scope.refresh = function () {
// @issue: OLD SEARCH
// $scope.search(list.iterator);
};
}
JobHostSummaryList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'JobHostList',
'generateList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'JobStatusToolTip', 'Wait'
];

View File

@ -1,759 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Projects
* @description This controller's for the projects page
*/
export function ProjectsList($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, ProjectList, Prompt, ReturnToCaller, ClearScope, ProcessErrors,
GetBasePath, ProjectUpdate, Wait, GetChoices, Empty, Find, GetProjectIcon,
GetProjectToolTip, $filter, $state, rbacUiControlService, Dataset, i18n, qs) {
var list = ProjectList,
defaultUrl = GetBasePath('projects');
init();
function init() {
$scope.canAdd = false;
rbacUiControlService.canAdd('projects')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
_.forEach($scope[list.name], buildTooltips);
$rootScope.flashMessage = null;
}
$scope.$on(`${list.iterator}_options`, function(event, data){
$scope.options = data.data.actions.GET;
optionsRequestDataProcessing();
});
$scope.$watchCollection(`${$scope.list.name}`, function() {
optionsRequestDataProcessing();
}
);
// iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
if ($scope[list.name] !== undefined) {
$scope[list.name].forEach(function(item, item_idx) {
var itm = $scope[list.name][item_idx];
// Set the item type label
if (list.fields.scm_type && $scope.options &&
$scope.options.hasOwnProperty('scm_type')) {
$scope.options.scm_type.choices.forEach(function(choice) {
if (choice[0] === item.scm_type) {
itm.type_label = choice[1];
}
});
}
buildTooltips(itm);
});
}
}
function buildTooltips(project) {
project.statusIcon = GetProjectIcon(project.status);
project.statusTip = GetProjectToolTip(project.status);
project.scm_update_tooltip = i18n._("Start an SCM update");
project.scm_schedule_tooltip = i18n._("Schedule future SCM updates");
project.scm_type_class = "";
if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') {
project.statusTip = i18n._('Canceled. Click for details');
}
if (project.status === 'running' || project.status === 'updating') {
project.scm_update_tooltip = i18n._("SCM update currently running");
project.scm_type_class = "btn-disabled";
}
if (project.scm_type === 'manual') {
project.scm_update_tooltip = i18n._('Manual projects do not require an SCM update');
project.scm_schedule_tooltip = i18n._('Manual projects do not require a schedule');
project.scm_type_class = 'btn-disabled';
project.statusTip = i18n._('Not configured for SCM');
project.statusIcon = 'none';
}
}
$scope.reloadList = function(){
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
qs.search(path, $state.params[`${list.iterator}_search`])
.then(function(searchResponse) {
$scope[`${list.iterator}_dataset`] = searchResponse.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
});
};
$scope.$on(`ws-jobs`, function(e, data) {
var project;
$log.debug(data);
if ($scope.projects) {
// Assuming we have a list of projects available
project = Find({ list: $scope.projects, key: 'id', val: data.project_id });
if (project) {
// And we found the affected project
$log.debug('Received event for project: ' + project.name);
$log.debug('Status changed to: ' + data.status);
if (data.status === 'successful' || data.status === 'failed') {
$scope.reloadList();
} else {
project.scm_update_tooltip = "SCM update currently running";
project.scm_type_class = "btn-disabled";
}
project.status = data.status;
project.statusIcon = GetProjectIcon(data.status);
project.statusTip = GetProjectToolTip(data.status);
}
}
});
$scope.addProject = function() {
$state.go('projects.add');
};
$scope.editProject = function(id) {
$state.go('projects.edit', { project_id: id });
};
if ($scope.removeGoToJobDetails) {
$scope.removeGoToJobDetails();
}
$scope.removeGoToJobDetails = $scope.$on('GoToJobDetails', function(e, data) {
if (data.summary_fields.current_update || data.summary_fields.last_update) {
Wait('start');
// Grab the id from summary_fields
var id = (data.summary_fields.current_update) ? data.summary_fields.current_update.id : data.summary_fields.last_update.id;
$state.go('scmUpdateStdout', { id: id });
} else {
Alert(i18n._('No Updates Available'), i18n._('There is no SCM update information available for this project. An update has not yet been ' +
' completed. If you have not already done so, start an update for this project.'), 'alert-info');
}
});
$scope.showSCMStatus = function(id) {
// Refresh the project list
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (Empty(project.scm_type) || project.scm_type === 'Manual') {
Alert(i18n._('No SCM Configuration'), i18n._('The selected project is not configured for SCM. To configure for SCM, edit the project and provide SCM settings, ' +
'and then run an update.'), 'alert-info');
} else {
// Refresh what we have in memory to insure we're accessing the most recent status record
Rest.setUrl(project.url);
Rest.get()
.success(function(data) {
$scope.$emit('GoToJobDetails', data);
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n._('Project lookup failed. GET returned: ') + status });
});
}
};
$scope.deleteProject = function(id, name) {
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function() {
if (parseInt($state.params.project_id) === id) {
$state.go("^", null, { reload: true });
} else {
$state.go('.', null, {reload: true});
}
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Call to %s failed. DELETE returned status: '), url) + status });
})
.finally(function() {
Wait('stop');
});
};
Prompt({
hdr: i18n._('Delete'),
body: '<div class="Prompt-bodyQuery">' + i18n._('Are you sure you want to delete the project below?') + '</div>' + '<div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
action: action,
actionText: 'DELETE'
});
};
if ($scope.removeCancelUpdate) {
$scope.removeCancelUpdate();
}
$scope.removeCancelUpdate = $scope.$on('Cancel_Update', function(e, url) {
// Cancel the project update process
Rest.setUrl(url);
Rest.post()
.success(function () {
Alert(i18n._('SCM Update Cancel'), i18n._('Your request to cancel the update was submitted to the task manager.'), 'alert-info');
$scope.refresh();
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST status: '), url) + status });
});
});
if ($scope.removeCheckCancel) {
$scope.removeCheckCancel();
}
$scope.removeCheckCancel = $scope.$on('Check_Cancel', function(e, data) {
// Check that we 'can' cancel the update
var url = data.related.cancel;
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.can_cancel) {
$scope.$emit('Cancel_Update', url);
} else {
Alert(i18n._('Cancel Not Allowed'), '<div>' + i18n.sprintf(i18n._('Either you do not have access or the SCM update process completed. ' +
'Click the %sRefresh%s button to view the latest status.'), '<em>', '</em>') + '</div>', 'alert-info', null, null, null, null, true);
}
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. GET status: '), url) + status });
});
});
$scope.cancelUpdate = function(id, name) {
Rest.setUrl(GetBasePath("projects") + id);
Rest.get()
.success(function(data) {
if (data.related.current_update) {
Rest.setUrl(data.related.current_update);
Rest.get()
.success(function(data) {
$scope.$emit('Check_Cancel', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Call to %s failed. GET status: '), data.related.current_update) + status });
});
} else {
Alert(i18n._('Update Not Found'), '<div>' + i18n.sprintf(i18n._('An SCM update does not appear to be running for project: %s. Click the %sRefresh%s ' +
'button to view the latest status.'), $filter('sanitize')(name), '<em>', '</em>') + '</div>', 'alert-info',undefined,undefined,undefined,undefined,true);
}
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n._('Call to get project failed. GET status: ') + status });
});
};
$scope.SCMUpdate = function(project_id, event) {
try {
$(event.target).tooltip('hide');
} catch (e) {
// ignore
}
$scope.projects.forEach(function(project) {
if (project.id === project_id) {
if (project.scm_type === "Manual" || Empty(project.scm_type)) {
// Do not respond. Button appears greyed out as if it is disabled. Not disabled though, because we need mouse over event
// to work. So user can click, but we just won't do anything.
//Alert('Missing SCM Setup', 'Before running an SCM update, edit the project and provide the SCM access information.', 'alert-info');
} else if (project.status === 'updating' || project.status === 'running' || project.status === 'pending') {
// Alert('Update in Progress', 'The SCM update process is running. Use the Refresh button to monitor the status.', 'alert-info');
} else {
ProjectUpdate({ scope: $scope, project_id: project.id });
}
}
});
};
$scope.editSchedules = function(id) {
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) {
$state.go('projectSchedules', { id: id });
}
};
}
ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams',
'Rest', 'Alert', 'ProjectList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'ProjectUpdate', 'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon',
'GetProjectToolTip', '$filter', '$state', 'rbacUiControlService', 'Dataset', 'i18n', 'QuerySet'
];
export function ProjectsAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, GenerateForm, ProjectsForm, Rest, Alert, ProcessErrors,
GetBasePath, GetProjectPath, GetChoices, Wait, $state, CreateSelect2, i18n) {
var form = ProjectsForm(),
base = $location.path().replace(/^\//, '').split('/')[0],
defaultUrl = GetBasePath('projects'),
master = {};
init();
function init() {
Rest.setUrl(GetBasePath('projects'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a project.'), 'alert-info');
}
});
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
}
GetProjectPath({ scope: $scope, master: master });
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
var i;
for (i = 0; i < $scope.scm_type_options.length; i++) {
if ($scope.scm_type_options[i].value === '') {
$scope.scm_type_options[i].value = "manual";
//$scope.scm_type = $scope.scm_type_options[i];
break;
}
}
CreateSelect2({
element: '#project_scm_type',
multiple: false
});
$scope.scmRequired = false;
master.scm_type = $scope.scm_type;
});
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'scm_type',
variable: 'scm_type_options',
callback: 'choicesReady'
});
CreateSelect2({
element: '#local-path-select',
multiple: false
});
// Save
$scope.formSave = function() {
var i, fld, url, data = {};
data = {};
for (fld in form.fields) {
if (form.fields[fld].type === 'checkbox_group') {
for (i = 0; i < form.fields[fld].fields.length; i++) {
data[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name];
}
} else {
if (form.fields[fld].type !== 'alertblock') {
data[fld] = $scope[fld];
}
}
}
if ($scope.scm_type.value === "manual") {
data.scm_type = "";
data.local_path = $scope.local_path.value;
} else {
data.scm_type = $scope.scm_type.value;
delete data.local_path;
}
url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl;
Wait('start');
Rest.setUrl(url);
Rest.post(data)
.success(function(data) {
$scope.addedItem = data.id;
$state.go('projects.edit', { project_id: data.id }, { reload: true });
})
.error(function(data, status) {
Wait('stop');
ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'),
msg: i18n._('Failed to create new project. POST returned status: ') + status });
});
};
$scope.scmChange = function() {
// When an scm_type is set, path is not required
if ($scope.scm_type) {
$scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false;
$scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false;
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch';
}
// Dynamically update popover values
if ($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' +
i18n._('Example URLs for GIT SCM include:') +
'</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
'<p>' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
'SSH. GIT read only protocol (git://) does not use username or password information.'), '<strong>', '</strong>');
break;
case 'svn':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for Subversion SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
'<li>svn+ssh://servername.example.com/path</li></ul>';
break;
case 'hg':
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p>' + i18n._('Example URLs for Mercurial SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://bitbucket.org/username/project</li><li>ssh://hg@bitbucket.org/username/project</li>' +
'<li>ssh://server.example.com/path</li></ul>' +
'<p>' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' +
'Do not put the username and key in the URL. ' +
'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '<strong>', '</strong>');
break;
case 'insights':
$scope.pathRequired = false;
$scope.scmRequired = false;
$scope.credentialLabel = "Red Hat Insights";
break;
default:
$scope.credentialLabel = "SCM Credential";
$scope.urlPopover = '<p> ' + i18n._('URL popover text');
}
}
};
$scope.formCancel = function() {
$state.go('projects');
};
}
ProjectsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'GenerateForm', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath',
'GetProjectPath', 'GetChoices', 'Wait', '$state', 'CreateSelect2', 'i18n'];
export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
$stateParams, ProjectsForm, Rest, Alert, ProcessErrors, GenerateForm,
Prompt, ClearScope, GetBasePath, GetProjectPath, Authorization,
GetChoices, Empty, DebugForm, Wait, ProjectUpdate, $state, CreateSelect2, ToggleNotification, i18n) {
ClearScope('htmlTemplate');
var form = ProjectsForm(),
defaultUrl = GetBasePath('projects') + $stateParams.project_id + '/',
master = {},
id = $stateParams.project_id;
init();
function init() {
$scope.project_local_paths = [];
$scope.base_dir = '';
}
$scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
if ($scope.pathsReadyRemove) {
$scope.pathsReadyRemove();
}
$scope.pathsReadyRemove = $scope.$on('pathsReady', function () {
CreateSelect2({
element: '#local-path-select',
multiple: false
});
});
// After the project is loaded, retrieve each related set
if ($scope.projectLoadedRemove) {
$scope.projectLoadedRemove();
}
$scope.projectLoadedRemove = $scope.$on('projectLoaded', function() {
var opts = [];
if (Authorization.getUserInfo('is_superuser') === true) {
GetProjectPath({ scope: $scope, master: master });
} else {
opts.push({
label: $scope.local_path,
value: $scope.local_path
});
$scope.project_local_paths = opts;
$scope.local_path = $scope.project_local_paths[0];
$scope.base_dir = i18n._('You do not have access to view this property');
$scope.$emit('pathsReady');
}
$scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false;
$scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false;
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch';
Wait('stop');
$scope.scmChange();
});
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
let i;
for (i = 0; i < $scope.scm_type_options.length; i++) {
if ($scope.scm_type_options[i].value === '') {
$scope.scm_type_options[i].value = "manual";
break;
}
}
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl);
Rest.get({ params: { id: id } })
.success(function(data) {
var fld, i;
for (fld in form.fields) {
if (form.fields[fld].type === 'checkbox_group') {
for (i = 0; i < form.fields[fld].fields.length; i++) {
$scope[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name];
master[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name];
}
} else {
if (data[fld] !== undefined) {
$scope[fld] = data[fld];
master[fld] = data[fld];
}
}
if (form.fields[fld].sourceModel && data.summary_fields &&
data.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
data.scm_type = (Empty(data.scm_type)) ? 'manual' : data.scm_type;
for (i = 0; i < $scope.scm_type_options.length; i++) {
if ($scope.scm_type_options[i].value === data.scm_type) {
$scope.scm_type = $scope.scm_type_options[i];
break;
}
}
if ($scope.scm_type.value !== 'manual') {
$scope.pathRequired = false;
$scope.scmRequired = true;
} else {
$scope.pathRequired = true;
$scope.scmRequired = false;
}
master.scm_type = $scope.scm_type;
CreateSelect2({
element: '#project_scm_type',
multiple: false
});
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch';
$scope.scm_update_tooltip = i18n._("Start an SCM update");
$scope.scm_type_class = "";
if (data.status === 'running' || data.status === 'updating') {
$scope.scm_update_tooltip = i18n._("SCM update currently running");
$scope.scm_type_class = "btn-disabled";
}
if (Empty(data.scm_type)) {
$scope.scm_update_tooltip = i18n._('Manual projects do not require an SCM update');
$scope.scm_type_class = "btn-disabled";
}
$scope.project_obj = data;
$scope.name = data.name;
$scope.$emit('projectLoaded');
Wait('stop');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed to retrieve project: %s. GET status: '), id) + status
});
});
});
// Load the list of options for Kind
Wait('start');
GetChoices({
url: GetBasePath('projects'),
scope: $scope,
field: 'scm_type',
variable: 'scm_type_options',
callback: 'choicesReady'
});
$scope.toggleNotification = function(event, id, column) {
var notifier = this.notification;
try {
$(event.target).tooltip('hide');
} catch (e) {
// ignore
}
ToggleNotification({
scope: $scope,
url: $scope.project_obj.url,
notifier: notifier,
column: column,
callback: 'NotificationRefresh'
});
};
// Save changes to the parent
$scope.formSave = function() {
var fld, i, params;
GenerateForm.clearApiErrors($scope);
Wait('start');
$rootScope.flashMessage = null;
params = {};
for (fld in form.fields) {
if (form.fields[fld].type === 'checkbox_group') {
for (i = 0; i < form.fields[fld].fields.length; i++) {
params[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name];
}
} else {
if (form.fields[fld].type !== 'alertblock') {
params[fld] = $scope[fld];
}
}
}
if ($scope.scm_type.value === "manual") {
params.scm_type = "";
params.local_path = $scope.local_path.value;
} else {
params.scm_type = $scope.scm_type.value;
delete params.local_path;
}
Rest.setUrl(defaultUrl);
Rest.put(params)
.success(function() {
Wait('stop');
$state.go($state.current, {}, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Failed to update project: %s. PUT status: '), id) + status });
});
};
// Related set: Delete button
$scope['delete'] = function(set, itm_id, name, title) {
var action = function() {
var url = GetBasePath('projects') + id + '/' + set + '/';
$rootScope.flashMessage = null;
Rest.setUrl(url);
Rest.post({ id: itm_id, disassociate: 1 })
.success(function() {
$('#prompt-modal').modal('hide');
// @issue: OLD SEARCH
// $scope.search(form.related[set].iterator);
})
.error(function(data, status) {
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST returned status: '), url) + status });
});
};
Prompt({
hdr: i18n._('Delete'),
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,
actionText: i18n._('DELETE')
});
};
$scope.scmChange = function() {
if ($scope.scm_type) {
$scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false;
$scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false;
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? i18n._('Revision #') : i18n._('SCM Branch');
}
// Dynamically update popover values
if ($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.urlPopover = '<p>' + i18n._('Example URLs for GIT SCM include:') + '</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
'<p>' + i18n.sprintf(i18n._('%sNote:%s When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
'SSH. GIT read only protocol (git://) does not use username or password information.'), '<strong>', '</strong>');
break;
case 'svn':
$scope.urlPopover = '<p>' + i18n._('Example URLs for Subversion SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
'<li>svn+ssh://servername.example.com/path</li></ul>';
break;
case 'hg':
$scope.urlPopover = '<p>' + i18n._('Example URLs for Mercurial SCM include:') + '</p>' +
'<ul class=\"no-bullets\"><li>https://bitbucket.org/username/project</li><li>ssh://hg@bitbucket.org/username/project</li>' +
'<li>ssh://server.example.com/path</li></ul>' +
'<p>' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' +
'Do not put the username and key in the URL. ' +
'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '<strong>', '</strong>');
break;
case 'insights':
$scope.pathRequired = false;
$scope.scmRequired = false;
$scope.credentialLabel = "Red Hat Insights";
break;
default:
$scope.urlPopover = '<p> ' + i18n._('URL popover text');
}
}
};
$scope.SCMUpdate = function() {
if ($scope.project_obj.scm_type === "Manual" || Empty($scope.project_obj.scm_type)) {
// ignore
} else if ($scope.project_obj.status === 'updating' || $scope.project_obj.status === 'running' || $scope.project_obj.status === 'pending') {
Alert(i18n._('Update in Progress'), i18n._('The SCM update process is running.'), 'alert-info');
} else {
ProjectUpdate({ scope: $scope, project_id: $scope.project_obj.id });
}
};
$scope.formCancel = function() {
$state.transitionTo('projects');
};
}
ProjectsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GenerateForm',
'Prompt', 'ClearScope', 'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty',
'DebugForm', 'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification', 'i18n'];

View File

@ -1,86 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Schedules
* @description This controller's for schedules
*/
export function ScheduleEditController($scope, $compile, $location, $stateParams, SchedulesList, Rest, ProcessErrors, ReturnToCaller, ClearScope,
GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices) {
ClearScope();
var base, id, url, parentObject;
// base = $location.path().replace(/^\//, '').split('/')[0];
// if ($scope.removePostRefresh) {
// $scope.removePostRefresh();
// }
// $scope.removePostRefresh = $scope.$on('PostRefresh', function() {
// var list = $scope.schedules;
// list.forEach(function(element, idx) {
// list[idx].play_tip = (element.enabled) ? 'Schedule is Active. Click to temporarily stop.' : 'Schedule is temporarily stopped. Click to activate.';
// });
// });
// if ($scope.removeParentLoaded) {
// $scope.removeParentLoaded();
// }
// $scope.removeParentLoaded = $scope.$on('ParentLoaded', function() {
// url += "schedules/";
// SchedulesList.well = true;
// LoadSchedulesScope({
// parent_scope: $scope,
// scope: $scope,
// list: SchedulesList,
// id: 'schedule-list-target',
// url: url,
// pageSize: 20
// });
// });
if ($scope.removeChoicesReady) {
$scope.removeChocesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
// Load the parent object
id = $stateParams.id;
url = GetBasePath(base) + id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
parentObject = data;
$scope.$emit('ParentLoaded');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status });
});
});
$scope.refreshJobs = function() {
// @issue: OLD SEARCH
// $scope.search(SchedulesList.iterator);
};
Wait('start');
GetChoices({
scope: $scope,
url: GetBasePath('unified_jobs'), //'/static/sample/data/types/data.json'
field: 'type',
variable: 'type_choices',
callback: 'choicesReady'
});
}
ScheduleEditController.$inject = [ '$scope', '$compile', '$location', '$stateParams', 'SchedulesList', 'Rest', 'ProcessErrors', 'ReturnToCaller', 'ClearScope',
'GetBasePath', 'Wait', 'Find', 'LoadSchedulesScope', 'GetChoices'];

View File

@ -1,119 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Sockets
* @description This controller's for controlling websockets
* discuss
*/
export function SocketsController ($scope, $compile, ClearScope, Socket) {
ClearScope();
var test_scope = $scope.$new(),
jobs_scope = $scope.$new(),
job_events_scope = $scope.$new(),
schedules_scope = $scope.$new(),
test_socket = Socket({ scope: test_scope, endpoint: "test" }),
jobs_socket = Socket({ scope: jobs_scope, endpoint: "jobs" }),
schedules_socket = Socket({ scope: schedules_scope, endpoint: "schedules" }),
job_events_socket = Socket({ scope: job_events_scope, endpoint: "job_events" }),
e, html;
test_scope.messages = [];
jobs_scope.messages = [];
schedules_scope.messages = [];
job_events_scope.messages = [];
html = "<div class=\"section-title\"><strong>Socket url</strong>: {{ socket_url }} &nbsp;<strong>Status:</strong> {{ socket_status }} {{ socket_reason }}</div>\n" +
"<div class=\"message-title\">\n" +
"<h5>Received Messages:</h5>\n" +
"</div>\n" +
"<div class=\"well message-section\">\n" +
"<ul>\n" +
"<li ng-repeat=\"message in messages\">{{ message }} </li>\n" +
"</ul>\n" +
"</div>\n";
e = angular.element(document.getElementById('test-container'));
e.append(html);
$compile(e)(test_scope);
e = angular.element(document.getElementById('schedules-container'));
e.append(html);
$compile(e)(schedules_scope);
e = angular.element(document.getElementById('jobs-container'));
e.append(html);
$compile(e)(jobs_scope);
html = "<div class=\"row events-section\">\n" +
"<div class=\"col-md-6\">\n" +
"<div class=\"section-title\"><strong>Socket url</strong>: {{ socket_url }} &nbsp;<strong>Status:</strong> {{ socket_status }} {{ socket_reason }}</div>\n" +
"</div>\n" +
"<div class=\"col-md-6\">\n" +
"<form class=\"form-inline\">\n" +
"<div class=\"form-group\">\n" +
"<label for=\"job_id\">Job Id</label>\n" +
"<input type=\"text\" name=\"job_id\" id=\"job_id\" ng-model=\"job_id\" class=\"input-sm form-control\">\n" +
"</div>\n" +
"<button type=\"submit\" ng-disabled=\"!job_id\" ng-click=\"subscribeToJobEvent()\" class=\"btn btn-sm btn-primary\"><i class=\"fa fa-check\"></i> Subscribe</button>\n" +
"</form>\n" +
"</div>\n" +
"</div>\n" +
"<div class=\"message-title\">" +
"<p>Subscribed to events for job: {{ jobs_list }}</p>\n" +
"<h5>Received Messages:</h5>\n" +
"</div>\n" +
"<div class=\"well message-section\" id=\"event-message-container\">\n" +
"<ul>\n" +
"<li ng-repeat=\"message in messages\">{{ message }} </li>\n" +
"</ul>\n" +
"</div>\n";
e = angular.element(document.getElementById('job-events-container'));
e.append(html);
$compile(e)(job_events_scope);
schedules_scope.url = schedules_socket.getUrl();
test_scope.url = test_socket.getUrl();
jobs_scope.url = jobs_socket.getUrl();
job_events_scope.url = job_events_socket.getUrl();
test_scope.messages.push('Message Displayed Before Connection');
test_socket.on('test', function(data) {
test_scope.messages.push(data);
});
schedules_socket.on("schedule_changed", function(data) {
schedules_scope.messages.push(data);
});
jobs_socket.on("status_changed", function(data) {
jobs_scope.messages.push(data);
});
jobs_socket.on("summary_complete", function(data) {
jobs_scope.messages.push(data);
});
job_events_scope.jobs_list = [];
job_events_scope.subscribeToJobEvent = function() {
job_events_scope.jobs_list.push(job_events_scope.job_id);
job_events_socket.on("job_events-" + job_events_scope.job_id, function(data) {
job_events_scope.messages.push(data);
setTimeout(function() {
$(document).scrollTop($(document).prop("scrollHeight"));
$('#event-message-container').scrollTop($('#event-message-container').prop("scrollHeight"));
}, 300);
});
};
}
SocketsController.$inject = [ '$scope', '$compile', 'ClearScope', 'Socket'];

View File

@ -1,247 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Teams
* @description This controller's for teams
*/
export function TeamsList($scope, $rootScope, $log, $stateParams,
Rest, Alert, TeamList, Prompt, ClearScope, ProcessErrors,
GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) {
ClearScope();
var list = TeamList,
defaultUrl = GetBasePath('teams');
init();
function init() {
$scope.canAdd = false;
rbacUiControlService.canAdd('teams')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
_.forEach($scope[list.name], (team) => {
team.organization_name = team.summary_fields.organization.name;
});
$scope.selected = [];
}
$scope.addTeam = function() {
$state.go('teams.add');
};
$scope.editTeam = function(id) {
$state.go('teams.edit', { team_id: id });
};
$scope.deleteTeam = function(id, name) {
var action = function() {
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function() {
Wait('stop');
$('#prompt-modal').modal('hide');
if (parseInt($state.params.team_id) === id) {
$state.go('^', null, { reload: true });
} else {
$state.go('.', null, { reload: true });
}
})
.error(function(data, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
});
});
};
Prompt({
hdr: 'Delete',
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the team below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
action: action,
actionText: 'DELETE'
});
};
}
TeamsList.$inject = ['$scope', '$rootScope', '$log',
'$stateParams', 'Rest', 'Alert', 'TeamList', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset'
];
export function TeamsAdd($scope, $rootScope, $stateParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, Wait, $state) {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//$scope.
Rest.setUrl(GetBasePath('teams'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a team.', 'alert-info');
}
});
// Inject dynamic view
var defaultUrl = GetBasePath('teams'),
form = TeamForm;
init();
function init() {
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$rootScope.flashMessage = null;
}
// Save
$scope.formSave = function() {
var fld, data;
GenerateForm.clearApiErrors($scope);
Wait('start');
Rest.setUrl(defaultUrl);
data = {};
for (fld in form.fields) {
data[fld] = $scope[fld];
}
Rest.post(data)
.success(function(data) {
Wait('stop');
$rootScope.flashMessage = "New team successfully created!";
$rootScope.$broadcast("EditIndicatorChange", "users", data.id);
$state.go('teams.edit', { team_id: data.id }, { reload: true });
})
.error(function(data, status) {
Wait('stop');
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to add new team. Post returned status: ' +
status
});
});
};
$scope.formCancel = function() {
$state.go('teams');
};
}
TeamsAdd.$inject = ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state'
];
export function TeamsEdit($scope, $rootScope, $stateParams,
TeamForm, Rest, ProcessErrors, ClearScope, GetBasePath, Wait, $state) {
ClearScope();
var form = TeamForm,
id = $stateParams.team_id,
defaultUrl = GetBasePath('teams') + id;
init();
function init() {
$scope.team_id = id;
Rest.setUrl(defaultUrl);
Wait('start');
Rest.get(defaultUrl).success(function(data) {
setScopeFields(data);
$scope.organization_name = data.summary_fields.organization.name;
$scope.team_obj = data;
Wait('stop');
});
$scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
}
// @issue I think all this really want to do is _.forEach(form.fields, (field) =>{ $scope[field] = data[field]})
function setScopeFields(data) {
_(data)
.pick(function(value, key) {
return form.fields.hasOwnProperty(key) === true;
})
.forEach(function(value, key) {
$scope[key] = value;
})
.value();
return;
}
// prepares a data payload for a PUT request to the API
function processNewData(fields) {
var data = {};
_.forEach(fields, function(value, key) {
if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) {
data[key] = $scope[key];
}
});
return data;
}
$scope.formCancel = function() {
$state.go('teams', null, { reload: true });
};
$scope.formSave = function() {
$rootScope.flashMessage = null;
if ($scope[form.name + '_form'].$valid) {
var data = processNewData(form.fields);
Rest.setUrl(defaultUrl);
Rest.put(data).success(function() {
$state.go($state.current, null, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status
});
});
}
};
init();
$scope.convertApiUrl = function(str) {
if (str) {
return str.replace("api/v1", "#");
} else {
return null;
}
};
}
TeamsEdit.$inject = ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state'
];

View File

@ -1,357 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Users
* @description This controller's the Users page
*/
import { N_ } from "../i18n";
const user_type_options = [
{ type: 'normal', label: N_('Normal User') },
{ type: 'system_auditor', label: N_('System Auditor') },
{ type: 'system_administrator', label: N_('System Administrator') },
];
function user_type_sync($scope) {
return (type_option) => {
$scope.is_superuser = false;
$scope.is_system_auditor = false;
switch (type_option.type) {
case 'system_administrator':
$scope.is_superuser = true;
break;
case 'system_auditor':
$scope.is_system_auditor = true;
break;
}
};
}
export function UsersList($scope, $rootScope, $stateParams,
Rest, Alert, UserList, Prompt, ClearScope, ProcessErrors, GetBasePath,
Wait, $state, $filter, rbacUiControlService, Dataset, i18n) {
for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label);
}
ClearScope();
var list = UserList,
defaultUrl = GetBasePath('users');
init();
function init() {
$scope.canAdd = false;
rbacUiControlService.canAdd('users')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$rootScope.flashMessage = null;
$scope.selected = [];
}
$scope.addUser = function() {
$state.go('users.add');
};
$scope.editUser = function(id) {
$state.go('users.edit', { user_id: id });
};
$scope.deleteUser = function(id, name) {
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function() {
if (parseInt($state.params.user_id) === id) {
$state.go('^', null, { reload: true });
} else {
$state.go('.', null, { reload: true });
}
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Call to %s failed. DELETE returned status: '), url) + status
});
});
};
Prompt({
hdr: i18n._('Delete'),
body: '<div class="Prompt-bodyQuery">' + i18n._('Are you sure you want to delete the user below?') + '</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
action: action,
actionText: i18n._('DELETE')
});
};
}
UsersList.$inject = ['$scope', '$rootScope', '$stateParams',
'Rest', 'Alert', 'UserList', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath',
'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n'
];
export function UsersAdd($scope, $rootScope, $stateParams, UserForm,
GenerateForm, Rest, Alert, ProcessErrors, ReturnToCaller, ClearScope,
GetBasePath, ResetForm, Wait, CreateSelect2, $state, $location, i18n) {
ClearScope();
var defaultUrl = GetBasePath('organizations'),
form = UserForm;
init();
function init() {
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$scope.ldap_user = false;
$scope.not_ldap_user = !$scope.ldap_user;
$scope.ldap_dn = null;
$scope.socialAuthUser = false;
$scope.external_account = null;
Rest.setUrl(GetBasePath('users'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a user.'), 'alert-info');
}
});
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
CreateSelect2({
element: '#user_user_type',
multiple: false
});
}
// Save
$scope.formSave = function() {
var fld, data = {};
if ($scope[form.name + '_form'].$valid) {
if ($scope.organization !== undefined && $scope.organization !== null && $scope.organization !== '') {
Rest.setUrl(defaultUrl + $scope.organization + '/users/');
for (fld in form.fields) {
if (form.fields[fld].realName) {
data[form.fields[fld].realName] = $scope[fld];
} else {
data[fld] = $scope[fld];
}
}
data.is_superuser = $scope.is_superuser;
data.is_system_auditor = $scope.is_system_auditor;
Wait('start');
Rest.post(data)
.success(function(data) {
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'users') {
$rootScope.flashMessage = i18n._('New user successfully created!');
$rootScope.$broadcast("EditIndicatorChange", "users", data.id);
$state.go('users.edit', { user_id: data.id }, { reload: true });
} else {
ReturnToCaller(1);
}
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n._('Failed to add new user. POST returned status: ') + status });
});
} else {
$scope.organization_name_api_error = i18n._('A value is required');
}
}
};
$scope.formCancel = function() {
$state.go('users');
};
// Password change
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
}
UsersAdd.$inject = ['$scope', '$rootScope', '$stateParams', 'UserForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath',
'ResetForm', 'Wait', 'CreateSelect2', '$state', '$location', 'i18n'
];
export function UsersEdit($scope, $rootScope, $location,
$stateParams, UserForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ResetForm, Wait, CreateSelect2, $state, i18n) {
for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label);
}
ClearScope();
var form = UserForm,
master = {},
id = $stateParams.user_id,
defaultUrl = GetBasePath('users') + id;
init();
function init() {
$scope.hidePagination = false;
$scope.hideSmartSearch = false;
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
$scope.$watch('is_superuser', hidePermissionsTabSmartSearchAndPaginationIfSuperUser($scope));
Rest.setUrl(defaultUrl);
Wait('start');
Rest.get(defaultUrl).success(function(data) {
$scope.user_id = id;
$scope.ldap_user = (data.ldap_dn !== null && data.ldap_dn !== undefined && data.ldap_dn !== '') ? true : false;
$scope.not_ldap_user = !$scope.ldap_user;
master.ldap_user = $scope.ldap_user;
$scope.socialAuthUser = (data.auth.length > 0) ? true : false;
$scope.external_account = data.external_account;
$scope.user_type = $scope.user_type_options[0];
$scope.is_system_auditor = false;
$scope.is_superuser = false;
if (data.is_system_auditor) {
$scope.user_type = $scope.user_type_options[1];
$scope.is_system_auditor = true;
}
if (data.is_superuser) {
$scope.user_type = $scope.user_type_options[2];
$scope.is_superuser = true;
}
$scope.user_obj = data;
$scope.name = data.username;
CreateSelect2({
element: '#user_user_type',
multiple: false
});
$scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
setScopeFields(data);
Wait('stop');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status
});
});
}
// Organizations and Teams tab pagination is hidden through other mechanism
function hidePermissionsTabSmartSearchAndPaginationIfSuperUser(scope) {
return function(isSuperuserNewValue) {
let shouldHide = isSuperuserNewValue;
if (shouldHide === true) {
scope.hidePagination = true;
scope.hideSmartSearch = true;
} else if (shouldHide === false) {
scope.hidePagination = false;
scope.hideSmartSearch = false;
}
};
}
function setScopeFields(data) {
_(data)
.pick(function(value, key) {
return form.fields.hasOwnProperty(key) === true;
})
.forEach(function(value, key) {
$scope[key] = value;
})
.value();
return;
}
$scope.convertApiUrl = function(str) {
if (str) {
return str.replace("api/v1", "#");
} else {
return null;
}
};
// prepares a data payload for a PUT request to the API
var processNewData = function(fields) {
var data = {};
_.forEach(fields, function(value, key) {
if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) {
data[key] = $scope[key];
}
});
data.is_superuser = $scope.is_superuser;
data.is_system_auditor = $scope.is_system_auditor;
return data;
};
$scope.formCancel = function() {
$state.go('users', null, { reload: true });
};
$scope.formSave = function() {
$rootScope.flashMessage = null;
if ($scope[form.name + '_form'].$valid) {
Rest.setUrl(defaultUrl + '/');
var data = processNewData(form.fields);
Rest.put(data).success(function() {
$state.go($state.current, null, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status
});
});
}
};
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
$rootScope.flashMessage = null;
};
}
UsersEdit.$inject = ['$scope', '$rootScope', '$location',
'$stateParams', 'UserForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
'ResetForm', 'Wait', 'CreateSelect2', '$state', 'i18n'
];

View File

@ -0,0 +1,177 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'BecomeMethodChange',
'OwnerChange', 'CredentialFormSave', '$state', 'CreateSelect2', 'i18n',
function($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, GetChoices, Empty, KindChange, BecomeMethodChange,
OwnerChange, CredentialFormSave, $state, CreateSelect2, i18n) {
ClearScope();
// Inject dynamic view
var form = CredentialForm,
defaultUrl = GetBasePath('credentials'),
url;
init();
function init() {
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
CreateSelect2({
element: '#credential_become_method',
multiple: false
});
CreateSelect2({
element: '#credential_kind',
multiple: false
});
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$scope.keyEntered = false;
$scope.permissionsTooltip = i18n._('Please save before assigning permissions');
// 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"
// I'm 99% sure this state's will never resolve block will be rejected if setup surrounding config endpoint hasn't completed
if ($rootScope.current_user && $rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
}).error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
}
if (!Empty($stateParams.user_id)) {
// Get the username based on incoming route
$scope.owner = 'user';
$scope.user = $stateParams.user_id;
OwnerChange({ scope: $scope });
url = GetBasePath('users') + $stateParams.user_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
$scope.user_username = data.username;
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user. GET status: ' + status });
});
} else if (!Empty($stateParams.team_id)) {
// Get the username based on incoming route
$scope.owner = 'team';
$scope.team = $stateParams.team_id;
OwnerChange({ scope: $scope });
url = GetBasePath('teams') + $stateParams.team_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
$scope.team_name = data.name;
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve team. GET status: ' + status });
});
} else {
// default type of owner to a user
$scope.owner = 'user';
OwnerChange({ scope: $scope });
}
$scope.$watch("ssh_key_data", function(val) {
if (val === "" || val === null || val === undefined) {
$scope.keyEntered = false;
$scope.ssh_key_unlock_ask = false;
$scope.ssh_key_unlock = "";
} else {
$scope.keyEntered = true;
}
});
// Handle Kind change
$scope.kindChange = function() {
KindChange({ scope: $scope, form: form, reset: true });
};
$scope.becomeMethodChange = function() {
BecomeMethodChange({ scope: $scope });
};
// Save
$scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) {
CredentialFormSave({ scope: $scope, mode: 'add' });
}
};
$scope.formCancel = function() {
$state.go('credentials');
};
// Password change
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
// Respond to 'Ask at runtime?' checkbox
$scope.ask = function(fld, associated) {
if ($scope[fld + '_ask']) {
$scope[fld] = 'ASK';
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
} else {
$scope[fld] = '';
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
}
};
// Click clear button
$scope.clear = function(fld, associated) {
$scope[fld] = '';
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
$scope[form.name + '_form'].$setDirty();
};
}
];

View File

@ -0,0 +1,344 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices',
'KindChange', 'BecomeMethodChange', 'Empty', 'OwnerChange',
'CredentialFormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization', 'i18n',
function($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt,
GetBasePath, GetChoices, KindChange, BecomeMethodChange, Empty, OwnerChange, CredentialFormSave, Wait,
$state, CreateSelect2, Authorization, i18n) {
ClearScope();
var defaultUrl = GetBasePath('credentials'),
form = CredentialForm,
base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.credential_id;
init();
function init() {
$scope.id = id;
$scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.canShareCredential = false;
Wait('start');
if (!$rootScope.current_user) {
Authorization.restoreUserInfo();
}
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options',
callback: 'choicesReadyCredential'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
if ($rootScope.current_user && $rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
Wait('stop');
}).error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
$scope.$watch('organization', function(val) {
if (val === undefined) {
$scope.permissionsTooltip = i18n._('Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.');
} else {
$scope.permissionsTooltip = '';
}
});
setAskCheckboxes();
OwnerChange({ scope: $scope });
$scope.$watch("ssh_key_data", function(val) {
if (val === "" || val === null || val === undefined) {
$scope.keyEntered = false;
$scope.ssh_key_unlock_ask = false;
$scope.ssh_key_unlock = "";
} else {
$scope.keyEntered = true;
}
});
}
function setAskCheckboxes() {
var fld, i;
for (fld in form.fields) {
if (form.fields[fld].type === 'sensitive' && $scope[fld] === 'ASK') {
// turn on 'ask' checkbox for password fields with value of 'ASK'
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
$("#" + fld + "-clear-btn").attr("disabled", "disabled");
$scope[fld + '_ask'] = true;
} else {
$scope[fld + '_ask'] = false;
$("#" + fld + "-clear-btn").removeAttr("disabled");
}
master[fld + '_ask'] = $scope[fld + '_ask'];
}
// Set kind field to the correct option
for (i = 0; i < $scope.credential_kind_options.length; i++) {
if ($scope.kind === $scope.credential_kind_options[i].value) {
$scope.kind = $scope.credential_kind_options[i];
break;
}
}
}
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function() {
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl + ':id/');
Rest.get({ params: { id: id } })
.success(function(data) {
if (data && data.summary_fields &&
data.summary_fields.organization &&
data.summary_fields.organization.id) {
$scope.needsRoleList = true;
} else {
$scope.needsRoleList = false;
}
$scope.credential_name = data.name;
var i, fld;
for (fld in form.fields) {
if (data[fld] !== null && data[fld] !== undefined) {
$scope[fld] = data[fld];
master[fld] = $scope[fld];
}
if (form.fields[fld].type === 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
}
if (!Empty($scope.user)) {
$scope.owner = 'user';
} else {
$scope.owner = 'team';
}
master.owner = $scope.owner;
for (i = 0; i < $scope.become_options.length; i++) {
if ($scope.become_options[i].value === data.become_method) {
$scope.become_method = $scope.become_options[i];
break;
}
}
if ($scope.become_method && $scope.become_method.value === "") {
$scope.become_method = null;
}
master.become_method = $scope.become_method;
$scope.$watch('become_method', function(val) {
if (val !== null) {
if (val.value === "") {
$scope.become_username = "";
$scope.become_password = "";
}
}
});
for (i = 0; i < $scope.credential_kind_options.length; i++) {
if ($scope.credential_kind_options[i].value === data.kind) {
$scope.kind = $scope.credential_kind_options[i];
break;
}
}
KindChange({
scope: $scope,
form: form,
reset: false
});
master.kind = $scope.kind;
CreateSelect2({
element: '#credential_become_method',
multiple: false
});
CreateSelect2({
element: '#credential_kind',
multiple: false
});
switch (data.kind) {
case 'aws':
$scope.access_key = data.username;
$scope.secret_key = data.password;
master.access_key = $scope.access_key;
master.secret_key = $scope.secret_key;
break;
case 'ssh':
$scope.ssh_password = data.password;
master.ssh_password = $scope.ssh_password;
break;
case 'rax':
$scope.api_key = data.password;
master.api_key = $scope.api_key;
break;
case 'gce':
$scope.email_address = data.username;
$scope.project = data.project;
break;
case 'azure':
$scope.subscription = data.username;
break;
}
$scope.credential_obj = data;
$scope.$emit('credentialLoaded');
Wait('stop');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to retrieve Credential: ' + $stateParams.id + '. GET status: ' + status
});
});
});
// Save changes to the parent
$scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) {
CredentialFormSave({ scope: $scope, mode: 'edit' });
}
};
// Handle Owner change
$scope.ownerChange = function() {
OwnerChange({ scope: $scope });
};
// Handle Kind change
$scope.kindChange = function() {
KindChange({ scope: $scope, form: form, reset: true });
};
$scope.becomeMethodChange = function() {
BecomeMethodChange({ scope: $scope });
};
$scope.formCancel = function() {
$state.transitionTo('credentials');
};
// Related set: Add button
$scope.add = function(set) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set + '/add');
};
// Related set: Edit button
$scope.edit = function(set, id) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set + '/' + id);
};
// Related set: Delete button
$scope['delete'] = function(set, itm_id, name, title) {
$rootScope.flashMessage = null;
var action = function() {
var url = defaultUrl + id + '/' + set + '/';
Rest.setUrl(url);
Rest.post({
id: itm_id,
disassociate: 1
})
.success(function() {
$('#prompt-modal').modal('hide');
})
.error(function(data, status) {
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. POST returned status: ' + status
});
});
};
Prompt({
hdr: i18n._('Delete'),
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,
actionText: i18n._('DELETE')
});
};
// Password change
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
// Respond to 'Ask at runtime?' checkbox
$scope.ask = function(fld, associated) {
if ($scope[fld + '_ask']) {
$scope[fld] = 'ASK';
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
} else {
$scope[fld] = '';
$("#" + form.name + "_" + fld + "_input").attr("type", "password");
$("#" + form.name + "_" + fld + "_show_input_button").html("Show");
if (associated !== "undefined") {
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
$("#" + form.name + "_" + fld + "_show_input_button").html("Hide");
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
}
}
};
$scope.clear = function(fld, associated) {
$scope[fld] = '';
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
$scope[form.name + '_form'].$setDirty();
};
}
];

View File

@ -0,0 +1,105 @@
export default
function BecomeMethodChange(Empty, i18n) {
return function(params) {
var scope = params.scope;
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
};
}
BecomeMethodChange.$inject =
[ 'Empty', 'i18n' ];

View File

@ -0,0 +1,105 @@
export default
function CredentialFormSave($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) {
return function(params) {
var scope = params.scope,
mode = params.mode,
form = CredentialForm,
data = {}, fld, url;
for (fld in form.fields) {
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
fld !== 'ssh_password') {
if (fld === "organization" && !scope[fld]) {
data.user = $rootScope.current_user.id;
} else if (scope[fld] === null) {
data[fld] = "";
} else {
data[fld] = scope[fld];
}
}
}
data.kind = scope.kind.value;
if (scope.become_method === null || typeof scope.become_method === 'undefined') {
data.become_method = "";
data.become_username = "";
data.become_password = "";
} else {
data.become_method = (scope.become_method.value) ? scope.become_method.value : "";
}
switch (data.kind) {
case 'ssh':
data.password = scope.ssh_password;
break;
case 'aws':
data.username = scope.access_key;
data.password = scope.secret_key;
break;
case 'rax':
data.password = scope.api_key;
break;
case 'gce':
data.username = scope.email_address;
data.project = scope.project;
break;
case 'azure':
data.username = scope.subscription;
}
Wait('start');
if (mode === 'add') {
url = GetBasePath("credentials");
Rest.setUrl(url);
Rest.post(data)
.success(function (data) {
scope.addedItem = data.id;
Wait('stop');
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'credentials') {
$state.go('credentials.edit', {credential_id: data.id}, {reload: true});
}
else {
ReturnToCaller(1);
}
})
.error(function (data, status) {
Wait('stop');
// TODO: hopefully this conditional error handling will to away in a future version of tower. The reason why we cannot
// simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show
// the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the
// error is never shown. In the future, the API will hopefully either behave or respond differently.
if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) {
scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported.");
}
else {
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to create new Credential. POST status: ') + status
});
}
});
} else {
url = GetBasePath('credentials') + scope.id + '/';
Rest.setUrl(url);
Rest.put(data)
.success(function () {
Wait('stop');
$state.go($state.current, {}, {reload: true});
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to update Credential. PUT status: ') + status
});
});
}
};
}
CredentialFormSave.$inject =
[ '$rootScope', '$location', 'Alert', 'Rest',
'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm',
'ReturnToCaller', 'Wait', '$state', 'i18n'
];

View File

@ -0,0 +1,192 @@
export default
function KindChange(Empty, i18n) {
return function(params) {
var scope = params.scope,
reset = params.reset,
collapse, id;
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.subscription_required = false;
scope.key_description = i18n.sprintf(i18n._("Paste the contents of the SSH private key file.%s or click to close%s"), "<div class=\"popover-footer\"><span class=\"key\">Esc</span>", "</div>");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.passwordLabel = i18n._('Password');
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.domain_required = false;
scope.subscription_required = false;
scope.key_description = i18n._("Paste the contents of the SSH private key file.");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.projectLabel = '';
scope.domainLabel = '';
scope.project_required = false;
scope.passwordLabel = i18n._('Password (API Key)');
scope.projectPopOver = "<p>" + i18n._("The project value") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host value") + "</p>";
scope.ssh_key_data_api_error = '';
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
// Reset all the field values related to Kind.
if (reset) {
scope.access_key = null;
scope.secret_key = null;
scope.api_key = null;
scope.username = null;
scope.password = null;
scope.password_confirm = null;
scope.ssh_key_data = null;
scope.ssh_key_unlock = null;
scope.ssh_key_unlock_confirm = null;
scope.become_username = null;
scope.become_password = null;
scope.authorize = false;
scope.authorize_password = null;
}
// Collapse or open help widget based on whether scm value is selected
collapse = $('#credential_kind').parent().find('.panel-collapse').first();
id = collapse.attr('id');
if (!Empty(scope.kind) && scope.kind.value !== '') {
if ($('#' + id + '-icon').hasClass('icon-minus')) {
scope.accordionToggle('#' + id);
}
} else {
if ($('#' + id + '-icon').hasClass('icon-plus')) {
scope.accordionToggle('#' + id);
}
}
};
}
KindChange.$inject =
[ 'Empty', 'i18n' ];

View File

@ -0,0 +1,18 @@
export default
function OwnerChange() {
return function(params) {
var scope = params.scope,
owner = scope.owner;
if (owner === 'team') {
scope.team_required = true;
scope.user_required = false;
scope.user = null;
scope.user_username = null;
} else {
scope.team_required = false;
scope.user_required = true;
scope.team = null;
scope.team_name = null;
}
};
}

View File

@ -0,0 +1,104 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n',
function($scope, $rootScope, $location, $log,
$stateParams, Rest, Alert, CredentialList, Prompt, ClearScope,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
i18n) {
ClearScope();
var list = CredentialList,
defaultUrl = GetBasePath('credentials');
init();
function init() {
rbacUiControlService.canAdd('credentials')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.selected = [];
}
$scope.$on(`${list.iterator}_options`, function(event, data){
$scope.options = data.data.actions.GET;
optionsRequestDataProcessing();
});
$scope.$watchCollection(`${$scope.list.name}`, function() {
optionsRequestDataProcessing();
});
// iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
if ($scope[list.name] !== undefined) {
$scope[list.name].forEach(function(item, item_idx) {
var itm = $scope[list.name][item_idx];
// Set the item type label
if (list.fields.kind && $scope.options &&
$scope.options.hasOwnProperty('kind')) {
$scope.options.kind.choices.forEach(function(choice) {
if (choice[0] === item.kind) {
itm.kind_label = choice[1];
}
});
}
});
}
}
$scope.addCredential = function() {
$state.go('credentials.add');
};
$scope.editCredential = function(id) {
$state.go('credentials.edit', { credential_id: id });
};
$scope.deleteCredential = function(id, name) {
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function() {
if (parseInt($state.params.credential_id) === id) {
$state.go("^", null, { reload: true });
} else {
$state.go('.', null, {reload: true});
}
Wait('stop');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
});
});
};
Prompt({
hdr: i18n._('Delete'),
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,
actionText: i18n._('DELETE')
});
};
}
];

View File

@ -5,7 +5,53 @@
*************************************************/
import ownerList from './ownerList.directive';
import CredentialsList from './list/credentials-list.controller';
import CredentialsAdd from './add/credentials-add.controller';
import CredentialsEdit from './edit/credentials-edit.controller';
import BecomeMethodChange from './factories/become-method-change.factory';
import CredentialFormSave from './factories/credential-form-save.factory';
import KindChange from './factories/kind-change.factory';
import OwnerChange from './factories/owner-change.factory';
import { N_ } from '../i18n';
export default
angular.module('credentials', [])
.directive('ownerList', ownerList);
.directive('ownerList', ownerList)
.factory('BecomeMethodChange', BecomeMethodChange)
.factory('CredentialFormSave', CredentialFormSave)
.factory('KindChange', KindChange)
.factory('OwnerChange', OwnerChange)
.controller('CredentialsList', CredentialsList)
.controller('CredentialsAdd', CredentialsAdd)
.controller('CredentialsEdit', CredentialsEdit)
.config(['$stateProvider', 'stateDefinitionsProvider',
function($stateProvider, stateDefinitionsProvider) {
let stateDefinitions = stateDefinitionsProvider.$get();
// lazily generate a tree of substates which will replace this node in ui-router's stateRegistry
// see: stateDefinition.factory for usage documentation
$stateProvider.state({
name: 'credentials',
url: '/credentials',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'credentials',
modes: ['add', 'edit'],
list: 'CredentialList',
form: 'CredentialForm',
controllers: {
list: CredentialsList,
add: CredentialsAdd,
edit: CredentialsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'credential'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_('CREDENTIALS')
}
})
});
}
]);

View File

@ -1,14 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import sanitizeFilter from './shared/xss-sanitizer.filter';
import capitalizeFilter from './shared/capitalize.filter';
import longDateFilter from './shared/long-date.filter';
export {
sanitizeFilter,
capitalizeFilter,
longDateFilter
};

View File

@ -1,60 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import './forms';
import './lists';
import Children from "./helpers/Children";
import Credentials from "./helpers/Credentials";
import Events from "./helpers/Events";
import Groups from "./helpers/Groups";
import Hosts from "./helpers/Hosts";
import JobDetail from "./helpers/JobDetail";
import JobSubmission from "./helpers/JobSubmission";
import JobTemplates from "./helpers/JobTemplates";
import Jobs from "./helpers/Jobs";
import LoadConfig from "./helpers/LoadConfig";
import Parse from "./helpers/Parse";
import ProjectPath from "./helpers/ProjectPath";
import Projects from "./helpers/Projects";
import Schedules from "./helpers/Schedules";
import Selection from "./helpers/Selection";
import Users from "./helpers/Users";
import Variables from "./helpers/Variables";
import ApiDefaults from "./helpers/api-defaults";
import inventory from "./helpers/inventory";
import MD5 from "./helpers/md5";
import Teams from "./helpers/teams";
import AdhocHelper from "./helpers/Adhoc";
import ApiModelHelper from "./helpers/ApiModel";
import ActivityStreamHelper from "./helpers/ActivityStream";
export
{ Children,
Credentials,
Events,
Groups,
Hosts,
JobDetail,
JobSubmission,
JobTemplates,
Jobs,
LoadConfig,
Parse,
ProjectPath,
Projects,
Schedules,
Selection,
Users,
Variables,
ApiDefaults,
inventory,
MD5,
Teams,
AdhocHelper,
ApiModelHelper,
ActivityStreamHelper
};

View File

@ -1,64 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ActivityStream
* @description Helper functions for the activity stream
*/
export default
angular.module('ActivityStreamHelper', ['Utilities'])
.factory('GetTargetTitle', ['i18n',
function (i18n) {
return function (target) {
var rtnTitle = i18n._('ALL ACTIVITY');
switch(target) {
case 'project':
rtnTitle = i18n._('PROJECTS');
break;
case 'inventory':
rtnTitle = i18n._('INVENTORIES');
break;
case 'credential':
rtnTitle = i18n._('CREDENTIALS');
break;
case 'user':
rtnTitle = i18n._('USERS');
break;
case 'team':
rtnTitle = i18n._('TEAMS');
break;
case 'notification_template':
rtnTitle = i18n._('NOTIFICATION TEMPLATES');
break;
case 'organization':
rtnTitle = i18n._('ORGANIZATIONS');
break;
case 'job':
rtnTitle = i18n._('JOBS');
break;
case 'custom_inventory_script':
rtnTitle = i18n._('INVENTORY SCRIPTS');
break;
case 'schedule':
rtnTitle = i18n._('SCHEDULES');
break;
case 'host':
rtnTitle = i18n._('HOSTS');
break;
case 'template':
rtnTitle = i18n._('TEMPLATES');
break;
}
return rtnTitle;
};
}
]);

View File

@ -1,172 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Adhoc
* @description These routines are shared by adhoc command related controllers.
* The content here is very similar to the JobSubmission helper, and in fact,
* certain services are pulled from that helper. This leads to an important
* point: if you need to create functionality that is shared between the command
* and playbook run process, put that code in the JobSubmission helper and make
* it into a reusable step (by specifying a callback parameter in the factory).
* For a good example of this, please see how the AdhocLaunch factory in this
* file utilizes the CheckPasswords factory from the JobSubmission helper.
*
* #AdhocRelaunch Step 1: preparing the GET to ad_hoc_commands/n/relaunch
* The adhoc relaunch process is called from the JobSubmission helper. It is a
* separate process from the initial adhoc run becuase of the way the API
* endpoints work. For AdhocRelaunch, we have access to the original run and
* we can pull the related relaunch URL by knowing the original Adhoc runs ID.
*
* #AdhocRelaunch Step 2: If we got passwords back, add them
* The relaunch URL gives us back the passwords we need to prompt for (if any).
* We'll go to step 3 if there are passwords, and step 4 if not.
*
* #AdhocRelaunch Step 3: PromptForPasswords and the CreateLaunchDialog
*
* #AdhocRelaunch Step 5: StartAdhocRun
*
* #AdhocRelaunch Step 6: LaunchJob and navigate to the standard out page.
* **If you are
* TODO: once the API endpoint is figured out for running an adhoc command
* from the form is figured out, the rest work should probably be excised from
* the controller and moved into here. See the todo statements in the
* controller for more information about this.
*/
export default
angular.module('AdhocHelper', ['RestServices', 'Utilities',
'CredentialFormDefinition', 'CredentialsListDefinition',
'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog',
'FormGenerator', 'JobVarsPromptFormDefinition'])
/**
* @ngdoc method
* @name helpers.function:JobSubmission#AdhocRun
* @methodOf helpers.function:JobSubmission
* @description The adhoc Run function is run when the user clicks the relaunch button
*
*/
// Submit request to run an adhoc comamand
.factory('AdhocRun', ['$location','$stateParams', 'LaunchJob',
'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors',
'Wait', 'Empty', 'CreateLaunchDialog', '$state',
function ($location, $stateParams, LaunchJob, PromptForPasswords,
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) {
return function (params) {
var id = params.project_id,
scope = params.scope.$new(),
new_job_id,
html,
url;
// this is used to cancel a running adhoc command from
// the jobs page
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
// Delete the job
Wait('start');
Rest.setUrl(GetBasePath('ad_hoc_commands') + new_job_id + '/');
Rest.destroy()
.success(function() {
Wait('stop');
})
.error(function (data, status) {
ProcessErrors(scope, data, status,
null, { hdr: 'Error!',
msg: 'Call to ' + url +
' failed. DELETE returned status: ' +
status });
});
});
if (scope.removeStartAdhocRun) {
scope.removeStartAdhocRun();
}
scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() {
var password,
postData={};
for (password in scope.passwords) {
postData[scope.passwords[password]] = scope[
scope.passwords[password]
];
}
// Re-launch the adhoc job
Rest.setUrl(url);
Rest.post(postData)
.success(function (data) {
Wait('stop');
if($location.path().replace(/^\//, '').split('/')[0] !== 'jobs') {
$state.go('adHocJobStdout', {id: data.id});
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, {
hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status });
});
});
// start routine only if passwords need to be prompted
if (scope.removeCreateLaunchDialog) {
scope.removeCreateLaunchDialog();
}
scope.removeCreateLaunchDialog = scope.$on('CreateLaunchDialog',
function(e, html, url) {
CreateLaunchDialog({
scope: scope,
html: html,
url: url,
callback: 'StartAdhocRun'
});
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords',
function(e, passwords_needed_to_start,html, url) {
PromptForPasswords({
scope: scope,
passwords: passwords_needed_to_start,
callback: 'CreateLaunchDialog',
html: html,
url: url
});
}); // end password prompting routine
// start the adhoc relaunch routine
Wait('start');
url = GetBasePath('ad_hoc_commands') + id + '/relaunch/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
new_job_id = data.id;
scope.passwords_needed_to_start = data.passwords_needed_to_start;
if (!Empty(data.passwords_needed_to_start) &&
data.passwords_needed_to_start.length > 0) {
// go through the password prompt routine before
// starting the adhoc run
scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url);
}
else {
// no prompting of passwords needed
scope.$emit('StartAdhocRun');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job template details. GET returned status: ' + status });
});
};
}]);

View File

@ -1,63 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ApiModel
* @description Helper functions to convert singular/plural versions of our models to the opposite
*/
export default
angular.module('ApiModelHelper', ['Utilities'])
.factory('ModelToBasePathKey', [
function () {
return function (model) {
// This function takes in the singular model string and returns the key needed
// to get the base path from $rootScope/local storage.
var basePathKey;
switch(model) {
case 'project':
basePathKey = 'projects';
break;
case 'inventory':
basePathKey = 'inventory';
break;
case 'job_template':
basePathKey = 'job_templates';
break;
case 'credential':
basePathKey = 'credentials';
break;
case 'user':
basePathKey = 'users';
break;
case 'team':
basePathKey = 'teams';
break;
case 'notification_template':
basePathKey = 'notification_templates';
break;
case 'organization':
basePathKey = 'organizations';
break;
case 'management_job':
basePathKey = 'management_jobs';
break;
case 'custom_inventory_script':
basePathKey = 'inventory_scripts';
break;
case 'workflow_job_template':
basePathKey = 'workflow_job_templates';
break;
}
return basePathKey;
};
}
]);

View File

@ -1,111 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Children
* @descriptionUsed in job_events to expand/collapse children by setting the
* 'show' attribute of each job_event in the set of job_events.
* See the filter in job_events.js list.
*/
export default
angular.module('ChildrenHelper', ['RestServices', 'Utilities'])
.factory('ToggleChildren', ['$location', 'Store', function ($location, Store) {
return function (params) {
var scope = params.scope,
list = params.list,
id = params.id,
set = scope[list.name],
clicked,
//base = $location.path().replace(/^\//, '').split('/')[0],
path = $location.path(),
local_child_store;
function updateExpand(key, expand) {
var found = false;
local_child_store.every(function(child, i) {
if (child.key === key) {
local_child_store[i].expand = expand;
found = true;
return false;
}
return true;
});
if (!found) {
local_child_store.push({ key: key, expand: expand });
}
}
function updateShow(key, show) {
var found = false;
local_child_store.every(function(child, i) {
if (child.key === key) {
local_child_store[i].show = show;
found = true;
return false;
}
return true;
});
if (!found) {
local_child_store.push({ key: key, show: show });
}
}
function expand(node) {
var i, has_children = false;
for (i = node + 1; i < set.length; i++) {
if (set[i].parent === set[node].id) {
updateShow(set[i].key, true);
set[i].show = true;
}
}
set[node].ngicon = (has_children) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-minus-square-o node-toggle';
}
function collapse(node) {
var i, has_children = false;
for (i = node + 1; i < set.length; i++) {
if (set[i].parent === set[node].id) {
set[i].show = false;
has_children = true;
updateShow(set[i].key, false);
if (set[i].related.children) {
collapse(i);
}
}
}
set[node].ngicon = (has_children) ? 'fa fa-plus-square-o node-toggle' : 'fa fa-square-o node-toggle';
}
local_child_store = Store(path + '_children');
if (!local_child_store) {
local_child_store = [];
}
// Scan the array list and find the clicked element
set.every(function(row, i) {
if (row.id === id) {
clicked = i;
return false;
}
return true;
});
// Expand or collapse children based on clicked element's icon
if (/plus-square-o/.test(set[clicked].ngicon)) {
// Expand: lookup and display children
expand(clicked);
updateExpand(set[clicked].key, true);
} else if (/minus-square-o/.test(set[clicked].ngicon)) {
collapse(clicked);
updateExpand(set[clicked].key, false);
}
Store(path + '_children', local_child_store);
};
}
]);

View File

@ -1,441 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Credentials
* @description Functions shared amongst Credential related controllers
*/
export default
angular.module('CredentialsHelper', ['Utilities'])
.factory('KindChange', ['Empty', 'i18n',
function (Empty, i18n) {
return function (params) {
var scope = params.scope,
reset = params.reset,
collapse, id;
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.subscription_required = false;
scope.key_description = i18n.sprintf(i18n._("Paste the contents of the SSH private key file.%s or click to close%s"), "<div class=\"popover-footer\"><span class=\"key\">Esc</span>", "</div>");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.passwordLabel = i18n._('Password');
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
// Put things in a default state
scope.usernameLabel = i18n._('Username');
scope.aws_required = false;
scope.email_required = false;
scope.rackspace_required = false;
scope.sshKeyDataLabel = i18n._('Private Key');
scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE)
scope.key_required = false; // JT -- doing the same for key and project
scope.project_required = false;
scope.domain_required = false;
scope.subscription_required = false;
scope.key_description = i18n._("Paste the contents of the SSH private key file.");
scope.host_required = false;
scope.password_required = false;
scope.hostLabel = '';
scope.projectLabel = '';
scope.domainLabel = '';
scope.project_required = false;
scope.passwordLabel = i18n._('Password (API Key)');
scope.projectPopOver = "<p>" + i18n._("The project value") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host value") + "</p>";
scope.ssh_key_data_api_error = '';
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
// Reset all the field values related to Kind.
if (reset) {
scope.access_key = null;
scope.secret_key = null;
scope.api_key = null;
scope.username = null;
scope.password = null;
scope.password_confirm = null;
scope.ssh_key_data = null;
scope.ssh_key_unlock = null;
scope.ssh_key_unlock_confirm = null;
scope.become_username = null;
scope.become_password = null;
scope.authorize = false;
scope.authorize_password = null;
}
// Collapse or open help widget based on whether scm value is selected
collapse = $('#credential_kind').parent().find('.panel-collapse').first();
id = collapse.attr('id');
if (!Empty(scope.kind) && scope.kind.value !== '') {
if ($('#' + id + '-icon').hasClass('icon-minus')) {
scope.accordionToggle('#' + id);
}
} else {
if ($('#' + id + '-icon').hasClass('icon-plus')) {
scope.accordionToggle('#' + id);
}
}
};
}
])
.factory('BecomeMethodChange', ['Empty', 'i18n',
function (Empty, i18n) {
return function (params) {
console.log('become method has changed');
var scope = params.scope;
if (!Empty(scope.kind)) {
// Apply kind specific settings
switch (scope.kind.value) {
case 'aws':
scope.aws_required = true;
break;
case 'rax':
scope.rackspace_required = true;
scope.username_required = true;
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
scope.passwordLabel = i18n._('Password');
break;
case 'gce':
scope.usernameLabel = i18n._('Service Account Email Address');
scope.sshKeyDataLabel = i18n._('RSA Private Key');
scope.email_required = true;
scope.key_required = true;
scope.project_required = true;
scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.');
scope.projectLabel = i18n._("Project");
scope.project_required = false;
scope.projectPopOver = "<p>" + i18n._("The Project ID is the " +
"GCE assigned identification. It is constructed as " +
"two words followed by a three digit number. Such " +
"as: ") + "</p><p>adjective-noun-000</p>";
break;
case 'azure':
scope.sshKeyDataLabel = i18n._('Management Certificate');
scope.subscription_required = true;
scope.key_required = true;
scope.key_description = i18n._("Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.");
break;
case 'azure_rm':
scope.usernameLabel = i18n._("Username");
scope.subscription_required = true;
scope.passwordLabel = i18n._('Password');
scope.azure_rm_required = true;
break;
case 'vmware':
scope.username_required = true;
scope.host_required = true;
scope.password_required = true;
scope.hostLabel = "vCenter Host";
scope.passwordLabel = i18n._('Password');
scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter.");
break;
case 'openstack':
scope.hostLabel = i18n._("Host (Authentication URL)");
scope.projectLabel = i18n._("Project (Tenant Name)");
scope.domainLabel = i18n._("Domain Name");
scope.password_required = true;
scope.project_required = true;
scope.host_required = true;
scope.username_required = true;
scope.projectPopOver = "<p>" + i18n._("This is the tenant name. " +
" This value is usually the same " +
" as the username.") + "</p>";
scope.hostPopOver = "<p>" + i18n._("The host to authenticate with.") +
"<br />" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
break;
case 'satellite6':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("Satellite 6 URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
"Red Hat Satellite 6 server. %s" +
"For example, %s"), "<br />", "<br />", "https://satellite.example.org");
break;
case 'cloudforms':
scope.username_required = true;
scope.password_required = true;
scope.passwordLabel = i18n._('Password');
scope.host_required = true;
scope.hostLabel = i18n._("CloudForms URL");
scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
"corresponds to your CloudForm instance. %s" +
"For example, %s"), "<br />", "<br />", "https://cloudforms.example.org");
break;
case 'net':
scope.username_required = true;
scope.password_required = false;
scope.passwordLabel = i18n._('Password');
scope.sshKeyDataLabel = i18n._('SSH Key');
break;
}
}
};
}
])
.factory('OwnerChange', [
function () {
return function (params) {
var scope = params.scope,
owner = scope.owner;
if (owner === 'team') {
scope.team_required = true;
scope.user_required = false;
scope.user = null;
scope.user_username = null;
} else {
scope.team_required = false;
scope.user_required = true;
scope.team = null;
scope.team_name = null;
}
};
}
])
.factory('FormSave', ['$rootScope', '$location', 'Alert', 'Rest', 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', 'ReturnToCaller', 'Wait', '$state', 'i18n',
function ($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) {
return function (params) {
var scope = params.scope,
mode = params.mode,
form = CredentialForm,
data = {}, fld, url;
for (fld in form.fields) {
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
fld !== 'ssh_password') {
if (fld === "organization" && !scope[fld]) {
data.user = $rootScope.current_user.id;
} else if (scope[fld] === null) {
data[fld] = "";
} else {
data[fld] = scope[fld];
}
}
}
data.kind = scope.kind.value;
if (scope.become_method === null || typeof scope.become_method === 'undefined') {
data.become_method = "";
data.become_username = "";
data.become_password = "";
} else {
data.become_method = (scope.become_method.value) ? scope.become_method.value : "";
}
switch (data.kind) {
case 'ssh':
data.password = scope.ssh_password;
break;
case 'aws':
data.username = scope.access_key;
data.password = scope.secret_key;
break;
case 'rax':
data.password = scope.api_key;
break;
case 'gce':
data.username = scope.email_address;
data.project = scope.project;
break;
case 'azure':
data.username = scope.subscription;
}
Wait('start');
if (mode === 'add') {
url = GetBasePath("credentials");
Rest.setUrl(url);
Rest.post(data)
.success(function (data) {
scope.addedItem = data.id;
// @issue: OLD SEARCH
// Refresh({
// scope: scope,
// set: 'credentials',
// iterator: 'credential',
// url: url
// });
Wait('stop');
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'credentials') {
$state.go('credentials.edit', {credential_id: data.id}, {reload: true});
}
else {
ReturnToCaller(1);
}
})
.error(function (data, status) {
Wait('stop');
// TODO: hopefully this conditional error handling will to away in a future version of tower. The reason why we cannot
// simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show
// the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the
// error is never shown. In the future, the API will hopefully either behave or respond differently.
if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) {
scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported.");
}
else {
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to create new Credential. POST status: ') + status
});
}
});
} else {
url = GetBasePath('credentials') + scope.id + '/';
Rest.setUrl(url);
Rest.put(data)
.success(function () {
Wait('stop');
$state.go($state.current, {}, {reload: true});
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, form, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to update Credential. PUT status: ') + status
});
});
}
};
}
]);

View File

@ -1,237 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Events
* @description EventView - show the job_events form in a modal dialog
*/
export default
angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefinition', 'JobEventsFormDefinition'])
.factory('EventView', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'JobEventDataForm', 'Empty', 'JobEventsForm',
function ($rootScope, $location, $log, $stateParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath,
FormatDate, JobEventDataForm, Empty, JobEventsForm) {
return function (params) {
var event_id = params.event_id,
generator = GenerateForm,
form = angular.copy(JobEventsForm),
scope,
defaultUrl = GetBasePath('base') + 'job_events/' + event_id + '/';
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl);
Rest.get()
.success(function (data) {
var i, n, fld, rows, txt, cDate;
// If event_data is not available, remove fields that depend on it
if ($.isEmptyObject(data.event_data) || !data.event_data.res || typeof data.event_data.res === 'string') {
for (fld in form.fields) {
switch (fld) {
case 'start':
case 'end':
case 'delta':
case 'msg':
case 'stdout':
case 'stderr':
case 'msg':
case 'results':
case 'module_name':
case 'module_args':
case 'rc':
delete form.fields[fld];
break;
}
}
}
if ($.isEmptyObject(data.event_data) || !data.event_data.res || typeof data.event_data.res !== 'string') {
delete form.fields.traceback;
}
// Remove remaining form fields that do not have a corresponding data value
for (fld in form.fields) {
switch (fld) {
case 'start':
case 'end':
case 'delta':
case 'msg':
case 'stdout':
case 'stderr':
case 'msg':
case 'rc':
if (data.event_data && data.event_data.res && Empty(data.event_data.res[fld])) {
delete form.fields[fld];
} else {
if (form.fields[fld].type === 'textarea') {
n = data.event_data.res[fld].match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
rows = (rows < 3) ? 3 : rows;
form.fields[fld].rows = rows;
}
}
break;
case 'results':
if (data.event_data && data.event_data.res && data.event_data.res[fld] === undefined) {
// not defined
delete form.fields[fld];
} else if (!Array.isArray(data.event_data.res[fld]) || data.event_data.res[fld].length === 0) {
// defined, but empty
delete form.fields[fld];
} else {
// defined and not empty, so attempt to size the textarea field
txt = '';
for (i = 0; i < data.event_data.res[fld].length; i++) {
txt += data.event_data.res[fld][i];
}
if (txt === '') {
// there's an array, but the actual text is empty
delete form.fields[fld];
} else {
n = txt.match(/\n/g);
rows = (n) ? n.length : 1;
rows = (rows > 10) ? 10 : rows;
rows = (rows < 3) ? 3 : rows;
form.fields[fld].rows = rows;
}
}
break;
case 'module_name':
case 'module_args':
if (data.event_data && data.event_data.res) {
if (data.event_data.res.invocation === undefined ||
data.event_data.res.invocation[fld] === undefined) {
delete form.fields[fld];
}
}
break;
}
}
// load the form
scope = generator.inject(form, {
mode: 'edit',
modal: true,
related: false
});
generator.reset();
scope.formModalAction = function () {
$('#form-modal').modal("hide");
};
scope.formModalActionLabel = 'OK';
scope.formModalCancelShow = false;
scope.formModalInfo = 'View JSON';
$('#form-modal .btn-success').removeClass('btn-success').addClass('btn-none');
$('#form-modal').addClass('skinny-modal');
scope.formModalHeader = data.event_display.replace(/^\u00a0*/g, '');
// Respond to View JSON button
scope.formModalInfoAction = function () {
var generator = GenerateForm,
scope = generator.inject(JobEventDataForm, {
mode: 'edit',
modal: true,
related: false,
modal_selector: '#form-modal2',
modal_body_id: 'form-modal2-body',
modal_title_id: 'formModal2Header'
});
generator.reset();
scope.formModal2Header = data.event_display.replace(/^\u00a0*/g, '');
scope.event_data = JSON.stringify(data.event_data, null, '\t');
scope.formModal2ActionLabel = 'OK';
scope.formModal2CancelShow = false;
scope.formModal2Info = false;
scope.formModalInfo = 'View JSON';
scope.formModal2Action = function () {
$('#form-modal2').modal("hide");
};
$('#form-modal2 .btn-success').removeClass('btn-success').addClass('btn-none');
};
if (typeof data.event_data.res === 'string') {
scope.traceback = data.event_data.res;
}
for (fld in form.fields) {
switch (fld) {
case 'status':
if (data.failed) {
scope.status = 'error';
} else if (data.changed) {
scope.status = 'changed';
} else {
scope.status = 'success';
}
break;
case 'created':
cDate = new Date(data.created);
scope.created = FormatDate(cDate);
break;
case 'host':
if (data.summary_fields && data.summary_fields.host) {
scope.host = data.summary_fields.host.name;
}
break;
case 'id':
case 'task':
case 'play':
scope[fld] = data[fld];
break;
case 'start':
case 'end':
if (data.event_data && data.event_data.res && !Empty(data.event_data.res[fld])) {
scope[fld] = data.event_data.res[fld];
}
break;
case 'results':
if (Array.isArray(data.event_data.res[fld]) && data.event_data.res[fld].length > 0) {
txt = '';
for (i = 0; i < data.event_data.res[fld].length; i++) {
txt += data.event_data.res[fld][i];
}
if (txt !== '') {
scope[fld] = txt;
}
}
break;
case 'msg':
case 'stdout':
case 'stderr':
case 'delta':
case 'rc':
if (data.event_data && data.event_data.res && data.event_data.res[fld] !== undefined) {
scope[fld] = data.event_data.res[fld];
}
break;
case 'module_name':
case 'module_args':
if (data.event_data.res && data.event_data.res.invocation) {
scope[fld] = data.event_data.res.invocation[fld];
}
break;
}
}
if (!scope.$$phase) {
scope.$digest();
}
})
.error(function (data, status) {
$('#form-modal').modal("hide");
ProcessErrors(scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to retrieve event: ' + event_id + '. GET status: ' + status });
});
};
}
]);

File diff suppressed because it is too large Load Diff

View File

@ -1,465 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/* jshint loopfunc: true */
/**
* @ngdoc function
* @name helpers.function:Hosts
* @description Routines that handle host add/edit/delete on the Inventory detail page.
*/
'use strict';
import listGenerator from '../shared/list-generator/main';
export default
angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'HostListDefinition',
listGenerator.name, 'HostsHelper',
'InventoryHelper', 'InventoryFormDefinition', 'SelectionHelper',
'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'StandardOutHelper',
'GroupListDefinition'
])
.factory('SetEnabledMsg', [ function() {
return function(host) {
if (host.has_inventory_sources) {
// Inventory sync managed, so not clickable
host.enabledToolTip = (host.enabled) ? 'Host is available' : 'Host is not available';
}
else {
// Clickable
host.enabledToolTip = (host.enabled) ? 'Host is available. Click to toggle.' : 'Host is not available. Click to toggle.';
}
};
}])
.factory('SetHostStatus', ['SetEnabledMsg', function(SetEnabledMsg) {
return function(host) {
// Set status related fields on a host object
host.activeFailuresLink = '/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + host.inventory +
'&host_name=' + encodeURI(host.name);
if (host.has_active_failures === true) {
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
host.active_failures = 'failed';
}
else if (host.has_active_failures === false && host.last_job === null) {
host.has_active_failures = 'none';
host.badgeToolTip = "No job data available.";
host.active_failures = 'n/a';
}
else if (host.has_active_failures === false && host.last_job !== null) {
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
host.active_failures = 'success';
}
host.enabled_flag = host.enabled;
SetEnabledMsg(host);
};
}])
.factory('SetStatus', ['$filter', 'SetEnabledMsg', 'Empty', function($filter, SetEnabledMsg, Empty) {
return function(params) {
var scope = params.scope,
host = params.host,
i, html, title;
function ellipsis(a) {
if (a.length > 25) {
return a.substr(0,25) + '...';
}
return a;
}
function noRecentJobs() {
title = 'No job data';
html = "<p>No recent job data available for this host.</p>\n";
}
function setMsg(host) {
var j, job, jobs;
if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) {
if (host.has_active_failures === true) {
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
host.active_failures = 'error';
}
else {
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
host.active_failures = 'successful';
}
if (host.summary_fields.recent_jobs.length > 0) {
// build html table of job status info
jobs = host.summary_fields.recent_jobs.sort(
function(a,b) {
// reverse numerical order
return -1 * (a - b);
});
title = "Recent Jobs";
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>\n";
html += "<th>Status</th>\n";
html += "<th>Finished</th>\n";
html += "<th>Name</th>\n";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
for (j=0; j < jobs.length; j++) {
job = jobs[j];
html += "<tr>\n";
// SmartStatus-tooltips are named --success whereas icon-job uses successful
var iconStatus = (job.status === 'successful') ? 'success' : 'failed';
html += "<td><a href=\"#/jobs/" + job.id + "\"><i class=\"fa DashboardList-status SmartStatus-tooltip--" + iconStatus + " icon-job-" +
job.status + "\"></i></a></td>\n";
html += "<td>" + ($filter('longDate')(job.finished)).replace(/ /,'<br />') + "</td>\n";
html += "<td class=\"break\"><a href=\"#/jobs/" + job.id + "\" " +
"aw-tool-tip=\"" + job.status.charAt(0).toUpperCase() + job.status.slice(1) +
". Click for details\" data-placement=\"top\">" + ellipsis(job.name) + "</a></td>\n";
html += "</tr>\n";
}
html += "</tbody>\n";
html += "</table>\n";
}
else {
noRecentJobs();
}
}
else if (host.has_active_failures === false && host.last_job === null) {
host.badgeToolTip = "No job data available.";
host.active_failures = 'none';
noRecentJobs();
}
host.job_status_html = html;
host.job_status_title = title;
}
if (!Empty(host)) {
// update single host
setMsg(host);
SetEnabledMsg(host);
}
else {
// update all hosts
for (i=0; i < scope.hosts.length; i++) {
setMsg(scope.hosts[i]);
SetEnabledMsg(scope.hosts[i]);
}
}
};
}])
.factory('HostsReload', [ '$stateParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'Wait',
'SetHostStatus', 'SetStatus', 'ApplyEllipsis',
function($stateParams, Empty, InventoryHosts, GetBasePath, Wait, SetHostStatus, SetStatus,
ApplyEllipsis) {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope;
// @issue: OLD SEARCH
// var list = InventoryHosts,
// group_id = params.group_id,
// inventory_id = params.inventory_id;
// pageSize = (params.pageSize) ? params.pageSize : 20,
//
// url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' :
// GetBasePath('inventory') + inventory_id + '/hosts/';
// @issue: OLD SEARCH
// scope.search_place_holder='Search ' + scope.selected_group_name;
if (scope.removeHostsReloadPostRefresh) {
scope.removeHostsReloadPostRefresh();
}
scope.removeHostsReloadPostRefresh = scope.$on('PostRefresh', function(e, set) {
if (set === 'hosts') {
for (var i=0; i < scope.hosts.length; i++) {
//Set tooltip for host enabled flag
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
}
SetStatus({ scope: scope });
setTimeout(function() { ApplyEllipsis('#hosts_table .host-name a'); }, 2500);
Wait('stop');
if (parent_scope) {
parent_scope.$emit('HostReloadComplete');
}
}
});
// @issue: OLD SEARCH
// SearchInit({ scope: scope, set: 'hosts', list: list, url: url });
// PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize });
//
// if ($stateParams.host_name) {
// scope[list.iterator + 'InputDisable'] = false;
// scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
// scope[list.iterator + 'SearchField'] = 'name';
// scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
// scope[list.iterator + 'SearchSelectValue'] = null;
// }
//
// if (scope.show_failures) {
// scope[list.iterator + 'InputDisable'] = true;
// scope[list.iterator + 'SearchValue'] = 'true';
// scope[list.iterator + 'SearchField'] = 'has_active_failures';
// scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label;
// scope[list.iterator + 'SearchSelectValue'] = { value: 1 };
// }
// scope.search(list.iterator, null, true);
};
}])
.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList',
function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList) {
return function(params) {
var host_id = params.host_id,
group_scope = params.group_scope,
parent_scope = params.host_scope,
parent_group = group_scope.selected_group_id,
scope = parent_scope.$new(),
buttonSet, url, host;
buttonSet = [{
label: "Cancel",
onClick: function() {
scope.cancel();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "host-copy-cancel-button"
},{
label: "OK",
onClick: function() {
scope.performCopy();
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "host-copy-ok-button"
}];
if (scope.removeHostCopyPostRefresh) {
scope.removeHostCopyPostRefresh();
}
scope.removeHostCopyPostRefresh = scope.$on('PostRefresh', function() {
scope.copy_groups.forEach(function(row, i) {
scope.copy_groups[i].checked = '0';
});
Wait('stop');
$('#host-copy-dialog').dialog('open');
$('#host-copy-ok-button').attr('disabled','disabled');
// prevent backspace from navigation when not in input or textarea field
$(document).on("keydown", function (e) {
if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) {
e.preventDefault();
}
});
});
if (scope.removeHostCopyDialogReady) {
scope.removeHostCopyDialogReady();
}
scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() {
// @issue: OLD SEARCH
// var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/';
GenerateList.inject(GroupList, {
mode: 'lookup',
id: 'copy-host-select-container',
scope: scope
//,
//instructions: instructions
});
// @issue: OLD SEARCH
// SearchInit({
// scope: scope,
// set: GroupList.name,
// list: GroupList,
// url: url
// });
// PaginateInit({
// scope: scope,
// list: GroupList,
// url: url,
// mode: 'lookup'
// });
// scope.search(GroupList.iterator, null, true, false);
});
if (scope.removeShowDialog) {
scope.removeShowDialog();
}
scope.removeShowDialog = scope.$on('ShowDialog', function() {
var d;
scope.name = host.name;
scope.copy_choice = "copy";
d = angular.element(document.getElementById('host-copy-dialog'));
$compile(d)(scope);
CreateDialog({
id: 'host-copy-dialog',
scope: scope,
buttons: buttonSet,
width: 650,
height: 650,
minWidth: 600,
title: 'Copy or Move Host',
callback: 'HostCopyDialogReady',
onClose: function() {
scope.cancel();
}
});
});
Wait('start');
url = GetBasePath('hosts') + host_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
host = data;
scope.$emit('ShowDialog');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status });
});
scope.cancel = function() {
$(document).off("keydown");
try {
$('#host-copy-dialog').dialog('close');
}
catch(e) {
// ignore
}
// @issue: OLD SEARCH
// scope.searchCleanup();
// @issue: OLD SEARCH
// group_scope.restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists
scope.$destroy();
};
scope['toggle_' + GroupList.iterator] = function (id) {
var count = 0,
list = GroupList;
scope[list.name].forEach( function(row, i) {
if (row.id === id) {
if (row.checked) {
scope[list.name][i].success_class = 'success';
}
else {
scope[list.name][i].success_class = '';
}
} else {
scope[list.name][i].checked = 0;
scope[list.name][i].success_class = '';
}
});
// Check if any rows are checked
scope[list.name].forEach(function(row) {
if (row.checked) {
count++;
}
});
if (count === 0) {
$('#host-copy-ok-button').attr('disabled','disabled');
}
else {
$('#host-copy-ok-button').removeAttr('disabled');
}
};
scope.performCopy = function() {
var list = GroupList,
target,
url;
Wait('start');
if (scope.use_root_group) {
target = null;
}
else {
scope[list.name].every(function(row) {
if (row.checked === 1) {
target = row;
return false;
}
return true;
});
}
if (scope.copy_choice === 'move') {
// Respond to move
// disassociate the host from the original parent
if (scope.removeHostRemove) {
scope.removeHostRemove();
}
scope.removeHostRemove = scope.$on('RemoveHost', function () {
if (parent_group > 0) {
// Only remove a host from a parent when the parent is a group and not the inventory root
url = GetBasePath('groups') + parent_group + '/hosts/';
Rest.setUrl(url);
Rest.post({ id: host.id, disassociate: 1 })
.success(function () {
scope.cancel();
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to remove ' + host.name + ' from group ' + parent_group + '. POST returned: ' + status });
});
} else {
scope.cancel();
}
});
// add the new host to the target
url = GetBasePath('groups') + target.id + '/hosts/';
Rest.setUrl(url);
Rest.post(host)
.success(function () {
scope.$emit('RemoveHost');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status });
});
}
else {
// Respond to copy by adding the new host to the target
url = GetBasePath('groups') + target.id + '/hosts/';
Rest.setUrl(url);
Rest.post(host)
.success(function () {
scope.cancel();
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status
});
});
}
};
};
}]);

File diff suppressed because it is too large Load Diff

View File

@ -1,337 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
'use strict';
export default
angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition',
'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition'])
.factory('CreateLaunchDialog', ['$compile', 'CreateDialog', 'Wait', 'ParseTypeChange',
function($compile, CreateDialog, Wait, ParseTypeChange) {
return function(params) {
var buttons,
scope = params.scope,
html = params.html,
// job_launch_data = {},
callback = params.callback || 'PlaybookLaunchFinished',
// url = params.url,
e;
// html+='<br>job_launch_form.$valid = {{job_launch_form.$valid}}<br>';
html+='</form>';
$('#password-modal').empty().html(html);
$('#password-modal').find('#job_extra_vars').before(scope.helpContainer);
e = angular.element(document.getElementById('password-modal'));
$compile(e)(scope);
if(scope.prompt_for_vars===true){
ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"});
}
buttons = [{
label: "Cancel",
onClick: function() {
$('#password-modal').dialog('close');
// scope.$emit('CancelJob');
// scope.$destroy();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "password-cancel-button"
},{
label: "Launch",
onClick: function() {
scope.$emit(callback);
$('#password-modal').dialog('close');
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "password-accept-button"
}];
CreateDialog({
id: 'password-modal',
scope: scope,
buttons: buttons,
width: 620,
height: 700, //(scope.passwords.length > 1) ? 700 : 500,
minWidth: 500,
title: 'Launch Configuration',
callback: 'DialogReady',
onOpen: function(){
Wait('stop');
}
});
if (scope.removeDialogReady) {
scope.removeDialogReady();
}
scope.removeDialogReady = scope.$on('DialogReady', function() {
$('#password-modal').dialog('open');
$('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' );
e = angular.element(document.getElementById('password-accept-button'));
$compile(e)(scope);
});
};
}])
.factory('PromptForPasswords', ['CredentialForm',
function(CredentialForm) {
return function(params) {
var scope = params.scope,
callback = params.callback || 'PasswordsAccepted',
url = params.url,
form = CredentialForm,
fld, field,
html=params.html || "";
scope.passwords = params.passwords;
html += "<div class=\"alert alert-info\">Launching this job requires the passwords listed below. Enter and confirm each password before continuing.</div>\n";
scope.passwords.forEach(function(password) {
// Prompt for password
field = form.fields[password];
fld = password;
scope[fld] = '';
html += "<div class=\"form-group prepend-asterisk\">\n";
html += "<label for=\"" + fld + "\">" + field.label + "</label>\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"password-field form-control input-sm\" ";
html += (field.associated) ? "ng-change=\"clearPWConfirm('" + field.associated + "')\" " : "";
html += "required ";
html += " >";
// Add error messages
html += "<div class=\"error\" ng-show=\"job_launch_form." + fld + ".$dirty && " +
"job_launch_form." + fld + ".$error.required\">Please enter a password.</div>\n";
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
// Add the related confirm field
if (field.associated) {
fld = field.associated;
field = form.fields[field.associated];
scope[fld] = '';
html += "<div class=\"form-group prepend-asterisk\">\n";
html += "<label for=\"" + fld + "\"> " + field.label + "</label>\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"form-control input-sm\" ";
html += "ng-change=\"checkStatus()\" ";
html += "required ";
html += (field.awPassMatch) ? "awpassmatch=\"" + field.associated + "\" " : "";
html += "/>";
// Add error messages
html += "<div class=\"error\" ng-show=\"job_launch_form." + fld + ".$dirty && " +
"job_launch_form." + fld + ".$error.required\">Please confirm the password.</span>\n";
html += (field.awPassMatch) ? "<span class=\"error\" ng-show=\"job_launch_form." + fld +
".$error.awpassmatch\">This value does not match the password you entered previously. Please confirm that password.</div>\n" : "";
html += "<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>\n";
html += "</div>\n";
}
});
scope.$emit(callback, html, url);
// Password change
scope.clearPWConfirm = function (fld) {
// If password value changes, make sure password_confirm must be re-entered
scope[fld] = '';
scope.job_launch_form[fld].$setValidity('awpassmatch', false);
scope.checkStatus();
};
scope.checkStatus = function() {
if (!scope.job_launch_form.$invalid) {
$('#password-accept-button').removeAttr('disabled');
}
else {
$('#password-accept-button').attr({ "disabled": "disabled" });
}
};
};
}])
.factory('CheckPasswords', ['Rest', 'GetBasePath', 'ProcessErrors', 'Empty',
function(Rest, GetBasePath, ProcessErrors, Empty) {
return function(params) {
var scope = params.scope,
callback = params.callback,
credential = params.credential;
var passwords = [];
if (!Empty(credential)) {
Rest.setUrl(GetBasePath('credentials')+credential);
Rest.get()
.success(function (data) {
if(data.kind === "ssh"){
if(data.password === "ASK" ){
passwords.push("ssh_password");
}
if(data.ssh_key_unlock === "ASK"){
passwords.push("ssh_key_unlock");
}
if(data.become_password === "ASK"){
passwords.push("become_password");
}
if(data.vault_password === "ASK"){
passwords.push("vault_password");
}
}
scope.$emit(callback, passwords);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get job template details. GET returned status: ' + status });
});
}
};
}])
// Submit SCM Update request
.factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) {
return function (params) {
var scope = params.scope,
project_id = params.project_id,
url = GetBasePath('projects') + project_id + '/update/',
project;
if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted();
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() {
// Refresh the project list after update request submitted
Wait('stop');
if (/\d$/.test($location.path())) {
//Request submitted from projects/N page. Navigate back to the list so user can see status
$location.path('/projects');
}
if (scope.socketStatus === 'error') {
Alert('Update Started', '<div>The request to start the SCM update process was submitted. ' +
'To monitor the update status, refresh the page by clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
if (scope.refresh) {
scope.refresh();
}
}
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate();
}
scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
});
// Check to see if we have permission to perform the update and if any passwords are needed
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
project = data;
if (project.can_update) {
if (project.passwords_needed_to_updated) {
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
}
else {
Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.',
'alert-danger');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to lookup project ' + url + ' GET returned: ' + status });
});
};
}
])
// Submit Inventory Update request
.factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
function (PromptForPasswords, LaunchJob, Rest, GetBasePath, ProcessErrors, Alert, Wait) {
return function (params) {
var scope = params.scope,
url = params.url,
inventory_source;
if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted();
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () {
Wait('stop');
if (scope.socketStatus === 'error') {
Alert('Sync Started', '<div>The request to start the inventory sync process was submitted. ' +
'To monitor the status refresh the page by clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
if (scope.refreshGroups) {
// inventory detail page
scope.refreshGroups();
}
else if (scope.refresh) {
scope.refresh();
}
}
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate();
}
scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
});
// Check to see if we have permission to perform the update and if any passwords are needed
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
inventory_source = data;
if (data.can_update) {
if (data.passwords_needed_to_update) {
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
} else {
Wait('stop');
Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.',
'alert-danger');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status });
});
};
}
]);

View File

@ -1,173 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:JobTemplatesHelper
* @description Routines shared by job related controllers
*/
export default
angular.module('JobTemplatesHelper', ['Utilities'])
/*
* Add bits to $scope for handling callback url help
*
*/
.factory('CallbackHelpInit', ['$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm', '$stateParams', 'ProcessErrors', 'ParseTypeChange',
'ParseVariableString', 'Empty', 'InventoryList', 'CredentialList','ProjectList', 'Wait',
function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, ParseTypeChange,
ParseVariableString, Empty, InventoryList, CredentialList, ProjectList, Wait) {
return function(params) {
var scope = params.scope,
defaultUrl = GetBasePath('job_templates'),
// generator = GenerateForm,
form = JobTemplateForm(),
// loadingFinishedCount = 0,
// base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.job_template_id;
// checkSCMStatus, getPlaybooks, callback,
// choicesCount = 0;
CredentialList = _.cloneDeep(CredentialList);
// The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the
// popover is activated, a function checks the value of scope.callback_help before constructing the content.
scope.setCallbackHelp = function() {
scope.callback_help = "<p>With a provisioning callback URL and a host config key a host can contact Tower and request a configuration update using this job " +
"template. The request from the host must be a POST. Here is an example using curl:</p>\n" +
"<pre>curl --data \"host_config_key=" + scope.example_config_key + "\" " +
scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/</pre>\n" +
"<p>Note the requesting host must be defined in the inventory associated with the job template. If Tower fails to " +
"locate the host, the request will be denied.</p>" +
"<p>Successful requests create an entry on the Jobs page, where results and history can be viewed.</p>";
};
// The md5 helper emits NewMD5Generated whenever a new key is available
if (scope.removeNewMD5Generated) {
scope.removeNewMD5Generated();
}
scope.removeNewMD5Generated = scope.$on('NewMD5Generated', function() {
scope.configKeyChange();
});
// Fired when user enters a key value
scope.configKeyChange = function() {
scope.example_config_key = scope.host_config_key;
scope.setCallbackHelp();
};
// Set initial values and construct help text
scope.callback_server_path = $location.protocol() + '://' + $location.host() + (($location.port()) ? ':' + $location.port() : '');
scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a';
scope.example_template_id = 'N';
scope.setCallbackHelp();
// this fills the job template form both on copy of the job template
// and on edit
scope.fillJobTemplate = function(){
// id = id || $rootScope.copy.id;
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl + id);
Rest.get()
.success(function (data) {
scope.job_template_obj = data;
scope.name = data.name;
var fld, i;
for (fld in form.fields) {
if (fld !== 'variables' && fld !== 'survey' && data[fld] !== null && data[fld] !== undefined) {
if (form.fields[fld].type === 'select') {
if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) {
for (i = 0; i < scope[fld + '_options'].length; i++) {
if (data[fld] === scope[fld + '_options'][i].value) {
scope[fld] = scope[fld + '_options'][i];
}
}
} else {
scope[fld] = data[fld];
}
} else {
scope[fld] = data[fld];
if(!Empty(data.summary_fields.survey)) {
scope.survey_exists = true;
}
}
master[fld] = scope[fld];
}
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
scope.variables = ParseVariableString(data.extra_vars);
master.variables = scope.variables;
}
if (form.fields[fld].type === 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
if (form.fields[fld].type === 'checkbox_group') {
for(var j=0; j<form.fields[fld].fields.length; j++) {
scope[form.fields[fld].fields[j].name] = data[form.fields[fld].fields[j].name];
}
}
}
Wait('stop');
scope.url = data.url;
scope.survey_enabled = data.survey_enabled;
scope.ask_variables_on_launch = (data.ask_variables_on_launch) ? true : false;
master.ask_variables_on_launch = scope.ask_variables_on_launch;
scope.ask_limit_on_launch = (data.ask_limit_on_launch) ? true : false;
master.ask_limit_on_launch = scope.ask_limit_on_launch;
scope.ask_tags_on_launch = (data.ask_tags_on_launch) ? true : false;
master.ask_tags_on_launch = scope.ask_tags_on_launch;
scope.ask_skip_tags_on_launch = (data.ask_skip_tags_on_launch) ? true : false;
master.ask_skip_tags_on_launch = scope.ask_skip_tags_on_launch;
scope.ask_job_type_on_launch = (data.ask_job_type_on_launch) ? true : false;
master.ask_job_type_on_launch = scope.ask_job_type_on_launch;
scope.ask_inventory_on_launch = (data.ask_inventory_on_launch) ? true : false;
master.ask_inventory_on_launch = scope.ask_inventory_on_launch;
scope.ask_credential_on_launch = (data.ask_credential_on_launch) ? true : false;
master.ask_credential_on_launch = scope.ask_credential_on_launch;
if (data.host_config_key) {
scope.example_config_key = data.host_config_key;
}
scope.example_template_id = id;
scope.setCallbackHelp();
scope.callback_url = scope.callback_server_path + ((data.related.callback) ? data.related.callback :
GetBasePath('job_templates') + id + '/callback/');
master.callback_url = scope.callback_url;
scope.can_edit = data.summary_fields.user_capabilities.edit;
if (scope.job_type.value === "scan" && (!scope.project || scope.project === "") && (!scope.playbook || scope.playbook === "")) {
scope.resetProjectToDefault();
}
scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to retrieve job template: ' + $stateParams.id + '. GET status: ' + status
});
});
};
};
}]);

View File

@ -1,308 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Jobs
* @description routines shared by job related controllers
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers',
'JobSubmissionHelper', 'StandardOutHelper', 'AdhocHelper', listGenerator.name])
.factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', 'RelaunchAdhoc',
function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) {
return function(params) {
var scope = params.scope,
id = params.id,
type = params.type,
name = params.name;
if (type === 'inventory_update') {
RelaunchInventory({ scope: scope, id: id});
}
else if (type === 'ad_hoc_command') {
RelaunchAdhoc({ scope: scope, id: id, name: name });
}
else if (type === 'job' || type === 'system_job' || type === 'workflow_job') {
RelaunchPlaybook({ scope: scope, id: id, name: name, job_type: type });
}
else if (type === 'project_update') {
RelaunchSCM({ scope: scope, id: id });
}
};
}
])
.factory('JobStatusToolTip', [
function () {
return function (status) {
var toolTip;
switch (status) {
case 'successful':
case 'success':
toolTip = 'There were no failed tasks.';
break;
case 'failed':
toolTip = 'Some tasks encountered errors.';
break;
case 'canceled':
toolTip = 'Stopped by user request.';
break;
case 'new':
toolTip = 'In queue, waiting on task manager.';
break;
case 'waiting':
toolTip = 'SCM Update or Inventory Update is executing.';
break;
case 'pending':
toolTip = 'Not in queue, waiting on task manager.';
break;
case 'running':
toolTip = 'Playbook tasks executing.';
break;
}
return toolTip;
};
}
])
.factory('JobsListUpdate', [function() {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope,
list = params.list;
scope[list.name].forEach(function(item, item_idx) {
var fld, field,
itm = scope[list.name][item_idx];
//if (item.type === 'inventory_update') {
// itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,'');
//}
// Set the item type label
if (list.fields.type) {
parent_scope.type_choices.forEach(function(choice) {
if (choice.value === item.type) {
itm.type_label = choice.label;
}
});
}
// Set the job status label
parent_scope.status_choices.forEach(function(status) {
if (status.value === item.status) {
itm.status_label = status.label;
}
});
if (list.name === 'completed_jobs' || list.name === 'running_jobs') {
itm.status_tip = itm.status_label + '. Click for details.';
}
else if (list.name === 'queued_jobs') {
itm.status_tip = 'Pending';
}
// Copy summary_field values
for (field in list.fields) {
fld = list.fields[field];
if (fld.sourceModel) {
if (itm.summary_fields[fld.sourceModel]) {
itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField];
}
}
}
});
};
}])
.factory('DeleteJob', ['$state', 'Find', 'GetBasePath', 'Rest', 'Wait',
'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n',
function($state, Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert,
$filter, i18n){
return function(params) {
var scope = params.scope,
id = params.id,
job = params.job,
callback = params.callback,
action, jobs, url, action_label, hdr;
if (!job) {
if (scope.completed_jobs) {
jobs = scope.completed_jobs;
}
else if (scope.running_jobs) {
jobs = scope.running_jobs;
}
else if (scope.queued_jobs) {
jobs = scope.queued_jobs;
}
else if (scope.all_jobs) {
jobs = scope.all_jobs;
}
else if (scope.jobs) {
jobs = scope.jobs;
}
job = Find({list: jobs, key: 'id', val: id });
}
if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') {
url = job.related.cancel;
action_label = 'cancel';
hdr = i18n._('Cancel');
} else {
url = job.url;
action_label = 'delete';
hdr = i18n._('Delete');
}
action = function () {
Wait('start');
Rest.setUrl(url);
if (action_label === 'cancel') {
Rest.post()
.success(function () {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.error(function(obj, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
// ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. POST returned status: ' + status });
});
} else {
Rest.destroy()
.success(function () {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.error(function (obj, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
//ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. DELETE returned status: ' + status });
});
}
};
if (scope.removeCancelNotAllowed) {
scope.removeCancelNotAllowed();
}
scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() {
Wait('stop');
Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info');
});
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
var cancelBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Submit the request to cancel?") + "</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({
hdr: hdr,
body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody,
action: action,
actionText: (action_label === 'cancel' || job.status === 'new') ? "OK" : "DELETE"
});
});
if (action_label === 'cancel') {
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.can_cancel) {
scope.$emit('CancelJob');
}
else {
scope.$emit('CancelNotAllowed');
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. GET returned: ' + status });
});
}
else {
scope.$emit('CancelJob');
}
};
}])
.factory('RelaunchInventory', ['Find', 'Wait', 'Rest', 'InventoryUpdate', 'ProcessErrors', 'GetBasePath',
function(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('inventory_sources') + id + '/';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function (data) {
InventoryUpdate({
scope: scope,
url: data.related.update,
group_name: data.summary_fields.group.name,
group_source: data.source,
tree_id: null,
group_id: data.group
});
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
url + ' GET returned: ' + status });
});
};
}])
.factory('RelaunchPlaybook', ['InitiatePlaybookRun', function(InitiatePlaybookRun) {
return function(params) {
var scope = params.scope,
id = params.id,
job_type = params.job_type;
InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type });
};
}])
.factory('RelaunchSCM', ['ProjectUpdate', function(ProjectUpdate) {
return function(params) {
var scope = params.scope,
id = params.id;
ProjectUpdate({ scope: scope, project_id: id });
};
}])
.factory('RelaunchAdhoc', ['AdhocRun', function(AdhocRun) {
return function(params) {
var scope = params.scope,
id = params.id;
AdhocRun({ scope: scope, project_id: id, relaunch: true });
};
}]);

View File

@ -1,108 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Parse
* @description
* Show the CodeMirror variable editor and allow
* toggle between JSON and YAML
*
*/
import 'codemirror/lib/codemirror.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/yaml/yaml.js';
import 'codemirror/addon/lint/lint.js';
import 'angular-codemirror/lib/yaml-lint.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/selection/active-line.js';
export default
angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule'])
.factory('ParseTypeChange', ['Alert', 'AngularCodeMirror',
function (Alert, AngularCodeMirror) {
return function (params) {
var scope = params.scope,
field_id = params.field_id,
fld = (params.variable) ? params.variable : 'variables',
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
onReady = params.onReady,
onChange = params.onChange,
readOnly = params.readOnly;
function removeField(fld) {
//set our model to the last change in CodeMirror and then destroy CodeMirror
scope[fld] = scope[fld + 'codeMirror'].getValue();
$('#cm-' + fld + '-container > .CodeMirror').empty().remove();
}
function createField(onChange, onReady, fld) {
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes);
scope[fld + 'codeMirror'].showTextArea({
scope: scope,
model: fld,
element: field_id,
lineNumbers: true,
mode: scope[pfld],
onReady: onReady,
onChange: onChange
});
}
// Hide the textarea and show a CodeMirror editor
createField(onChange, onReady, fld);
// Toggle displayed variable string between JSON and YAML
scope.parseTypeChange = function(model, fld) {
var json_obj;
if (scope[model] === 'json') {
// converting yaml to json
try {
removeField(fld);
json_obj = jsyaml.load(scope[fld]);
if ($.isEmptyObject(json_obj)) {
scope[fld] = "{}";
}
else {
scope[fld] = JSON.stringify(json_obj, null, " ");
}
createField(onReady, onChange, fld);
}
catch (e) {
Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message);
setTimeout( function() { scope.$apply( function() { scope[model] = 'yaml'; createField(onReady, onChange, fld); }); }, 500);
}
}
else {
// convert json to yaml
try {
removeField(fld);
json_obj = JSON.parse(scope[fld]);
if ($.isEmptyObject(json_obj)) {
scope[fld] = '---';
}
else {
scope[fld] = jsyaml.safeDump(json_obj);
}
createField(onReady, onChange, fld);
}
catch (e) {
Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message);
setTimeout( function() { scope.$apply( function() { scope[model] = 'json'; createField(onReady, onChange, fld); }); }, 500 );
}
}
};
};
}
]);

View File

@ -1,91 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ProjectPath
* @description
* Use GetProjectPath({ scope: <scope>, master: <master obj> }) to
* load scope.project_local_paths (array of options for drop-down) and
* scope.base_dir (readonly field).
*
*/
export default
angular.module('ProjectPathHelper', ['RestServices', 'Utilities'])
.factory('GetProjectPath', ['Alert', 'Rest', 'GetBasePath', 'ProcessErrors',
function (Alert, Rest, GetBasePath, ProcessErrors) {
return function (params) {
var scope = params.scope,
master = params.master;
function arraySort(data) {
//Sort nodes by name
var i, j, names = [],
newData = [];
for (i = 0; i < data.length; i++) {
names.push(data[i].value);
}
names.sort();
for (j = 0; j < names.length; j++) {
for (i = 0; i < data.length; i++) {
if (data[i].value === names[j]) {
newData.push(data[i]);
}
}
}
return newData;
}
scope.showMissingPlaybooksAlert = false;
Rest.setUrl(GetBasePath('config'));
Rest.get()
.success(function (data) {
var opts = [], i;
if (data.project_local_paths) {
for (i = 0; i < data.project_local_paths.length; i++) {
opts.push({
label: data.project_local_paths[i],
value: data.project_local_paths[i]
});
}
}
if (scope.local_path) {
// List only includes paths not assigned to projects, so add the
// path assigned to the current project.
opts.push({
label: scope.local_path,
value: scope.local_path
});
}
scope.project_local_paths = arraySort(opts);
if (scope.local_path) {
for (i = 0; scope.project_local_paths.length; i++) {
if (scope.project_local_paths[i].value === scope.local_path) {
scope.local_path = scope.project_local_paths[i];
break;
}
}
}
scope.base_dir = data.project_base_dir;
master.local_path = scope.local_path;
master.base_dir = scope.base_dir; // Keep in master object so that it doesn't get
// wiped out on form reset.
if (opts.length === 0) {
// trigger display of alert block when scm_type == manual
scope.showMissingPlaybooksAlert = true;
}
scope.$emit('pathsReady');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to access API config. GET status: ' + status });
});
};
}
]);

View File

@ -1,84 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Projects
* @description
* Use GetProjectPath({ scope: <scope>, master: <master obj> }) to
* load scope.project_local_paths (array of options for drop-down) and
* scope.base_dir (readonly field).
*
*/
export default
angular.module('ProjectsHelper', ['RestServices', 'Utilities', 'ProjectStatusDefinition', 'ProjectFormDefinition'])
.factory('GetProjectIcon', [ function() {
return function(status) {
var result = '';
switch (status) {
case 'n/a':
case 'ok':
case 'never updated':
result = 'none';
break;
case 'pending':
case 'waiting':
case 'new':
result = 'none';
break;
case 'updating':
case 'running':
result = 'running';
break;
case 'successful':
result = 'success';
break;
case 'failed':
case 'missing':
case 'canceled':
result = 'error';
}
return result;
};
}])
.factory('GetProjectToolTip', ['i18n', function(i18n) {
return function(status) {
var result = '';
switch (status) {
case 'n/a':
case 'ok':
case 'never updated':
result = i18n._('No SCM updates have run for this project');
break;
case 'pending':
case 'waiting':
case 'new':
result = i18n._('Queued. Click for details');
break;
case 'updating':
case 'running':
result = i18n._('Running! Click for details');
break;
case 'successful':
result = i18n._('Success! Click for details');
break;
case 'failed':
result = i18n._('Failed. Click for details');
break;
case 'missing':
result = i18n._('Missing. Click for details');
break;
case 'canceled':
result = i18n._('Canceled. Click for details');
break;
}
return result;
};
}]);

View File

@ -1,506 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Schedules
* @description
* Schedules Helper
*
* Display the scheduler widget in a dialog
*
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', listGenerator.name, 'ModalDialog',
'GeneratorHelpers'])
.factory('EditSchedule', ['SchedulerInit', '$rootScope', 'Wait', 'Rest',
'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state',
function(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors,
GetBasePath, SchedulePost, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
schedule, scheduler,
url = GetBasePath('schedules') + id + '/';
delete scope.isFactCleanup;
delete scope.cleanupJob;
function setGranularity(){
var a,b, prompt_for_days,
keep_unit,
granularity,
granularity_keep_unit;
if(scope.cleanupJob){
scope.schedulerPurgeDays = Number(schedule.extra_data.days);
// scope.scheduler_form.schedulerPurgeDays.$setViewValue( Number(schedule.extra_data.days));
}
else if(scope.isFactCleanup){
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
// the API returns something like 20w or 1y
a = schedule.extra_data.older_than; // "20y"
b = schedule.extra_data.granularity; // "1w"
prompt_for_days = Number(_.initial(a,1).join('')); // 20
keep_unit = _.last(a); // "y"
granularity = Number(_.initial(b,1).join('')); // 1
granularity_keep_unit = _.last(b); // "w"
scope.keep_amount = prompt_for_days;
scope.granularity_keep_amount = granularity;
scope.keep_unit = _.find(scope.keep_unit_choices, function(i){
return i.value === keep_unit;
});
scope.granularity_keep_unit =_.find(scope.granularity_keep_unit_choices, function(i){
return i.value === granularity_keep_unit;
});
}
}
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function() {
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
if (!/DTSTART/.test(schedule.rrule)) {
schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z');
}
schedule.rrule = schedule.rrule.replace(/ RRULE:/,';');
schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART=');
scope.$on("htmlDetailReady", function() {
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
if(scope.isFactCleanup || scope.cleanupJob){
setGranularity();
}
});
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^");
});
scope.saveSchedule = function() {
schedule.extra_data = scope.extraVars;
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'edit',
schedule: schedule
});
};
Wait('start');
// Get the existing record
Rest.setUrl(url);
Rest.get()
.success(function(data) {
schedule = data;
try {
schedule.extra_data = JSON.parse(schedule.extra_data);
} catch(e) {
// do nothing
}
scope.extraVars = data.extra_data === '' ? '---' : '---\n' + jsyaml.safeDump(data.extra_data);
if(schedule.extra_data.hasOwnProperty('granularity')){
scope.isFactCleanup = true;
}
if (schedule.extra_data.hasOwnProperty('days')){
scope.cleanupJob = true;
}
scope.schedule_obj = data;
scope.$emit('ScheduleFound');
})
.error(function(data,status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}])
.factory('AddSchedule', ['$location', '$rootScope', '$stateParams',
'SchedulerInit', 'Wait', 'GetBasePath', 'Empty', 'SchedulePost', '$state', 'Rest', 'ProcessErrors',
function($location, $rootScope, $stateParams, SchedulerInit,
Wait, GetBasePath, Empty, SchedulePost, $state, Rest,
ProcessErrors) {
return function(params) {
var scope = params.scope,
callback= params.callback,
base = params.base || $location.path().replace(/^\//, '').split('/')[0],
url = params.url || null,
scheduler,
job_type;
job_type = scope.parentObject.job_type;
if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !url) {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
}
else if(base === "inventories"){
if (!params.url){
url = GetBasePath('groups') + $stateParams.id + '/';
Rest.setUrl(url);
Rest.get().
then(function (data) {
url = data.data.related.inventory_source + 'schedules/';
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory group info. GET returned status: ' +
response.status
});
});
}
else {
url = params.url;
}
}
else if (base === 'system_job_templates') {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
if(job_type === "cleanup_facts"){
scope.isFactCleanup = true;
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30);
scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1);
scope.keep_unit = scope.keep_unit_choices[0];
scope.granularity_keep_unit = scope.granularity_keep_unit_choices[1];
}
else {
scope.cleanupJob = true;
}
}
Wait('start');
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
if(scope.schedulerUTCTime) {
// The UTC time is already set
scope.processSchedulerEndDt();
}
else {
// We need to wait for it to be set by angular-scheduler because the following function depends
// on it
var schedulerUTCTimeWatcher = scope.$watch('schedulerUTCTime', function(newVal) {
if(newVal) {
// Remove the watcher
schedulerUTCTimeWatcher();
scope.processSchedulerEndDt();
}
});
}
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
scheduler.clear();
scope.$on("htmlDetailReady", function() {
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^", null, {reload: true});
});
scope.saveSchedule = function() {
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'add'
});
};
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
if ($(e.target).text() === 'Details') {
if (!scheduler.isValid()) {
$('#scheduler-tabs a:first').tab('show');
}
}
});
};
}])
.factory('SchedulePost', ['Rest', 'ProcessErrors', 'RRuleToAPI', 'Wait',
function(Rest, ProcessErrors, RRuleToAPI, Wait) {
return function(params) {
var scope = params.scope,
url = params.url,
scheduler = params.scheduler,
mode = params.mode,
schedule = (params.schedule) ? params.schedule : {},
callback = params.callback,
newSchedule, rrule, extra_vars;
if (scheduler.isValid()) {
Wait('start');
newSchedule = scheduler.getValue();
rrule = scheduler.getRRule();
schedule.name = newSchedule.name;
schedule.rrule = RRuleToAPI(rrule.toString());
schedule.description = (/error/.test(rrule.toText())) ? '' : rrule.toText();
if (scope.isFactCleanup) {
extra_vars = {
"older_than": scope.scheduler_form.keep_amount.$viewValue + scope.scheduler_form.keep_unit.$viewValue.value,
"granularity": scope.scheduler_form.granularity_keep_amount.$viewValue + scope.scheduler_form.granularity_keep_unit.$viewValue.value
};
schedule.extra_data = JSON.stringify(extra_vars);
} else if (scope.cleanupJob) {
extra_vars = {
"days" : scope.scheduler_form.schedulerPurgeDays.$viewValue
};
schedule.extra_data = JSON.stringify(extra_vars);
}
else if(scope.extraVars){
schedule.extra_data = scope.parseType === 'yaml' ?
(scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars;
}
Rest.setUrl(url);
if (mode === 'add') {
Rest.post(schedule)
.success(function(){
if (callback) {
scope.$emit(callback);
}
else {
Wait('stop');
}
})
.error(function(data, status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status });
});
}
else {
Rest.put(schedule)
.success(function(){
if (callback) {
scope.$emit(callback, schedule);
}
else {
Wait('stop');
}
})
.error(function(data, status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status });
});
}
}
else {
return false;
}
};
}])
/**
* Flip a schedule's enable flag
*
* ToggleSchedule({
* scope: scope,
* id: schedule.id to update
* callback: scope.$emit label to call when update completes
* });
*
*/
.factory('ToggleSchedule', ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', '$state',
function(Wait, GetBasePath, ProcessErrors, Rest, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('schedules') + id +'/';
// Perform the update
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function(e, data) {
data.enabled = (data.enabled) ? false : true;
Rest.put(data)
.success( function() {
Wait('stop');
$state.go('.', null, {reload: true});
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status });
});
});
Wait('start');
// Get the schedule
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.$emit('ScheduleFound', data);
})
.error(function(data,status){
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}])
/**
* Delete a schedule. Prompts user to confirm delete
*
* DeleteSchedule({
* scope: $scope containing list of schedules
* id: id of schedule to delete
* callback: $scope.$emit label to call when delete is completed
* })
*
*/
.factory('DeleteSchedule', ['GetBasePath','Rest', 'Wait', '$state',
'ProcessErrors', 'Prompt', 'Find', '$location', '$filter',
function(GetBasePath, Rest, Wait, $state, ProcessErrors, Prompt, Find,
$location, $filter) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
action, schedule, list, url, hdr;
if (scope.schedules) {
list = scope.schedules;
}
else if (scope.scheduled_jobs) {
list = scope.scheduled_jobs;
}
url = GetBasePath('schedules') + id + '/';
schedule = Find({list: list, key: 'id', val: id });
hdr = 'Delete Schedule';
action = function () {
Wait('start');
Rest.setUrl(url);
Rest.destroy()
.success(function () {
$('#prompt-modal').modal('hide');
scope.$emit(callback, id);
if (new RegExp('/' + id + '$').test($location.$$url)) {
$location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view
}
else{
$state.go('.', null, {reload: true});
}
})
.error(function (data, status) {
try {
$('#prompt-modal').modal('hide');
}
catch(e) {
// ignore
}
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. DELETE returned: ' + status });
});
};
Prompt({
hdr: hdr,
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the schedule below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(schedule.name) + '</div>',
action: action,
actionText: 'DELETE',
backdrop: false
});
};
}])
/**
* Convert rrule string to an API agreeable format
*
*/
.factory('RRuleToAPI', [ function() {
return function(rrule) {
var response;
response = rrule.replace(/(^.*(?=DTSTART))(DTSTART=.*?;)(.*$)/, function(str, p1, p2, p3) {
return p2.replace(/\;/,'').replace(/=/,':') + ' ' + 'RRULE:' + p1 + p3;
});
return response;
};
}]);

View File

@ -1,177 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Selection
* @description
* SelectionHelper
* Used in list controllers where the list might also be used as a selection list.
*
* SelectionInit( {
* scope: <list scope>,
* list: <list object>
* })
*/
export default
angular.module('SelectionHelper', ['Utilities', 'RestServices'])
.factory('SelectionInit', ['Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'Wait',
function (Rest, Alert, ProcessErrors, ReturnToCaller, Wait) {
return function (params) {
var scope = params.scope,
list = params.list,
target_url = params.url,
returnToCaller = params.returnToCaller,
selected;
if (params.selected !== undefined) {
selected = params.selected;
} else {
selected = []; //array of selected row IDs
}
scope.formModalActionDisabled = true;
scope.disableSelectBtn = true;
// toggle row selection
scope['toggle_' + list.iterator] = function (id, ischeckbox) {
var i, j, found;
for (i = 0; i < scope[list.name].length; i++) {
if (scope[list.name][i].id === id) {
var control = scope[list.name][i];
if (ischeckbox && control.checked) {
scope[list.name][i].success_class = 'success';
// add selected object to the array
found = false;
for (j = 0; j < selected.length; j++) {
if (selected[j].id === id) {
found = true;
break;
}
}
if (!found) {
selected.push(scope[list.name][i]);
}
} else if (ischeckbox) {
scope[list.name][i].success_class = '';
// remove selected object from the array
for (j = 0; j < selected.length; j++) {
if (selected[j].id === id) {
selected.splice(j, 1);
break;
}
}
}
}
}
if (selected.length > 0) {
scope.formModalActionDisabled = false;
scope.disableSelectBtn = false;
} else {
scope.formModalActionDisabled = true;
scope.disableSelectBtn = true;
}
};
// Add the selections
scope.finishSelection = function () {
Rest.setUrl(target_url);
var queue = [], j;
scope.formModalActionDisabled = true;
scope.disableSelectBtn = true;
Wait('start');
function finished() {
selected = [];
if (returnToCaller !== undefined) {
ReturnToCaller(returnToCaller);
} else {
$('#form-modal').modal('hide');
scope.$emit('modalClosed');
}
}
function postIt(data) {
Rest.post(data)
.success(function (data, status) {
queue.push({ result: 'success', data: data, status: status });
scope.$emit('callFinished');
})
.error(function (data, status, headers) {
queue.push({ result: 'error', data: data, status: status, headers: headers });
scope.$emit('callFinished');
});
}
if (scope.callFinishedRemove) {
scope.callFinishedRemove();
}
scope.callFinishedRemove = scope.$on('callFinished', function () {
// We call the API for each selected item. We need to hang out until all the api
// calls are finished.
var i, errors=0;
if (queue.length === selected.length) {
Wait('stop');
for (i = 0; i < queue.length; i++) {
if (queue[i].result === 'error') {
ProcessErrors(scope, queue[i].data, queue[i].status, null, { hdr: 'POST Failure',
msg: 'Failed to add ' + list.iterator + '. POST returned status: ' + queue[i].status });
errors++;
}
}
if (errors === 0) {
finished();
}
}
});
if (selected.length > 0) {
for (j = 0; j < selected.length; j++) {
postIt(selected[j]);
}
} else {
finished();
}
};
scope.formModalAction = scope.finishSelection;
// Initialize our data set after a refresh (page change or search)
if (scope.SelectPostRefreshRemove) {
scope.SelectPostRefreshRemove();
}
scope.SelectPostRefreshRemove = scope.$on('PostRefresh', function () {
var i, j, found;
if (scope[list.name]) {
for (i = 0; i < scope[list.name].length; i++) {
found = false;
for (j = 0; j < selected.length; j++) {
if (selected[j].id === scope[list.name][i].id) {
found = true;
break;
}
}
if (found) {
scope[list.name][i].checked = '1';
scope[list.name][i].success_class = 'success';
} else {
scope[list.name][i].checked = '0';
scope[list.name][i].success_class = '';
}
}
}
});
};
}
]);

View File

@ -1,46 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Users
* @description
* UserHelper
* Routines shared amongst the user controllers
*
*/
export default
angular.module('UserHelper', ['UserFormDefinition'])
.factory('ResetForm', ['UserForm',
function (UserForm) {
return function () {
// Restore form to default conditions. Run before applying LDAP configuration.
// LDAP may manage some or all of these fields in which case the user cannot
// make changes to their values in AWX.
UserForm.fields.first_name.readonly = false;
UserForm.fields.first_name.editRequired = true;
UserForm.fields.last_name.readonly = false;
UserForm.fields.last_name.editRequired = true;
UserForm.fields.email.readonly = false;
UserForm.fields.email.editRequired = true;
UserForm.fields.organization.awRequiredWhen = {
reqExpression: "orgrequired",
init: true
};
UserForm.fields.organization.readonly = false;
UserForm.fields.username.awRequiredWhen = {
reqExpression: "not_ldap_user",
init: true
};
UserForm.fields.username.readonly = false;
UserForm.fields.password.editRequired = false;
UserForm.fields.password.addRrequired = true;
};
}
]);

View File

@ -1,180 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Variables
* @description
* VariablesHelper
*
*
*/
export default
angular.module('VariablesHelper', ['Utilities'])
/**
variables: string containing YAML or JSON | a JSON object.
If JSON string, convert to JSON object and run through jsyaml.safeDump() to create a YAML document. If YAML,
will attempt to load via jsyaml.safeLoad() and return a YAML document using jsyaml.safeDump(). In all cases
a YAML document is returned.
**/
.factory('ParseVariableString', ['$log', 'ProcessErrors', 'SortVariables', function ($log, ProcessErrors, SortVariables) {
return function (variables) {
var result = "---", json_obj;
if (typeof variables === 'string') {
if (variables === "{}" || variables === "null" || variables === "" || variables === "\"\"") {
// String is empty, return ---
} else {
try {
json_obj = JSON.parse(variables);
json_obj = SortVariables(json_obj);
result = jsyaml.safeDump(json_obj);
}
catch (e) {
$log.debug('Attempt to parse extra_vars as JSON failed. Check that the variables parse as yaml. Set the raw string as the result.');
try {
// do safeLoad, which well error if not valid yaml
json_obj = jsyaml.safeLoad(variables);
// but just send the variables
result = variables;
}
catch(e2) {
ProcessErrors(null, variables, e2.message, null, { hdr: 'Error!',
msg: 'Attempts to parse variables as JSON and YAML failed. Last attempt returned: ' + e2.message });
}
}
}
}
else {
if ($.isEmptyObject(variables) || variables === null) {
// Empty object, return ---
}
else {
// convert object to yaml
try {
json_obj = SortVariables(variables);
result = jsyaml.safeDump(json_obj);
// result = variables;
}
catch(e3) {
ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!',
msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message });
}
}
}
return result;
};
}])
/**
parseType: 'json' | 'yaml'
variables: string containing JSON or YAML
stringify: optional, boolean
Parse the given string according to the parseType to a JSON object. If stringify true,
stringify the object and return the string. Otherwise, return the JSON object.
**/
.factory('ToJSON', ['$log', 'ProcessErrors', function($log, ProcessErrors) {
return function(parseType, variables, stringify, reviver) {
var json_data,
result,
tmp;
// bracketVar,
// key,
// lines, i, newVars = [];
if (parseType === 'json') {
try {
// perform a check to see if the user cleared the field completly
if(variables.trim() === "" || variables.trim() === "{" || variables.trim() === "}" ){
variables = "{}";
}
//parse a JSON string
if (reviver) {
json_data = JSON.parse(variables, reviver);
}
else {
json_data = JSON.parse(variables);
}
}
catch(e) {
json_data = {};
$log.error('Failed to parse JSON string. Parser returned: ' + e.message);
ProcessErrors(null, variables, e.message, null, { hdr: 'Error!',
msg: 'Failed to parse JSON string. Parser returned: ' + e.message });
throw 'Parse error. Failed to parse variables.';
}
} else {
try {
if(variables.trim() === "" || variables.trim() === "-" || variables.trim() === "--"){
variables = '---';
}
json_data = jsyaml.safeLoad(variables);
if(json_data!==null){
// unparsing just to make sure no weird characters are included.
tmp = jsyaml.dump(json_data);
if(tmp.indexOf('[object Object]')!==-1){
throw "Failed to parse YAML string. Parser returned' + key + ' : ' +value + '.' ";
}
}
}
catch(e) {
json_data = undefined; // {};
// $log.error('Failed to parse YAML string. Parser returned undefined');
ProcessErrors(null, variables, e.message, null, { hdr: 'Error!',
msg: 'Failed to parse YAML string. Parser returned undefined'});
}
}
// Make sure our JSON is actually an object
if (typeof json_data !== 'object') {
ProcessErrors(null, variables, null, null, { hdr: 'Error!',
msg: 'Failed to parse variables. Attempted to parse ' + parseType + '. Parser did not return an object.' });
// setTimeout( function() {
throw 'Parse error. Failed to parse variables.';
// }, 1000);
}
result = json_data;
if (stringify) {
if(json_data === undefined){
result = undefined;
}
else if ($.isEmptyObject(json_data)) {
result = "";
} else {
// utilize the parsing to get here
// but send the raw variable string
result = variables;
}
}
return result;
};
}])
.factory('SortVariables', [ function() {
return function(variableObj) {
var newObj;
function sortIt(objToSort) {
var i,
keys = Object.keys(objToSort),
newObj = {};
keys = keys.sort();
for (i=0; i < keys.length; i++) {
if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) {
newObj[keys[i]] = sortIt(objToSort[keys[i]]);
}
else {
newObj[keys[i]] = objToSort[keys[i]];
}
}
return newObj;
}
newObj = sortIt(variableObj);
return newObj;
};
}]);

View File

@ -1,94 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:api-defaults
* @description this could use more discussion
*/
export default
angular.module('APIDefaults', ['RestServices', 'Utilities'])
.factory('GetAPIDefaults', ['Alert', 'Rest', '$rootScope',
function (Alert, Rest, $rootScope) {
return function (key) {
//Reload a related collection on pagination or search change
var result = {}, cnt = 0, url;
function lookup(key) {
var id, result = {};
for (id in $rootScope.apiDefaults) {
if (id === key || id.iterator === key) {
result[id] = $rootScope.apiDefaults[id];
break;
}
}
return result;
}
function wait() {
if ($.isEmptyObject(result) && cnt < 5) {
cnt++;
setTimeout(1000, wait());
} else if (result.status === 'success') {
return lookup(key);
}
}
if ($rootScope.apiDefaults === null || $rootScope.apiDefaults === undefined) {
url = '/api/v1/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
var id, defaults = data;
for (id in defaults) {
switch (id) {
case 'organizations':
defaults[id].iterator = 'organization';
break;
case 'jobs':
defaults[id].iterator = 'job';
break;
case 'users':
defaults[id].iterator = 'user';
break;
case 'teams':
defaults[id].iterator = 'team';
break;
case 'hosts':
defaults[id].iterator = 'host';
break;
case 'groups':
defaults[id].iterator = 'group';
break;
case 'projects':
defaults[id].iterator = 'project';
break;
case 'inventories':
defaults[id].iterator = 'inventory';
break;
}
}
$rootScope.apiDefaults = defaults;
result = {
status: 'success'
};
})
.error(function (data, status) {
result = {
status: 'error',
msg: 'Call to ' + url + ' failed. GET returned status: ' + status
};
});
return wait();
} else {
return lookup(key);
}
};
}
]);

View File

@ -1,84 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Inventory
* @description InventoryHelper
* Routines for building the tree. Everything related to the tree is here except
* for the menu piece. The routine for building the menu is in InventoriesEdit controller
* (controllers/Inventories.js)
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('InventoryHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name,
'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'VariablesHelper',
])
.factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'OrganizationList',
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON',
function (InventoryForm, Rest, Alert, ProcessErrors, OrganizationList, GetBasePath, ParseTypeChange, Wait,
ToJSON) {
return function (params) {
// Save inventory property modifications
var scope = params.scope,
form = InventoryForm,
defaultUrl = GetBasePath('inventory'),
fld, json_data, data;
Wait('start');
// Make sure we have valid variable data
json_data = ToJSON(scope.parseType, scope.variables);
data = {};
for (fld in form.fields) {
if (fld !== 'variables') {
if (form.fields[fld].realName) {
data[form.fields[fld].realName] = scope[fld];
} else {
data[fld] = scope[fld];
}
}
}
if (scope.removeUpdateInventoryVariables) {
scope.removeUpdateInventoryVariables();
}
scope.removeUpdateInventoryVariables = scope.$on('UpdateInventoryVariables', function(e, data) {
Rest.setUrl(data.related.variable_data);
Rest.put(json_data)
.success(function () {
Wait('stop');
scope.$emit('InventorySaved');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to update inventory varaibles. PUT returned status: ' + status
});
});
});
Rest.setUrl(defaultUrl + scope.inventory_id + '/');
Rest.put(data)
.success(function (data) {
if (scope.variables) {
scope.$emit('UpdateInventoryVariables', data);
} else {
scope.$emit('InventorySaved');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to update inventory. PUT returned status: ' + status });
});
};
}
]);

View File

@ -1,46 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:md5
* @description
* Run md5Setup({ scope: , master:, check_field:, default_val: })
* to initialize md5 fields (checkbox and text field).
* discussion
*/
export default
angular.module('md5Helper', ['RestServices', 'Utilities', require('angular-md5')])
.factory('md5Setup', ['md5', function (md5) {
return function (params) {
var scope = params.scope,
master = params.master,
check_field = params.check_field,
default_val = params.default_val;
scope[check_field] = default_val;
master[check_field] = default_val;
scope.genMD5 = function (fld) {
var now = new Date();
scope[fld] = md5.createHash('AnsibleWorks' + now.getTime());
scope.$emit('NewMD5Generated');
};
scope.toggleCallback = function (fld) {
if (scope.allow_callbacks === false) {
scope[fld] = '';
}
};
scope.selectAll = function (fld) {
$('input[name="' + fld + '"]').focus().select();
};
};
}]);

View File

@ -1,76 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:teams
* @description
* TeamHelper
* Routines shared amongst the team controllers
*/
import listGenerator from '../shared/list-generator/main';
export default
angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name
])
.factory('SetTeamListeners', ['Alert', 'Rest',
function (Alert, Rest) {
return function (params) {
var scope = params.scope,
set = params.set;
// Listeners to perform lookups after main inventory list loads
scope.$on('TeamResultFound', function (e, results, lookup_results) {
var i, j, key, property;
if (lookup_results.length === results.length) {
key = 'organization';
property = 'organization_name';
for (i = 0; i < results.length; i++) {
for (j = 0; j < lookup_results.length; j++) {
if (results[i][key] === lookup_results[j].id) {
results[i][property] = lookup_results[j].value;
}
}
}
// @issue: OLD SEARCH
// scope[iterator + 'SearchSpin'] = false;
scope[set] = results;
}
});
scope.$on('TeamRefreshFinished', function (e, results) {
// Loop through the result set (sent to us by the search helper) and
// lookup the id and name of each organization. After each lookup
// completes, call resultFound.
var i, lookup_results = [], url;
function getOrganization(url) {
Rest.setUrl(url);
Rest.get()
.success(function (data) {
lookup_results.push({ id: data.id, value: data.name });
scope.$emit('TeamResultFound', results, lookup_results);
})
.error(function () {
lookup_results.push({ id: 'error' });
scope.$emit('TeamResultFound', results, lookup_results);
});
}
for (i = 0; i < results.length; i++) {
url = '/api/v1/organizations/' + results[i].organization + '/';
getOrganization(url);
}
});
};
}
]);

View File

@ -1,4 +1,4 @@
@import "../../shared/branding/colors.default.less";
@import "../../../shared/branding/colors.default.less";
/** @define DashboardCounts */

View File

@ -9,7 +9,7 @@ export default
data: '='
},
replace: false,
templateUrl: templateUrl('dashboard/counts/dashboard-counts'),
templateUrl: templateUrl('home/dashboard/counts/dashboard-counts'),
link: function(scope, element, attrs) {
scope.$watch("data", function(data) {
if (data && data.hosts) {

View File

@ -1,5 +1,5 @@
/** @define Dashboard */
@import "../shared/branding/colors.default.less";
@import "../../shared/branding/colors.default.less";
.Dashboard {
display: flex;

View File

@ -5,7 +5,7 @@ export default
return {
restrict: 'E',
scope: true,
templateUrl: templateUrl('dashboard/dashboard'),
templateUrl: templateUrl('home/dashboard/dashboard'),
link: function(scope, element, attrs) {
}
};

View File

@ -1,6 +1,6 @@
/** @define DashboardGraphs */
@import "../../shared/branding/colors.default.less";
@import "../../../shared/branding/colors.default.less";
.DashboardGraphs {
margin-top: 20px;

View File

@ -4,7 +4,7 @@ export default ['templateUrl',
return {
restrict: 'E',
scope: true,
templateUrl: templateUrl('dashboard/graphs/dashboard-graphs'),
templateUrl: templateUrl('home/dashboard/graphs/dashboard-graphs'),
link: function(scope, element, attrs) {
function clearStatus() {

View File

@ -23,7 +23,7 @@ function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustG
scope: {
data: '='
},
templateUrl: templateUrl('dashboard/graphs/job-status/job_status_graph'),
templateUrl: templateUrl('home/dashboard/graphs/job-status/job_status_graph'),
link: link
};

View File

@ -1,7 +1,7 @@
import JobStatusGraphDirective from './job-status-graph.directive';
import JobStatusGraphService from './job-status-graph.service';
import DashboardGraphHelpers from '../graph-helpers/main';
import templateUrl from '../../../shared/template-url/main';
import templateUrl from '../../../../shared/template-url/main';
export default angular.module('JobStatusGraph', [DashboardGraphHelpers.name, templateUrl.name])
.directive('jobStatusGraph', JobStatusGraphDirective)

View File

@ -9,7 +9,7 @@ import form from './dashboard-hosts.form';
import listController from './dashboard-hosts-list.controller';
import editController from './dashboard-hosts-edit.controller';
import service from './dashboard-hosts.service';
import { N_ } from '../../i18n';
import { N_ } from '../../../i18n';
export default
angular.module('dashboardHosts', [])

View File

@ -1,6 +1,6 @@
/** @define DashboardList */
@import "../../shared/branding/colors.default.less";
@import "../../../shared/branding/colors.default.less";
.DashboardList {
flex: 1;

View File

@ -11,7 +11,7 @@ export default
scope: {
data: '='
},
templateUrl: templateUrl('dashboard/lists/job-templates/job-templates-list')
templateUrl: templateUrl('home/dashboard/lists/job-templates/job-templates-list')
};
function link(scope, element, attr) {

View File

@ -1,6 +1,5 @@
import JobTemplatesListDirective from './job-templates-list.directive';
import systemStatus from '../../../smart-status/main';
import jobSubmissionHelper from '../../../helpers/JobSubmission';
import systemStatus from '../../../../smart-status/main';
export default angular.module('JobTemplatesList', [systemStatus.name, jobSubmissionHelper.name])
export default angular.module('JobTemplatesList', [systemStatus.name])
.directive('jobTemplatesList', JobTemplatesListDirective);

View File

@ -10,7 +10,7 @@ export default
scope: {
data: '='
},
templateUrl: templateUrl('dashboard/lists/jobs/jobs-list')
templateUrl: templateUrl('home/dashboard/lists/jobs/jobs-list')
};
function link(scope, element, attr) {

View File

@ -0,0 +1,125 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', '$compile', '$stateParams', '$rootScope', '$location', '$log','Wait',
'ClearScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$window', 'graphData',
function($scope, $compile, $stateParams, $rootScope, $location, $log, Wait,
ClearScope, Rest, GetBasePath, ProcessErrors, $window, graphData) {
ClearScope('home');
var dataCount = 0;
$scope.$on('ws-jobs', function () {
Rest.setUrl(GetBasePath('dashboard'));
Rest.get()
.success(function (data) {
$scope.dashboardData = data;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard host graph data: ' + status });
});
Rest.setUrl("api/v1/unified_jobs?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job");
Rest.get()
.success(function (data) {
$scope.dashboardJobsListData = data.results;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
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()
.success(function (data) {
$scope.dashboardJobTemplatesListData = data.results;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
});
if ($scope.removeDashboardDataLoadComplete) {
$scope.removeDashboardDataLoadComplete();
}
$scope.removeDashboardDataLoadComplete = $scope.$on('dashboardDataLoadComplete', function () {
dataCount++;
if (dataCount === 3) {
Wait("stop");
dataCount = 0;
}
});
if ($scope.removeDashboardReady) {
$scope.removeDashboardReady();
}
$scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) {
$scope.dashboardCountsData = data;
$scope.graphData = graphData;
$scope.$emit('dashboardDataLoadComplete');
var cleanupJobListener =
$rootScope.$on('DataReceived:JobStatusGraph', function(e, data) {
$scope.graphData.jobStatus = data;
});
$scope.$on('$destroy', function() {
cleanupJobListener();
});
});
if ($scope.removeDashboardJobsListReady) {
$scope.removeDashboardJobsListReady();
}
$scope.removeDashboardJobsListReady = $scope.$on('dashboardJobsListReady', function (e, data) {
$scope.dashboardJobsListData = data;
$scope.$emit('dashboardDataLoadComplete');
});
if ($scope.removeDashboardJobTemplatesListReady) {
$scope.removeDashboardJobTemplatesListReady();
}
$scope.removeDashboardJobTemplatesListReady = $scope.$on('dashboardJobTemplatesListReady', function (e, data) {
$scope.dashboardJobTemplatesListData = data;
$scope.$emit('dashboardDataLoadComplete');
});
$scope.refresh = function () {
Wait('start');
Rest.setUrl(GetBasePath('dashboard'));
Rest.get()
.success(function (data) {
$scope.dashboardData = data;
$scope.$emit('dashboardReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
});
Rest.setUrl("api/v1/unified_jobs?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job");
Rest.get()
.success(function (data) {
data = data.results;
$scope.$emit('dashboardJobsListReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard jobs list: ' + status });
});
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()
.success(function (data) {
data = data.results;
$scope.$emit('dashboardJobTemplatesListReady', data);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard job templates list: ' + status });
});
};
$scope.refresh();
}
];

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