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.
This commit is contained in:
Chris Houseknecht
2014-06-16 00:24:26 -04:00
parent 56ad7d2e9e
commit 9c11145914
7 changed files with 1903 additions and 183 deletions

View File

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

View File

@@ -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'
];

View File

@@ -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 = "<p><i class=\"fa fa-circle successful-hosts-color\"></i> Successful</p>\n" +
"<p><i class=\"fa fa-circle changed-hosts-color\"></i> Changed</p>\n" +
"<p><i class=\"fa fa-circle unreachable-hosts-color\"></i> Unreachable</p>\n" +
"<p><i class=\"fa fa-circle failed-hosts-color\"></i> Failed</p>\n" +
"<div class=\"popover-footer\"><span class=\"key\">esc</span> or click to close</div>\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'
];

View File

@@ -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();

View File

@@ -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');
}
};
}])

File diff suppressed because it is too large Load Diff

View File

@@ -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
'<div id=\"form-container\" style=\"width: 100%;\"></div></div>\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;