diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 20810acaff..ba6f4d8acc 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1537,6 +1537,12 @@ class JobRelaunchSerializer(JobSerializer): obj = self.context.get('obj') if not obj.credential or obj.credential.active is False: raise serializers.ValidationError(dict(credential=["Credential not found or deleted."])) + + if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None or not obj.project.active): + raise serializers.ValidationError(dict(errors=["Job Template Project is missing or undefined"])) + if obj.inventory is None or not obj.inventory.active: + raise serializers.ValidationError(dict(errors=["Job Template Inventory is missing or undefined"])) + return attrs class AdHocCommandSerializer(UnifiedJobSerializer): diff --git a/awx/api/views.py b/awx/api/views.py index fadcd19b13..e52dd77009 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -619,7 +619,7 @@ class ProjectDetail(RetrieveUpdateDestroyAPIView): serializer_class = ProjectSerializer def destroy(self, request, *args, **kwargs): - obj = Project.objects.get(pk=kwargs['pk']) + obj = self.get_object() can_delete = request.user.can_access(Project, 'delete', obj) if not can_delete: raise PermissionDenied("Cannot delete project") @@ -1323,7 +1323,7 @@ class InventorySourceDetail(RetrieveUpdateAPIView): new_in_14 = True def destroy(self, request, *args, **kwargs): - obj = InventorySource.objects.get(pk=kwargs['pk']) + obj = self.get_object() can_delete = request.user.can_access(InventorySource, 'delete', obj) if not can_delete: raise PermissionDenied("Cannot delete inventory source") @@ -1425,7 +1425,7 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView): serializer_class = JobTemplateSerializer def destroy(self, request, *args, **kwargs): - obj = JobTemplate.objects.get(pk=kwargs['pk']) + obj = self.get_object() can_delete = request.user.can_access(JobTemplate, 'delete', obj) if not can_delete: raise PermissionDenied("Cannot delete job template") diff --git a/awx/main/management/commands/cleanup_jobs.py b/awx/main/management/commands/cleanup_jobs.py index e76d72109d..ad34d09a82 100644 --- a/awx/main/management/commands/cleanup_jobs.py +++ b/awx/main/management/commands/cleanup_jobs.py @@ -23,7 +23,7 @@ class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--days', dest='days', type='int', default=90, metavar='N', - help='Remove jobs/updates executed more than N days ago'), + help='Remove jobs/updates executed more than N days ago. Defaults to 90.'), make_option('--dry-run', dest='dry_run', action='store_true', default=False, help='Dry run mode (show items that would ' 'be removed)'), diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 06bfa61a20..93d960aa58 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1357,7 +1357,7 @@ class RunSystemJob(BaseTask): if 'days' in json_vars and system_job.job_type != 'cleanup_facts': args.extend(['--days', str(json_vars.get('days', 60))]) if system_job.job_type == 'cleanup_jobs': - args.extend(['--jobs', '--project-updates', '--inventory-updates', '--management-jobs']) + args.extend(['--jobs', '--project-updates', '--inventory-updates', '--management-jobs', '--ad-hoc-commands']) if system_job.job_type == 'cleanup_facts': if 'older_than' in json_vars: args.extend(['--older_than', str(json_vars['older_than'])]) diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 9660c1ca71..688f1f1fd4 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -16,7 +16,7 @@ import 'tower/job-templates/main'; export function InventoriesList($scope, $rootScope, $location, $log, - $routeParams, $compile, $filter, Rest, Alert, InventoryList, generateList, + $routeParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, generateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, Wait, Stream, EditInventoryProperties, Find, Empty, LogViewer) { @@ -346,7 +346,7 @@ export function InventoriesList($scope, $rootScope, $location, $log, Prompt({ hdr: 'Delete', - body: '
Delete inventory ' + name + '?
', + body: '
Delete inventory ' + $filter('sanitize')(name) + '?
', action: action }); }; @@ -370,7 +370,7 @@ export function InventoriesList($scope, $rootScope, $location, $log, }; } -InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', '$compile', '$filter', 'Rest', 'Alert', 'InventoryList', 'generateList', +InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', 'generateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Stream', 'EditInventoryProperties', 'Find', 'Empty', 'LogViewer' ]; diff --git a/awx/ui/static/js/helpers/ConfigureTower.js b/awx/ui/static/js/helpers/ConfigureTower.js index 15142da257..b0c5e5ccf2 100644 --- a/awx/ui/static/js/helpers/ConfigureTower.js +++ b/awx/ui/static/js/helpers/ConfigureTower.js @@ -427,28 +427,6 @@ export default action: action, backdrop: false }); - // } - // } else { - // //a schedule doesn't exist - // $("#prompt_action_btn").text('OK'); - // $('#prompt_cancel_btn').hide(); - // var action2 = function(){ - // $('#prompt-modal').modal('hide'); - // $("#prompt_action_btn").text('Yes'); - // $('#prompt_cancel_btn').show(); - // }; - // Prompt({ - // hdr: "Delete", - // body: "
No schedule exists for that job.
", - // action: action2, - // backdrop: false - // }); - // } - // }) - // .error(function(data, status) { - // ProcessErrors(scope, data, status, null, { hdr: 'Error!', - // msg: 'Failed updating job ' + scope.job_template_id + ' with variables. PUT returned: ' + status }); - // }); }; scope.cancelScheduleForm = function() { diff --git a/awx/ui/static/js/shared/Utilities.js b/awx/ui/static/js/shared/Utilities.js index 496453faad..a09f730feb 100644 --- a/awx/ui/static/js/shared/Utilities.js +++ b/awx/ui/static/js/shared/Utilities.js @@ -15,7 +15,7 @@ export default -angular.module('Utilities', ['RestServices', 'Utilities']) +angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) /** * @ngdoc method @@ -99,9 +99,10 @@ angular.module('Utilities', ['RestServices', 'Utilities']) * alert-info...). Pass an optional function(){}, if you want a specific action to occur when user * clicks 'OK' button. Set secondAlert to true, when a second dialog is needed. */ -.factory('Alert', ['$rootScope', function ($rootScope) { +.factory('Alert', ['$rootScope', '$filter', function ($rootScope, $filter) { return function (hdr, msg, cls, action, secondAlert, disableButtons, backdrop) { var scope = $rootScope.$new(), alertClass, local_backdrop; + msg = $filter('sanitize')(msg); if (secondAlert) { $('#alertHeader2').html(hdr); diff --git a/awx/ui/static/js/shared/prompt-dialog.js b/awx/ui/static/js/shared/prompt-dialog.js index 2e49fa4545..96f0308fbe 100644 --- a/awx/ui/static/js/shared/prompt-dialog.js +++ b/awx/ui/static/js/shared/prompt-dialog.js @@ -27,16 +27,16 @@ */ export default -angular.module('PromptDialog', ['Utilities']) - .factory('Prompt', ['$sce', - function ($sce) { +angular.module('PromptDialog', ['Utilities', 'sanitizeFilter']) + .factory('Prompt', ['$sce', '$filter', + function ($sce, $filter) { return function (params) { var dialog = angular.element(document.getElementById('prompt-modal')), scope = dialog.scope(), cls, local_backdrop; - + scope.promptHeader = params.hdr; - scope.promptBody = $sce.trustAsHtml(params.body); + scope.promptBody = params.body; scope.promptAction = params.action; local_backdrop = (params.backdrop === undefined) ? "static" : params.backdrop; diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 5b80afef66..ddc5e628bc 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -30,6 +30,7 @@ @green: #60D66F; @red: #ff5850; @red-hover: #FA8C87; +@red-focus: #FF1105; @font-face { @@ -1551,6 +1552,19 @@ input[type="checkbox"].checkbox-no-label { } } + + .btn-danger { + background-color: @red; + border-color: @red-focus; + } + + .btn-danger:hover, + .btn-danger:focus, + .btn-danger:active { + border-color: @red-focus; + background-color: @red-focus; + } + // ad hoc permission checkbox .squeeze.form-group { margin-bottom: 10px; diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index e09d7e8616..1c685ac545 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -193,7 +193,7 @@