summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Jones <matburt@redhat.com>2016-07-19 03:16:01 +0200
committerMatthew Jones <matburt@redhat.com>2016-07-19 03:16:01 +0200
commit7b1fd964e3bb1deb62b22c977fddd362da7fac19 (patch)
tree5469f011471ba5c79c0c8df13df3ff968a91476a
parentMerge branch 'release_3.0.0' into devel (diff)
parentremove aws ask at runtime prompt from cred form config, resolves #3055 (#3058) (diff)
downloadawx-7b1fd964e3bb1deb62b22c977fddd362da7fac19.tar.xz
awx-7b1fd964e3bb1deb62b22c977fddd362da7fac19.zip
Merge branch 'release_3.0.0' into devel
* release_3.0.0: (22 commits) remove aws ask at runtime prompt from cred form config, resolves #3055 (#3058) fix missing URI encoding in event summary serch, kickback on #2980 (#3050) Switch base class for StateConflict Fixed password show/hide on enter for survey maker password type previews Fixed password show/hide on enter for survey taker survey questions where type is password Prevent populate_user from being registered multiple times. Fixing iterator used when jobs list refreshes Explicit super user check for JT can_delete Fix for populating teams for LDAP user. Update hubspot template for marketting Fix up flake8 Rolled back the onExit solution previously implemented to handle the backspace navigation on the job launch modal. New solution listens for state changes within the directive and cleans itself up. Switch disallowed object delete to 409 Password enter show/hide fix add test for CustomInventoryScript serializer Fixed bug where hitting enter in a password field in the job launch/survey maker modal would toggle the show/hide. Setting the local var CredentialList to the deep clone seems to be problematic. Moving this out so that the original object itself is overwritten which is how it's done in other places. (#3017) Jobs list page size (#3019) resolves kickback on #2980 (#3008) add read_role to organization select_related ...
-rw-r--r--awx/api/serializers.py4
-rw-r--r--awx/api/views.py10
-rw-r--r--awx/main/access.py49
-rw-r--r--awx/main/migrations/0026_v300_credential_unique.py2
-rw-r--r--awx/main/tests/functional/api/test_job_template.py12
-rw-r--r--awx/main/tests/unit/api/test_serializers.py48
-rw-r--r--awx/sso/backends.py4
-rw-r--r--awx/ui/client/src/app.js20
-rw-r--r--awx/ui/client/src/controllers/Jobs.js6
-rw-r--r--awx/ui/client/src/forms/Credentials.js5
-rw-r--r--awx/ui/client/src/helpers/JobDetail.js4
-rw-r--r--awx/ui/client/src/helpers/JobTemplates.js3
-rw-r--r--awx/ui/client/src/job-detail/host-events/host-events.controller.js4
-rw-r--r--awx/ui/client/src/job-detail/host-summary/host-summary.controller.js2
-rw-r--r--awx/ui/client/src/job-detail/host-summary/host-summary.route.js10
-rw-r--r--awx/ui/client/src/job-detail/job-detail.route.js12
-rw-r--r--awx/ui/client/src/job-submission/job-submission.directive.js6
-rw-r--r--awx/ui/client/src/job-submission/job-submission.partial.html18
-rw-r--r--awx/ui/client/src/job-templates/add/job-templates-add.route.js8
-rw-r--r--awx/ui/client/src/job-templates/edit/job-templates-edit.route.js8
-rw-r--r--awx/ui/client/src/job-templates/list/job-templates-list.route.js10
-rw-r--r--awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html2
-rw-r--r--awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js2
-rw-r--r--awx/ui/client/src/organizations/linkout/organizations-linkout.route.js10
-rw-r--r--awx/ui/client/src/portal-mode/portal-mode.route.js10
-rw-r--r--awx/ui/client/src/search/tagSearch.service.js2
-rw-r--r--awx/ui/client/src/shared/form-generator.js2
27 files changed, 129 insertions, 144 deletions
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index 1487fe17e3..679d23aeee 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -1283,7 +1283,9 @@ class CustomInventoryScriptSerializer(BaseSerializer):
if obj is None:
return ret
request = self.context.get('request', None)
- if request.user not in obj.admin_role:
+ if request.user not in obj.admin_role and \
+ not request.user.is_superuser and \
+ not request.user.is_system_auditor:
ret['script'] = None
return ret
diff --git a/awx/api/views.py b/awx/api/views.py
index 64ea980f66..11ae8f6766 100644
--- a/awx/api/views.py
+++ b/awx/api/views.py
@@ -653,7 +653,7 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
def get_queryset(self):
qs = Organization.accessible_objects(self.request.user, 'read_role')
- qs = qs.select_related('admin_role', 'auditor_role', 'member_role')
+ qs = qs.select_related('admin_role', 'auditor_role', 'member_role', 'read_role')
return qs
def create(self, request, *args, **kwargs):
@@ -2184,14 +2184,6 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
serializer_class = JobTemplateSerializer
always_allow_superuser = False
- def destroy(self, request, *args, **kwargs):
- obj = self.get_object()
- can_delete = request.user.can_access(JobTemplate, 'delete', obj)
- if not can_delete:
- raise PermissionDenied("Cannot delete job template.")
- return super(JobTemplateDetail, self).destroy(request, *args, **kwargs)
-
-
class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
model = JobTemplate
diff --git a/awx/main/access.py b/awx/main/access.py
index af05c58be6..5b2ee91851 100644
--- a/awx/main/access.py
+++ b/awx/main/access.py
@@ -25,7 +25,7 @@ from awx.main.conf import tower_settings
__all__ = ['get_user_queryset', 'check_user_access',
'user_accessible_objects',
- 'user_admin_role',]
+ 'user_admin_role', 'StateConflict',]
PERMISSION_TYPES = [
PERM_INVENTORY_ADMIN,
@@ -57,6 +57,8 @@ access_registry = {
# ...
}
+class StateConflict(ValidationError):
+ status_code = 409
def register_access(model_class, access_class):
access_classes = access_registry.setdefault(model_class, [])
@@ -315,11 +317,15 @@ class OrganizationAccess(BaseAccess):
if not is_change_possible:
return False
active_jobs = []
- active_jobs.extend(Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES))
- active_jobs.extend(ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES))
- active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES))
+ active_jobs.extend([dict(type="job", id=o.id)
+ for o in Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)])
+ active_jobs.extend([dict(type="project_update", id=o.id)
+ for o in ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)])
+ active_jobs.extend([dict(type="inventory_update", id=o.id)
+ for o in InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
- raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
+ raise StateConflict({"conflict": "Resource is being used by running jobs",
+ "active_jobs": active_jobs})
return True
class InventoryAccess(BaseAccess):
@@ -387,10 +393,13 @@ class InventoryAccess(BaseAccess):
if not is_can_admin:
return False
active_jobs = []
- active_jobs.extend(Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES))
- active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES))
+ active_jobs.extend([dict(type="job", id=o.id)
+ for o in Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES)])
+ active_jobs.extend([dict(type="inventory_update", id=o.id)
+ for o in InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
- raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
+ raise StateConflict({"conflict": "Resource is being used by running jobs",
+ "active_jobs": active_jobs})
return True
def can_run_ad_hoc_commands(self, obj):
@@ -508,9 +517,11 @@ class GroupAccess(BaseAccess):
if not is_delete_allowed:
return False
active_jobs = []
- active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES))
+ active_jobs.extend([dict(type="inventory_update", id=o.id)
+ for o in InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
- raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
+ raise StateConflict({"conflict": "Resource is being used by running jobs",
+ "active_jobs": active_jobs})
return True
class InventorySourceAccess(BaseAccess):
@@ -765,10 +776,13 @@ class ProjectAccess(BaseAccess):
if not is_change_allowed:
return False
active_jobs = []
- active_jobs.extend(Job.objects.filter(project=obj, status__in=ACTIVE_STATES))
- active_jobs.extend(ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES))
+ active_jobs.extend([dict(type="job", id=o.id)
+ for o in Job.objects.filter(project=obj, status__in=ACTIVE_STATES)])
+ active_jobs.extend([dict(type="project_update", id=o.id)
+ for o in ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
- raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
+ raise StateConflict({"conflict": "Resource is being used by running jobs",
+ "active_jobs": active_jobs})
return True
@check_superuser
@@ -989,14 +1003,15 @@ class JobTemplateAccess(BaseAccess):
return True
- @check_superuser
def can_delete(self, obj):
- is_delete_allowed = self.user in obj.admin_role
+ is_delete_allowed = self.user.is_superuser or self.user in obj.admin_role
if not is_delete_allowed:
return False
- active_jobs = obj.jobs.filter(status__in=ACTIVE_STATES)
+ active_jobs = [dict(type="job", id=o.id)
+ for o in obj.jobs.filter(status__in=ACTIVE_STATES)]
if len(active_jobs) > 0:
- raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
+ raise StateConflict({"conflict": "Resource is being used by running jobs",
+ "active_jobs": active_jobs})
return True
class JobAccess(BaseAccess):
diff --git a/awx/main/migrations/0026_v300_credential_unique.py b/awx/main/migrations/0026_v300_credential_unique.py
index b354ce3d62..3c1d714327 100644
--- a/awx/main/migrations/0026_v300_credential_unique.py
+++ b/awx/main/migrations/0026_v300_credential_unique.py
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='credential',
name='read_role',
- field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_auditor', b'use_role', b'admin_role', b'organization.auditor_role'], to='main.Role', null=b'True'),
+ field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_auditor', b'organization.auditor_role', b'use_role', b'admin_role'], to='main.Role', null=b'True'),
),
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
migrations.RunPython(rbac.rebuild_role_hierarchy),
diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py
index 14c92c4a54..cab2e53731 100644
--- a/awx/main/tests/functional/api/test_job_template.py
+++ b/awx/main/tests/functional/api/test_job_template.py
@@ -335,3 +335,15 @@ def test_jt_without_project(inventory):
data["job_type"] = "scan"
serializer = JobTemplateSerializer(data=data)
assert serializer.is_valid()
+
+@pytest.mark.django_db
+def test_disallow_template_delete_on_running_job(job_template_factory, delete, admin_user):
+ objects = job_template_factory('jt',
+ credential='c',
+ job_type="run",
+ project='p',
+ inventory='i',
+ organization='o')
+ objects.job_template.create_unified_job()
+ delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user)
+ assert delete_response.status_code == 409
diff --git a/awx/main/tests/unit/api/test_serializers.py b/awx/main/tests/unit/api/test_serializers.py
index a8fb25d005..2496ba9a2d 100644
--- a/awx/main/tests/unit/api/test_serializers.py
+++ b/awx/main/tests/unit/api/test_serializers.py
@@ -1,14 +1,31 @@
# Python
import pytest
import mock
+from mock import PropertyMock
import json
# AWX
-from awx.api.serializers import JobTemplateSerializer, JobSerializer, JobOptionsSerializer
-from awx.main.models import Label, Job
+from awx.api.serializers import (
+ JobTemplateSerializer,
+ JobSerializer,
+ JobOptionsSerializer,
+ CustomInventoryScriptSerializer,
+)
+from awx.main.models import (
+ Label,
+ Job,
+ CustomInventoryScript,
+ User,
+)
#DRF
+from rest_framework.request import Request
from rest_framework import serializers
+from rest_framework.test import (
+ APIRequestFactory,
+ force_authenticate,
+)
+
def mock_JT_resource_data():
return ({}, [])
@@ -189,3 +206,30 @@ class TestJobTemplateSerializerValidation(object):
for ev in self.bad_extra_vars:
with pytest.raises(serializers.ValidationError):
serializer.validate_extra_vars(ev)
+
+class TestCustomInventoryScriptSerializer(object):
+
+ @pytest.mark.parametrize("superuser,sysaudit,admin_role,value",
+ ((True, False, False, '#!/python'),
+ (False, True, False, '#!/python'),
+ (False, False, True, '#!/python'),
+ (False, False, False, None)))
+ def test_to_representation_orphan(self, superuser, sysaudit, admin_role, value):
+ with mock.patch.object(CustomInventoryScriptSerializer, 'get_summary_fields', return_value={}):
+ User.add_to_class('is_system_auditor', sysaudit)
+ user = User(username="root", is_superuser=superuser)
+ roles = [user] if admin_role else []
+
+ with mock.patch('awx.main.models.CustomInventoryScript.admin_role', new_callable=PropertyMock, return_value=roles):
+ cis = CustomInventoryScript(pk=1, script='#!/python')
+ serializer = CustomInventoryScriptSerializer()
+
+ factory = APIRequestFactory()
+ wsgi_request = factory.post("/inventory_script/1", {'id':1}, format="json")
+ force_authenticate(wsgi_request, user)
+
+ request = Request(wsgi_request)
+ serializer.context['request'] = request
+
+ representation = serializer.to_representation(cis)
+ assert representation['script'] == value
diff --git a/awx/sso/backends.py b/awx/sso/backends.py
index 3d5edb7ec9..91999034d5 100644
--- a/awx/sso/backends.py
+++ b/awx/sso/backends.py
@@ -201,7 +201,7 @@ def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=True):
rel.remove(user)
-@receiver(populate_user)
+@receiver(populate_user, dispatch_uid='populate-ldap-user')
def on_populate_user(sender, **kwargs):
'''
Handle signal from LDAP backend to populate the user object. Update user
@@ -239,7 +239,7 @@ def on_populate_user(sender, **kwargs):
team, created = Team.objects.get_or_create(name=team_name, organization=org)
users_opts = team_opts.get('users', None)
remove = bool(team_opts.get('remove', True))
- _update_m2m_from_groups(user, ldap_user, team.member_role.users, users_opts,
+ _update_m2m_from_groups(user, ldap_user, team.member_role.members, users_opts,
remove)
# Update user profile to store LDAP DN.
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index 0698a2fdbb..e25d53ac51 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -255,16 +255,6 @@ var tower = angular.module('Tower', [
});
});
}]
- },
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
}
}).
@@ -274,16 +264,6 @@ var tower = angular.module('Tower', [
controller: JobsListController,
ncyBreadcrumb: {
label: "JOBS"
- },
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
}
}).
diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js
index 27cbd6f8f5..a7408259a2 100644
--- a/awx/ui/client/src/controllers/Jobs.js
+++ b/awx/ui/client/src/controllers/Jobs.js
@@ -66,6 +66,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
scope: jobs_scope,
list: AllJobsList,
id: 'active-jobs',
+ pageSize: 20,
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled&order_by=-finished',
searchParams: search_params,
spinner: false
@@ -77,15 +78,14 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
parent_scope: $scope,
scope: scheduled_scope,
list: ScheduledJobsList,
+ pageSize: 20,
id: 'scheduled-jobs-tab',
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
url: GetBasePath('schedules') + '?next_run__isnull=false'
});
$scope.refreshJobs = function() {
- jobs_scope.search('queued_job');
- jobs_scope.search('running_job');
- jobs_scope.search('completed_job');
+ jobs_scope.search('all_job');
scheduled_scope.search('schedule');
};
diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js
index f23852cebc..fe61186cce 100644
--- a/awx/ui/client/src/forms/Credentials.js
+++ b/awx/ui/client/src/forms/Credentials.js
@@ -107,11 +107,6 @@ export default
init: false
},
autocomplete: false,
- subCheckbox: {
- variable: 'secret_key_ask',
- text: 'Ask at runtime?',
- ngChange: 'ask(\'secret_key\', \'undefined\')'
- },
clear: false,
hasShowInputButton: true,
apiField: 'password',
diff --git a/awx/ui/client/src/helpers/JobDetail.js b/awx/ui/client/src/helpers/JobDetail.js
index dde9264109..3b7dcca1ce 100644
--- a/awx/ui/client/src/helpers/JobDetail.js
+++ b/awx/ui/client/src/helpers/JobDetail.js
@@ -687,7 +687,7 @@ export default
scope.plays = [];
url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id';
- url += (scope.search_play_name) ? '&play__icontains=' + scope.search_play_name : '';
+ url += (scope.search_play_name) ? '&play__icontains=' + encodeURIComponent(scope.search_play_name) : '';
url += (scope.search_play_status === 'failed') ? '&failed=true' : '';
scope.playsLoading = true;
Rest.setUrl(url);
@@ -786,7 +786,7 @@ export default
scope.tasks = [];
if (scope.selectedPlay) {
url = scope.job.url + 'job_tasks/?event_id=' + scope.selectedPlay;
- url += (scope.search_task_name) ? '&task__icontains=' + scope.search_task_name : '';
+ url += (scope.search_task_name) ? '&task__icontains=' + encodeURIComponent(scope.search_task_name) : '';
url += (scope.search_task_status === 'failed') ? '&failed=true' : '';
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
scope.plays.every(function(p, idx) {
diff --git a/awx/ui/client/src/helpers/JobTemplates.js b/awx/ui/client/src/helpers/JobTemplates.js
index 55a4a9aa5f..2947c05546 100644
--- a/awx/ui/client/src/helpers/JobTemplates.js
+++ b/awx/ui/client/src/helpers/JobTemplates.js
@@ -25,7 +25,6 @@ angular.module('JobTemplatesHelper', ['Utilities'])
return function(params) {
var scope = params.scope,
- CredentialList = _.cloneDeep(CredentialList),
defaultUrl = GetBasePath('job_templates'),
// generator = GenerateForm,
form = JobTemplateForm(),
@@ -37,6 +36,8 @@ angular.module('JobTemplatesHelper', ['Utilities'])
// 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() {
diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js
index 55abf51e04..f56111bdb6 100644
--- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js
+++ b/awx/ui/client/src/job-detail/host-events/host-events.controller.js
@@ -25,8 +25,8 @@
host_name: $scope.hostName,
};
if ($scope.searchStr && $scope.searchStr !== ''){
- params.or__play__icontains = $scope.searchStr;
- params.or__task__icontains = $scope.searchStr;
+ params.or__play__icontains = encodeURIComponent($scope.searchStr);
+ params.or__task__icontains = encodeURIComponent($scope.searchStr);
}
switch($scope.activeFilter){
diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js
index 34763c838e..d5c826ac35 100644
--- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js
+++ b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js
@@ -104,7 +104,7 @@
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size,
- host_name__icontains: $scope.searchTerm,
+ host_name__icontains: encodeURIComponent($scope.searchTerm),
}).success(function(res){
$scope.hosts = res.results;
$scope.next = res.next;
diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js
index f7722462a0..a2de70e5d4 100644
--- a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js
+++ b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js
@@ -17,15 +17,5 @@ export default {
},
ncyBreadcrumb: {
skip: true // Never display this state in breadcrumb.
- },
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
}
};
diff --git a/awx/ui/client/src/job-detail/job-detail.route.js b/awx/ui/client/src/job-detail/job-detail.route.js
index bf21fb8e5a..dda2722511 100644
--- a/awx/ui/client/src/job-detail/job-detail.route.js
+++ b/awx/ui/client/src/job-detail/job-detail.route.js
@@ -30,15 +30,5 @@ export default {
}]
},
templateUrl: templateUrl('job-detail/job-detail'),
- controller: 'JobDetailController',
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
- }
+ controller: 'JobDetailController'
};
diff --git a/awx/ui/client/src/job-submission/job-submission.directive.js b/awx/ui/client/src/job-submission/job-submission.directive.js
index bd1c90ff92..26a3d9c826 100644
--- a/awx/ui/client/src/job-submission/job-submission.directive.js
+++ b/awx/ui/client/src/job-submission/job-submission.directive.js
@@ -89,6 +89,12 @@ export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2', 'ParseT
}
};
+ scope.$on("$stateChangeStart", function() {
+ scope.$evalAsync(function( scope ) {
+ scope.clearDialog();
+ });
+ });
+
scope.init();
}
diff --git a/awx/ui/client/src/job-submission/job-submission.partial.html b/awx/ui/client/src/job-submission/job-submission.partial.html
index 95ca9df877..3f69234d91 100644
--- a/awx/ui/client/src/job-submission/job-submission.partial.html
+++ b/awx/ui/client/src/job-submission/job-submission.partial.html
@@ -54,9 +54,9 @@
</label>
<div class="input-group">
<span class="input-group-btn">
- <button class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-ssh-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-ssh-password')" data-original-title="" title="">Show</button>
+ <button type="button" class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-ssh-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-ssh-password')" data-original-title="" title="">Show</button>
</span>
- <input id="job-submission-ssh-password" type="password" ng-model="passwords.ssh_password" ng-keydown="keydown($event)" name="ssh_password" class="password-field form-control input-sm Form-textInput" required>
+ <input id="job-submission-ssh-password" type="password" ng-model="passwords.ssh_password" name="ssh_password" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="forms.credentialpasswords.ssh_password.$dirty && forms.credentialpasswords.ssh_password.$error.required">Please enter a password.</div>
<div class="error api-error" ng-bind="ssh_password_api_error"></div>
@@ -67,9 +67,9 @@
</label>
<div class="input-group">
<span class="input-group-btn">
- <button class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-ssh-key-unlock_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-ssh-key-unlock')" data-original-title="" title="">Show</button>
+ <button type="button" class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-ssh-key-unlock_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-ssh-key-unlock')" data-original-title="" title="">Show</button>
</span>
- <input id="job-submission-ssh-key-unlock" type="password" ng-model="passwords.ssh_key_unlock" ng-keydown="keydown($event)" name="ssh_key_unlock" class="password-field form-control input-sm Form-textInput" required>
+ <input id="job-submission-ssh-key-unlock" type="password" ng-model="passwords.ssh_key_unlock" name="ssh_key_unlock" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="forms.credentialpasswords.ssh_key_unlock.$dirty && forms.credentialpasswords.ssh_key_unlock.$error.required">Please enter a password.</div>
<div class="error api-error" ng-bind="ssh_key_unlock_api_error"></div>
@@ -80,9 +80,9 @@
</label>
<div class="input-group">
<span class="input-group-btn">
- <button class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-become-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-become-password')" data-original-title="" title="">Show</button>
+ <button type="button" class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-become-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-become-password')" data-original-title="" title="">Show</button>
</span>
- <input id="job-submission-become-password" type="password" ng-model="passwords.become_password" ng-keydown="keydown($event)" name="become_password" class="password-field form-control input-sm Form-textInput" required>
+ <input id="job-submission-become-password" type="password" ng-model="passwords.become_password" name="become_password" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="forms.credentialpasswords.become_password.$dirty && forms.credentialpasswords.become_password.$error.required">Please enter a password.</div>
<div class="error api-error" ng-bind="become_password_api_error"></div>
@@ -93,9 +93,9 @@
</label>
<div class="input-group">
<span class="input-group-btn">
- <button class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-vault-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-vault-password')" data-original-title="" title="">Show</button>
+ <button type="button" class="btn btn-default show_input_button JobSubmission-passwordButton" id="job-submission-vault-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#job-submission-vault-password')" data-original-title="" title="">Show</button>
</span>
- <input id="job-submission-vault-password" type="password" ng-model="passwords.vault_password" ng-keydown="keydown($event)" name="vault_password" class="password-field form-control input-sm Form-textInput" required>
+ <input id="job-submission-vault-password" type="password" ng-model="passwords.vault_password" name="vault_password" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="forms.credentialpasswords.vault_password.$dirty && forms.credentialpasswords.vault_password.$error.required">Please enter a password.</div>
<div class="error api-error" ng-bind="vault_password_api_error"></div>
@@ -176,7 +176,7 @@
<div ng-if="question.type === 'password'">
<div class="input-group">
<span class="input-group-btn">
- <button class="btn btn-default show_input_button JobSubmission-passwordButton" id="survey_question_{{$index}}_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#survey_question_' + $index)" data-original-title="" title="">Show</button>
+ <button type="button" class="btn btn-default show_input_button JobSubmission-passwordButton" id="survey_question_{{$index}}_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#survey_question_' + $index)" data-original-title="" title="">Show</button>
</span>
<input id="survey_question_{{$index}}" type="password" ng-model="question.model" name="survey_question_{{$index}}" ng-required="question.required" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control Form-textInput" autocomplete="false">
</div>
diff --git a/awx/ui/client/src/job-templates/add/job-templates-add.route.js b/awx/ui/client/src/job-templates/add/job-templates-add.route.js
index 8218cabc32..68275c0f22 100644
--- a/awx/ui/client/src/job-templates/add/job-templates-add.route.js
+++ b/awx/ui/client/src/job-templates/add/job-templates-add.route.js
@@ -16,14 +16,8 @@ export default {
label: "CREATE JOB TEMPLATE"
},
onExit: function(){
- // close the job launch modal
+ // close the survey maker modal
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
if($("#survey-modal-dialog").hasClass('ui-dialog-content')) {
$('#survey-modal-dialog').dialog('destroy');
diff --git a/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js b/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js
index 78d383fa42..b2dffa229b 100644
--- a/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js
+++ b/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js
@@ -19,14 +19,8 @@ export default {
label: "{{name}}"
},
onExit: function(){
- // close the job launch modal
+ // close the survey maker modal
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
if($("#survey-modal-dialog").hasClass('ui-dialog-content')) {
$('#survey-modal-dialog').dialog('destroy');
diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.route.js b/awx/ui/client/src/job-templates/list/job-templates-list.route.js
index 6c646af729..deeb1982bd 100644
--- a/awx/ui/client/src/job-templates/list/job-templates-list.route.js
+++ b/awx/ui/client/src/job-templates/list/job-templates-list.route.js
@@ -17,15 +17,5 @@ export default {
},
ncyBreadcrumb: {
label: "JOB TEMPLATES"
- },
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
}
};
diff --git a/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html b/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html
index 72e772bd62..0777b94c90 100644
--- a/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html
+++ b/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html
@@ -17,7 +17,7 @@
</div>
<div ng-if="question.type === 'password'" class="input_area input-group">
<span class="input-group-btn">
- <button class="btn btn-default SurveyMaker-previewPasswordButton" id="{{ question.variable + '_show_input_button' }}" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="toggleInput('#' + question.variable)" data-original-title="" title="">SHOW</button>
+ <button type="button" class="btn btn-default SurveyMaker-previewPasswordButton" id="{{ question.variable + '_show_input_button' }}" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="toggleInput('#' + question.variable)" data-original-title="" title="">SHOW</button>
</span>
<input id="{{question.variable}}" type="password" name="" class="form-control ng-pristine ng-valid-api-error ng-invalid" autocomplete="false" ng-model="defaultValue" readonly>
</div>
diff --git a/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js b/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js
index 32b1c1dd9a..56a3903f69 100644
--- a/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js
+++ b/awx/ui/client/src/job-templates/survey-maker/shared/question-definition.form.js
@@ -280,7 +280,7 @@ export default
'<div>'+
'<div class="input-group">'+
'<span class="input-group-btn">'+
- '<button class="btn btn-default show_input_button" id="default_password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="toggleInput(&quot;#default_password&quot;)" data-original-title="" title="">SHOW</button>'+
+ '<button type="button" class="btn btn-default show_input_button" id="default_password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="toggleInput(&quot;#default_password&quot;)" data-original-title="" title="">SHOW</button>'+
'</span>'+
'<input id="default_password" type="password" ng-model="default_password" name="default_password" class="form-control Form-textInput" autocomplete="false">'+
'</div>'+
diff --git a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js
index caa8d7692f..13d51cc68f 100644
--- a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js
+++ b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js
@@ -121,16 +121,6 @@ export default [
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
- },
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
}
},
{
diff --git a/awx/ui/client/src/portal-mode/portal-mode.route.js b/awx/ui/client/src/portal-mode/portal-mode.route.js
index 6982f10620..c0f7e5f3fb 100644
--- a/awx/ui/client/src/portal-mode/portal-mode.route.js
+++ b/awx/ui/client/src/portal-mode/portal-mode.route.js
@@ -24,15 +24,5 @@ export default {
templateUrl: templateUrl('portal-mode/portal-mode-jobs'),
controller: PortalModeJobsController
}
- },
- onExit: function(){
- // close the job launch modal
- // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
- // Destroy the dialog
- if($("#job-launch-modal").hasClass('ui-dialog-content')) {
- $('#job-launch-modal').dialog('destroy');
- }
- // Remove the directive from the page (if it's there)
- $('#content-container').find('submit-job').remove();
}
};
diff --git a/awx/ui/client/src/search/tagSearch.service.js b/awx/ui/client/src/search/tagSearch.service.js
index fdd808d3ad..4e5e6ae3ec 100644
--- a/awx/ui/client/src/search/tagSearch.service.js
+++ b/awx/ui/client/src/search/tagSearch.service.js
@@ -85,7 +85,7 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu
if (needsRequest.length) {
// make the options request to reutrn the typeOptions
var url = needsRequest[0].basePath ? GetBasePath(needsRequest[0].basePath) : basePath;
- if(url.indexOf('null') === 0 ){
+ if(url.indexOf('null') === -1 ){
Rest.setUrl(url);
Rest.options()
.success(function (data) {
diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js
index 4b889eeb75..4c8382db38 100644
--- a/awx/ui/client/src/shared/form-generator.js
+++ b/awx/ui/client/src/shared/form-generator.js
@@ -922,7 +922,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "'>\n";
// TODO: make it so that the button won't show up if the mode is edit, hasShowInputButton !== true, and there are no contents in the field.
html += "<span class='input-group-btn'>\n";
- html += "<button class='btn btn-default show_input_button Form-passwordButton' ";
+ html += "<button type='button' class='btn btn-default show_input_button Form-passwordButton' ";
html += buildId(field, fld + "_show_input_button", this.form);
html += "aw-tool-tip='Toggle the display of plaintext.' aw-tip-placement='top' ";
html += "tabindex='-1' ";