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 @@