diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index ef19b69111..a511ffa33f 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -2234,3 +2234,8 @@ a:hover { padding-left: 2px; width: 14px; } + +button[disabled], +html input[disabled] { + cursor: not-allowed; +} diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 63e41b8fa8..b0b3d85f10 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -35,11 +35,14 @@
+ ng-show="tooManyEvents || tooManyPastEvents">
-
The standard output is too large to display. Please specify additional filters to narrow the standard out.
+
The standard output is too large to display. Please specify additional filters to narrow the standard out.
+
Too much previous output to display. Showing running standard output.
diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 92d311a204..0215c816fb 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -1,5 +1,5 @@ -export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', 'Rest', '$state', 'QuerySet', '$rootScope', 'moment', 'i18n', -function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, Rest, $state, QuerySet, $rootScope, moment, i18n) { +export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', 'Rest', '$state', 'QuerySet', '$rootScope', 'moment', '$stateParams', 'i18n', +function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, Rest, $state, QuerySet, $rootScope, moment, $stateParams, i18n) { var toDestroy = []; var cancelRequests = false; @@ -9,6 +9,25 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // this allows you to manage the timing of rest-call based events as // filters are updated. see processPage for more info var currentContext = 1; + $scope.firstCounterFromSocket = -1; + + // if the user enters the page mid-run, reset the search to include a param + // to only grab events less than the first counter from the websocket events + toDestroy.push($scope.$watch('firstCounterFromSocket', function(counter) { + if (counter > -1) { + // make it so that the search include a counter less than the + // first counter from the socket + let params = _.cloneDeep($stateParams.job_event_search); + params.counter__lte = "" + counter; + + Dataset = QuerySet.search(jobData.related.job_events, + params); + + Dataset.then(function(actualDataset) { + $scope.job_event_dataset = actualDataset.data; + }); + } + })); // used for tag search $scope.job_event_dataset = Dataset.data; @@ -424,57 +443,87 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // grab non-header recap lines toDestroy.push($scope.$watch('job_event_dataset', function(val) { - eventQueue.initialize(); + if (val) { + eventQueue.initialize(); - Object.keys($scope.events) - .forEach(v => { - // dont destroy scope events for skeleton lines - let name = $scope.events[v].event.name; + Object.keys($scope.events) + .forEach(v => { + // dont destroy scope events for skeleton lines + let name = $scope.events[v].event.name; - if (!(name === "playbook_on_play_start" || - name === "playbook_on_task_start" || - name === "playbook_on_stats")) { - $scope.events[v].$destroy(); - $scope.events[v] = null; - delete $scope.events[v]; + if (!(name === "playbook_on_play_start" || + name === "playbook_on_task_start" || + name === "playbook_on_stats")) { + $scope.events[v].$destroy(); + $scope.events[v] = null; + delete $scope.events[v]; + } + }); + + // pause websocket events from coming in to the pane + $scope.gotPreviouslyRanEvents = $q.defer(); + currentContext += 1; + + let context = currentContext; + + $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); + $scope.hasSkeleton.promise.then(() => { + if (val.count > parseInt(val.maxEvents)) { + $(".header_task").hide(); + $(".header_play").hide(); + $scope.standardOutTooltip = '
' + + i18n._('The output is too large to display. Please download.') + + '
' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
'; + + if ($scope.job_status === "successful" || + $scope.job_status === "failed" || + $scope.job_status === "error" || + $scope.job_status === "canceled") { + $scope.tooManyEvents = true; + $scope.tooManyPastEvents = false; + } else { + $scope.tooManyPastEvents = true; + $scope.tooManyEvents = false; + $scope.gotPreviouslyRanEvents.resolve(""); + } + } else { + $(".header_task").show(); + $(".header_play").show(); + $scope.tooManyEvents = false; + $scope.tooManyPastEvents = false; + processPage(val, context); } }); - - // pause websocket events from coming in to the pane - $scope.gotPreviouslyRanEvents = $q.defer(); - currentContext += 1; - - let context = currentContext; - - $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); - $scope.hasSkeleton.promise.then(() => { - if (val.count > parseInt(val.maxEvents)) { - $(".header_task").hide(); - $(".header_play").hide(); - $scope.tooManyEvents = true; - $scope.standardOutTooltip = '
' + - i18n._('The output is too large to display. Please download.') + - '
' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
'; - } else { - $(".header_task").show(); - $(".header_play").show(); - $scope.tooManyEvents = false; - processPage(val, context); - } - }); + } })); // Processing of job_events messages from the websocket toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { + + // use the lowest counter coming over the socket to retrigger pull data + // to only be for stuff lower than that id + // + // only do this for entering the jobs page mid-run (thus the + // data.counter is 1 conditional + if (data.counter === 1) { + $scope.firstCounterFromSocket = -2; + } + + if ($scope.firstCounterFromSocket !== -2 && + $scope.firstCounterFromSocket === -1 || + data.counter < $scope.firstCounterFromSocket) { + $scope.firstCounterFromSocket = data.counter; + } + $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { // put the line in the diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html index 86d7430d21..24b2dce775 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -506,7 +506,9 @@ list="list" collection="job_events" dataset="job_event_dataset" - search-tags="searchTags"> + search-tags="searchTags" + disable-search="job_status == 'running' || + job_status=='pending'">
diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js index ec47cc7757..063804320e 100644 --- a/awx/ui/client/src/job-results/job-results.route.js +++ b/awx/ui/client/src/job-results/job-results.route.js @@ -6,6 +6,12 @@ import {templateUrl} from '../shared/template-url/template-url.factory'; +const defaultParams = { + page_size: "200", + order_by: 'start_line', + not__event__in: 'playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats' +} + export default { name: 'jobDetail', url: '/jobs/{id: int}', @@ -24,11 +30,7 @@ export default { }, params: { job_event_search: { - value: { - page_size: 100, - order_by: 'id', - not__event__in: 'playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats' - }, + value: defaultParams, dynamic: true, squash: '' } @@ -56,7 +58,7 @@ export default { // flashing as rest data comes in. If the job is finished and // there's a playbook_on_stats event, go ahead and resolve the count // so you don't get that flashing! - count: ['jobData', 'jobResultsService', 'Rest', '$q', function(jobData, jobResultsService, Rest, $q) { + count: ['jobData', 'jobResultsService', 'Rest', '$q', '$stateParams', '$state', function(jobData, jobResultsService, Rest, $q, $stateParams, $state) { var defer = $q.defer(); if (jobData.finished) { // if the job is finished, grab the playbook_on_stats @@ -92,6 +94,15 @@ export default { }, countFinished: false}); }); } else { + // make sure to not include any extra + // search params for a running job (because we can't filter + // incoming job events) + if (!_.isEqual($stateParams.job_event_search, defaultParams)) { + let params = _.cloneDeep($stateParams); + params.job_event_search = defaultParams; + $state.go('.', params, { reload: true }); + } + // job isn't finished so just send an empty count and read // from events defer.resolve({val: { diff --git a/awx/ui/client/src/shared/smart-search/smart-search.directive.js b/awx/ui/client/src/shared/smart-search/smart-search.directive.js index 4cf67ed51f..73d3eafa89 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.directive.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.directive.js @@ -15,6 +15,7 @@ export default ['templateUrl', dataset: '=', collection: '=', searchTags: '=', + disableSearch: '=' }, controller: 'SmartSearchController', templateUrl: templateUrl('shared/smart-search/smart-search') diff --git a/awx/ui/client/src/shared/smart-search/smart-search.partial.html b/awx/ui/client/src/shared/smart-search/smart-search.partial.html index 57a3bfb52c..a7a8f9caa2 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.partial.html +++ b/awx/ui/client/src/shared/smart-search/smart-search.partial.html @@ -4,7 +4,8 @@
- +