Merge branch 'devel' into addOrgToPermModal

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

View File

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

View File

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

View File

@@ -149,6 +149,11 @@ class FieldLookupBackend(BaseFilterBackend):
return field.to_python(value) return field.to_python(value)
def value_to_python(self, model, lookup, 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) field, new_lookup = self.get_field_from_lookup(model, lookup)
# Type names are stored without underscores internally, but are presented and # 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. # Add version number in which view was added to Tower.
added_in_version = '1.2' 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): if getattr(view, 'new_in_%s' % version.replace('.', ''), False):
added_in_version = version added_in_version = version
break break

View File

@@ -26,6 +26,9 @@ class JSONParser(parsers.JSONParser):
try: try:
data = stream.read().decode(encoding) 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: except ValueError as exc:
raise ParseError(_('JSON parse error - %s') % six.text_type(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') logger = logging.getLogger('awx.api.permissions')
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission', __all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
'TaskPermission', 'ProjectUpdatePermission', 'UserPermission',] 'TaskPermission', 'ProjectUpdatePermission', 'UserPermission',
'IsSuperUser']
class ModelAccessPermission(permissions.BasePermission): class ModelAccessPermission(permissions.BasePermission):
@@ -208,3 +209,10 @@ class UserPermission(ModelAccessPermission):
raise PermissionDenied() 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', 'awx.conf.views',
url(r'^$', 'setting_category_list'), url(r'^$', 'setting_category_list'),
url(r'^(?P<category_slug>[a-z0-9-]+)/$', 'setting_singleton_detail'), 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 # Tower
from awx.api.generics import * # noqa from awx.api.generics import * # noqa
from awx.api.permissions import IsSuperUser
from awx.main.utils import * # noqa 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.license import get_licensed_features
from awx.conf.models import Setting from awx.conf.models import Setting
from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer
@@ -130,6 +132,32 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
settings.TOWER_URL_BASE = url 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 # Create view functions for all of the class-based views to simplify inclusion
# in URL patterns and reverse URL lookups, converting CamelCase names to # in URL patterns and reverse URL lookups, converting CamelCase names to
# lowercase_with_underscore (e.g. MyView.as_view() becomes my_view). # 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) }, 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 @pytest.mark.django_db
def test_edit_nonsenstive(patch, job_template_factory, alice): def test_edit_nonsenstive(patch, job_template_factory, alice):
objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred') objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred')

View File

@@ -2,14 +2,19 @@
# All Rights Reserved. # All Rights Reserved.
# Python # Python
from collections import OrderedDict
import pytest import pytest
import os import os
# Mock
import mock
# Django # Django
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
# AWX # AWX
from awx.conf.models import Setting 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_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' 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) response = get(url, user=admin, expect=200)
assert not response.data['CUSTOM_LOGO'] assert not response.data['CUSTOM_LOGO']
assert not response.data['CUSTOM_LOGIN_INFO'] 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 False
return True 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 base_dir = settings.BASE_DIR
requirements_path = os.path.join(base_dir, '../', 'requirements/requirements.txt') requirements_path = os.path.join(base_dir, '../', 'requirements/requirements.txt')
reqs_actual = [] reqs_actual = []
xs = freeze.freeze(local_only=True) xs = freeze.freeze(local_only=True)
for x in xs: for x in xs:
if '## The following requirements were added by pip freeze' in x: if skip_line(x):
break continue
x = x.lower() x = x.lower()
(pkg_name, pkg_version) = x.split('==') (pkg_name, pkg_version) = x.split('==')
reqs_actual.append([pkg_name, pkg_version]) reqs_actual.append([pkg_name, pkg_version])
@@ -33,11 +40,7 @@ def test_env_matches_requirements_txt():
line = line.partition('#')[0] line = line.partition('#')[0]
line = line.rstrip().lower() line = line.rstrip().lower()
# TODO: process git requiremenst and use egg # TODO: process git requiremenst and use egg
if line == '': if skip_line(line):
continue
if line.strip().startswith('#') or line.strip().startswith('git'):
continue
if line.startswith('-e'):
continue continue
''' '''

View File

@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
@@ -24,6 +26,14 @@ def test_valid_in(valid_value):
assert 'foo' in 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('lookup_suffix', ['', 'contains', 'startswith', 'in'])
@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS) @pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS)
def test_filter_on_password_field(password_field, lookup_suffix): def test_filter_on_password_field(password_field, lookup_suffix):

View File

@@ -1,13 +1,15 @@
import base64 import base64
import json import json
import logging import logging
from uuid import uuid4
from django.conf import LazySettings from django.conf import LazySettings
import pytest import pytest
import requests import requests
from requests_futures.sessions import FuturesSession 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 from awx.main.utils.formatters import LogstashFormatter
@@ -25,19 +27,21 @@ def dummy_log_record():
@pytest.fixture() @pytest.fixture()
def ok200_adapter(): def http_adapter():
class OK200Adapter(requests.adapters.HTTPAdapter): class FakeHTTPAdapter(requests.adapters.HTTPAdapter):
requests = [] requests = []
status = 200
reason = None
def send(self, request, **kwargs): def send(self, request, **kwargs):
self.requests.append(request) self.requests.append(request)
resp = requests.models.Response() resp = requests.models.Response()
resp.status_code = 200 resp.status_code = self.status
resp.raw = '200 OK' resp.reason = self.reason
resp.request = request resp.request = request
return resp return resp
return OK200Adapter() return FakeHTTPAdapter()
def test_https_logging_handler_requests_sync_implementation(): 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' 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(): def test_https_logging_handler_logstash_auth_info():
handler = HTTPSHandler(message_type='logstash', username='bob', password='ansible') handler = HTTPSHandler(message_type='logstash', username='bob', password='ansible')
handler.add_auth_information() handler.add_auth_information()
@@ -120,19 +160,19 @@ def test_https_logging_handler_skip_log(params, logger_name, expected):
('splunk', False), ('splunk', False),
('splunk', True), ('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): message_type, async):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
message_type=message_type, message_type=message_type,
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'], enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'],
async=async) async=async)
handler.setFormatter(LogstashFormatter()) handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter) handler.session.mount('http://', http_adapter)
async_futures = handler.emit(dummy_log_record) async_futures = handler.emit(dummy_log_record)
[future.result() for future in async_futures] [future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 1 assert len(http_adapter.requests) == 1
request = ok200_adapter.requests[0] request = http_adapter.requests[0]
assert request.url == 'http://127.0.0.1/' assert request.url == 'http://127.0.0.1/'
assert request.method == 'POST' assert request.method == 'POST'
body = json.loads(request.body) 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)) @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): dummy_log_record, async):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
username='user', password='pass', 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'], enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'],
async=async) async=async)
handler.setFormatter(LogstashFormatter()) handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter) handler.session.mount('http://', http_adapter)
async_futures = handler.emit(dummy_log_record) async_futures = handler.emit(dummy_log_record)
[future.result() for future in async_futures] [future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 1 assert len(http_adapter.requests) == 1
request = ok200_adapter.requests[0] request = http_adapter.requests[0]
assert request.headers['Authorization'] == 'Basic %s' % base64.b64encode("user:pass") assert request.headers['Authorization'] == 'Basic %s' % base64.b64encode("user:pass")
@pytest.mark.parametrize('async', (True, False)) @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): dummy_log_record, async):
handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True, handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
password='pass', message_type='splunk', password='pass', message_type='splunk',
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'], enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'],
async=async) async=async)
handler.setFormatter(LogstashFormatter()) handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter) handler.session.mount('http://', http_adapter)
async_futures = handler.emit(dummy_log_record) async_futures = handler.emit(dummy_log_record)
[future.result() for future in async_futures] [future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 1 assert len(http_adapter.requests) == 1
request = ok200_adapter.requests[0] request = http_adapter.requests[0]
assert request.headers['Authorization'] == 'Splunk pass' 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, handler = HTTPSHandler(host='127.0.0.1', enabled_flag=True,
message_type='logstash', indv_facts=True, message_type='logstash', indv_facts=True,
enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking']) enabled_loggers=['awx', 'activity_stream', 'job_events', 'system_tracking'])
handler.setFormatter(LogstashFormatter()) handler.setFormatter(LogstashFormatter())
handler.session.mount('http://', ok200_adapter) handler.session.mount('http://', http_adapter)
record = logging.LogRecord( record = logging.LogRecord(
'awx.analytics.system_tracking', # logger name 'awx.analytics.system_tracking', # logger name
20, # loglevel INFO 20, # loglevel INFO
@@ -212,8 +252,8 @@ def test_https_logging_handler_emit_one_record_per_fact(ok200_adapter):
async_futures = handler.emit(record) async_futures = handler.emit(record)
[future.result() for future in async_futures] [future.result() for future in async_futures]
assert len(ok200_adapter.requests) == 2 assert len(http_adapter.requests) == 2
requests = sorted(ok200_adapter.requests, key=lambda request: json.loads(request.body)['version']) requests = sorted(http_adapter.requests, key=lambda request: json.loads(request.body)['version'])
request = requests[0] request = requests[0]
assert request.url == 'http://127.0.0.1/' assert request.url == 'http://127.0.0.1/'

View File

@@ -5,6 +5,7 @@
import logging import logging
import json import json
import requests import requests
from requests.exceptions import RequestException
from copy import copy from copy import copy
# loggly # loggly
@@ -40,6 +41,10 @@ def unused_callback(sess, resp):
pass pass
class LoggingConnectivityException(Exception):
pass
class HTTPSNullHandler(logging.NullHandler): class HTTPSNullHandler(logging.NullHandler):
"Placeholder null handler to allow loading without database access" "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) kwargs[param] = getattr(settings, django_setting_name, None)
return cls(*args, **kwargs) 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): def get_full_message(self, record):
if record.exc_info: if record.exc_info:
return '\n'.join(traceback.format_exception(*record.exc_info)) return '\n'.join(traceback.format_exception(*record.exc_info))

View File

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

View File

@@ -6,10 +6,10 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList', export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList',
'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit', 'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath',
'OrganizationList', 'OrganizationList',
function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList, function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList,
InventoryList, CredentialList, $compile, generateList, GetBasePath, SelectionInit, InventoryList, CredentialList, $compile, generateList, GetBasePath,
OrganizationList) { OrganizationList) {
return { return {
restrict: 'E', 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 activityStreamRoute from './activitystream.route';
import activityStreamController from './activitystream.controller'; import activityStreamController from './activitystream.controller';
import streamDropdownNav from './streamDropdownNav/stream-dropdown-nav.directive'; import streamDropdownNav from './streamDropdownNav/stream-dropdown-nav.directive';
import streamDetailModal from './streamDetailModal/main'; 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]) export default angular.module('activityStream', [streamDetailModal.name])
.controller('activityStreamController', activityStreamController) .controller('activityStreamController', activityStreamController)
.directive('streamDropdownNav', streamDropdownNav) .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) { .run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(activityStreamRoute); $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 // Modules
import './helpers'; import './forms';
import './lists'; 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 portalMode from './portal-mode/main';
import systemTracking from './system-tracking/main'; import systemTracking from './system-tracking/main';
import inventories from './inventories/main'; import inventories from './inventories/main';
import inventoryScripts from './inventory-scripts/main'; import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/main'; import organizations from './organizations/main';
import managementJobs from './management-jobs/main'; import managementJobs from './management-jobs/main';
import jobDetail from './job-detail/main';
import workflowResults from './workflow-results/main'; import workflowResults from './workflow-results/main';
import jobResults from './job-results/main'; import jobResults from './job-results/main';
import jobSubmission from './job-submission/main'; import jobSubmission from './job-submission/main';
@@ -62,30 +56,21 @@ import mainMenu from './main-menu/main';
import breadCrumb from './bread-crumb/main'; import breadCrumb from './bread-crumb/main';
import browserData from './browser-data/main'; import browserData from './browser-data/main';
import configuration from './configuration/main'; import configuration from './configuration/main';
import dashboard from './dashboard/main'; import home from './home/main';
import moment from './shared/moment/main';
import login from './login/main'; import login from './login/main';
import activityStream from './activity-stream/main'; import activityStream from './activity-stream/main';
import standardOut from './standard-out/main'; import standardOut from './standard-out/main';
import Templates from './templates/main'; import Templates from './templates/main';
import credentials from './credentials/main'; import credentials from './credentials/main';
import jobs from './jobs/main'; import jobs from './jobs/main';
import { ProjectsList, ProjectsAdd, ProjectsEdit } from './controllers/Projects'; import teams from './teams/main';
import { UsersList, UsersAdd, UsersEdit } from './controllers/Users'; import users from './users/main';
import { TeamsList, TeamsAdd, TeamsEdit } from './controllers/Teams'; import projects from './projects/main';
import RestServices from './rest/main'; import RestServices from './rest/main';
import access from './access/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 './login/authenticationServices/pendo/ng-pendo';
import footer from './footer/main'; import footer from './footer/main';
import scheduler from './scheduler/main'; import scheduler from './scheduler/main';
import { N_ } from './i18n';
var tower = angular.module('Tower', [ var tower = angular.module('Tower', [
// how to add CommonJS / AMD third-party dependencies: // how to add CommonJS / AMD third-party dependencies:
@@ -97,6 +82,7 @@ var tower = angular.module('Tower', [
require('angular-sanitize'), require('angular-sanitize'),
require('angular-scheduler').name, require('angular-scheduler').name,
require('angular-tz-extensions'), require('angular-tz-extensions'),
require('angular-md5'),
require('lr-infinite-scroll'), require('lr-infinite-scroll'),
require('ng-toast'), require('ng-toast'),
'gettext', 'gettext',
@@ -119,12 +105,10 @@ var tower = angular.module('Tower', [
setupMenu.name, setupMenu.name,
mainMenu.name, mainMenu.name,
breadCrumb.name, breadCrumb.name,
dashboard.name, home.name,
moment.name,
login.name, login.name,
activityStream.name, activityStream.name,
footer.name, footer.name,
jobDetail.name,
workflowResults.name, workflowResults.name,
jobResults.name, jobResults.name,
jobSubmission.name, jobSubmission.name,
@@ -132,9 +116,11 @@ var tower = angular.module('Tower', [
standardOut.name, standardOut.name,
Templates.name, Templates.name,
portalMode.name, portalMode.name,
config.name,
credentials.name, credentials.name,
jobs.name, jobs.name,
teams.name,
users.name,
projects.name,
//'templates', //'templates',
'Utilities', 'Utilities',
'OrganizationFormDefinition', 'OrganizationFormDefinition',
@@ -142,68 +128,44 @@ var tower = angular.module('Tower', [
'OrganizationListDefinition', 'OrganizationListDefinition',
'templates', 'templates',
'UserListDefinition', 'UserListDefinition',
'UserHelper',
'PromptDialog', 'PromptDialog',
'AWDirectives', 'AWDirectives',
'InventoriesListDefinition', 'InventoriesListDefinition',
'InventoryFormDefinition', 'InventoryFormDefinition',
'InventoryHelper',
'InventoryGroupsDefinition', 'InventoryGroupsDefinition',
'InventoryHostsDefinition', 'InventoryHostsDefinition',
'HostsHelper',
'AWFilters',
'HostFormDefinition', 'HostFormDefinition',
'HostListDefinition', 'HostListDefinition',
'GroupFormDefinition', 'GroupFormDefinition',
'GroupListDefinition', 'GroupListDefinition',
'GroupsHelper',
'TeamsListDefinition', 'TeamsListDefinition',
'TeamFormDefinition', 'TeamFormDefinition',
'TeamHelper',
'CredentialsListDefinition', 'CredentialsListDefinition',
'CredentialFormDefinition', 'CredentialFormDefinition',
'TemplatesListDefinition', 'TemplatesListDefinition',
'PortalJobTemplatesListDefinition', 'PortalJobTemplatesListDefinition',
'JobTemplateFormDefinition', 'JobTemplateFormDefinition',
'JobTemplatesHelper',
'JobSubmissionHelper',
'ProjectsListDefinition', 'ProjectsListDefinition',
'ProjectFormDefinition', 'ProjectFormDefinition',
'ProjectStatusDefinition', 'ProjectStatusDefinition',
'ProjectsHelper',
'CompletedJobsDefinition', 'CompletedJobsDefinition',
'AllJobsDefinition', 'AllJobsDefinition',
'JobSummaryDefinition', 'JobSummaryDefinition',
'ParseHelper',
'ChildrenHelper',
'ProjectPathHelper',
'md5Helper',
'SelectionHelper',
'HostGroupsFormDefinition', 'HostGroupsFormDefinition',
'StreamWidget',
'JobsHelper',
'CredentialsHelper',
'StreamListDefinition', 'StreamListDefinition',
'ActivityDetailDefinition', 'ActivityDetailDefinition',
'VariablesHelper',
'SchedulesListDefinition', 'SchedulesListDefinition',
'ScheduledJobsDefinition', 'ScheduledJobsDefinition',
//'Timezones', //'Timezones',
'SchedulesHelper',
'JobsListDefinition', 'JobsListDefinition',
'LogViewerStatusDefinition', 'LogViewerStatusDefinition',
'StandardOutHelper', 'StandardOutHelper',
'LogViewerOptionsDefinition', 'LogViewerOptionsDefinition',
'JobDetailHelper',
'lrInfiniteScroll', 'lrInfiniteScroll',
'LoadConfigHelper',
'PortalJobsListDefinition', 'PortalJobsListDefinition',
'features', 'features',
'longDateFilter',
'pendolytics', 'pendolytics',
scheduler.name, scheduler.name,
'ApiModelHelper',
'ActivityStreamHelper',
'WorkflowFormDefinition', 'WorkflowFormDefinition',
'InventorySourcesListDefinition', 'InventorySourcesListDefinition',
'WorkflowMakerFormDefinition' 'WorkflowMakerFormDefinition'
@@ -227,10 +189,9 @@ var tower = angular.module('Tower', [
}); });
}]) }])
.config(['$urlRouterProvider', '$breadcrumbProvider', 'QuerySetProvider', .config(['$urlRouterProvider', '$breadcrumbProvider', 'QuerySetProvider',
'$urlMatcherFactoryProvider', 'stateDefinitionsProvider', '$stateProvider', '$urlMatcherFactoryProvider',
function($urlRouterProvider, $breadcrumbProvider, QuerySet, function($urlRouterProvider, $breadcrumbProvider, QuerySet,
$urlMatcherFactoryProvider, stateDefinitionsProvider, $stateProvider) { $urlMatcherFactoryProvider) {
let stateDefinitions = stateDefinitionsProvider.$get();
$urlMatcherFactoryProvider.strictMode(false); $urlMatcherFactoryProvider.strictMode(false);
$breadcrumbProvider.setOptions({ $breadcrumbProvider.setOptions({
templateUrl: urlPrefix + 'partials/breadcrumb.html' templateUrl: urlPrefix + 'partials/breadcrumb.html'
@@ -266,117 +227,14 @@ var tower = angular.module('Tower', [
// $stateProvider.stateRegistry.onStatesChanged((event, states) =>{ // $stateProvider.stateRegistry.onStatesChanged((event, states) =>{
// console.log(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', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest', 'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService', 'FeaturesService', '$filter', 'SocketService',
function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, $stateParams, function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer, CheckLicense, $location, Authorization, LoadBasePaths, Timer,
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait, ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
@@ -392,67 +250,13 @@ var tower = angular.module('Tower', [
$log.debug(`$state.defaultErrorHandler: ${error}`); $log.debug(`$state.defaultErrorHandler: ${error}`);
}); });
$stateExtender.addState({ $rootScope.refresh = function() {
name: 'dashboard', $state.go('.', null, {reload: true});
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"),
});
});
}
]
}
});
$stateExtender.addState({ $rootScope.refreshJobs = function(){
name: 'userCredentials', $state.go('.', null, {reload: true});
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')
}
});
function activateTab() { function activateTab() {
// Make the correct tab active // Make the correct tab active
@@ -511,19 +315,19 @@ var tower = angular.module('Tower', [
$rootScope.$on("$stateChangeStart", function (event, next) { $rootScope.$on("$stateChangeStart", function (event, next) {
// Remove any lingering intervals // Remove any lingering intervals
// except on jobDetails.* states // except on jobResults.* states
var jobDetailStates = [ var jobResultStates = [
'jobDetail', 'jobResult',
'jobDetail.host-summary', 'jobResult.host-summary',
'jobDetail.host-event.details', 'jobResult.host-event.details',
'jobDetail.host-event.json', 'jobResult.host-event.json',
'jobDetail.host-events', 'jobResult.host-events',
'jobDetail.host-event.stdout' 'jobResult.host-event.stdout'
]; ];
if ($rootScope.jobDetailInterval && !_.includes(jobDetailStates, next.name) ) { if ($rootScope.jobResultInterval && !_.includes(jobResultStates, next.name) ) {
window.clearInterval($rootScope.jobDetailInterval); window.clearInterval($rootScope.jobResultInterval);
} }
if ($rootScope.jobStdOutInterval && !_.includes(jobDetailStates, next.name) ) { if ($rootScope.jobStdOutInterval && !_.includes(jobResultStates, next.name) ) {
window.clearInterval($rootScope.jobStdOutInterval); window.clearInterval($rootScope.jobStdOutInterval);
} }
@@ -532,7 +336,7 @@ var tower = angular.module('Tower', [
// capture most recent URL, excluding login/logout // capture most recent URL, excluding login/logout
$rootScope.lastPath = $location.path(); $rootScope.lastPath = $location.path();
$rootScope.enteredPath = $location.path(); $rootScope.enteredPath = $location.path();
$cookieStore.put('lastPath', $location.path()); $cookies.put('lastPath', $location.path());
} }
if (Authorization.isUserLoggedIn() === false) { if (Authorization.isUserLoggedIn() === false) {
@@ -598,7 +402,7 @@ var tower = angular.module('Tower', [
// User not authenticated, redirect to login page // User not authenticated, redirect to login page
$location.path('/login'); $location.path('/login');
} else { } else {
var lastUser = $cookieStore.get('current_user'), var lastUser = $cookies.getObject('current_user'),
timestammp = Store('sessionTime'); timestammp = Store('sessionTime');
if (lastUser && lastUser.id && timestammp && timestammp[lastUser.id] && timestammp[lastUser.id].loggedIn) { if (lastUser && lastUser.id && timestammp && timestammp[lastUser.id] && timestammp[lastUser.id].loggedIn) {
var stime = timestammp[lastUser.id].time, 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 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 export default
angular.module('credentials', []) 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 */ /** @define DashboardCounts */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import JobStatusGraphDirective from './job-status-graph.directive'; import JobStatusGraphDirective from './job-status-graph.directive';
import JobStatusGraphService from './job-status-graph.service'; import JobStatusGraphService from './job-status-graph.service';
import DashboardGraphHelpers from '../graph-helpers/main'; 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]) export default angular.module('JobStatusGraph', [DashboardGraphHelpers.name, templateUrl.name])
.directive('jobStatusGraph', JobStatusGraphDirective) .directive('jobStatusGraph', JobStatusGraphDirective)

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ export default
scope: { scope: {
data: '=' 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) { function link(scope, element, attr) {

View File

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

View File

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