diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 3d4ce52bc9..589bb41364 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -31,8 +31,8 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.tasksMaxRows = 200; scope.playsMaxRows = 200; - scope.liveEventProcessing = true; // control play/pause state of event processing - scope.pauseLiveEvents = false; + scope.liveEventProcessing = true; // true while job is active and live events are arriving + scope.pauseLiveEvents = false; // control play/pause state of event processing scope.job_status = {}; scope.job_id = job_id; @@ -105,7 +105,9 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar if ($rootScope.jobDetailInterval) { window.clearInterval($rootScope.jobDetailInterval); } - $scope.$emit('LoadJob'); //this is what is used for the refresh + if (!scope.pauseLiveEvents) { + $scope.$emit('LoadJob'); //this is what is used for the refresh + } } } }); @@ -118,8 +120,8 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar var url; Wait('stop'); if (JobIsFinished(scope)) { - $scope.liveEventProcessing = false; // signal that event processing is over and endless scroll - // should be enabled + scope.liveEventProcessing = false; // signal that event processing is over and endless scroll + scope.pauseLiveEvents = false; // should be enabled url = scope.job.related.job_events + '?event=playbook_on_stats'; Rest.setUrl(url); Rest.get() @@ -143,7 +145,6 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { api_complete = true; //trigger events to start processing $rootScope.jobDetailInterval = setInterval(function() { - $log.debug('Updating the DOM...'); UpdateDOM({ scope: scope }); }, 2000); } @@ -291,6 +292,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { scope.activeTask = data.results[0].id; } + scope.selectedTask = scope.activeTask; } data.results.forEach(function(event, idx) { var end, elapsed, status, status_text; @@ -352,7 +354,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar task: play.tasks[event.id] }); }); - if (scope.activeTask) { + if (scope.activeTask && scope.jobData.plays[scope.activePlay] && scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) { scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = 'active'; } scope.$emit('LoadHosts'); @@ -395,6 +397,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { scope.activePlay = data.results[0].id; } + scope.selectedPlay = scope.activePlay; } data.results.forEach(function(event, idx) { var status, status_text, start, end, elapsed, ok, changed, failed, skipped; @@ -457,7 +460,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable + scope.host_summary.failed; }); - if (scope.activePlay) { + if (scope.activePlay && scope.jobData.plays[scope.activePlay]) { scope.jobData.plays[scope.activePlay].playActiveClass = 'active'; } scope.$emit('LoadTasks', events_url); @@ -507,6 +510,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar } scope.removeLoadJobRow = scope.$on('LoadJob', function() { Wait('start'); + scope.job_status = {}; // Load the job record Rest.setUrl(GetBasePath('jobs') + job_id + '/'); Rest.get() @@ -552,6 +556,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') { scope.job_status.finished = data.finsished; scope.liveEventProcessing = false; + scope.pauseLiveEvents = false; } else { scope.job_status.finished = null; @@ -653,9 +658,19 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.adjustSize(); }, 500)); + function flashPlayTip() { + setTimeout(function(){ + $('#play-help').popover('show'); + },500); + setTimeout(function() { + $('#play-help').popover('hide'); + }, 5000); + } + scope.selectPlay = function(id) { - if (scope.liveEventProcessing) { + if (scope.liveEventProcessing && !scope.pauseLiveEvents) { scope.pauseLiveEvents = true; + flashPlayTip(); } SelectPlay({ scope: scope, @@ -664,8 +679,9 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar }; scope.selectTask = function(id) { - if (scope.liveEventProcessing) { + if (scope.liveEventProcessing && !scope.pauseLiveEvents) { scope.pauseLiveEvents = true; + flashPlayTip(); } SelectTask({ scope: scope, @@ -673,6 +689,13 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar }); }; + scope.togglePlayButton = function() { + if (scope.pauseLiveEvents) { + scope.pauseLiveEvents = false; + scope.$emit('LoadJob'); + } + }; + scope.toggleSummary = function(hide) { var docw, doch, height = $('#job-detail-container').height(), slide_width; if (!hide) { @@ -739,7 +762,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.filterPlayStatus = function() { scope.search_play_status = (scope.search_play_status === 'all') ? 'failed' : 'all'; - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { LoadPlays({ scope: scope }); @@ -753,7 +776,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { scope.searchPlaysEnabled = true; } - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { LoadPlays({ scope: scope }); @@ -774,7 +797,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { scope.searchTasksEnabled = true; } - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { LoadTasks({ scope: scope }); @@ -795,7 +818,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { scope.searchHostsEnabled = true; } - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { LoadHosts({ scope: scope }); @@ -816,7 +839,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { scope.searchHostSummaryEnabled = true; } - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { ReloadHostSummaryList({ scope: scope }); @@ -832,7 +855,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.filterTaskStatus = function() { scope.search_task_status = (scope.search_task_status === 'all') ? 'failed' : 'all'; - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { LoadTasks({ scope: scope }); @@ -841,7 +864,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.filterHostStatus = function() { scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all'; - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { LoadHosts({ scope: scope }); @@ -850,7 +873,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.filterHostSummaryStatus = function() { scope.search_host_summary_status = (scope.search_host_summary_status === 'all') ? 'failed' : 'all'; - if (!scope.liveEventProcessing) { + if (!scope.liveEventProcessing || scope.pauseLiveEvents) { ReloadHostSummaryList({ scope: scope }); @@ -861,7 +884,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar EventViewer({ scope: scope, url: scope.job.related.job_events, - parent_id: scope.activeTask, + parent_id: scope.selectedTask, event_id: id, title: 'Host Events' }); @@ -975,7 +998,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar else { // no next event (task), get the end time of the play scope.plays.every(function(p, j) { - if (p.id === scope.activePlay) { + if (p.id === scope.selectedPlay) { end = scope.plays[j].finished; return false; } @@ -997,7 +1020,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar scope.tasks.push({ id: event.id, - play_id: scope.activePlay, + play_id: scope.selectedPlay, name: event.name, status: status, status_text: status_text, diff --git a/awx/ui/static/js/helpers/HostEventsViewer.js b/awx/ui/static/js/helpers/HostEventsViewer.js index 2294f7ab5d..e322225e33 100644 --- a/awx/ui/static/js/helpers/HostEventsViewer.js +++ b/awx/ui/static/js/helpers/HostEventsViewer.js @@ -116,9 +116,9 @@ angular.module('HostEventsViewerHelper', ['ModalDialog', 'Utilities', 'EventView var html = ''; html += "\n"; html += " " + res.status_text + "\n"; - html += "" + res.host_name + "\n"; - html += "" + res.play + "\n"; - html += "" + res.task + "\n"; + html += "" + res.host_name + "\n"; + html += "" + res.play + "\n"; + html += "" + res.task + "\n"; html += ""; return html; }; diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 29a709854f..4607dcc81e 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -725,16 +725,16 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge }; }]) -// Call SelectPlay whenever the the activePlay needs to change -.factory('SelectPlay', ['SelectTask', 'LoadTasks', function(SelectTask, LoadTasks) { +// Call when the selected Play needs to change +.factory('SelectPlay', ['LoadTasks', function(LoadTasks) { return function(params) { var scope = params.scope, id = params.id, callback = params.callback; - scope.activePlay = id; + scope.selectedPlay = id; scope.plays.forEach(function(play, idx) { - if (play.id === scope.activePlay) { + if (play.id === scope.selectedPlay) { scope.plays[idx].playActiveClass = 'active'; } else { @@ -760,14 +760,14 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge scope.tasks = []; scope.tasksMap = {}; - if (scope.activePlay) { - url = scope.job.url + 'job_tasks/?event_id=' + scope.activePlay; + 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_status === 'failed') ? '&failed=true' : ''; url += '&page_size=' + scope.tasksMaxRows + '&order_by=id'; scope.plays.every(function(p, idx) { - if (p.id === scope.activePlay) { + if (p.id === scope.selectedPlay) { play = scope.plays[idx]; return false; } @@ -793,7 +793,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge else { // no next event (task), get the end time of the play scope.plays.every(function(play) { - if (play.id === scope.activePlay) { + if (play.id === scope.selectedPlay) { end = play.finished; return false; } @@ -816,7 +816,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge scope.tasks.push({ id: event.id, - play_id: scope.activePlay, + play_id: scope.selectedPlay, name: event.name, status: status, status_text: status_text, @@ -859,7 +859,6 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge }); } else { - //$('#tasks-table-detail').mCustomScrollbar("update"); SelectTask({ scope: scope, id: null, @@ -869,16 +868,16 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge }; }]) -// Call SelectTask whenever the activeTask needs to change +// Call when the selected task needs to change .factory('SelectTask', ['LoadHosts', function(LoadHosts) { return function(params) { var scope = params.scope, id = params.id, callback = params.callback; - scope.activeTask = id; + scope.selectedTask = id; scope.tasks.forEach(function(task, idx) { - if (task.id === scope.activeTask) { + if (task.id === scope.selectedTask) { scope.tasks[idx].taskActiveClass = 'active'; } else { @@ -904,9 +903,9 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge scope.hostResults = []; scope.hostResultsMap = {}; - if (scope.activeTask) { + if (scope.selectedTask) { // If we have a selected task, then get the list of hosts - url = scope.job.related.job_events + '?parent=' + scope.activeTask + '&'; + url = scope.job.related.job_events + '?parent=' + scope.selectedTask + '&'; url += (scope.search_host_name) ? 'host__name__icontains=' + scope.search_host_name + '&' : ''; url += (scope.search_host_status === 'failed') ? 'failed=true&' : ''; url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order_by=host__name'; @@ -1178,6 +1177,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge setTimeout( function() { scope.$apply( function() { scope.plays = result; + scope.selectedPlay = scope.activePlay; if (scope.liveEventProcessing) { $('#plays-table-detail').scrollTop($('#plays-table-detail').prop("scrollHeight")); } @@ -1203,7 +1203,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge return 0; } - if (scope.activePlay) { + if (scope.activePlay && scope.jobData.plays[scope.activePlay]) { tasks = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks)); @@ -1253,6 +1253,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge setTimeout( function() { scope.$apply( function() { scope.tasks = result; + scope.selectedTask = scope.activeTask; if (scope.liveEventProcessing) { $('#tasks-table-detail').scrollTop($('#tasks-table-detail').prop("scrollHeight")); } @@ -1273,7 +1274,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge key, keys; - if (scope.activePlay && scope.activeTask) { + if (scope.activePlay && scope.activeTask && scope.jobData.plays[scope.activePlay] && + scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) { hostResults = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults)); diff --git a/awx/ui/static/less/job-details.less b/awx/ui/static/less/job-details.less index b427ae3fb2..f2ed39ff02 100644 --- a/awx/ui/static/less/job-details.less +++ b/awx/ui/static/less/job-details.less @@ -80,6 +80,12 @@ #jobs-detail { + #play-help { + width: 1px; + height: 1px; + color: #fff; + } + .job_summary { .table { margin-bottom: 0; diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index 1fc39f8e0f..e72fbc2b5f 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -229,7 +229,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job * aw-tool-tip="<< tooltip text here >>" * * Include the standard TB data-XXX attributes to controll a tooltip's appearance. We will - * default placement to the left and delay to 2 seconds. + * default placement to the left and delay to the config setting. */ .directive('awToolTip', function() { return function(scope, element, attrs) { diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index 7a0d9fd0a3..a951dd2ca8 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -15,6 +15,8 @@
+ +