From f2db52cca0d1d7b3aa20575b3957410683b11930 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 13 Jun 2014 11:11:44 -0400 Subject: [PATCH 01/11] Job detail page refactor Using dictionaries rather than arrays. Filters now working. New endpoints wired in. --- awx/ui/static/js/controllers/JobDetail.js | 62 +++-- awx/ui/static/js/helpers/JobDetail.js | 303 ++++++++++++---------- awx/ui/static/partials/job_detail.html | 7 +- 3 files changed, 208 insertions(+), 164 deletions(-) diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 2f92d7d4cf..42a324ea21 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -147,7 +147,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, if (scope.removeInitialDataLoaded) { scope.removeInitialDataLoaded(); } - scope.removeInitialDataLoaded= scope.$on('InitialDataLoaded', function() { + scope.removeInitialDataLoaded = scope.$on('InitialDataLoaded', function() { // Load data for the host summary table if (!api_complete) { ReloadHostSummaryList({ @@ -167,6 +167,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, SelectPlay({ scope: scope, id: lastPlay, + callback: 'InitialDataLoaded' }); }); @@ -181,14 +182,15 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, .success( function(data) { data.forEach(function(event, idx) { var status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'none', - start = event.start, + start = event.started, end, elapsed; - if (idx < data.results.length - 1) { + if (idx < data.length - 1) { // end date = starting date of the next event - end = data.results[idx + 1].started; + end = data[idx + 1].started; } - else if (scope.job_status.status === 'successful' || scope.job_status.status === 'failed' || scope.job_status.status === 'error' || scope.job_status.status === 'canceled') { + else if (scope.job_status.status === 'successful' || scope.job_status.status === 'failed' || + scope.job_status.status === 'error' || scope.job_status.status === 'canceled') { // this is the last play and the job already finished end = scope.job_status.finished; } @@ -640,11 +642,8 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } }; - scope.searchSummaryHosts = function() { - - }; - scope.searchAllByHost = function() { + var nxtPlay; if (scope.search_all_hosts_name) { FilterAllByHostName({ scope: scope, @@ -656,12 +655,18 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.search_all_tasks = []; scope.search_all_plays = []; scope.searchAllHostsEnabled = true; - scope.activePlay = scope.plays[scope.plays.length - 1].id; - setTimeout(function() { - SelectPlay({ scope: scope, id: scope.activePlay }); - }, 2000); + nxtPlay = scope.plays[scope.plays.length - 1].id; + SelectPlay({ + scope: scope, + id: nxtPlay + }); + ReloadHostSummaryList({ + scope: scope + }); + //setTimeout(function() { + // SelectPlay({ scope: scope, id: scope.activePlay }); + //}, 2000); } - scope.searchSummaryHosts(); }; scope.allHostNameKeyPress = function(e) { @@ -671,25 +676,30 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, }; scope.filterByStatus = function(choice) { - var tmp = []; + var key, keys, nxtPlay; if (choice === 'Failed') { scope.searchAllStatus = 'failed'; - scope.plays.forEach(function(row) { - if (row.status === 'failed') { - tmp.push(row.id); + for(key in scope.plays) { + if (scope.plays[key].status === 'failed') { + nxtPlay = key; } - }); - tmp.sort(); - scope.activePlay = tmp[tmp.length - 1]; + } } else { scope.searchAllStatus = ''; - scope.activePlay = scope.plays[scope.plays.length - 1].id; + keys = Object.keys(scope.plays); + nxtPlay = (keys.length > 0) ? keys[keys.length - 1] : null; } - scope.searchSummaryHosts(); - setTimeout(function() { - SelectPlay({ scope: scope, id: scope.activePlay }); - }, 2000); + SelectPlay({ + scope: scope, + id: nxtPlay + }); + ReloadHostSummaryList({ + scope: scope + }); + //setTimeout(function() { + // SelectPlay({ scope: scope, id: scope.activePlay }); + //}, 2000); }; scope.viewEvent = function(event_id) { diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 13bdb2c9c2..09a5522dd3 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -474,7 +474,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa }]) // Add a new host result -.factory('AddHostResult', ['FindFirstTaskofPlay', function(FindFirstTaskofPlay) { +.factory('AddHostResult', ['FindFirstTaskofPlay', 'SetTaskStyles', function(FindFirstTaskofPlay, SetTaskStyles) { return function(params) { var scope = params.scope, task_id = params.task_id, @@ -484,7 +484,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa created = params.created, name = params.name, msg = params.message, - pId, diff, task, play_id, first; + task, play_id, first; if (scope.activeTask === task_id && !scope.hostResultsMap[host_id]) { // the event applies to the currently selected task @@ -510,30 +510,39 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa if (scope.hostResults.length > scope.hostTableRows) { scope.hostResults.splice(0,1); } + // Refresh the map + scope.hostResultsMap = {}; + scope.hostResults.forEach(function(result, idx) { + scope.hostResultsMap[result.id] = idx; + }); } - // get the correct play_id for this event. the task_id is always correct/available, but sometimes - // the event does not have a play_id - for (pId in scope.tasks) { - if (scope.tasks[pId][task_id]) { - play_id = pId; - } - } + // update the task + if (scope.tasks[task_id]) { + task = scope.tasks[task_id]; + play_id = scope.tasks[task_id].play_id; - first = FindFirstTaskofPlay({ - scope: scope, - play_id: play_id - }); + first = FindFirstTaskofPlay({ + scope: scope, + play_id: play_id + }); - task = scope.tasks[play_id][task_id]; - if (task_id === first) { task.hostCount += 1; + task.reportedHosts++; + task.failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0; + task.changedCount += (status === 'changed') ? 1 : 0; + task.successfulCount += (status === 'successful') ? 1 : 0; + task.skippedCount += (status === 'skipped') ? 1 : 0; + + SetTaskStyles({ task: task }); } - task.reportedHosts++; - task.failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0; - task.changedCount += (status === 'changed') ? 1 : 0; - task.successfulCount += (status === 'successful') ? 1 : 0; - task.skippedCount += (status === 'skipped') ? 1 : 0; + }; +}]) + +.factory('SetTaskStyles', [ function() { + return function(params) { + var task = params.task, + diff; task.failedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.failedCount / task.hostCount))) : 0; task.changedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.changedCount / task.hostCount))) : 0; @@ -555,14 +564,10 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa task.successfulPct = task.successfulPct - diff; } } - task.successfulStyle = (task.successfulPct > 0) ? { display: 'inline-block', width: task.successfulPct + '%' } : { display: 'none' }; - task.changedStyle = (task.changedPct > 0) ? { display: 'inline-block', width: task.changedPct + '%' } : { display: 'none' }; - task.skippedStyle = (task.skippedPct > 0) ? { display: 'inline-block', width: task.skippedPct + '%' } : { display: 'none' }; - task.failedStyle = (task.failedPct > 0) ? { display: 'inline-block', width: task.failedPct + '%' } : { display: 'none' }; - $('#' + task.id + '-' + task.play_id + '-' + 'successful-bar').css(task.successfulStyle); - $('#' + task.id + '-' + task.play_id + '-' + 'changed-bar').css(task.changedStyle); - $('#' + task.id + '-' + task.play_id + '-' + 'skipped-bar').css(task.skippedStyle); - $('#' + task.id + '-' + task.play_id + '-' + 'failed-bar').css(task.failedStyle); + task.successfulStyle = (task.successfulPct > 0) ? { 'display': 'inline-block', 'width': task.successfulPct + "%" } : { 'display': 'none' }; + task.changedStyle = (task.changedPct > 0) ? { 'display': 'inline-block', 'width': task.changedPct + "%" } : { 'display': 'none' }; + task.skippedStyle = (task.skippedPct > 0) ? { 'display': 'inline-block', 'width': task.skippedPct + "%" } : { 'display': 'none' }; + task.failedStyle = (task.failedPct > 0) ? { 'display': 'inline-block', 'width': task.failedPct + "%" } : { 'display': 'none' }; }; }]) @@ -570,93 +575,112 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa .factory('SelectPlay', ['SelectTask', 'LoadTasks', function(SelectTask, LoadTasks) { return function(params) { var scope = params.scope, - id = params.id; + id = params.id, + callback = params.callback; if (scope.plays[scope.activePlay]) { scope.plays[scope.activePlay].playActiveClass = ''; } - scope.plays[id].playActiveClass = 'active'; + if (id) { + scope.plays[id].playActiveClass = 'active'; + } scope.activePlay = id; - scope.activePlayName = scope.plays[id].name; LoadTasks({ - scope: scope + scope: scope, + callback: callback }); - }; }]) -.factory('LoadTasks', ['Rest', 'ProcessErrors', 'GetElapsed', 'SelectTask', function(Rest, ProcessErrors, GetElapsed, SelectTask) { +.factory('LoadTasks', ['Rest', 'ProcessErrors', 'GetElapsed', 'SelectTask', 'SetTaskStyles', function(Rest, ProcessErrors, GetElapsed, SelectTask, SetTaskStyles) { return function(params) { var scope = params.scope, callback = params.callback, - url = scope.job.related.job_tasks + '?parent=' + scope.activePlay + '&order_by=id'; + url, tIds, lastId; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - var tIds, lastId; - scope.tasks = {}; - data.results.forEach(function(event, idx) { - var end, elapsed; + if (scope.activePlay) { + url = scope.job.url + 'job_tasks/?event_id=' + scope.activePlay; + // job_tasks seems to ignore all query predicates other than event_id + //+ '&'; + //url += (scope.search_all_plays.length > 0) ? 'event_id__in=' + scope.search_all_plays.join() + '&' : ''; + //url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + //url += 'order_by=id'; - if (idx < data.results.length - 1) { - // end date = starting date of the next event - end = data.results[idx + 1].created; - } - else { - // no next event (task), get the end time of the play - end = scope.plays[scope.activePlay].finished; - } - if (end) { - elapsed = GetElapsed({ - start: event.created, - end: end - }); - } - else { - elapsed = '00:00:00'; - } + Rest.setUrl(url); + Rest.get() + .success(function(data) { + scope.tasks = {}; + data.forEach(function(event, idx) { + var end, elapsed; + if ((!scope.searchAllStatus) || (scope.searchAllStatus === 'failed' && event.failed) && + ((scope.search_all_tasks.length === 0) || (scope.searchAllTasks.indexOf(event.id)))) { - scope.tasks[event.id] = { - id: event.id, - play_id: event.id, - name: event.event_display, - status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), - created: event.created, - modified: event.modified, - finished: end, - elapsed: elapsed, - hostCount: 0, // hostCount, - reportedHosts: 0, - successfulCount: 0, - failedCount: 0, - changedCount: 0, - skippedCount: 0, - successfulStyle: { display: 'none'}, - failedStyle: { display: 'none' }, - changedStyle: { display: 'none' }, - skippedStyle: { display: 'none' }, - taskActiveClass: '' - }; + if (idx < data.length - 1) { + // end date = starting date of the next event + end = data[idx + 1].created; + } + else { + // no next event (task), get the end time of the play + end = scope.plays[scope.activePlay].finished; + } + + if (end) { + elapsed = GetElapsed({ + start: event.created, + end: end + }); + } + else { + elapsed = '00:00:00'; + } + + scope.tasks[event.id] = { + id: event.id, + play_id: event.id, + name: event.name, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + created: event.created, + modified: event.modified, + finished: end, + elapsed: elapsed, + hostCount: event.host_count, // hostCount, + reportedHosts: event.reported_hosts, + successfulCount: event.successful_count, + failedCount: event.failed_count, + changedCount: event.changed_count, + skippedCount: event.skipped_count, + taskActiveClass: '' + }; + + SetTaskStyles({ task: scope.tasks[event.id] }); + } + }); + + // set the active task + tIds = Object.keys(scope.tasks); + lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; + SelectTask({ + scope: scope, + id: lastId, + callback: callback + }); + }) + .error(function(data) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); }); - - // set the active task - tIds = Object.keys(scope.tasks); - lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; - SelectTask({ - scope: scope, - id: lastId - }); - - if (callback) { - scope.$emit(callback); - } - }) - .error(function(data) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); + } + else { + // set the active task + tIds = Object.keys(scope.tasks); + lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; + SelectTask({ + scope: scope, + id: lastId, + callback: callback }); + } }; }]) @@ -667,15 +691,16 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa id = params.id, callback = params.callback; - scope.tasks[scope.activePlay][scope.activeTask].taskActiveClass = ''; - scope.tasks[scope.activePlay][id].taskActiveClass = 'active'; - scope.activeTask = id; - scope.activeTaskName = scope.tasks[scope.activePlay][id].name; - - if (callback) { - callback(); + if (scope.activeTask && scope.tasks[scope.activeTask]) { + scope.tasks[scope.activeTask].taskActiveClass = ''; } + if (id) { + scope.tasks[id].taskActiveClass = 'active'; + scope.activeTaskName = scope.tasks[id].name; + } + scope.activeTask = id; + $('#tasks-table-detail').mCustomScrollbar("update"); setTimeout( function() { scope.auto_scroll = true; @@ -683,7 +708,8 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa }, 700); LoadHosts({ - scope: scope + scope: scope, + callback: callback }); }; }]) @@ -696,38 +722,43 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa url; scope.hostResults = []; scope.hostResultsMap = {}; - // If we have a selected task, then get the list of hosts - url = scope.job.related.job_events + '?parent=' + scope.activeTask + '&'; - url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; - url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; - url += 'host__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - data.results.forEach(function(event) { - scope.hostResults.push({ - id: event.id, - status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), - host_id: event.host, - task_id: event.parent, - name: event.event_data.host, - created: event.created, - msg: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' ) + if (scope.activeTask) { + // If we have a selected task, then get the list of hosts + url = scope.job.related.job_events + '?parent=' + scope.activeTask + '&'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'host__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + data.results.forEach(function(event) { + scope.hostResults.push({ + id: event.id, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + host_id: event.host, + task_id: event.parent, + name: event.event_data.host, + created: event.created, + msg: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' ) + }); + scope.hostResultsMap[event.id] = scope.hostResults.length - 1; }); - scope.hostResultsMap[event.id] = scope.hostResults.length - 1; + if (callback) { + scope.$emit(callback); + } + SelectHost({ scope: scope }); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); }); - SelectHost({ scope: scope }); - if (callback) { - scope.$emit(callback); - } - else { - scope.$emit('InitialDataLoaded'); - } - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); + } + else { + if (callback) { + scope.$emit(callback); + } + SelectHost({ scope: scope }); + } }; }]) @@ -874,6 +905,10 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa }); }, 500); } + else { + scope.tasks = {}; + scope.hostResults = []; + } }); if (scope.removeAllTasksReady) { diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index daa88788aa..ae3305ca41 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -85,10 +85,9 @@
- - +
-
+
No tasks matched
From 05e17e66fcc354ec37ffba063f23117a79545d2a Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Fri, 13 Jun 2014 12:47:27 -0400 Subject: [PATCH 02/11] Add unreachable event counts to play stats --- awx/api/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/api/views.py b/awx/api/views.py index 343db6c210..70f2d157ec 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1494,6 +1494,7 @@ class JobJobPlaysList(BaseJobEventsList): ok_count = 0 changed_count = 0 skipped_count = 0 + unreachable_count = 0 for event_aggregate in event_aggregates: if event_aggregate['event'] == 'runner_on_failed': failed_count += event_aggregate['id__count'] @@ -1501,6 +1502,8 @@ class JobJobPlaysList(BaseJobEventsList): failed_count += event_aggregate['id_count'] elif event_aggregate['event'] == 'runner_on_skipped': skipped_count = event_aggregate['id__count'] + elif event_aggregate['event'] == 'runner_on_unreachable': + unreachable_count = event_aggregate['id__count'] for change_aggregate in change_aggregates: if change_aggregate['changed'] == False: ok_count = change_aggregate['id__count'] @@ -1510,6 +1513,7 @@ class JobJobPlaysList(BaseJobEventsList): play_details['failed_count'] = failed_count play_details['changed_count'] = changed_count play_details['skipped_count'] = skipped_count + play_details['unreachable_count'] = unreachable_count all_plays.append(play_details) return Response(all_plays) From c297342ba05326e439b1631f1c99248a0b2c5d3c Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 13 Jun 2014 14:30:42 -0400 Subject: [PATCH 03/11] Job detail page refactor Latest changes. Things are still broken thought. Task status not working correctly or at all, and hosts results are not showing up. --- awx/ui/static/js/controllers/JobDetail.js | 27 ++++-- awx/ui/static/js/helpers/JobDetail.js | 106 +++++++++++----------- awx/ui/static/partials/job_detail.html | 6 +- 3 files changed, 73 insertions(+), 66 deletions(-) diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 42a324ea21..c4bd10d4fd 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -61,6 +61,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, event_socket.on("job_events-" + job_id, function(data) { if (api_complete && data.id > lastEventId) { // api loading is complete, process incoming events + data.event = data.event_name; DigestEvents({ scope: scope, events: [ data ] @@ -77,7 +78,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } scope.removeAPIComplete = scope.$on('APIComplete', function() { // process any events sitting in the queue - var events = [], url; + var events = [], url, hostId = 0, taskId = 0, playId = 0; function notEmpty(x) { return Object.keys(x).length > 0; @@ -91,14 +92,15 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, // Find the max event.id value in memory if (notEmpty(scope.hostResults)) { - lastEventId = getMaxId(scope.hostResults); + hostId = getMaxId(scope.hostResults); } else if (notEmpty(scope.tasks)) { - lastEventId = getMaxId(scope.tasks); + taskId = getMaxId(scope.tasks); } else if (notEmpty(scope.plays)) { - lastEventId = getMaxId(scope.plays); + playId = getMaxId(scope.plays); } + lastEventId = Math.max(hostId, taskId, playId); // Only process queued events > the max event in memory if (event_queue.length > 0) { @@ -117,7 +119,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, api_complete = true; // Draw the graph - if (scope.job.status === 'successful' && scope.job.status === 'failed' && scope.job.status === 'error') { + if (scope.job.status === 'successful' || scope.job.status === 'failed' || scope.job.status === 'error') { // The job has already completed. graph values found on playbook stats url = scope.job.related.job_events + '?event=playbook_on_stats'; Rest.setUrl(url); @@ -212,11 +214,12 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, elapsed: elapsed, playActiveClass: '' }; - scope.host_summary.total += data.total; - scope.host_summary.ok += data.ok; - scope.host_summary.changed += data.changed; - scope.host_summary.unreachable += data.unreachable; - scope.host_summary.failed += data.failed; + scope.host_summary.ok += data.ok_count; + scope.host_summary.changed += data.changed_count; + scope.host_summary.unreachable += (data.unreachable_count) ? data.unreachable_count : 0; + scope.host_summary.failed += data.failed_count; + scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + + scope.host_summary.unreachable + scope.host_summary.failed; }); scope.$emit('PlaysReady', events_url); @@ -462,6 +465,10 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } }; + scope.objectIsEmpty = function(obj) { + return (Object.keys(obj).length > 0) ? false : true; + }; + scope.HostDetailOnTotalScroll = _.debounce(function() { // Called when user scrolls down (or forward in time). Using _.debounce var url, mcs = arguments[0]; diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 09a5522dd3..c5c22ffc44 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -50,7 +50,6 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa events.forEach(function(event) { var hostCount; - if (event.event === 'playbook_on_start') { if (scope.job_status.status!== 'failed' && scope.job_status.status !== 'canceled' && scope.job_status.status !== 'error' && scope.job_status !== 'successful') { @@ -125,6 +124,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope: scope, play_id: event.parent }); + scope.tasks[event.id] = { id: event.id, name: event.task, @@ -258,10 +258,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa var scope = params.scope, taskIds; taskIds = Object.keys(scope.tasks); - if (taskIds.length > 0) { - return scope.tasks[taskIds.length - 1].id; - } - return null; + return (taskIds.length > 0) ? taskIds[0] : null; }; }) @@ -329,24 +326,27 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa status_text = params.status_text, play = scope.plays[id]; - if (failed) { - scope.plays[id].status = 'failed'; - } - else if (play.status !== 'changed' && play.status !== 'failed') { - // once the status becomes 'changed' or 'failed' don't modify it - if (no_hosts) { - scope.plays[id].status = 'no-matching-hosts'; + if (scope.plays[id]) { + if (failed) { + scope.plays[id].status = 'failed'; } - else { - scope.plays[id].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful'; + else if (play.status !== 'changed' && play.status !== 'failed') { + // once the status becomes 'changed' or 'failed' don't modify it + if (no_hosts) { + scope.plays[id].status = 'no-matching-hosts'; + } + else { + scope.plays[id].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful'; + } } + scope.plays[id].finished = modified; + scope.plays[id].elapsed = GetElapsed({ + start: play.created, + end: modified + }); + scope.plays[id].status_text = (status_text) ? status_text : scope.plays[id].status; } - scope.plays[id].finished = modified; - scope.plays[id].elapsed = GetElapsed({ - start: play.created, - end: modified - }); - scope.plays[id].status_text = (status_text) ? status_text : scope.plays[id].status; + UpdateJobStatus({ scope: scope, failed: null, @@ -363,30 +363,34 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa id = params.task_id, modified = params.modified, no_hosts = params.no_hosts, - task = scope.tasks[scope.activePlay][id]; - if (no_hosts){ - task.status = 'no-matching-hosts'; + task = scope.tasks[id]; + + if (scope.tasks[id]) { + if (no_hosts){ + task.status = 'no-matching-hosts'; + } + else if (failed) { + task.status = 'failed'; + } + else if (task.status !== 'changed' && task.status !== 'failed') { + // once the status becomes 'changed' or 'failed' don't modify it + task.status = (failed) ? 'failed' : (changed) ? 'changed' : 'successful'; + } + task.finished = params.modified; + task.elapsed = GetElapsed({ + start: task.created, + end: modified + }); + + UpdatePlayStatus({ + scope: scope, + failed: failed, + changed: changed, + play_id: task.play_id, + modified: modified, + no_hosts: no_hosts + }); } - else if (failed) { - task.status = 'failed'; - } - else if (task.status !== 'changed' && task.status !== 'failed') { - // once the status becomes 'changed' or 'failed' don't modify it - task.status = (failed) ? 'failed' : (changed) ? 'changed' : 'successful'; - } - task.finished = params.modified; - task.elapsed = GetElapsed({ - start: task.created, - end: modified - }); - UpdatePlayStatus({ - scope: scope, - failed: failed, - changed: changed, - play_id: task.play_id, - modified: modified, - no_hosts: no_hosts - }); }; }]) @@ -419,6 +423,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope.host_summary.changed += (status === 'changed') ? 1 : 0; scope.host_summary.unreachable += (status === 'unreachable') ? 1 : 0; scope.host_summary.failed += (status === 'failed') ? 1 : 0; + scope.hosts.push({ id: host_id, name: name, @@ -446,10 +451,6 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope.hostsMap[host.id] = idx; }); $('#tasks-table-detail').mCustomScrollbar("update"); - /*setTimeout( function() { - scope.auto_scroll = true; - $('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom"); - }, 700);*/ } UpdateTaskStatus({ @@ -526,14 +527,14 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope: scope, play_id: play_id }); - - task.hostCount += 1; - task.reportedHosts++; + if (task.id === first) { + task.hostCount += 1; + } + task.reportedHosts += 1; task.failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0; task.changedCount += (status === 'changed') ? 1 : 0; task.successfulCount += (status === 'successful') ? 1 : 0; task.skippedCount += (status === 'skipped') ? 1 : 0; - SetTaskStyles({ task: task }); } }; @@ -543,7 +544,6 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa return function(params) { var task = params.task, diff; - task.failedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.failedCount / task.hostCount))) : 0; task.changedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.changedCount / task.hostCount))) : 0; task.skippedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.skippedCount / task.hostCount))) : 0; @@ -637,7 +637,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope.tasks[event.id] = { id: event.id, - play_id: event.id, + play_id: scope.activePlay, name: event.name, status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), created: event.created, diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index ae3305ca41..739cea2894 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -100,7 +100,7 @@
{{ task.successfulCount }}
{{ task.changedCount }}
{{ task.skippedCount }}
{{ task.failedCount }}
No matching hosts
-
+
No tasks matched
@@ -120,7 +120,7 @@
-
+
{{ result.name }} @@ -129,7 +129,7 @@ {{ result.msg }}
-
+
No hosts matched
From 1c8bed371ba996d4e6d1180b2ab5a8e5c78b0305 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 13 Jun 2014 15:18:24 -0400 Subject: [PATCH 04/11] Job detail page refactor The task is now updating correctly as host runner events arrive. W00t! --- awx/ui/static/js/helpers/JobDetail.js | 66 +++++++++++++++------------ 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index c5c22ffc44..e9243f27fb 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -258,7 +258,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa var scope = params.scope, taskIds; taskIds = Object.keys(scope.tasks); - return (taskIds.length > 0) ? taskIds[0] : null; + return (taskIds.length > 0) ? scope.tasks[taskIds[0]].id : null; }; }) @@ -485,7 +485,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa created = params.created, name = params.name, msg = params.message, - task, play_id, first; + play_id, first; if (scope.activeTask === task_id && !scope.hostResultsMap[host_id]) { // the event applies to the currently selected task @@ -520,54 +520,57 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa // update the task if (scope.tasks[task_id]) { - task = scope.tasks[task_id]; play_id = scope.tasks[task_id].play_id; first = FindFirstTaskofPlay({ scope: scope, play_id: play_id }); - if (task.id === first) { - task.hostCount += 1; + if (task_id === first) { + scope.tasks[task_id].hostCount += 1; } - task.reportedHosts += 1; - task.failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0; - task.changedCount += (status === 'changed') ? 1 : 0; - task.successfulCount += (status === 'successful') ? 1 : 0; - task.skippedCount += (status === 'skipped') ? 1 : 0; - SetTaskStyles({ task: task }); + scope.tasks[task_id].reportedHosts += 1; + scope.tasks[task_id].failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0; + scope.tasks[task_id].changedCount += (status === 'changed') ? 1 : 0; + scope.tasks[task_id].successfulCount += (status === 'successful') ? 1 : 0; + scope.tasks[task_id].skippedCount += (status === 'skipped') ? 1 : 0; + SetTaskStyles({ + scope: scope, + task_id: task_id + }); } }; }]) .factory('SetTaskStyles', [ function() { return function(params) { - var task = params.task, + var task_id = params.task_id, + scope = params.scope, diff; - task.failedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.failedCount / task.hostCount))) : 0; - task.changedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.changedCount / task.hostCount))) : 0; - task.skippedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.skippedCount / task.hostCount))) : 0; - task.successfulPct = (task.hostCount > 0) ? Math.ceil((100 * (task.successfulCount / task.hostCount))) : 0; + scope.tasks[task_id].failedPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].failedCount / scope.tasks[task_id].hostCount))) : 0; + scope.tasks[task_id].changedPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].changedCount / scope.tasks[task_id].hostCount))) : 0; + scope.tasks[task_id].skippedPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].skippedCount / scope.tasks[task_id].hostCount))) : 0; + scope.tasks[task_id].successfulPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].successfulCount / scope.tasks[task_id].hostCount))) : 0; - diff = (task.failedPct + task.changedPct + task.skippedPct + task.successfulPct) - 100; + diff = (scope.tasks[task_id].failedPct + scope.tasks[task_id].changedPct + scope.tasks[task_id].skippedPct + scope.tasks[task_id].successfulPct) - 100; if (diff > 0) { - if (task.failedPct > diff) { - task.failedPct = task.failedPct - diff; + if (scope.tasks[task_id].failedPct > diff) { + scope.tasks[task_id].failedPct = scope.tasks[task_id].failedPct - diff; } - else if (task.changedPct > diff) { - task.changedPct = task.changedPct - diff; + else if (scope.tasks[task_id].changedPct > diff) { + scope.tasks[task_id].changedPct = scope.tasks[task_id].changedPct - diff; } - else if (task.skippedPct > diff) { - task.skippedPct = task.skippedPct - diff; + else if (scope.tasks[task_id].skippedPct > diff) { + scope.tasks[task_id].skippedPct = scope.tasks[task_id].skippedPct - diff; } - else if (task.successfulPct > diff) { - task.successfulPct = task.successfulPct - diff; + else if (scope.tasks[task_id].successfulPct > diff) { + scope.tasks[task_id].successfulPct = scope.tasks[task_id].successfulPct - diff; } } - task.successfulStyle = (task.successfulPct > 0) ? { 'display': 'inline-block', 'width': task.successfulPct + "%" } : { 'display': 'none' }; - task.changedStyle = (task.changedPct > 0) ? { 'display': 'inline-block', 'width': task.changedPct + "%" } : { 'display': 'none' }; - task.skippedStyle = (task.skippedPct > 0) ? { 'display': 'inline-block', 'width': task.skippedPct + "%" } : { 'display': 'none' }; - task.failedStyle = (task.failedPct > 0) ? { 'display': 'inline-block', 'width': task.failedPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].successfulStyle = (scope.tasks[task_id].successfulPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].successfulPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].changedStyle = (scope.tasks[task_id].changedPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].changedPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].skippedStyle = (scope.tasks[task_id].skippedPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].skippedPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].failedStyle = (scope.tasks[task_id].failedPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].failedPct + "%" } : { 'display': 'none' }; }; }]) @@ -653,7 +656,10 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa taskActiveClass: '' }; - SetTaskStyles({ task: scope.tasks[event.id] }); + SetTaskStyles({ + scope: scope, + task_id: event.id + }); } }); From e6bba3ed481ff9bd10ccd1a71cd1dd4a0e55b983 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 13 Jun 2014 16:39:35 -0400 Subject: [PATCH 05/11] Job detail page refactor Added an event queue, allowing the UI to process events as fast as it can and hopefully not get overwhelmed by the API. --- awx/ui/static/js/controllers/JobDetail.js | 27 +++++++----- awx/ui/static/js/helpers/JobDetail.js | 50 ++++++++++++++++++++--- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index c4bd10d4fd..3cf1949aae 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -59,18 +59,16 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, event_socket.init(); event_socket.on("job_events-" + job_id, function(data) { - if (api_complete && data.id > lastEventId) { + data.event = data.event_name; + $log.debug('push event: ' + data.id); + event_queue.push(data); + + /* if (api_complete && data.id > lastEventId) { // api loading is complete, process incoming events - data.event = data.event_name; - DigestEvents({ - scope: scope, - events: [ data ] - }); } else { // Waiting on values from the api to load. Until then queue incoming events. - event_queue.push(data); - } + } */ }); if (scope.removeAPIComplete) { @@ -78,7 +76,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } scope.removeAPIComplete = scope.$on('APIComplete', function() { // process any events sitting in the queue - var events = [], url, hostId = 0, taskId = 0, playId = 0; + var url, hostId = 0, taskId = 0, playId = 0; function notEmpty(x) { return Object.keys(x).length > 0; @@ -103,7 +101,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, lastEventId = Math.max(hostId, taskId, playId); // Only process queued events > the max event in memory - if (event_queue.length > 0) { + /*if (event_queue.length > 0) { event_queue.forEach(function(event) { if (event.id > lastEventId) { events.push(event); @@ -115,7 +113,14 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, events: events }); } - } + }*/ + + DigestEvents({ + scope: scope, + queue: event_queue, + lastEventId: lastEventId + }); + api_complete = true; // Draw the graph diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index e9243f27fb..acef8cf254 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -39,17 +39,45 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) -.factory('DigestEvents', ['UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', 'SelectPlay', 'SelectTask', +.factory('DigestEvents', ['$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', 'SelectPlay', 'SelectTask', 'GetHostCount', 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', -function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, +function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, UpdateTaskStatus, DrawGraph, LoadHostSummary) { return function(params) { var scope = params.scope, - events = params.events; + queue = params.queue, + lastEventId = params.lastEventId, + myInterval; - events.forEach(function(event) { + if (scope.removeGetNextEvent) { + scope.removeGetNextEvent(); + } + scope.removeGetNextEvent = scope.$on('GetNextEvent', function() { + if (myInterval) { + window.clearInterval(myInterval); + } + if (scope.job.status !== 'successful' && scope.job.status !== 'failed' && scope.job.status !== 'error') { + myInterval = window.setInterval(function() { + var event; + $log.debug('checking queue length is: ' + queue.length); + if (queue.length > 0) { + event = queue.splice(0,1); + if (event[0].id > lastEventId) { + $log.debug('processing event: ' + event[0].id); + scope.$emit('ProcessEvent', event[0]); + } + } + }, 2000); + } + }); + + if (scope.removeProcessEvent) { + scope.removeProcessEvent(); + } + scope.removeProcessEvent = scope.$on('ProcessEvent', function(e, event) { var hostCount; + $log.debug('handling event: ' + event.id); if (event.event === 'playbook_on_start') { if (scope.job_status.status!== 'failed' && scope.job_status.status !== 'canceled' && scope.job_status.status !== 'error' && scope.job_status !== 'successful') { @@ -117,6 +145,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa modified: event.modified, status_text: 'failed- no hosts matched' }); + scope.$emit('GetNextEvent'); } if (event.event === 'playbook_on_task_start') { if (scope.activePlay === event.parent) { @@ -159,6 +188,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa changed: event.changed, modified: event.modified }); + scope.$emit('GetNextEvent'); } if (event.event === 'runner_on_unreachable') { @@ -173,7 +203,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa modified: event.modified, message: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' ) }); - + scope.$emit('GetNextEvent'); } if (event.event === 'runner_on_error' || event.event === 'runner_on_async_failed') { UpdateHostStatus({ @@ -187,6 +217,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa modified: event.modified, message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' }); + scope.$emit('GetNextEvent'); } if (event.event === 'runner_on_no_hosts') { UpdateTaskStatus({ @@ -197,6 +228,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa modified: event.modified, no_hosts: true }); + scope.$emit('GetNextEvent'); } if (event.event === 'runner_on_skipped') { UpdateHostStatus({ @@ -210,6 +242,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa modified: event.modified, message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' }); + scope.$emit('GetNextEvent'); } if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok') { UpdateHostStatus({ @@ -223,6 +256,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa modified: event.modified, message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' }); + scope.$emit('GetNextEvent'); } if (event.event === 'playbook_on_stats') { scope.job_status.finished = event.modified; @@ -235,8 +269,12 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope.host_summary = {}; LoadHostSummary({ scope: scope, data: event.event_data }); DrawGraph({ scope: scope, resize: true }); + scope.$emit('GetNextEvent'); } }); + + scope.$emit('GetNextEvent'); + }; }]) @@ -753,6 +791,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope.$emit(callback); } SelectHost({ scope: scope }); + scope.$emit('GetNextEvent'); }) .error(function(data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', @@ -764,6 +803,7 @@ function(UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTa scope.$emit(callback); } SelectHost({ scope: scope }); + scope.$emit('GetNextEvent'); } }; }]) From e2aa7be9ce0e5e7cc224d70ccd87b8d9be8ad22b Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 13 Jun 2014 16:43:03 -0400 Subject: [PATCH 06/11] Job detail page refactor Added an event queue, allowing the UI to process events as fast as it can and hopefully not get overwhelmed by the API. --- awx/ui/static/js/helpers/JobDetail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index acef8cf254..6da6df434c 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -68,7 +68,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se scope.$emit('ProcessEvent', event[0]); } } - }, 2000); + }, 500); } }); From d300eab7901c7e672058a1459030a149c524bfad Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Sat, 14 Jun 2014 03:58:52 -0400 Subject: [PATCH 07/11] Job detail page refactor If the queue grows beyond 500 events, reset and start over. --- awx/ui/static/js/controllers/JobDetail.js | 73 ++++++++--------------- awx/ui/static/js/helpers/JobDetail.js | 61 ++++++++++++------- 2 files changed, 66 insertions(+), 68 deletions(-) diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 3cf1949aae..2d270daa6a 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -7,8 +7,9 @@ 'use strict'; -function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, DigestEvents, - SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList) { +function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, + DigestEvents, SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList, + JobIsFinished) { ClearScope(); @@ -20,13 +21,6 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, refresh_count = 0, lastEventId = 0; - scope.plays = {}; - scope.tasks = {}; - scope.hosts = []; - scope.hostResults = []; - scope.hostResultsMap = {}; - scope.hostsMap = {}; - scope.search_all_tasks = []; scope.search_all_plays = []; scope.job_status = {}; @@ -62,13 +56,6 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, data.event = data.event_name; $log.debug('push event: ' + data.id); event_queue.push(data); - - /* if (api_complete && data.id > lastEventId) { - // api loading is complete, process incoming events - } - else { - // Waiting on values from the api to load. Until then queue incoming events. - } */ }); if (scope.removeAPIComplete) { @@ -100,20 +87,8 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } lastEventId = Math.max(hostId, taskId, playId); - // Only process queued events > the max event in memory - /*if (event_queue.length > 0) { - event_queue.forEach(function(event) { - if (event.id > lastEventId) { - events.push(event); - } - }); - if (events.length > 0) { - DigestEvents({ - scope: scope, - events: events - }); - } - }*/ + api_complete = true; + Wait('stop'); DigestEvents({ scope: scope, @@ -121,11 +96,8 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, lastEventId: lastEventId }); - api_complete = true; - // Draw the graph - if (scope.job.status === 'successful' || scope.job.status === 'failed' || scope.job.status === 'error') { - // The job has already completed. graph values found on playbook stats + if (JobIsFinished(scope)) { url = scope.job.related.job_events + '?event=playbook_on_stats'; Rest.setUrl(url); Rest.get() @@ -146,7 +118,6 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } else { // Draw the graph based on summary values in memory - Wait('stop'); DrawGraph({ scope: scope, resize: true }); } }); @@ -178,12 +149,22 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, }); }); - if (scope.removeJobReady) { - scope.removeJobReady(); + if (scope.removeLoadJobDetails) { + scope.removeLoadJobDetails(); } - scope.removeJobReady = scope.$on('JobReady', function(e, events_url) { - // Job finished loading. Now get the set of plays + scope.removeRefreshJobDetails = scope.$on('LoadJobDetails', function(e, events_url) { + + // Call to load all the job bits including, plays, tasks, hosts results and host summary var url = scope.job.url + 'job_plays/?order_by=id'; + + scope.plays = {}; + scope.tasks = {}; + scope.hostResults = []; + scope.hostResultsMap = {}; + scope.hosts = []; + scope.hostsMap = {}; + api_complete = false; + Rest.setUrl(url); Rest.get() .success( function(data) { @@ -196,8 +177,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, // end date = starting date of the next event end = data[idx + 1].started; } - else if (scope.job_status.status === 'successful' || scope.job_status.status === 'failed' || - scope.job_status.status === 'error' || scope.job_status.status === 'canceled') { + else if (JobIsFinished(scope)) { // this is the last play and the job already finished end = scope.job_status.finished; } @@ -312,9 +292,11 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, else { scope.job_status.elapsed = '00:00:00'; } - + if (scope.myInterval) { + window.clearInterval(scope.myInterval); + } scope.setSearchAll('host'); - scope.$emit('JobReady', data.related.job_events); + scope.$emit('LoadJobDetails', data.related.job_events); scope.$emit('GetCredentialNames', data); }) .error(function(data, status) { @@ -675,9 +657,6 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, ReloadHostSummaryList({ scope: scope }); - //setTimeout(function() { - // SelectPlay({ scope: scope, id: scope.activePlay }); - //}, 2000); } }; @@ -722,5 +701,5 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait', 'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost', 'FilterAllByHostName', 'DrawGraph', - 'LoadHostSummary', 'ReloadHostSummaryList' + 'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished' ]; diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 6da6df434c..44ab67e3c1 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -40,35 +40,43 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) .factory('DigestEvents', ['$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', 'SelectPlay', 'SelectTask', - 'GetHostCount', 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', + 'GetHostCount', 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, - UpdateTaskStatus, DrawGraph, LoadHostSummary) { + UpdateTaskStatus, DrawGraph, LoadHostSummary, JobIsFinished) { return function(params) { var scope = params.scope, queue = params.queue, - lastEventId = params.lastEventId, - myInterval; + lastEventId = params.lastEventId; + + function popEvent() { + $log.debug('queue length: ' + queue.length); + if (queue.length > 0 && queue.length < 500) { + var event = queue.splice(0,1); + if (event[0].id > lastEventId) { + $log.debug('processing event: ' + event[0].id); + scope.$emit('ProcessEvent', event[0]); + } + } + else if (queue.length > 500) { + // if we get too far behind, clear the queue and refresh + queue = []; + scope.emit('LoadJob'); + } + } if (scope.removeGetNextEvent) { scope.removeGetNextEvent(); } scope.removeGetNextEvent = scope.$on('GetNextEvent', function() { - if (myInterval) { - window.clearInterval(myInterval); + if (scope.myInterval) { + window.clearInterval(scope.myInterval); } - if (scope.job.status !== 'successful' && scope.job.status !== 'failed' && scope.job.status !== 'error') { - myInterval = window.setInterval(function() { - var event; - $log.debug('checking queue length is: ' + queue.length); - if (queue.length > 0) { - event = queue.splice(0,1); - if (event[0].id > lastEventId) { - $log.debug('processing event: ' + event[0].id); - scope.$emit('ProcessEvent', event[0]); - } - } - }, 500); + popEvent(); + if (!JobIsFinished(scope)) { + scope.myInterval = window.setInterval(function() { + popEvent(); + }, 600); } }); @@ -79,11 +87,11 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se var hostCount; $log.debug('handling event: ' + event.id); if (event.event === 'playbook_on_start') { - if (scope.job_status.status!== 'failed' && scope.job_status.status !== 'canceled' && - scope.job_status.status !== 'error' && scope.job_status !== 'successful') { + if (!JobIsFinished(scope)) { scope.job_status.started = event.created; scope.job_status.status = 'running'; } + scope.$emit('GetNextEvent'); } if (event.event === 'playbook_on_play_start') { @@ -94,6 +102,9 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se status: (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'none', elapsed: '00:00:00' }; + if (scope.plays.length > 1) { + DrawGraph({ scope: scope, resize: false }); + } SelectPlay({ scope: scope, id: event.id @@ -135,6 +146,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changed: event.changed, modified: event.modified }); + scope.$emit('GetNextEvent'); } if (event.event === 'playbook_on_no_hosts_matched') { UpdatePlayStatus({ @@ -273,11 +285,18 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se } }); - scope.$emit('GetNextEvent'); + scope.$emit('GetNextEvent'); // Start checking the queue }; }]) +.factory('JobIsFinished', [ function() { + return function(scope) { + return (scope.job_status.status === 'failed' || scope.job_status.status === 'canceled' || + scope.job_status.status === 'error' || scope.job_status.status === 'successful'); + }; +}]) + //Get the # of expected hosts for a task by looking at the number //on the very first task for a play .factory('GetHostCount', [ 'FindFirstTaskofPlay', function(FindFirstTaskofPlay) { From 37cd9d9aebfcaefa1d1ea60a478e2b8c9dbfef14 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Sat, 14 Jun 2014 04:19:02 -0400 Subject: [PATCH 08/11] Job detail page refactor Fixed empty object detection --- awx/ui/static/js/controllers/JobDetail.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 2d270daa6a..7fee4be686 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -453,7 +453,10 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, }; scope.objectIsEmpty = function(obj) { - return (Object.keys(obj).length > 0) ? false : true; + if (angular.isObject(obj)) { + return (Object.keys(obj).length > 0) ? false : true; + } + return true; }; scope.HostDetailOnTotalScroll = _.debounce(function() { From 56ad7d2e9e35bc506c631798a477a8ba0074018f Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Sat, 14 Jun 2014 04:23:47 -0400 Subject: [PATCH 09/11] Job detail page refactor Adjust queueing strategy --- awx/ui/static/js/helpers/JobDetail.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 44ab67e3c1..614628bd41 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -76,7 +76,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se if (!JobIsFinished(scope)) { scope.myInterval = window.setInterval(function() { popEvent(); - }, 600); + }, 1000); } }); @@ -146,7 +146,6 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changed: event.changed, modified: event.modified }); - scope.$emit('GetNextEvent'); } if (event.event === 'playbook_on_no_hosts_matched') { UpdatePlayStatus({ @@ -200,7 +199,6 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changed: event.changed, modified: event.modified }); - scope.$emit('GetNextEvent'); } if (event.event === 'runner_on_unreachable') { @@ -281,7 +279,6 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se scope.host_summary = {}; LoadHostSummary({ scope: scope, data: event.event_data }); DrawGraph({ scope: scope, resize: true }); - scope.$emit('GetNextEvent'); } }); From 9c1114591494eb660ad14fc59c831cec610861c3 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Mon, 16 Jun 2014 00:24:26 -0400 Subject: [PATCH 10/11] Job detail page refactor Reconfigured event processing and queueing. 50 no op tasks for 200 hosts seems to be working without melting the CPU and staying below 300MB. --- awx/ui/static/js/app.js | 4 + awx/ui/static/js/controllers/JobDetail.js | 72 +- awx/ui/static/js/controllers/JobDetail.js.old | 715 ++++++++++++ awx/ui/static/js/controllers/Jobs.js | 4 - awx/ui/static/js/helpers/JobDetail.js | 254 ++-- awx/ui/static/js/helpers/JobDetail.js.old | 1017 +++++++++++++++++ awx/ui/static/js/helpers/Jobs.js | 20 +- 7 files changed, 1903 insertions(+), 183 deletions(-) create mode 100644 awx/ui/static/js/controllers/JobDetail.js.old create mode 100644 awx/ui/static/js/helpers/JobDetail.js.old diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index a3ef0a3242..078983b04c 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -432,6 +432,10 @@ angular.module('Tower', [ HideStream(); } + if ($rootScope.myInterval) { + window.clearInterval($rootScope.myInterval); + } + // On each navigation request, check that the user is logged in if (!/^\/(login|logout)/.test($location.path())) { // capture most recent URL, excluding login/logout diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 7fee4be686..85b057e087 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -7,19 +7,27 @@ 'use strict'; -function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, - DigestEvents, SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList, +function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, + ProcessErrors, ProcessEventQueue, SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList, JobIsFinished) { ClearScope(); var job_id = $routeParams.id, event_socket, - event_queue = [], scope = $scope, api_complete = false, refresh_count = 0, - lastEventId = 0; + lastEventId = 0, + queue = []; + + scope.plays = {}; + scope.hosts = []; + scope.hostsMap = {}; + scope.tasks = {}; + scope.hostResults = []; + scope.hostResultsMap = {}; + api_complete = false; scope.search_all_tasks = []; scope.search_all_plays = []; @@ -28,9 +36,10 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.auto_scroll = false; scope.searchTaskHostsEnabled = true; scope.searchSummaryHostsEnabled = true; - scope.hostTableRows = 300; - scope.hostSummaryTableRows = 300; + scope.hostTableRows = 150; + scope.hostSummaryTableRows = 150; scope.searchAllHostsEnabled = true; + scope.haltEventQueue = false; scope.host_summary = {}; scope.host_summary.ok = 0; @@ -54,10 +63,25 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, event_socket.on("job_events-" + job_id, function(data) { data.event = data.event_name; - $log.debug('push event: ' + data.id); - event_queue.push(data); + if (api_complete && data.id > lastEventId) { + $log.debug('received event: ' + data.id); + if (queue.length < 50) { + queue.unshift(data); + } + else { + api_complete = false; // stop more events from hitting the queue + $log.debug('queue halted. reloading in 1.'); + setTimeout(function() { + $log.debug('reloading'); + scope.haltEventQueue = true; + queue = []; + scope.$emit('LoadJob'); + }, 1000); + } + } }); + if (scope.removeAPIComplete) { scope.removeAPIComplete(); } @@ -90,10 +114,9 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, api_complete = true; Wait('stop'); - DigestEvents({ + ProcessEventQueue({ scope: scope, - queue: event_queue, - lastEventId: lastEventId + eventQueue: queue }); // Draw the graph @@ -153,23 +176,13 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.removeLoadJobDetails(); } scope.removeRefreshJobDetails = scope.$on('LoadJobDetails', function(e, events_url) { - // Call to load all the job bits including, plays, tasks, hosts results and host summary var url = scope.job.url + 'job_plays/?order_by=id'; - - scope.plays = {}; - scope.tasks = {}; - scope.hostResults = []; - scope.hostResultsMap = {}; - scope.hosts = []; - scope.hostsMap = {}; - api_complete = false; - Rest.setUrl(url); Rest.get() .success( function(data) { data.forEach(function(event, idx) { - var status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'none', + var status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful', start = event.started, end, elapsed; @@ -254,7 +267,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.removeLoadJob(); } scope.removeLoadJobRow = scope.$on('LoadJob', function() { - Wait('start'); + //Wait('start'); // Load the job record Rest.setUrl(GetBasePath('jobs') + job_id + '/'); Rest.get() @@ -275,7 +288,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.verbosity = data.verbosity; scope.job_tags = data.job_tags; - // In the case that the job is already completed, or an error already happened, + // In the case the job is already completed, or an error already happened, // populate scope.job_status info scope.job_status.status = (data.status === 'waiting' || data.status === 'new') ? 'pending' : data.status; scope.job_status.started = data.started; @@ -292,9 +305,6 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, else { scope.job_status.elapsed = '00:00:00'; } - if (scope.myInterval) { - window.clearInterval(scope.myInterval); - } scope.setSearchAll('host'); scope.$emit('LoadJobDetails', data.related.job_events); scope.$emit('GetCredentialNames', data); @@ -315,7 +325,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.$emit('LoadJob'); } else { - // Check if we need to redraw the group + // Check if the graph needs to redraw setTimeout(function() { DrawGraph({ scope: scope, resize: true }); }, 500); } }); @@ -459,6 +469,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, return true; }; + /* scope.HostDetailOnTotalScroll = _.debounce(function() { // Called when user scrolls down (or forward in time). Using _.debounce var url, mcs = arguments[0]; @@ -638,6 +649,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, scope.auto_scroll = false; } }; + */ scope.searchAllByHost = function() { var nxtPlay; @@ -702,7 +714,7 @@ function JobDetailController ($scope, $compile, $routeParams, $log, ClearScope, } -JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait', - 'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost', 'FilterAllByHostName', 'DrawGraph', +JobDetailController.$inject = [ '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', + 'Wait', 'Rest', 'ProcessErrors', 'ProcessEventQueue', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost', 'FilterAllByHostName', 'DrawGraph', 'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished' ]; diff --git a/awx/ui/static/js/controllers/JobDetail.js.old b/awx/ui/static/js/controllers/JobDetail.js.old new file mode 100644 index 0000000000..2d99d733d8 --- /dev/null +++ b/awx/ui/static/js/controllers/JobDetail.js.old @@ -0,0 +1,715 @@ +/************************************ + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * JobDetail.js + * + */ + +'use strict'; + +function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, + ProcessErrors, DigestEvents, SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList, + JobIsFinished) { + + ClearScope(); + + var job_id = $routeParams.id, + event_socket, + event_queue = [], + scope = $scope, + api_complete = false, + refresh_count = 0, + lastEventId = 0; + + scope.plays = {}; + scope.hosts = []; + scope.hostsMap = {}; + + scope.search_all_tasks = []; + scope.search_all_plays = []; + scope.job_status = {}; + scope.job_id = job_id; + scope.auto_scroll = false; + scope.searchTaskHostsEnabled = true; + scope.searchSummaryHostsEnabled = true; + scope.hostTableRows = 300; + scope.hostSummaryTableRows = 300; + scope.searchAllHostsEnabled = true; + + scope.host_summary = {}; + scope.host_summary.ok = 0; + scope.host_summary.changed = 0; + scope.host_summary.unreachable = 0; + scope.host_summary.failed = 0; + scope.host_summary.total = 0; + + scope.eventsHelpText = "

Successful

\n" + + "

Changed

\n" + + "

Unreachable

\n" + + "

Failed

\n" + + "
esc or click to close
\n"; + + event_socket = Socket({ + scope: scope, + endpoint: "job_events" + }); + + event_socket.init(); + + event_socket.on("job_events-" + job_id, function(data) { + data.event = data.event_name; + $log.debug('push event: ' + data.id); + if (event_queue.length < 100) { + event_queue.push(data); + } + else { + // if we get too far behind, clear the queue and refresh + scope.$emit('LoadJob'); + } + }); + + if (scope.removeAPIComplete) { + scope.removeAPIComplete(); + } + scope.removeAPIComplete = scope.$on('APIComplete', function() { + // process any events sitting in the queue + var url, hostId = 0, taskId = 0, playId = 0; + + function notEmpty(x) { + return Object.keys(x).length > 0; + } + + function getMaxId(x) { + var keys = Object.keys(x); + keys.sort(); + return keys[keys.length - 1]; + } + + // Find the max event.id value in memory + if (notEmpty(scope.hostResults)) { + hostId = getMaxId(scope.hostResults); + } + else if (notEmpty(scope.tasks)) { + taskId = getMaxId(scope.tasks); + } + else if (notEmpty(scope.plays)) { + playId = getMaxId(scope.plays); + } + lastEventId = Math.max(hostId, taskId, playId); + + api_complete = true; + Wait('stop'); + + DigestEvents({ + scope: scope, + queue: event_queue, + lastEventId: lastEventId + }); + + // Draw the graph + if (JobIsFinished(scope)) { + url = scope.job.related.job_events + '?event=playbook_on_stats'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (data.count > 0) { + LoadHostSummary({ + scope: scope, + data: data.results[0].event_data + }); + DrawGraph({ scope: scope, resize: true }); + Wait('stop'); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + // Draw the graph based on summary values in memory + DrawGraph({ scope: scope, resize: true }); + } + }); + + if (scope.removeInitialDataLoaded) { + scope.removeInitialDataLoaded(); + } + scope.removeInitialDataLoaded = scope.$on('InitialDataLoaded', function() { + // Load data for the host summary table + if (!api_complete) { + ReloadHostSummaryList({ + scope: scope, + callback: 'APIComplete' + }); + } + }); + + if (scope.removePlaysReady) { + scope.removePlaysReady(); + } + scope.removePlaysReady = scope.$on('PlaysReady', function() { + // Select the most recent play, which will trigger tasks and hosts to load + var ids = Object.keys(scope.plays), + lastPlay = (ids.length > 0) ? ids[ids.length - 1] : null; + SelectPlay({ + scope: scope, + id: lastPlay, + callback: 'InitialDataLoaded' + }); + }); + + if (scope.removeLoadJobDetails) { + scope.removeLoadJobDetails(); + } + scope.removeRefreshJobDetails = scope.$on('LoadJobDetails', function(e, events_url) { + + // Call to load all the job bits including, plays, tasks, hosts results and host summary + var url = scope.job.url + 'job_plays/?order_by=id'; + + scope.tasks = {}; + scope.hostResults = []; + scope.hostResultsMap = {}; + api_complete = false; + + Rest.setUrl(url); + Rest.get() + .success( function(data) { + data.forEach(function(event, idx) { + var status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful', + start = event.started, + end, + elapsed; + if (idx < data.length - 1) { + // end date = starting date of the next event + end = data[idx + 1].started; + } + else if (JobIsFinished(scope)) { + // this is the last play and the job already finished + end = scope.job_status.finished; + } + if (end) { + elapsed = GetElapsed({ + start: start, + end: end + }); + } + else { + elapsed = '00:00:00'; + } + scope.plays[event.id] = { + id: event.id, + name: event.play, + created: start, + finished: end, + status: status, + elapsed: elapsed, + playActiveClass: '' + }; + scope.host_summary.ok += data.ok_count; + scope.host_summary.changed += data.changed_count; + scope.host_summary.unreachable += (data.unreachable_count) ? data.unreachable_count : 0; + scope.host_summary.failed += data.failed_count; + scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + + scope.host_summary.unreachable + scope.host_summary.failed; + }); + + scope.$emit('PlaysReady', events_url); + }) + .error( function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }); + + + if (scope.removeGetCredentialNames) { + scope.removeGetCredentialNames(); + } + scope.removeGetCredentialNames = scope.$on('GetCredentialNames', function(e, data) { + var url; + if (data.credential) { + url = GetBasePath('credentials') + data.credential + '/'; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + scope.credential_name = data.name; + }) + .error( function(data, status) { + scope.credential_name = ''; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + if (data.cloud_credential) { + url = GetBasePath('credentials') + data.credential + '/'; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + scope.cloud_credential_name = data.name; + }) + .error( function(data, status) { + scope.credential_name = ''; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + }); + + + if (scope.removeLoadJob) { + scope.removeLoadJob(); + } + scope.removeLoadJobRow = scope.$on('LoadJob', function() { + //Wait('start'); + // Load the job record + Rest.setUrl(GetBasePath('jobs') + job_id + '/'); + Rest.get() + .success(function(data) { + scope.job = data; + scope.job_template_name = data.name; + scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; + scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; + scope.job_template_url = '/#/job_templates/' + data.unified_job_template; + scope.inventory_url = (scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : ''; + scope.project_url = (scope.project_name && data.project) ? '/#/projects/' + data.project : ''; + scope.job_type = data.job_type; + scope.playbook = data.playbook; + scope.credential = data.credential; + scope.cloud_credential = data.cloud_credential; + scope.forks = data.forks; + scope.limit = data.limit; + scope.verbosity = data.verbosity; + scope.job_tags = data.job_tags; + + // In the case that the job is already completed, or an error already happened, + // populate scope.job_status info + scope.job_status.status = (data.status === 'waiting' || data.status === 'new') ? 'pending' : data.status; + scope.job_status.started = data.started; + scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : ""; + scope.job_status.finished = data.finished; + scope.job_status.explanation = data.job_explanation; + + if (data.started && data.finished) { + scope.job_status.elapsed = GetElapsed({ + start: data.started, + end: data.finished + }); + } + else { + scope.job_status.elapsed = '00:00:00'; + } + if ($rootScope.myInterval) { + window.clearInterval($rootScope.myInterval); + } + scope.setSearchAll('host'); + scope.$emit('LoadJobDetails', data.related.job_events); + scope.$emit('GetCredentialNames', data); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve job: ' + $routeParams.id + '. GET returned: ' + status }); + }); + }); + + if (scope.removeRefreshCompleted) { + scope.removeRefreshCompleted(); + } + scope.removeRefreshCompleted = scope.$on('RefreshCompleted', function() { + refresh_count++; + if (refresh_count === 1) { + // First time. User just loaded page. + scope.$emit('LoadJob'); + } + else { + // Check if we need to redraw the group + setTimeout(function() { DrawGraph({ scope: scope, resize: true }); }, 500); + } + }); + + scope.adjustSize = function() { + var height, ww = $(window).width(); + if (ww < 1240) { + $('#job-summary-container').hide(); + $('#job-detail-container').css({ "width": "100%", "padding-right": "15px" }); + $('#summary-button').show(); + } + else { + $('.overlay').hide(); + $('#summary-button').hide(); + $('#hide-summary-button').hide(); + $('#job-detail-container').css({ "width": "58.33333333%", "padding-right": "7px" }); + $('#job-summary-container .job_well').css({ + 'box-shadow': 'none', + 'height': 'auto' + }); + $('#job-summary-container').css({ + "width": "41.66666667%", + "padding-left": "7px", + "padding-right": "15px", + "z-index": 0 + }); + setTimeout(function() { $('#job-summary-container .job_well').height($('#job-detail-container').height() - 18); }, 500); + $('#job-summary-container').show(); + } + // Detail table height adjusting. First, put page height back to 'normal'. + $('#plays-table-detail').height(150); + $('#plays-table-detail').mCustomScrollbar("update"); + $('#tasks-table-detail').height(150); + $('#tasks-table-detail').mCustomScrollbar("update"); + $('#hosts-table-detail').height(150); + $('#hosts-table-detail').mCustomScrollbar("update"); + height = $('#wrap').height() - $('.site-footer').outerHeight() - $('.main-container').height(); + if (height > 15) { + // there's a bunch of white space at the bottom, let's use it + $('#plays-table-detail').height(150 + (height / 3)); + $('#plays-table-detail').mCustomScrollbar("update"); + $('#tasks-table-detail').height(150 + (height / 3)); + $('#tasks-table-detail').mCustomScrollbar("update"); + $('#hosts-table-detail').height(150 + (height / 3)); + $('#hosts-table-detail').mCustomScrollbar("update"); + } + // Summary table height adjusting. + height = ($('#job-detail-container').height() / 2) - $('#hosts-summary-section .header').outerHeight() - + $('#hosts-summary-section .table-header').outerHeight() - + $('#summary-search-section').outerHeight() - 20; + $('#hosts-summary-table').height(height); + $('#hosts-summary-table').mCustomScrollbar("update"); + scope.$emit('RefreshCompleted'); + }; + + setTimeout(function() { scope.adjustSize(); }, 500); + + // Use debounce for the underscore library to adjust after user resizes window. + $(window).resize(_.debounce(function(){ + scope.adjustSize(); + }, 500)); + + scope.setSearchAll = function(search) { + if (search === 'host') { + scope.search_all_label = 'Host'; + scope.searchAllDisabled = false; + scope.search_all_placeholder = 'Search all by host name'; + } + else { + scope.search_all_label = 'Failures'; + scope.search_all_placeholder = 'Show failed events'; + scope.searchAllDisabled = true; + scope.search_all_placeholder = ''; + } + }; + + scope.selectPlay = function(id) { + SelectPlay({ + scope: scope, + id: id + }); + }; + + scope.selectTask = function(id) { + SelectTask({ + scope: scope, + id: id + }); + }; + + scope.toggleSummary = function(hide) { + var docw, doch, height = $('#job-detail-container').height(), slide_width; + if (!hide) { + docw = $(window).width(); + doch = $(window).height(); + slide_width = (docw < 840) ? '100%' : '80%'; + $('#summary-button').hide(); + $('.overlay').css({ + width: $(document).width(), + height: $(document).height() + }).show(); + + // Adjust the summary table height + $('#job-summary-container .job_well').height(height - 18).css({ + 'box-shadow': '-3px 3px 5px 0 #ccc' + }); + height = Math.floor($('#job-detail-container').height() * 0.5) - + $('#hosts-summary-section .header').outerHeight() - + $('#hosts-summary-section .table-header').outerHeight() - + $('#hide-summary-button').outerHeight() - + $('#summary-search-section').outerHeight() - + $('#hosts-summary-section .header').outerHeight() - + $('#hosts-summary-section .legend').outerHeight(); + $('#hosts-summary-table').height(height - 50); + $('#hosts-summary-table').mCustomScrollbar("update"); + + $('#hide-summary-button').show(); + + $('#job-summary-container').css({ + top: 0, + right: 0, + width: slide_width, + 'z-index': 2000, + 'padding-right': '15px', + 'padding-left': '15px' + }).show('slide', {'direction': 'right'}); + + setTimeout(function() { DrawGraph({ scope: scope, resize: true }); }, 500); + } + else { + $('.overlay').hide(); + $('#summary-button').show(); + $('#job-summary-container').hide('slide', {'direction': 'right'}); + } + }; + + scope.objectIsEmpty = function(obj) { + if (angular.isObject(obj)) { + return (Object.keys(obj).length > 0) ? false : true; + } + return true; + }; + + scope.HostDetailOnTotalScroll = _.debounce(function() { + // Called when user scrolls down (or forward in time). Using _.debounce + var url, mcs = arguments[0]; + scope.$apply(function() { + if (!scope.auto_scroll && scope.activeTask && scope.hostResults.length) { + scope.auto_scroll = true; + url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask + '&'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'host__name__gt=' + scope.hostResults[scope.hostResults.length - 1].name + '&host__isnull=false&page_size=' + (scope.hostTableRows / 3) + '&order_by=host__name'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + data.results.forEach(function(row) { + scope.hostResults.push({ + id: row.id, + status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ), + host_id: row.host, + task_id: row.parent, + name: row.event_data.host, + created: row.created, + msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' ) + }); + if (scope.hostResults.length > scope.hostTableRows) { + scope.hostResults.splice(0,1); + } + }); + if (data.next) { + // there are more rows. move dragger up, letting user know. + setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop - 15) + 'px'}); }, 700); + } + scope.auto_scroll = false; + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + scope.auto_scroll = false; + } + }); + }, 300); + + scope.HostDetailOnTotalScrollBack = _.debounce(function() { + // Called when user scrolls up (or back in time) + var url, mcs = arguments[0]; + scope.$apply(function() { + if (!scope.auto_scroll && scope.activeTask && scope.hostResults.length) { + scope.auto_scroll = true; + url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask + '&'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'host__name__lt=' + scope.hostResults[0].name + '&host__isnull=false&page_size=' + (scope.hostTableRows / 3) + '&order_by=-host__name'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + data.results.forEach(function(row) { + scope.hostResults.unshift({ + id: row.id, + status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ), + host_id: row.host, + task_id: row.parent, + name: row.event_data.host, + created: row.created, + msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' ) + }); + if (scope.hostResults.length > scope.hostTableRows) { + scope.hostResults.pop(); + } + }); + if (data.next) { + // there are more rows. move dragger down, letting user know. + setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop + 15) + 'px' }); }, 700); + } + Wait('stop'); + scope.auto_scroll = false; + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + scope.auto_scroll = false; + } + }); + }, 300); + + scope.HostSummaryOnTotalScroll = function(mcs) { + var url; + if (!scope.auto_scroll && scope.hosts) { + url = GetBasePath('jobs') + job_id + '/job_host_summaries/?'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'host__name__gt=' + scope.hosts[scope.hosts.length - 1].name + '&page_size=' + (scope.hostSummaryTableRows / 3) + '&order_by=host__name'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + setTimeout(function() { + scope.$apply(function() { + data.results.forEach(function(row) { + scope.hosts.push({ + id: row.host, + name: row.summary_fields.host.name, + ok: row.ok, + changed: row.changed, + unreachable: row.dark, + failed: row.failures + }); + if (scope.hosts.length > scope.hostSummaryTableRows) { + scope.hosts.splice(0,1); + } + }); + if (data.next) { + // there are more rows. move dragger up, letting user know. + setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop - 15) + 'px'}); }, 700); + } + }); + }, 100); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + scope.auto_scroll = false; + } + }; + + scope.HostSummaryOnTotalScrollBack = function(mcs) { + var url; + if (!scope.auto_scroll && scope.hosts) { + url = GetBasePath('jobs') + job_id + '/job_host_summaries/?'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'host__name__lt=' + scope.hosts[0].name + '&page_size=' + (scope.hostSummaryTableRows / 3) + '&order_by=-host__name'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + setTimeout(function() { + scope.$apply(function() { + data.results.forEach(function(row) { + scope.hosts.unshift({ + id: row.host, + name: row.summary_fields.host.name, + ok: row.ok, + changed: row.changed, + unreachable: row.dark, + failed: row.failures + }); + if (scope.hosts.length > scope.hostSummaryTableRows) { + scope.hosts.pop(); + } + }); + if (data.next) { + // there are more rows. move dragger down, letting user know. + setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop + 15) + 'px' }); }, 700); + } + }); + }, 100); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + scope.auto_scroll = false; + } + }; + + scope.searchAllByHost = function() { + var nxtPlay; + if (scope.search_all_hosts_name) { + FilterAllByHostName({ + scope: scope, + host: scope.search_all_hosts_name + }); + scope.searchAllHostsEnabled = false; + } + else { + scope.search_all_tasks = []; + scope.search_all_plays = []; + scope.searchAllHostsEnabled = true; + nxtPlay = scope.plays[scope.plays.length - 1].id; + SelectPlay({ + scope: scope, + id: nxtPlay + }); + ReloadHostSummaryList({ + scope: scope + }); + } + }; + + scope.allHostNameKeyPress = function(e) { + if (e.keyCode === 13) { + scope.searchAllByHost(); + } + }; + + scope.filterByStatus = function(choice) { + var key, keys, nxtPlay; + if (choice === 'Failed') { + scope.searchAllStatus = 'failed'; + for(key in scope.plays) { + if (scope.plays[key].status === 'failed') { + nxtPlay = key; + } + } + } + else { + scope.searchAllStatus = ''; + keys = Object.keys(scope.plays); + nxtPlay = (keys.length > 0) ? keys[keys.length - 1] : null; + } + SelectPlay({ + scope: scope, + id: nxtPlay + }); + ReloadHostSummaryList({ + scope: scope + }); + //setTimeout(function() { + // SelectPlay({ scope: scope, id: scope.activePlay }); + //}, 2000); + }; + + scope.viewEvent = function(event_id) { + $log.debug(event_id); + }; + +} + +JobDetailController.$inject = [ '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', + 'Wait', 'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost', 'FilterAllByHostName', 'DrawGraph', + 'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished' +]; diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index 716e1952d3..7e10f69c85 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -92,7 +92,6 @@ function JobsListController ($scope, $compile, $routeParams, ClearScope, Breadcr var event; listCount=0; if (event_queue.length > 0) { - //console.log('found queued events'); event = event_queue[0]; processEvent(event); event_queue.splice(0,1); @@ -107,9 +106,6 @@ function JobsListController ($scope, $compile, $routeParams, ClearScope, Breadcr } }); } - //else { - //console.log('no more events'); - //} }); LoadBreadCrumbs(); diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 614628bd41..88c9691cba 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -39,83 +39,61 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) -.factory('DigestEvents', ['$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', 'SelectPlay', 'SelectTask', +.factory('ProcessEventQueue', ['$log', 'DigestEvent', 'JobIsFinished', function ($log, DigestEvent, JobIsFinished) { + return function(params) { + var scope = params.scope, + eventQueue = params.eventQueue, + event; + function runTheQ() { + while (eventQueue.length > 0) { + event = eventQueue.pop(); + $log.debug('read event: ' + event.id); + DigestEvent({ scope: scope, event: event }); + } + if (!JobIsFinished(scope) && !scope.haltEventQueue) { + setTimeout( function() { + runTheQ(); + }, 300); + } + } + runTheQ(); + }; +}]) + +.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', 'SelectPlay', 'SelectTask', 'GetHostCount', 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', -function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, +function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, UpdateTaskStatus, DrawGraph, LoadHostSummary, JobIsFinished) { return function(params) { var scope = params.scope, - queue = params.queue, - lastEventId = params.lastEventId; + event = params.event, + hostCount; - function popEvent() { - $log.debug('queue length: ' + queue.length); - if (queue.length > 0 && queue.length < 500) { - var event = queue.splice(0,1); - if (event[0].id > lastEventId) { - $log.debug('processing event: ' + event[0].id); - scope.$emit('ProcessEvent', event[0]); - } - } - else if (queue.length > 500) { - // if we get too far behind, clear the queue and refresh - queue = []; - scope.emit('LoadJob'); - } - } - - if (scope.removeGetNextEvent) { - scope.removeGetNextEvent(); - } - scope.removeGetNextEvent = scope.$on('GetNextEvent', function() { - if (scope.myInterval) { - window.clearInterval(scope.myInterval); - } - popEvent(); - if (!JobIsFinished(scope)) { - scope.myInterval = window.setInterval(function() { - popEvent(); - }, 1000); - } - }); - - if (scope.removeProcessEvent) { - scope.removeProcessEvent(); - } - scope.removeProcessEvent = scope.$on('ProcessEvent', function(e, event) { - var hostCount; - $log.debug('handling event: ' + event.id); - if (event.event === 'playbook_on_start') { + switch (event.event) { + case 'playbook_on_start': if (!JobIsFinished(scope)) { scope.job_status.started = event.created; scope.job_status.status = 'running'; } - scope.$emit('GetNextEvent'); - } + break; - if (event.event === 'playbook_on_play_start') { + case 'playbook_on_play_start': scope.plays[event.id] = { id: event.id, name: event.play, created: event.created, - status: (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'none', + status: (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful', elapsed: '00:00:00' }; - if (scope.plays.length > 1) { - DrawGraph({ scope: scope, resize: false }); - } - SelectPlay({ - scope: scope, - id: event.id - }); - } - if (event.event === 'playbook_on_setup') { + scope.tasks = {}; + scope.hostResults = []; + scope.hostResultsMap = {}; + scope.activePlay = event.id; + break; + + case 'playbook_on_setup': if (scope.activePlay === event.parent) { - hostCount = GetHostCount({ - scope: scope, - play_id: event.parent - }); scope.tasks[event.id] = { id: event.id, play_id: event.parent, @@ -123,7 +101,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), created: event.created, modified: event.modified, - hostCount: hostCount, + hostCount: 0, reportedHosts: 0, successfulCount: 0, failedCount: 0, @@ -134,10 +112,9 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changedStyle: { display: 'none' }, skippedStyle: { display: 'none' } }; - SelectTask({ - scope: scope, - id: event.id - }); + scope.hostResults = []; + scope.hostResultsMap = {}; + scope.activeTask = event.id; } UpdatePlayStatus({ scope: scope, @@ -146,25 +123,11 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changed: event.changed, modified: event.modified }); - } - if (event.event === 'playbook_on_no_hosts_matched') { - UpdatePlayStatus({ - scope: scope, - play_id: event.parent, - failed: true, - changed: false, - modified: event.modified, - status_text: 'failed- no hosts matched' - }); - scope.$emit('GetNextEvent'); - } - if (event.event === 'playbook_on_task_start') { - if (scope.activePlay === event.parent) { - hostCount = GetHostCount({ - scope: scope, - play_id: event.parent - }); + break; + case 'playbook_on_task_start': + if (scope.activePlay === event.parent) { + hostCount = GetHostCount({ scope: scope }); scope.tasks[event.id] = { id: event.id, name: event.task, @@ -184,10 +147,9 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changedStyle: { display: 'none' }, skippedStyle: { display: 'none' } }; - SelectTask({ - scope: scope, - id: event.id - }); + scope.hostResults = []; + scope.hostResultsMap = {}; + scope.activeTask = event.id; } if (event.role) { scope.hasRoles = true; @@ -199,37 +161,64 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se changed: event.changed, modified: event.modified }); - } + break; - if (event.event === 'runner_on_unreachable') { + case 'runner_on_ok': + case 'runner_on_async_ok': + UpdateHostStatus({ + scope: scope, + name: event.event_data.host, + host_id: event.host, + task_id: event.parent, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + id: event.id, + created: event.created, + modified: event.modified, + message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' + }); + break; + + case 'playbook_on_no_hosts_matched': + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: true, + changed: false, + modified: event.modified, + status_text: 'failed- no hosts matched' + }); + break; + + case 'runner_on_unreachable': UpdateHostStatus({ scope: scope, name: event.event_data.host, host_id: event.host, task_id: event.parent, status: 'unreachable', - event_id: event.id, + id: event.id, created: event.created, modified: event.modified, message: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' ) }); - scope.$emit('GetNextEvent'); - } - if (event.event === 'runner_on_error' || event.event === 'runner_on_async_failed') { + break; + + case 'runner_on_error': + case 'runner_on_async_failed': UpdateHostStatus({ scope: scope, name: event.event_data.host, host_id: event.host, task_id: event.parent, status: 'failed', - event_id: event.id, + id: event.id, created: event.created, modified: event.modified, message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' }); - scope.$emit('GetNextEvent'); - } - if (event.event === 'runner_on_no_hosts') { + break; + + case 'runner_on_no_hosts': UpdateTaskStatus({ scope: scope, failed: event.failed, @@ -238,37 +227,23 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se modified: event.modified, no_hosts: true }); - scope.$emit('GetNextEvent'); - } - if (event.event === 'runner_on_skipped') { + break; + + case 'runner_on_skipped': UpdateHostStatus({ scope: scope, name: event.event_data.host, host_id: event.host, task_id: event.parent, status: 'skipped', - event_id: event.id, + id: event.id, created: event.created, modified: event.modified, message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' }); - scope.$emit('GetNextEvent'); - } - if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok') { - UpdateHostStatus({ - scope: scope, - name: event.event_data.host, - host_id: event.host, - task_id: event.parent, - status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), - event_id: event.id, - created: event.created, - modified: event.modified, - message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' - }); - scope.$emit('GetNextEvent'); - } - if (event.event === 'playbook_on_stats') { + break; + + case 'playbook_on_stats': scope.job_status.finished = event.modified; scope.job_status.elapsed = GetElapsed({ start: scope.job_status.started, @@ -279,11 +254,8 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se scope.host_summary = {}; LoadHostSummary({ scope: scope, data: event.event_data }); DrawGraph({ scope: scope, resize: true }); - } - }); - - scope.$emit('GetNextEvent'); // Start checking the queue - + break; + } }; }]) @@ -455,7 +427,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se var scope = params.scope, status = params.status, // successful, changed, unreachable, failed, skipped name = params.name, - event_id = params.event_id, + event_id = params.id, host_id = params.host_id, task_id = params.task_id, modified = params.modified, @@ -541,8 +513,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se msg = params.message, play_id, first; - if (scope.activeTask === task_id && !scope.hostResultsMap[host_id]) { - // the event applies to the currently selected task + if (!scope.hostResultsMap[host_id]) { scope.hostResults.push({ id: event_id, status: status, @@ -633,8 +604,9 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se return function(params) { var scope = params.scope, id = params.id, - callback = params.callback; - + callback = params.callback, + clear; + clear = (scope.activePlay === id) ? false : true; //are we moving to a new play? if (scope.plays[scope.activePlay]) { scope.plays[scope.activePlay].playActiveClass = ''; } @@ -645,7 +617,8 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se LoadTasks({ scope: scope, - callback: callback + callback: callback, + clear: clear }); }; }]) @@ -654,6 +627,7 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se return function(params) { var scope = params.scope, callback = params.callback, + clear = params.clear, url, tIds, lastId; if (scope.activePlay) { @@ -667,7 +641,9 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se Rest.setUrl(url); Rest.get() .success(function(data) { - scope.tasks = {}; + if (clear) { + scope.tasks = {}; + } data.forEach(function(event, idx) { var end, elapsed; if ((!scope.searchAllStatus) || (scope.searchAllStatus === 'failed' && event.failed) && @@ -749,12 +725,13 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se return function(params) { var scope = params.scope, id = params.id, - callback = params.callback; + callback = params.callback, + clear; + clear = (scope.activeTask === id) ? false : true; if (scope.activeTask && scope.tasks[scope.activeTask]) { scope.tasks[scope.activeTask].taskActiveClass = ''; } - if (id) { scope.tasks[id].taskActiveClass = 'active'; scope.activeTaskName = scope.tasks[id].name; @@ -765,11 +742,13 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se setTimeout( function() { scope.auto_scroll = true; $('#tasks-table-detail').mCustomScrollbar("scrollTo", "bottom"); - }, 700); + + }, 1500); LoadHosts({ scope: scope, - callback: callback + callback: callback, + clear: clear }); }; }]) @@ -779,9 +758,12 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se return function(params) { var scope = params.scope, callback = params.callback, + clear = params.clear, url; - scope.hostResults = []; - scope.hostResultsMap = {}; + if (clear) { + scope.hostResults = []; + scope.hostResultsMap = {}; + } if (scope.activeTask) { // If we have a selected task, then get the list of hosts url = scope.job.related.job_events + '?parent=' + scope.activeTask + '&'; @@ -807,7 +789,6 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se scope.$emit(callback); } SelectHost({ scope: scope }); - scope.$emit('GetNextEvent'); }) .error(function(data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', @@ -819,7 +800,6 @@ function($log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, Se scope.$emit(callback); } SelectHost({ scope: scope }); - scope.$emit('GetNextEvent'); } }; }]) diff --git a/awx/ui/static/js/helpers/JobDetail.js.old b/awx/ui/static/js/helpers/JobDetail.js.old new file mode 100644 index 0000000000..35c935fe46 --- /dev/null +++ b/awx/ui/static/js/helpers/JobDetail.js.old @@ -0,0 +1,1017 @@ +/************************************ + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * JobDetail.js + * + * Helper moduler for JobDetails controller + * + */ + +/* + # Playbook events will be structured to form the following hierarchy: + # - playbook_on_start (once for each playbook file) + # - playbook_on_vars_prompt (for each play, but before play starts, we + # currently don't handle responding to these prompts) + # - playbook_on_play_start (once for each play) + # - playbook_on_import_for_host + # - playbook_on_not_import_for_host + # - playbook_on_no_hosts_matched + # - playbook_on_no_hosts_remaining + # - playbook_on_setup + # - runner_on* + # - playbook_on_task_start (once for each task within a play) + # - runner_on_failed + # - runner_on_ok + # - runner_on_error + # - runner_on_skipped + # - runner_on_unreachable + # - runner_on_no_hosts + # - runner_on_async_poll + # - runner_on_async_ok + # - runner_on_async_failed + # - runner_on_file_diff + # - playbook_on_notify (once for each notification from the play) + # - playbook_on_stats + +*/ + +'use strict'; + +angular.module('JobDetailHelper', ['Utilities', 'RestServices']) + +.factory('DigestEvents', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult', 'SelectPlay', 'SelectTask', + 'GetHostCount', 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', +function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, + UpdateTaskStatus, DrawGraph, LoadHostSummary, JobIsFinished) { + return function(params) { + + var scope = params.scope, + queue = params.queue, + lastEventId = params.lastEventId; + + function popEvent() { + $log.debug('queue length: ' + queue.length); + if (queue.length > 0) { + var event = queue.splice(0,1); + if (event[0].id > lastEventId) { + $log.debug('processing event: ' + event[0].id); + scope.$emit('ProcessEvent', event[0]); + } + } + } + + if (scope.removeGetNextEvent) { + scope.removeGetNextEvent(); + } + scope.removeGetNextEvent = scope.$on('GetNextEvent', function() { + if ($rootScope.myInterval) { + window.clearInterval(scope.myInterval); + } + if (!JobIsFinished(scope)) { + $rootScope.myInterval = window.setInterval(function() { + popEvent(); + }, 300); + } + }); + + if (scope.removeProcessEvent) { + scope.removeProcessEvent(); + } + scope.removeProcessEvent = scope.$on('ProcessEvent', function(e, event) { + var hostCount; + $log.debug('handling event: ' + event.id); + if (event.event === 'playbook_on_start') { + if (!JobIsFinished(scope)) { + scope.job_status.started = event.created; + scope.job_status.status = 'running'; + } + } + + if (event.event === 'playbook_on_play_start') { + scope.plays[event.id] = { + id: event.id, + name: event.play, + created: event.created, + status: (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'none', + elapsed: '00:00:00' + }; + if (scope.plays.length > 1) { + DrawGraph({ scope: scope, resize: false }); + } + SelectPlay({ + scope: scope, + id: event.id + }); + } + if (event.event === 'playbook_on_setup') { + if (scope.activePlay === event.parent) { + hostCount = GetHostCount({ + scope: scope, + play_id: event.parent + }); + scope.tasks[event.id] = { + id: event.id, + play_id: event.parent, + name: event.event_display, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + created: event.created, + modified: event.modified, + hostCount: hostCount, + reportedHosts: 0, + successfulCount: 0, + failedCount: 0, + changedCount: 0, + skippedCount: 0, + successfulStyle: { display: 'none'}, + failedStyle: { display: 'none' }, + changedStyle: { display: 'none' }, + skippedStyle: { display: 'none' } + }; + SelectTask({ + scope: scope, + id: event.id + }); + } + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: event.failed, + changed: event.changed, + modified: event.modified + }); + } + if (event.event === 'playbook_on_no_hosts_matched') { + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: true, + changed: false, + modified: event.modified, + status_text: 'failed- no hosts matched' + }); + } + if (event.event === 'playbook_on_task_start') { + if (scope.activePlay === event.parent) { + hostCount = GetHostCount({ + scope: scope, + play_id: event.parent + }); + + scope.tasks[event.id] = { + id: event.id, + name: event.task, + play_id: event.parent, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + role: event.role, + created: event.created, + modified: event.modified, + hostCount: hostCount, + reportedHosts: 0, + successfulCount: 0, + failedCount: 0, + changedCount: 0, + skippedCount: 0, + successfulStyle: { display: 'none'}, + failedStyle: { display: 'none' }, + changedStyle: { display: 'none' }, + skippedStyle: { display: 'none' } + }; + SelectTask({ + scope: scope, + id: event.id + }); + } + if (event.role) { + scope.hasRoles = true; + } + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: event.failed, + changed: event.changed, + modified: event.modified + }); + } + + if (event.event === 'runner_on_unreachable') { + UpdateHostStatus({ + scope: scope, + name: event.event_data.host, + host_id: event.host, + task_id: event.parent, + status: 'unreachable', + event_id: event.id, + created: event.created, + modified: event.modified, + message: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' ) + }); + } + if (event.event === 'runner_on_error' || event.event === 'runner_on_async_failed') { + UpdateHostStatus({ + scope: scope, + name: event.event_data.host, + host_id: event.host, + task_id: event.parent, + status: 'failed', + event_id: event.id, + created: event.created, + modified: event.modified, + message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' + }); + } + if (event.event === 'runner_on_no_hosts') { + UpdateTaskStatus({ + scope: scope, + failed: event.failed, + changed: event.changed, + task_id: event.parent, + modified: event.modified, + no_hosts: true + }); + } + if (event.event === 'runner_on_skipped') { + UpdateHostStatus({ + scope: scope, + name: event.event_data.host, + host_id: event.host, + task_id: event.parent, + status: 'skipped', + event_id: event.id, + created: event.created, + modified: event.modified, + message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' + }); + } + if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok') { + UpdateHostStatus({ + scope: scope, + name: event.event_data.host, + host_id: event.host, + task_id: event.parent, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + event_id: event.id, + created: event.created, + modified: event.modified, + message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' + }); + } + if (event.event === 'playbook_on_stats') { + scope.job_status.finished = event.modified; + scope.job_status.elapsed = GetElapsed({ + start: scope.job_status.started, + end: scope.job_status.finished + }); + scope.job_status.status = (event.failed) ? 'failed' : 'successful'; + scope.job_status.status_class = ""; + scope.host_summary = {}; + LoadHostSummary({ scope: scope, data: event.event_data }); + DrawGraph({ scope: scope, resize: true }); + } + }); + + scope.$emit('GetNextEvent'); // Start checking the queue + + }; +}]) + +.factory('JobIsFinished', [ function() { + return function(scope) { + return (scope.job_status.status === 'failed' || scope.job_status.status === 'canceled' || + scope.job_status.status === 'error' || scope.job_status.status === 'successful'); + }; +}]) + +//Get the # of expected hosts for a task by looking at the number +//on the very first task for a play +.factory('GetHostCount', [ 'FindFirstTaskofPlay', function(FindFirstTaskofPlay) { + return function(params) { + var scope = params.scope, + task_id = FindFirstTaskofPlay({ scope: scope }); + if (task_id) { + return scope.tasks[task_id].hostCount; + } + return 0; + }; +}]) + +.factory('FindFirstTaskofPlay', function() { + return function(params) { + var scope = params.scope, + taskIds; + taskIds = Object.keys(scope.tasks); + return (taskIds.length > 0) ? scope.tasks[taskIds[0]].id : null; + }; +}) + +.factory('GetElapsed', [ function() { + return function(params) { + var start = params.start, + end = params.end, + dt1, dt2, sec, hours, min; + dt1 = new Date(start); + dt2 = new Date(end); + if ( dt2.getTime() !== dt1.getTime() ) { + sec = Math.floor( (dt2.getTime() - dt1.getTime()) / 1000 ); + hours = Math.floor(sec / 3600); + sec = sec - (hours * 3600); + if (('' + hours).length < 2) { + hours = ('00' + hours).substr(-2, 2); + } + min = Math.floor(sec / 60); + sec = sec - (min * 60); + min = ('00' + min).substr(-2,2); + sec = ('00' + sec).substr(-2,2); + return hours + ':' + min + ':' + sec; + } + else { + return '00:00:00'; + } + }; +}]) + +.factory('UpdateJobStatus', ['GetElapsed', 'Empty', function(GetElapsed, Empty) { + return function(params) { + var scope = params.scope, + failed = params.failed, + modified = params.modified, + started = params.started; + + if (failed && scope.job_status.status !== 'failed' && scope.job_status.status !== 'error' && + scope.job_status.status !== 'canceled') { + scope.job_status.status = 'failed'; + } + if (!Empty(modified)) { + scope.job_status.finished = modified; + } + if (!Empty(started) && Empty(scope.job_status.started)) { + scope.job_status.started = started; + } + if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) { + scope.job_status.elapsed = GetElapsed({ + start: scope.job_status.started, + end: scope.job_status.finished + }); + } + }; +}]) + +// Update the status of a play +.factory('UpdatePlayStatus', ['GetElapsed', 'UpdateJobStatus', function(GetElapsed, UpdateJobStatus) { + return function(params) { + var scope = params.scope, + failed = params.failed, + changed = params.changed, + id = params.play_id, + modified = params.modified, + no_hosts = params.no_hosts, + status_text = params.status_text, + play = scope.plays[id]; + + if (scope.plays[id]) { + if (failed) { + scope.plays[id].status = 'failed'; + } + else if (play.status !== 'changed' && play.status !== 'failed') { + // once the status becomes 'changed' or 'failed' don't modify it + if (no_hosts) { + scope.plays[id].status = 'no-matching-hosts'; + } + else { + scope.plays[id].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful'; + } + } + scope.plays[id].finished = modified; + scope.plays[id].elapsed = GetElapsed({ + start: play.created, + end: modified + }); + scope.plays[id].status_text = (status_text) ? status_text : scope.plays[id].status; + } + + UpdateJobStatus({ + scope: scope, + failed: null, + modified: modified + }); + }; +}]) + +.factory('UpdateTaskStatus', ['UpdatePlayStatus', 'GetElapsed', function(UpdatePlayStatus, GetElapsed) { + return function(params) { + var scope = params.scope, + failed = params.failed, + changed = params.changed, + id = params.task_id, + modified = params.modified, + no_hosts = params.no_hosts, + task = scope.tasks[id]; + + if (scope.tasks[id]) { + if (no_hosts){ + task.status = 'no-matching-hosts'; + } + else if (failed) { + task.status = 'failed'; + } + else if (task.status !== 'changed' && task.status !== 'failed') { + // once the status becomes 'changed' or 'failed' don't modify it + task.status = (failed) ? 'failed' : (changed) ? 'changed' : 'successful'; + } + task.finished = params.modified; + task.elapsed = GetElapsed({ + start: task.created, + end: modified + }); + + UpdatePlayStatus({ + scope: scope, + failed: failed, + changed: changed, + play_id: task.play_id, + modified: modified, + no_hosts: no_hosts + }); + } + }; +}]) + +// Each time a runner event is received update host summary totals and the parent task +.factory('UpdateHostStatus', ['UpdateTaskStatus', 'AddHostResult', + function(UpdateTaskStatus, AddHostResult) { + return function(params) { + var scope = params.scope, + status = params.status, // successful, changed, unreachable, failed, skipped + name = params.name, + event_id = params.event_id, + host_id = params.host_id, + task_id = params.task_id, + modified = params.modified, + created = params.created, + msg = params.message, + host; + + if (scope.hostsMap[host_id]) { + host = scope.hosts[scope.hostsMap[host_id]]; + host.ok += (status === 'successful') ? 1 : 0; + host.changed += (status === 'changed') ? 1 : 0; + host.unreachable += (status === 'unreachable') ? 1 : 0; + host.failed += (status === 'failed') ? 1 : 0; + } + else { + // Totals for the summary graph + scope.host_summary.total += 1; + scope.host_summary.ok += (status === 'successful') ? 1 : 0; + scope.host_summary.changed += (status === 'changed') ? 1 : 0; + scope.host_summary.unreachable += (status === 'unreachable') ? 1 : 0; + scope.host_summary.failed += (status === 'failed') ? 1 : 0; + + scope.hosts.push({ + id: host_id, + name: name, + ok: (status === 'successful') ? 1 : 0, + changed: (status === 'changed') ? 1 : 0, + unreachable: (status === 'unreachable') ? 1 : 0, + failed: (status === 'failed') ? 1 : 0 + }); + + scope.hosts.sort(function (a, b) { + if (a.name > b.name) + return 1; + if (a.name < b.name) + return -1; + // a must be equal to b + return 0; + }); + + // prune the hosts array and rebuild the map + if (scope.hosts.length > scope.hostSummaryTableRows) { + scope.hosts.pop(); + } + scope.hostsMap = {}; + scope.hosts.forEach(function(host, idx){ + scope.hostsMap[host.id] = idx; + }); + $('#tasks-table-detail').mCustomScrollbar("update"); + } + + UpdateTaskStatus({ + scope: scope, + task_id: task_id, + failed: ((status === 'failed' || status === 'unreachable') ? true :false), + changed: ((status === 'changed') ? true : false), + modified: modified + }); + + AddHostResult({ + scope: scope, + task_id: task_id, + host_id: host_id, + event_id: event_id, + status: status, + name: name, + created: created, + message: msg + }); + }; +}]) + +// Add a new host result +.factory('AddHostResult', ['FindFirstTaskofPlay', 'SetTaskStyles', function(FindFirstTaskofPlay, SetTaskStyles) { + return function(params) { + var scope = params.scope, + task_id = params.task_id, + host_id = params.host_id, + event_id = params.event_id, + status = params.status, + created = params.created, + name = params.name, + msg = params.message, + play_id, first; + + if (scope.activeTask === task_id && !scope.hostResultsMap[host_id]) { + // the event applies to the currently selected task + scope.hostResults.push({ + id: event_id, + status: status, + host_id: host_id, + task_id: task_id, + name: name, + created: created, + msg: msg + }); + scope.hostResults.sort(function(a,b) { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + // Keep the list pruned to a limited # of hosts + if (scope.hostResults.length > scope.hostTableRows) { + scope.hostResults.splice(0,1); + } + // Refresh the map + scope.hostResultsMap = {}; + scope.hostResults.forEach(function(result, idx) { + scope.hostResultsMap[result.id] = idx; + }); + } + + // update the task + if (scope.tasks[task_id]) { + play_id = scope.tasks[task_id].play_id; + + first = FindFirstTaskofPlay({ + scope: scope, + play_id: play_id + }); + if (task_id === first) { + scope.tasks[task_id].hostCount += 1; + } + scope.tasks[task_id].reportedHosts += 1; + scope.tasks[task_id].failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0; + scope.tasks[task_id].changedCount += (status === 'changed') ? 1 : 0; + scope.tasks[task_id].successfulCount += (status === 'successful') ? 1 : 0; + scope.tasks[task_id].skippedCount += (status === 'skipped') ? 1 : 0; + SetTaskStyles({ + scope: scope, + task_id: task_id + }); + } + }; +}]) + +.factory('SetTaskStyles', [ function() { + return function(params) { + var task_id = params.task_id, + scope = params.scope, + diff; + scope.tasks[task_id].failedPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].failedCount / scope.tasks[task_id].hostCount))) : 0; + scope.tasks[task_id].changedPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].changedCount / scope.tasks[task_id].hostCount))) : 0; + scope.tasks[task_id].skippedPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].skippedCount / scope.tasks[task_id].hostCount))) : 0; + scope.tasks[task_id].successfulPct = (scope.tasks[task_id].hostCount > 0) ? Math.ceil((100 * (scope.tasks[task_id].successfulCount / scope.tasks[task_id].hostCount))) : 0; + + diff = (scope.tasks[task_id].failedPct + scope.tasks[task_id].changedPct + scope.tasks[task_id].skippedPct + scope.tasks[task_id].successfulPct) - 100; + if (diff > 0) { + if (scope.tasks[task_id].failedPct > diff) { + scope.tasks[task_id].failedPct = scope.tasks[task_id].failedPct - diff; + } + else if (scope.tasks[task_id].changedPct > diff) { + scope.tasks[task_id].changedPct = scope.tasks[task_id].changedPct - diff; + } + else if (scope.tasks[task_id].skippedPct > diff) { + scope.tasks[task_id].skippedPct = scope.tasks[task_id].skippedPct - diff; + } + else if (scope.tasks[task_id].successfulPct > diff) { + scope.tasks[task_id].successfulPct = scope.tasks[task_id].successfulPct - diff; + } + } + scope.tasks[task_id].successfulStyle = (scope.tasks[task_id].successfulPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].successfulPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].changedStyle = (scope.tasks[task_id].changedPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].changedPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].skippedStyle = (scope.tasks[task_id].skippedPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].skippedPct + "%" } : { 'display': 'none' }; + scope.tasks[task_id].failedStyle = (scope.tasks[task_id].failedPct > 0) ? { 'display': 'inline-block', 'width': scope.tasks[task_id].failedPct + "%" } : { 'display': 'none' }; + }; +}]) + +// Call SelectPlay whenever the the activePlay needs to change +.factory('SelectPlay', ['SelectTask', 'LoadTasks', function(SelectTask, LoadTasks) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback; + + if (scope.plays[scope.activePlay]) { + scope.plays[scope.activePlay].playActiveClass = ''; + } + if (id) { + scope.plays[id].playActiveClass = 'active'; + } + scope.activePlay = id; + + LoadTasks({ + scope: scope, + callback: callback + }); + }; +}]) + +.factory('LoadTasks', ['Rest', 'ProcessErrors', 'GetElapsed', 'SelectTask', 'SetTaskStyles', function(Rest, ProcessErrors, GetElapsed, SelectTask, SetTaskStyles) { + return function(params) { + var scope = params.scope, + callback = params.callback, + url, tIds, lastId; + + if (scope.activePlay) { + url = scope.job.url + 'job_tasks/?event_id=' + scope.activePlay; + // job_tasks seems to ignore all query predicates other than event_id + //+ '&'; + //url += (scope.search_all_plays.length > 0) ? 'event_id__in=' + scope.search_all_plays.join() + '&' : ''; + //url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + //url += 'order_by=id'; + + Rest.setUrl(url); + Rest.get() + .success(function(data) { + scope.tasks = {}; + data.forEach(function(event, idx) { + var end, elapsed; + if ((!scope.searchAllStatus) || (scope.searchAllStatus === 'failed' && event.failed) && + ((scope.search_all_tasks.length === 0) || (scope.searchAllTasks.indexOf(event.id)))) { + + if (idx < data.length - 1) { + // end date = starting date of the next event + end = data[idx + 1].created; + } + else { + // no next event (task), get the end time of the play + end = scope.plays[scope.activePlay].finished; + } + + if (end) { + elapsed = GetElapsed({ + start: event.created, + end: end + }); + } + else { + elapsed = '00:00:00'; + } + + scope.tasks[event.id] = { + id: event.id, + play_id: scope.activePlay, + name: event.name, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + created: event.created, + modified: event.modified, + finished: end, + elapsed: elapsed, + hostCount: event.host_count, // hostCount, + reportedHosts: event.reported_hosts, + successfulCount: event.successful_count, + failedCount: event.failed_count, + changedCount: event.changed_count, + skippedCount: event.skipped_count, + taskActiveClass: '' + }; + + SetTaskStyles({ + scope: scope, + task_id: event.id + }); + } + }); + + // set the active task + tIds = Object.keys(scope.tasks); + lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; + SelectTask({ + scope: scope, + id: lastId, + callback: callback + }); + }) + .error(function(data) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + // set the active task + tIds = Object.keys(scope.tasks); + lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; + SelectTask({ + scope: scope, + id: lastId, + callback: callback + }); + } + }; +}]) + +// Call SelectTask whenever the activeTask needs to change +.factory('SelectTask', ['LoadHosts', function(LoadHosts) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback; + + if (scope.activeTask && scope.tasks[scope.activeTask]) { + scope.tasks[scope.activeTask].taskActiveClass = ''; + } + + if (id) { + scope.tasks[id].taskActiveClass = 'active'; + scope.activeTaskName = scope.tasks[id].name; + } + scope.activeTask = id; + + $('#tasks-table-detail').mCustomScrollbar("update"); + setTimeout( function() { + scope.auto_scroll = true; + $('#tasks-table-detail').mCustomScrollbar("scrollTo", "bottom"); + + }, 1500); + LoadHosts({ + scope: scope, + callback: callback + }); + }; +}]) + +// Refresh the list of hosts +.factory('LoadHosts', ['Rest', 'ProcessErrors', 'SelectHost', function(Rest, ProcessErrors, SelectHost) { + return function(params) { + var scope = params.scope, + callback = params.callback, + url; + scope.hostResults = []; + scope.hostResultsMap = {}; + if (scope.activeTask) { + // If we have a selected task, then get the list of hosts + url = scope.job.related.job_events + '?parent=' + scope.activeTask + '&'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&' : ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'host__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + data.results.forEach(function(event) { + scope.hostResults.push({ + id: event.id, + status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ), + host_id: event.host, + task_id: event.parent, + name: event.event_data.host, + created: event.created, + msg: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' ) + }); + scope.hostResultsMap[event.id] = scope.hostResults.length - 1; + }); + if (callback) { + scope.$emit(callback); + } + SelectHost({ scope: scope }); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + if (callback) { + scope.$emit(callback); + } + SelectHost({ scope: scope }); + } + }; +}]) + +.factory('SelectHost', [ function() { + return function(params) { + var scope = params.scope; + $('#tasks-table-detail').mCustomScrollbar("update"); + setTimeout( function() { + scope.auto_scroll = true; + $('#hosts-table-detail').mCustomScrollbar("scrollTo", "bottom"); + }, 700); + }; +}]) + +// Refresh the list of hosts in the hosts summary section +.factory('ReloadHostSummaryList', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { + return function(params) { + var scope = params.scope, + callback = params.callback, + url; + scope.hosts = []; + scope.hostsMap = {}; + url = scope.job.related.job_host_summaries + '?'; + url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&': ''; + url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; + url += 'page_size=' + scope.hostSummaryTableRows + '&order_by=host__name'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + data.results.forEach(function(event) { + scope.hosts.push({ + id: event.host, + name: event.summary_fields.host.name, + ok: event.ok, + changed: event.changed, + unreachable: event.dark, + failed: event.failures + }); + scope.hostsMap[event.id] = scope.hosts.length - 1; + }); + $('#hosts-summary-table').mCustomScrollbar("update"); + if (callback) { + scope.$emit(callback); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }; +}]) + +.factory('LoadHostSummary', [ function() { + return function(params) { + var scope = params.scope, + data = params.data; + scope.host_summary.ok = Object.keys(data.ok).length; + scope.host_summary.changed = Object.keys(data.changed).length; + scope.host_summary.unreachable = Object.keys(data.dark).length; + scope.host_summary.failed = Object.keys(data.failures).length; + scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + + scope.host_summary.unreachable + scope.host_summary.failed; + }; +}]) + +.factory('DrawGraph', [ function() { + return function(params) { + var scope = params.scope, + resize = params.resize, + width, height, svg_height, svg_width, svg_radius, svg, graph_data = []; + + // Ready the data + if (scope.host_summary.ok) { + graph_data.push({ + label: 'OK', + value: (scope.host_summary.ok === scope.host_summary.total) ? 1 : scope.host_summary.ok, + color: '#5bb75b' + }); + } + if (scope.host_summary.changed) { + graph_data.push({ + label: 'Changed', + value: (scope.host_summary.changed === scope.host_summary.total) ? 1 : scope.host_summary.changed, + color: '#FF9900' + }); + } + if (scope.host_summary.unreachable) { + graph_data.push({ + label: 'Unreachable', + value: (scope.host_summary.unreachable === scope.host_summary.total) ? 1 : scope.host_summary.unreachable, + color: '#A9A9A9' + }); + } + if (scope.host_summary.failed) { + graph_data.push({ + label: 'Failed', + value: (scope.host_summary.failed === scope.host_summary.total) ? 1 : scope.host_summary.failed, + color: '#DA4D49' + }); + } + + // Adjust the size + width = $('#job-summary-container .job_well').width(); + height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15; + svg_radius = Math.min(width, height); + svg_width = width; + svg_height = height; + if (svg_height > 0 && svg_width > 0) { + if (!resize && $('#graph-section svg').length > 0) { + Donut3D.transition("completedHostsDonut", graph_data, Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4); + } + else { + if ($('#graph-section svg').length > 0) { + $('#graph-section svg').remove(); + } + svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height); + svg.append("g").attr("id","completedHostsDonut"); + Donut3D.draw("completedHostsDonut", graph_data, Math.floor(svg_width / 2), Math.floor(svg_height / 2), Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4); + $('#graph-section .header .legend').show(); + } + } + }; +}]) + +.factory('FilterAllByHostName', ['Rest', 'GetBasePath', 'ProcessErrors', 'SelectPlay', function(Rest, GetBasePath, ProcessErrors, SelectPlay) { + return function(params) { + var scope = params.scope, + host = params.host, + newActivePlay, + url = scope.job.related.job_events + '?event__icontains=runner&host_name__icontains=' + host + '&parent__isnull=false'; + + scope.search_all_tasks = []; + scope.search_all_plays = []; + + if (scope.removeAllPlaysReady) { + scope.removeAllPlaysReady(); + } + scope.removeAllPlaysReady = scope.$on('AllPlaysReady', function() { + if (scope.activePlay) { + setTimeout(function() { + SelectPlay({ + scope: scope, + id: newActivePlay + }); + }, 500); + } + else { + scope.tasks = {}; + scope.hostResults = []; + } + }); + + if (scope.removeAllTasksReady) { + scope.removeAllTasksReady(); + } + scope.removeAllTasksReady = scope.$on('AllTasksReady', function() { + url = scope.job.related.job_events + '?id__in=' + scope.search_all_tasks.join(); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (data.count > 0) { + data.results.forEach(function(row) { + if (row.parent) { + scope.search_all_plays.push(row.parent); + } + }); + if (scope.search_all_plays.length > 0) { + scope.search_all_plays.sort(); + newActivePlay = scope.search_all_plays[scope.search_all_plays.length - 1]; + } + else { + newActivePlay = null; + } + } + else { + scope.search_all_plays.push(0); + } + scope.$emit('AllPlaysReady'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }); + + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (data.count > 0) { + data.results.forEach(function(row) { + if (row.parent) { + scope.search_all_tasks.push(row.parent); + } + }); + if (scope.search_all_tasks.length > 0) { + scope.search_all_tasks.sort(); + } + } + else { + scope.search_all_tasks.push(0); + } + scope.$emit('AllTasksReady'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }; +}]); \ No newline at end of file diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index 594b4f0732..9f0bfbef1b 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -14,7 +14,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job /** * JobsControllerInit({ scope: $scope }); - * + * * Initialize calling scope with all the bits required to support a jobs list * */ @@ -22,10 +22,9 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job function($location, Find, DeleteJob, RelaunchJob, LogViewer) { return function(params) { var scope = params.scope, - parent_scope = params.parent_scope, iterator = (params.iterator) ? params.iterator : scope.iterator, base = $location.path().replace(/^\//, '').split('/')[0]; - + scope.deleteJob = function(id) { DeleteJob({ scope: scope, id: id }); }; @@ -61,10 +60,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job }; scope.refreshJobs = function() { - if (base === 'jobs') { - parent_scope.refreshJobs(); - } - else { + if (base !== 'jobs') { scope.search(iterator); } @@ -169,7 +165,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job '
\n'; $('#inventory-modal-container').empty().append(html); - + scope = generator.inject(form, { mode: 'edit', id: 'form-container', breadCrumbs: false, related: false }); // Set modal dimensions based on viewport width @@ -321,7 +317,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job else if (item.type === "job") { itm.nameHref = ""; } - + if (list.name === 'completed_jobs' || list.name === 'running_jobs') { itm.status_tip = itm.status_label + '. Click for details.'; } @@ -343,7 +339,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job }]) /** - * + * * Called from JobsList controller to load each section or list on the page * */ @@ -390,7 +386,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job url: url, pageSize: pageSize }); - + scope.iterator = list.iterator; if (scope.removePostRefresh) { @@ -422,7 +418,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job .factory('DeleteJob', ['Find', 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Prompt', 'Alert', function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert){ return function(params) { - + var scope = params.scope, id = params.id, action, jobs, job, url, action_label, hdr; From 1c57d7f949525a44d834b4800830f83c5f73fe0c Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Mon, 16 Jun 2014 14:31:35 -0400 Subject: [PATCH 11/11] Job detail page refactor Wired filters up again. Fixed Host Summary section to refresh without first needed to be completely cleared. --- awx/ui/static/js/app.js | 4 - awx/ui/static/js/controllers/JobDetail.js | 12 +- awx/ui/static/js/helpers/JobDetail.js | 150 ++++++++++++++-------- awx/ui/static/lib/ansible/filters.js | 22 ++++ awx/ui/static/partials/job_detail.html | 8 +- 5 files changed, 131 insertions(+), 65 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 078983b04c..a3ef0a3242 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -432,10 +432,6 @@ angular.module('Tower', [ HideStream(); } - if ($rootScope.myInterval) { - window.clearInterval($rootScope.myInterval); - } - // On each navigation request, check that the user is logged in if (!/^\/(login|logout)/.test($location.path())) { // capture most recent URL, excluding login/logout diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 85b057e087..837ade928e 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -652,7 +652,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, */ scope.searchAllByHost = function() { - var nxtPlay; + var keys, nxtPlay; if (scope.search_all_hosts_name) { FilterAllByHostName({ scope: scope, @@ -664,15 +664,17 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, scope.search_all_tasks = []; scope.search_all_plays = []; scope.searchAllHostsEnabled = true; - nxtPlay = scope.plays[scope.plays.length - 1].id; + keys = Object.keys(scope.plays); + nxtPlay = (keys.length > 0) ? keys[keys.length - 1] : null; SelectPlay({ scope: scope, id: nxtPlay }); - ReloadHostSummaryList({ - scope: scope - }); + } + ReloadHostSummaryList({ + scope: scope + }); }; scope.allHostNameKeyPress = function(e) { diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 88c9691cba..c5cd38cc3d 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -605,8 +605,16 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se var scope = params.scope, id = params.id, callback = params.callback, - clear; - clear = (scope.activePlay === id) ? false : true; //are we moving to a new play? + clear = false; + + // Determine if the tasks and hostResults arrays should be initialized + if (scope.search_all_hosts_name || scope.searchAllStatus === 'failed') { + clear = true; + } + else { + clear = (scope.activePlay === id) ? false : true; //are we moving to a new play? + } + if (scope.plays[scope.activePlay]) { scope.plays[scope.activePlay].playActiveClass = ''; } @@ -615,11 +623,16 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se } scope.activePlay = id; - LoadTasks({ - scope: scope, - callback: callback, - clear: clear + setTimeout(function() { + scope.$apply(function() { + LoadTasks({ + scope: scope, + callback: callback, + clear: clear + }); + }); }); + }; }]) @@ -630,6 +643,10 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se clear = params.clear, url, tIds, lastId; + if (clear) { + scope.tasks = {}; + } + if (scope.activePlay) { url = scope.job.url + 'job_tasks/?event_id=' + scope.activePlay; // job_tasks seems to ignore all query predicates other than event_id @@ -641,13 +658,10 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se Rest.setUrl(url); Rest.get() .success(function(data) { - if (clear) { - scope.tasks = {}; - } data.forEach(function(event, idx) { var end, elapsed; if ((!scope.searchAllStatus) || (scope.searchAllStatus === 'failed' && event.failed) && - ((scope.search_all_tasks.length === 0) || (scope.searchAllTasks.indexOf(event.id)))) { + ((!scope.search_all_hosts_name) || (scope.search_all_hosts_name && scope.search_all_tasks.indexOf(event.id)))) { if (idx < data.length - 1) { // end date = starting date of the next event @@ -709,11 +723,15 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se } else { // set the active task - tIds = Object.keys(scope.tasks); - lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; + //tIds = Object.keys(scope.tasks); + //lastId = (tIds.length > 0) ? tIds[tIds.length - 1] : null; + //console.log('selecting task: ' + lastId); + //console.log('tasks: '); + //console.log(scope.tasks); + scope.tasks = {}; SelectTask({ scope: scope, - id: lastId, + id: null, callback: callback }); } @@ -726,8 +744,14 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se var scope = params.scope, id = params.id, callback = params.callback, - clear; - clear = (scope.activeTask === id) ? false : true; + clear=false; + + if (scope.search_all_hosts_name || scope.searchAllStatus === 'failed') { + clear = true; + } + else { + clear = (scope.activeTask === id) ? false : true; + } if (scope.activeTask && scope.tasks[scope.activeTask]) { scope.tasks[scope.activeTask].taskActiveClass = ''; @@ -760,10 +784,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se callback = params.callback, clear = params.clear, url; + if (clear) { scope.hostResults = []; scope.hostResultsMap = {}; } + if (scope.activeTask) { // If we have a selected task, then get the list of hosts url = scope.job.related.job_events + '?parent=' + scope.activeTask + '&'; @@ -796,6 +822,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se }); } else { + scope.hostResults = []; + scope.hostResultsMap = {}; if (callback) { scope.$emit(callback); } @@ -821,25 +849,39 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se var scope = params.scope, callback = params.callback, url; - scope.hosts = []; - scope.hostsMap = {}; + url = scope.job.related.job_host_summaries + '?'; url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&': ''; url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : ''; url += 'page_size=' + scope.hostSummaryTableRows + '&order_by=host__name'; + + if (scope.search_all_hosts_name || scope.searchAllStatus === 'failed') { + // User initiated a search + scope.hosts = []; + scope.hostsMap = {}; + } + Rest.setUrl(url); Rest.get() .success(function(data) { data.results.forEach(function(event) { - scope.hosts.push({ - id: event.host, - name: event.summary_fields.host.name, - ok: event.ok, - changed: event.changed, - unreachable: event.dark, - failed: event.failures - }); - scope.hostsMap[event.id] = scope.hosts.length - 1; + if (scope.hostsMap[event.host]) { + scope.hosts[scope.hostsMap[event.host]].ok = event.ok; + scope.hosts[scope.hostsMap[event.host]].changed = event.changed; + scope.hosts[scope.hostsMap[event.host]].dark = event.dark; + scope.hosts[scope.hostsMap[event.host]].failures = event.failures; + } + else { + scope.hosts.push({ + id: event.host, + name: event.summary_fields.host.name, + ok: event.ok, + changed: event.changed, + unreachable: event.dark, + failed: event.failures + }); + scope.hostsMap[event.host] = scope.hosts.length - 1; + } }); $('#hosts-summary-table').mCustomScrollbar("update"); if (callback) { @@ -957,33 +999,40 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se scope.removeAllTasksReady(); } scope.removeAllTasksReady = scope.$on('AllTasksReady', function() { - url = scope.job.related.job_events + '?id__in=' + scope.search_all_tasks.join(); - Rest.setUrl(url); - Rest.get() - .success(function(data) { - if (data.count > 0) { - data.results.forEach(function(row) { - if (row.parent) { - scope.search_all_plays.push(row.parent); + if (scope.search_all_tasks.length > 0) { + url = scope.job.related.job_events + '?id__in=' + scope.search_all_tasks.join(); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + if (data.count > 0) { + data.results.forEach(function(row) { + if (row.parent) { + scope.search_all_plays.push(row.parent); + } + }); + if (scope.search_all_plays.length > 0) { + scope.search_all_plays.sort(); + newActivePlay = scope.search_all_plays[scope.search_all_plays.length - 1]; + } + else { + newActivePlay = null; } - }); - if (scope.search_all_plays.length > 0) { - scope.search_all_plays.sort(); - newActivePlay = scope.search_all_plays[scope.search_all_plays.length - 1]; } else { - newActivePlay = null; + scope.search_all_plays.push(0); } - } - else { - scope.search_all_plays.push(0); - } - scope.$emit('AllPlaysReady'); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); + scope.$emit('AllPlaysReady'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + else { + newActivePlay = null; + scope.search_all_plays.push(0); + scope.$emit('AllPlaysReady'); + } }); Rest.setUrl(url); @@ -999,9 +1048,6 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Se scope.search_all_tasks.sort(); } } - else { - scope.search_all_tasks.push(0); - } scope.$emit('AllTasksReady'); }) .error(function(data, status) { diff --git a/awx/ui/static/lib/ansible/filters.js b/awx/ui/static/lib/ansible/filters.js index 1261554e8e..0ddb47a41b 100644 --- a/awx/ui/static/lib/ansible/filters.js +++ b/awx/ui/static/lib/ansible/filters.js @@ -43,4 +43,26 @@ angular.module('AWFilters', []) } return input; }; + }]) + + .filter('FilterByField', [ function() { + return function(input, list) { + var fld, key, search = {}, results = {}; + for (fld in list) { + if (list[fld]) { + search[fld] = list[fld]; + } + } + if (Object.keys(search).length > 0) { + for (fld in search) { + for (key in input) { + if (input[key][fld] === search[fld]) { + results[key] = input[key]; + } + } + } + return results; + } + return input; + }; }]); \ No newline at end of file diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index 739cea2894..bd16937a7a 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -52,7 +52,7 @@
-
@@ -65,7 +65,7 @@ {{ play.name }}
-
+
No plays matched
@@ -160,8 +160,8 @@
- - + +