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
  ...
This commit is contained in:
Matthew Jones
2016-07-18 21:16:01 -04:00
27 changed files with 129 additions and 144 deletions

View File

@@ -1283,7 +1283,9 @@ class CustomInventoryScriptSerializer(BaseSerializer):
if obj is None: if obj is None:
return ret return ret
request = self.context.get('request', None) 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 ret['script'] = None
return ret return ret

View File

@@ -653,7 +653,7 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
qs = Organization.accessible_objects(self.request.user, 'read_role') 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 return qs
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
@@ -2184,14 +2184,6 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
serializer_class = JobTemplateSerializer serializer_class = JobTemplateSerializer
always_allow_superuser = False 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): class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
model = JobTemplate model = JobTemplate

View File

@@ -25,7 +25,7 @@ from awx.main.conf import tower_settings
__all__ = ['get_user_queryset', 'check_user_access', __all__ = ['get_user_queryset', 'check_user_access',
'user_accessible_objects', 'user_accessible_objects',
'user_admin_role',] 'user_admin_role', 'StateConflict',]
PERMISSION_TYPES = [ PERMISSION_TYPES = [
PERM_INVENTORY_ADMIN, PERM_INVENTORY_ADMIN,
@@ -57,6 +57,8 @@ access_registry = {
# ... # ...
} }
class StateConflict(ValidationError):
status_code = 409
def register_access(model_class, access_class): def register_access(model_class, access_class):
access_classes = access_registry.setdefault(model_class, []) access_classes = access_registry.setdefault(model_class, [])
@@ -315,11 +317,15 @@ class OrganizationAccess(BaseAccess):
if not is_change_possible: if not is_change_possible:
return False return False
active_jobs = [] active_jobs = []
active_jobs.extend(Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)) active_jobs.extend([dict(type="job", id=o.id)
active_jobs.extend(ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)) for o in Job.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="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: 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 return True
class InventoryAccess(BaseAccess): class InventoryAccess(BaseAccess):
@@ -387,10 +393,13 @@ class InventoryAccess(BaseAccess):
if not is_can_admin: if not is_can_admin:
return False return False
active_jobs = [] active_jobs = []
active_jobs.extend(Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES)) active_jobs.extend([dict(type="job", id=o.id)
active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES)) 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: 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 return True
def can_run_ad_hoc_commands(self, obj): def can_run_ad_hoc_commands(self, obj):
@@ -508,9 +517,11 @@ class GroupAccess(BaseAccess):
if not is_delete_allowed: if not is_delete_allowed:
return False return False
active_jobs = [] 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: 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 return True
class InventorySourceAccess(BaseAccess): class InventorySourceAccess(BaseAccess):
@@ -765,10 +776,13 @@ class ProjectAccess(BaseAccess):
if not is_change_allowed: if not is_change_allowed:
return False return False
active_jobs = [] active_jobs = []
active_jobs.extend(Job.objects.filter(project=obj, status__in=ACTIVE_STATES)) active_jobs.extend([dict(type="job", id=o.id)
active_jobs.extend(ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)) 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: 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 return True
@check_superuser @check_superuser
@@ -989,14 +1003,15 @@ class JobTemplateAccess(BaseAccess):
return True return True
@check_superuser
def can_delete(self, obj): 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: if not is_delete_allowed:
return False 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: 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 return True
class JobAccess(BaseAccess): class JobAccess(BaseAccess):

View File

@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='credential', model_name='credential',
name='read_role', 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(migration_utils.set_current_apps_for_migrations),
migrations.RunPython(rbac.rebuild_role_hierarchy), migrations.RunPython(rbac.rebuild_role_hierarchy),

View File

@@ -335,3 +335,15 @@ def test_jt_without_project(inventory):
data["job_type"] = "scan" data["job_type"] = "scan"
serializer = JobTemplateSerializer(data=data) serializer = JobTemplateSerializer(data=data)
assert serializer.is_valid() 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

View File

@@ -1,14 +1,31 @@
# Python # Python
import pytest import pytest
import mock import mock
from mock import PropertyMock
import json import json
# AWX # AWX
from awx.api.serializers import JobTemplateSerializer, JobSerializer, JobOptionsSerializer from awx.api.serializers import (
from awx.main.models import Label, Job JobTemplateSerializer,
JobSerializer,
JobOptionsSerializer,
CustomInventoryScriptSerializer,
)
from awx.main.models import (
Label,
Job,
CustomInventoryScript,
User,
)
#DRF #DRF
from rest_framework.request import Request
from rest_framework import serializers from rest_framework import serializers
from rest_framework.test import (
APIRequestFactory,
force_authenticate,
)
def mock_JT_resource_data(): def mock_JT_resource_data():
return ({}, []) return ({}, [])
@@ -189,3 +206,30 @@ class TestJobTemplateSerializerValidation(object):
for ev in self.bad_extra_vars: for ev in self.bad_extra_vars:
with pytest.raises(serializers.ValidationError): with pytest.raises(serializers.ValidationError):
serializer.validate_extra_vars(ev) 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

View File

@@ -201,7 +201,7 @@ def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=True):
rel.remove(user) rel.remove(user)
@receiver(populate_user) @receiver(populate_user, dispatch_uid='populate-ldap-user')
def on_populate_user(sender, **kwargs): def on_populate_user(sender, **kwargs):
''' '''
Handle signal from LDAP backend to populate the user object. Update user 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) team, created = Team.objects.get_or_create(name=team_name, organization=org)
users_opts = team_opts.get('users', None) users_opts = team_opts.get('users', None)
remove = bool(team_opts.get('remove', True)) 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) remove)
# Update user profile to store LDAP DN. # Update user profile to store LDAP DN.

View File

@@ -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, controller: JobsListController,
ncyBreadcrumb: { ncyBreadcrumb: {
label: "JOBS" 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();
} }
}). }).

View File

@@ -66,6 +66,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
scope: jobs_scope, scope: jobs_scope,
list: AllJobsList, list: AllJobsList,
id: 'active-jobs', id: 'active-jobs',
pageSize: 20,
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled&order_by=-finished', url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled&order_by=-finished',
searchParams: search_params, searchParams: search_params,
spinner: false spinner: false
@@ -77,15 +78,14 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
parent_scope: $scope, parent_scope: $scope,
scope: scheduled_scope, scope: scheduled_scope,
list: ScheduledJobsList, list: ScheduledJobsList,
pageSize: 20,
id: 'scheduled-jobs-tab', id: 'scheduled-jobs-tab',
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12', searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
url: GetBasePath('schedules') + '?next_run__isnull=false' url: GetBasePath('schedules') + '?next_run__isnull=false'
}); });
$scope.refreshJobs = function() { $scope.refreshJobs = function() {
jobs_scope.search('queued_job'); jobs_scope.search('all_job');
jobs_scope.search('running_job');
jobs_scope.search('completed_job');
scheduled_scope.search('schedule'); scheduled_scope.search('schedule');
}; };

View File

@@ -107,11 +107,6 @@ export default
init: false init: false
}, },
autocomplete: false, autocomplete: false,
subCheckbox: {
variable: 'secret_key_ask',
text: 'Ask at runtime?',
ngChange: 'ask(\'secret_key\', \'undefined\')'
},
clear: false, clear: false,
hasShowInputButton: true, hasShowInputButton: true,
apiField: 'password', apiField: 'password',

View File

@@ -687,7 +687,7 @@ export default
scope.plays = []; scope.plays = [];
url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id'; 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' : ''; url += (scope.search_play_status === 'failed') ? '&failed=true' : '';
scope.playsLoading = true; scope.playsLoading = true;
Rest.setUrl(url); Rest.setUrl(url);
@@ -786,7 +786,7 @@ export default
scope.tasks = []; scope.tasks = [];
if (scope.selectedPlay) { if (scope.selectedPlay) {
url = scope.job.url + 'job_tasks/?event_id=' + 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 += (scope.search_task_status === 'failed') ? '&failed=true' : '';
url += '&page_size=' + scope.tasksMaxRows + '&order=id'; url += '&page_size=' + scope.tasksMaxRows + '&order=id';
scope.plays.every(function(p, idx) { scope.plays.every(function(p, idx) {

View File

@@ -25,7 +25,6 @@ angular.module('JobTemplatesHelper', ['Utilities'])
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
CredentialList = _.cloneDeep(CredentialList),
defaultUrl = GetBasePath('job_templates'), defaultUrl = GetBasePath('job_templates'),
// generator = GenerateForm, // generator = GenerateForm,
form = JobTemplateForm(), form = JobTemplateForm(),
@@ -37,6 +36,8 @@ angular.module('JobTemplatesHelper', ['Utilities'])
// checkSCMStatus, getPlaybooks, callback, // checkSCMStatus, getPlaybooks, callback,
// choicesCount = 0; // choicesCount = 0;
CredentialList = _.cloneDeep(CredentialList);
// The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the // 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. // popover is activated, a function checks the value of scope.callback_help before constructing the content.
scope.setCallbackHelp = function() { scope.setCallbackHelp = function() {

View File

@@ -25,8 +25,8 @@
host_name: $scope.hostName, host_name: $scope.hostName,
}; };
if ($scope.searchStr && $scope.searchStr !== ''){ if ($scope.searchStr && $scope.searchStr !== ''){
params.or__play__icontains = $scope.searchStr; params.or__play__icontains = encodeURIComponent($scope.searchStr);
params.or__task__icontains = $scope.searchStr; params.or__task__icontains = encodeURIComponent($scope.searchStr);
} }
switch($scope.activeFilter){ switch($scope.activeFilter){

View File

@@ -104,7 +104,7 @@
Wait('start'); Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, { JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size, page_size: page_size,
host_name__icontains: $scope.searchTerm, host_name__icontains: encodeURIComponent($scope.searchTerm),
}).success(function(res){ }).success(function(res){
$scope.hosts = res.results; $scope.hosts = res.results;
$scope.next = res.next; $scope.next = res.next;

View File

@@ -17,15 +17,5 @@ export default {
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
skip: true // Never display this state in breadcrumb. 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();
} }
}; };

View File

@@ -30,15 +30,5 @@ export default {
}] }]
}, },
templateUrl: templateUrl('job-detail/job-detail'), templateUrl: templateUrl('job-detail/job-detail'),
controller: 'JobDetailController', 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();
}
}; };

View File

@@ -89,6 +89,12 @@ export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2', 'ParseT
} }
}; };
scope.$on("$stateChangeStart", function() {
scope.$evalAsync(function( scope ) {
scope.clearDialog();
});
});
scope.init(); scope.init();
} }

View File

@@ -54,9 +54,9 @@
</label> </label>
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <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> </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>
<div class="error" ng-show="forms.credentialpasswords.ssh_password.$dirty && forms.credentialpasswords.ssh_password.$error.required">Please enter a password.</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> <div class="error api-error" ng-bind="ssh_password_api_error"></div>
@@ -67,9 +67,9 @@
</label> </label>
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <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> </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>
<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" 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> <div class="error api-error" ng-bind="ssh_key_unlock_api_error"></div>
@@ -80,9 +80,9 @@
</label> </label>
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <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> </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>
<div class="error" ng-show="forms.credentialpasswords.become_password.$dirty && forms.credentialpasswords.become_password.$error.required">Please enter a password.</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> <div class="error api-error" ng-bind="become_password_api_error"></div>
@@ -93,9 +93,9 @@
</label> </label>
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <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> </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>
<div class="error" ng-show="forms.credentialpasswords.vault_password.$dirty && forms.credentialpasswords.vault_password.$error.required">Please enter a password.</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> <div class="error api-error" ng-bind="vault_password_api_error"></div>
@@ -176,7 +176,7 @@
<div ng-if="question.type === 'password'"> <div ng-if="question.type === 'password'">
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <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> </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"> <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> </div>

View File

@@ -16,14 +16,8 @@ export default {
label: "CREATE JOB TEMPLATE" label: "CREATE JOB TEMPLATE"
}, },
onExit: function(){ 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" // 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')) { if($("#survey-modal-dialog").hasClass('ui-dialog-content')) {
$('#survey-modal-dialog').dialog('destroy'); $('#survey-modal-dialog').dialog('destroy');

View File

@@ -19,14 +19,8 @@ export default {
label: "{{name}}" label: "{{name}}"
}, },
onExit: function(){ 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" // 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')) { if($("#survey-modal-dialog").hasClass('ui-dialog-content')) {
$('#survey-modal-dialog').dialog('destroy'); $('#survey-modal-dialog').dialog('destroy');

View File

@@ -17,15 +17,5 @@ export default {
}, },
ncyBreadcrumb: { ncyBreadcrumb: {
label: "JOB TEMPLATES" 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();
} }
}; };

View File

@@ -17,7 +17,7 @@
</div> </div>
<div ng-if="question.type === 'password'" class="input_area input-group"> <div ng-if="question.type === 'password'" class="input_area input-group">
<span class="input-group-btn"> <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> </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> <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> </div>

View File

@@ -280,7 +280,7 @@ export default
'<div>'+ '<div>'+
'<div class="input-group">'+ '<div class="input-group">'+
'<span class="input-group-btn">'+ '<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>'+ '</span>'+
'<input id="default_password" type="password" ng-model="default_password" name="default_password" class="form-control Form-textInput" autocomplete="false">'+ '<input id="default_password" type="password" ng-model="default_password" name="default_password" class="form-control Form-textInput" autocomplete="false">'+
'</div>'+ '</div>'+

View File

@@ -121,16 +121,6 @@ export default [
features: ['FeaturesService', function(FeaturesService) { features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get(); 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();
} }
}, },
{ {

View File

@@ -24,15 +24,5 @@ export default {
templateUrl: templateUrl('portal-mode/portal-mode-jobs'), templateUrl: templateUrl('portal-mode/portal-mode-jobs'),
controller: PortalModeJobsController 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();
} }
}; };

View File

@@ -85,7 +85,7 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu
if (needsRequest.length) { if (needsRequest.length) {
// make the options request to reutrn the typeOptions // make the options request to reutrn the typeOptions
var url = needsRequest[0].basePath ? GetBasePath(needsRequest[0].basePath) : basePath; var url = needsRequest[0].basePath ? GetBasePath(needsRequest[0].basePath) : basePath;
if(url.indexOf('null') === 0 ){ if(url.indexOf('null') === -1 ){
Rest.setUrl(url); Rest.setUrl(url);
Rest.options() Rest.options()
.success(function (data) { .success(function (data) {

View File

@@ -922,7 +922,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "'>\n"; 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. // 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 += "<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 += buildId(field, fld + "_show_input_button", this.form);
html += "aw-tool-tip='Toggle the display of plaintext.' aw-tip-placement='top' "; html += "aw-tool-tip='Toggle the display of plaintext.' aw-tip-placement='top' ";
html += "tabindex='-1' "; html += "tabindex='-1' ";