awx/awx/ui/static/js/controllers/JobDetail.js
2015-02-04 14:30:32 -05:00

1324 lines
55 KiB
JavaScript

/************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* JobDetail.js
*
*/
/**
* @ngdoc function
* @name controllers.function:JobDetail
* @description This controller's for the Job Detail Page
*/
'use strict';
function JobDetailController ($location, $rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest,
ProcessErrors, SelectPlay, SelectTask, Socket, GetElapsed, DrawGraph, LoadHostSummary, ReloadHostSummaryList, JobIsFinished, SetTaskStyles, DigestEvent,
UpdateDOM, EventViewer, DeleteJob, PlaybookRun, HostEventsViewer, LoadPlays, LoadTasks, LoadHosts, HostsEdit, ParseVariableString) {
ClearScope();
var job_id = $routeParams.id,
event_socket,
scope = $scope,
api_complete = false,
refresh_count = 0,
lastEventId = 0,
verbosity_options,
job_type_options,
checkCount = 0;
scope.plays = [];
scope.hosts = [];
scope.tasks = [];
scope.hostResults = [];
scope.hostResultsMaxRows = 200;
scope.hostSummariesMaxRows = 200;
scope.tasksMaxRows = 200;
scope.playsMaxRows = 200;
// Set the following to true when 'Loading...' message desired
scope.playsLoading = true;
scope.tasksLoading = true;
scope.hostResultsLoading = true;
scope.hostSummariesLoading = true;
// Turn on the 'Waiting...' message until events begin arriving
scope.waiting = true;
scope.liveEventProcessing = true; // true while job is active and live events are arriving
scope.pauseLiveEvents = false; // control play/pause state of event processing
scope.job_status = {};
scope.job_id = job_id;
scope.auto_scroll = false;
scope.searchPlaysEnabled = true;
scope.searchTasksEnabled = true;
scope.searchHostsEnabled = true;
scope.searchHostSummaryEnabled = true;
scope.search_play_status = 'all';
scope.search_task_status = 'all';
scope.search_host_status = 'all';
scope.search_host_summary_status = 'all';
scope.haltEventQueue = false;
scope.processing = false;
scope.lessStatus = 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.jobData = {};
scope.jobData.hostSummaries = {};
verbosity_options = [
{ value: 0, label: 'Default' },
{ value: 1, label: 'Verbose' },
{ value: 3, label: 'Debug' }
];
job_type_options = [
{ value: 'run', label: 'Run' },
{ value: 'check', label: 'Check' }
];
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";
function openSocket() {
event_socket = Socket({
scope: scope,
endpoint: "job_events"
});
event_socket.init();
event_socket.on("job_events-" + job_id, function(data) {
if (api_complete && data.id > lastEventId) {
scope.waiting = false;
data.event = data.event_name;
DigestEvent({ scope: scope, event: data });
}
});
}
openSocket();
$rootScope.checkSocketConnectionInterval = setInterval(function() {
if (event_socket.checkStatus() === 'error' || checkCount > 2) {
// there's an error or we're stuck in a 'connecting' state. attempt to reconnect
$log.debug('job detail page: initializing and restarting socket connections');
event_socket = null;
openSocket();
checkCount = 0;
}
else if (event_socket.checkStatus() === 'connecting') {
checkCount++;
}
else {
checkCount = 0;
}
}, 3000);
if ($rootScope.removeJobStatusChange) {
$rootScope.removeJobStatusChange();
}
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function(e, data) {
// if we receive a status change event for the current job indicating the job
// is finished, stop event queue processing and reload
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
if (data.status === 'failed' || data.status === 'canceled' ||
data.status === 'error' || data.status === 'successful') {
$scope.liveEventProcessing = false;
if ($rootScope.jobDetailInterval) {
window.clearInterval($rootScope.jobDetailInterval);
}
if (!scope.pauseLiveEvents) {
$scope.$emit('LoadJob'); //this is what is used for the refresh
}
}
}
});
if ($rootScope.removeJobSummaryComplete) {
$rootScope.removeJobSummaryComplete();
}
$rootScope.removeJobSummaryComplete = $rootScope.$on('JobSummaryComplete', function() {
// the job host summary should now be available from the API
$log.debug('Trigging reload of job_host_summaries');
scope.$emit('LoadHostSummaries');
});
if (scope.removeInitialLoadComplete) {
scope.removeInitialLoadComplete();
}
scope.removeInitialLoadComplete = scope.$on('InitialLoadComplete', function() {
var url;
Wait('stop');
if (JobIsFinished(scope)) {
scope.liveEventProcessing = false; // signal that event processing is over and endless scroll
scope.pauseLiveEvents = false; // should be enabled
url = scope.job.related.job_events + '?event=playbook_on_stats';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.results.length > 0) {
LoadHostSummary({
scope: scope,
data: data.results[0].event_data
});
}
UpdateDOM({ scope: scope });
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
if ($rootScope.jobDetailInterval) {
window.clearInterval($rootScope.jobDetailInterval);
}
$log.debug('Job completed!');
$log.debug(scope.jobData);
}
else {
api_complete = true; //trigger events to start processing
if ($rootScope.jobDetailInterval) {
window.clearInterval($rootScope.jobDetailInterval);
}
$rootScope.jobDetailInterval = setInterval(function() {
UpdateDOM({ scope: scope });
}, 2000);
}
});
if (scope.removeLoadHostSummaries) {
scope.removeLoadHostSummaries();
}
scope.removeHostSummaries = scope.$on('LoadHostSummaries', function() {
if(scope.job.related){
var url = scope.job.related.job_host_summaries + '?';
url += '&page_size=' + scope.hostSummariesMaxRows + '&order=host_name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.next_host_summaries = data.next;
if (data.results.length > 0) {
// only dump what's in memory when job_host_summaries is available.
scope.jobData.hostSummaries = {};
}
data.results.forEach(function(event) {
var name;
if (event.host_name) {
name = event.host_name;
}
else {
name = "<deleted host>";
}
scope.jobData.hostSummaries[name] = {
id: event.host,
name: name,
ok: event.ok,
changed: event.changed,
unreachable: event.dark,
failed: event.failures,
status: (event.failed) ? 'failed' : 'successful'
};
});
scope.$emit('InitialLoadComplete');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
});
if (scope.removeLoadHosts) {
scope.removeLoadHosts();
}
scope.removeLoadHosts = scope.$on('LoadHosts', function() {
if (scope.activeTask) {
var play = scope.jobData.plays[scope.activePlay],
task = play.tasks[scope.activeTask],
url;
if (play && task) {
url = scope.job.related.job_events + '?parent=' + task.id + '&';
url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order=host_name,counter';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
var idx, event, status, status_text, item, msg;
if (data.results.length > 0) {
lastEventId = data.results[0].id;
}
scope.next_host_results = data.next;
for (idx=data.results.length - 1; idx >= 0; idx--) {
event = data.results[idx];
if (event.event === "runner_on_skipped") {
status = 'skipped';
}
else if (event.event === "runner_on_unreachable") {
status = 'unreachable';
}
else {
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
}
switch(status) {
case "successful":
status_text = 'OK';
break;
case "changed":
status_text = "Changed";
break;
case "failed":
status_text = "Failed";
break;
case "unreachable":
status_text = "Unreachable";
break;
case "skipped":
status_text = "Skipped";
}
if (event.event_data && event.event_data.res) {
item = event.event_data.res.item;
if (typeof item === "object") {
item = JSON.stringify(item);
}
}
msg = '';
if (event.event_data && event.event_data.res) {
if (typeof event.event_data.res === 'object') {
msg = event.event_data.res.msg;
} else {
msg = event.event_data.res;
}
}
if (event.event !== "runner_on_no_hosts") {
task.hostResults[event.id] = {
id: event.id,
status: status,
status_text: status_text,
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
msg: msg,
counter: event.counter,
item: item
};
}
}
scope.$emit('LoadHostSummaries');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
} else {
scope.$emit('LoadHostSummaries');
}
} else {
scope.$emit('LoadHostSummaries');
}
});
if (scope.removeLoadTasks) {
scope.removeLoadTasks();
}
scope.removeLoadTasks = scope.$on('LoadTasks', function() {
if (scope.activePlay) {
var play = scope.jobData.plays[scope.activePlay], url;
if (play) {
url = scope.job.url + 'job_tasks/?event_id=' + play.id;
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.next_tasks = data.next;
if (data.results.length > 0) {
lastEventId = data.results[data.results.length - 1].id;
if (scope.liveEventProcessing) {
scope.activeTask = data.results[data.results.length - 1].id;
}
else {
scope.activeTask = data.results[0].id;
}
scope.selectedTask = scope.activeTask;
}
data.results.forEach(function(event, idx) {
var end, elapsed, status, status_text;
if (play.firstTask === undefined || play.firstTask === null) {
play.firstTask = event.id;
play.hostCount = (event.host_count) ? event.host_count : 0;
}
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
if(scope.jobData.plays[scope.activePlay]){
end = scope.jobData.plays[scope.activePlay].finished;
}
}
if (end) {
elapsed = GetElapsed({
start: event.created,
end: end
});
}
else {
elapsed = '00:00:00';
}
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK';
play.tasks[event.id] = {
id: event.id,
play_id: scope.activePlay,
name: event.name,
status: status,
status_text: status_text,
status_tip: "Event ID: " + event.id + "<br />Status: " + status_text,
created: event.created,
modified: event.modified,
finished: end,
elapsed: elapsed,
hostCount: (event.host_count) ? event.host_count : 0,
reportedHosts: (event.reported_hosts) ? event.reported_hosts : 0,
successfulCount: (event.successful_count) ? event.successful_count : 0,
failedCount: (event.failed_count) ? event.failed_count : 0,
changedCount: (event.changed_count) ? event.changed_count : 0,
skippedCount: (event.skipped_count) ? event.skipped_count : 0,
unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0,
taskActiveClass: '',
hostResults: {}
};
if (play.firstTask !== event.id) {
// this is not the first task
play.tasks[event.id].hostCount = play.tasks[play.firstTask].hostCount;
}
if (play.tasks[event.id].reportedHosts === 0 && play.tasks[event.id].successfulCount === 0 &&
play.tasks[event.id].failedCount === 0 && play.tasks[event.id].changedCount === 0 &&
play.tasks[event.id].skippedCount === 0 && play.tasks[event.id].unreachableCount === 0) {
play.tasks[event.id].status = 'no-matching-hosts';
play.tasks[event.id].status_text = 'No matching hosts';
play.tasks[event.id].status_tip = "Event ID: " + event.id + "<br />Status: No matching hosts";
}
play.taskCount++;
SetTaskStyles({
task: play.tasks[event.id]
});
});
if (scope.activeTask && scope.jobData.plays[scope.activePlay] && scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) {
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = 'active';
}
scope.$emit('LoadHosts');
})
.error(function(data) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
} else {
scope.$emit('LoadHostSummaries');
}
} else {
scope.$emit('LoadHostSummaries');
}
});
if (scope.removeLoadPlays) {
scope.removeLoadPlays();
}
scope.removeLoadPlays = scope.$on('LoadPlays', function(e, events_url) {
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.jobData.plays = {};
var url = scope.job.url + 'job_plays/?order_by=id';
url += '&page_size=' + scope.playsMaxRows + '&order_by=id';
Rest.setUrl(url);
Rest.get()
.success( function(data) {
scope.next_plays = data.next;
if (data.results.length > 0) {
lastEventId = data.results[data.results.length - 1].id;
if (scope.liveEventProcessing) {
scope.activePlay = data.results[data.results.length - 1].id;
}
else {
scope.activePlay = data.results[0].id;
}
scope.selectedPlay = scope.activePlay;
} else {
// if we are here, there are no plays and the job has failed, let the user know they may want to consult stdout
if ( (scope.job_status.status === 'failed' || scope.job_status.status === 'error') &&
(!scope.job_status.explanation)) {
scope.job_status.explanation = "<a href=\"/#/jobs/" + scope.job_id + "/stdout\" target=\"_blank\">View stdout for more detail <i class=\"fa fa-external-link\"></i></a>";
}
}
data.results.forEach(function(event, idx) {
var status, status_text, start, end, elapsed, ok, changed, failed, skipped;
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK';
start = event.started;
if (idx < data.results.length - 1) {
// end date = starting date of the next event
end = data.results[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.jobData.plays[event.id] = {
id: event.id,
name: event.play,
created: start,
finished: end,
status: status,
status_text: status_text,
status_tip: "Event ID: " + event.id + "<br />Status: " + status_text,
elapsed: elapsed,
hostCount: 0,
fistTask: null,
taskCount: 0,
playActiveClass: '',
unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0,
tasks: {}
};
ok = (event.ok_count) ? event.ok_count : 0;
changed = (event.changed_count) ? event.changed_count : 0;
failed = (event.failed_count) ? event.failed_count : 0;
skipped = (event.skipped_count) ? event.skipped_count : 0;
scope.jobData.plays[event.id].hostCount = ok + changed + failed + skipped;
if (scope.jobData.plays[event.id].hostCount > 0 || event.unreachable_count > 0 || scope.job_status.status === 'successful' ||
scope.job_status.status === 'failed' || scope.job_status.status === 'error' || scope.job_status.status === 'canceled') {
// force the play to be on the 'active' list
scope.jobData.plays[event.id].taskCount = 1;
}
if (scope.jobData.plays[event.id].hostCount === 0 && event.unreachable_count === 0) {
scope.jobData.plays[event.id].status = 'no-matching-hosts';
scope.jobData.plays[event.id].status_text = 'No matching hosts';
scope.jobData.plays[event.id].status_tip = "Event ID: " + event.id + "<br />Status: No matching hosts";
}
scope.host_summary.ok += ok;
scope.host_summary.changed += changed;
scope.host_summary.unreachable += (event.unreachable_count) ? event.unreachable_count : 0;
scope.host_summary.failed += failed;
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable +
scope.host_summary.failed;
});
if (scope.activePlay && scope.jobData.plays[scope.activePlay]) {
scope.jobData.plays[scope.activePlay].playActiveClass = 'active';
}
scope.$emit('LoadTasks', 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.cloud_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.removeGetCreatedByNames) {
scope.removeGetCreatedByNames();
}
scope.removeGetCreatedByNames = scope.$on('GetCreatedByNames', function(e, data) {
var url;
data = data.slice(0, data.length-1);
data = data.slice(data.lastIndexOf('/')+1, data.length);
url = GetBasePath('users') + data + '/';
scope.users_url = '/#/users/' + data;
Rest.setUrl(url);
Rest.get()
.success( function(data) {
scope.created_by = data.username;
})
.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');
scope.job_status = {};
scope.playsLoading = true;
scope.tasksLoading = true;
scope.hostResultsLoading = true;
scope.LoadHostSummaries = true;
// Load the job record
Rest.setUrl(GetBasePath('jobs') + job_id + '/');
Rest.get()
.success(function(data) {
var i;
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.credential_url = (data.credential) ? '/#/credentials/' + data.credential : '';
scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : '';
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;
scope.variables = ParseVariableString(data.extra_vars);
for (i=0; i < verbosity_options.length; i++) {
if (verbosity_options[i].value === data.verbosity) {
scope.verbosity = verbosity_options[i].label;
}
}
for (i=0; i < job_type_options.length; i++) {
if (job_type_options[i].value === data.job_type) {
scope.job_type = job_type_options[i].label;
}
}
// 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;
scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : "";
scope.job_status.explanation = data.job_explanation;
if(data.result_traceback) {
scope.job_status.traceback = data.result_traceback.trim().split('\n').join('<br />');
}
if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') {
scope.job_status.finished = data.finished;
scope.liveEventProcessing = false;
scope.pauseLiveEvents = false;
scope.waiting = false;
scope.playsLoading = false;
scope.tasksLoading = false;
scope.hostResultsLoading = false;
scope.hostSummariesLoading = false;
}
else {
scope.job_status.finished = null;
}
if (data.started && data.finished) {
scope.job_status.elapsed = GetElapsed({
start: data.started,
end: data.finished
});
}
else {
scope.job_status.elapsed = '00:00:00';
}
//scope.setSearchAll('host');
scope.$emit('LoadPlays', data.related.job_events);
scope.$emit('GetCreatedByNames', data.related.created_by);
if (!scope.credential_name) {
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 the graph needs to redraw
setTimeout(function() { DrawGraph({ scope: scope, resize: true }); }, 500);
}
});
scope.adjustSize = function() {
var height, ww = $(window).width();
if (ww < 1024) {
$('#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();
}
scope.lessStatus = true; // close the view more status option
// Detail table height adjusting. First, put page height back to 'normal'.
$('#plays-table-detail').height(80);
//$('#plays-table-detail').mCustomScrollbar("update");
$('#tasks-table-detail').height(120);
//$('#tasks-table-detail').mCustomScrollbar("update");
$('#hosts-table-detail').height(150);
//$('#hosts-table-detail').mCustomScrollbar("update");
height = $(window).height() - $('#main-menu-container .navbar').outerHeight() - $('#breadcrumb-container').outerHeight() -
$('#job-detail-container').outerHeight() - $('#job-detail-footer').outerHeight() - 20;
if (height > 15) {
// there's a bunch of white space at the bottom, let's use it
$('#plays-table-detail').height(80 + (height * 0.10));
$('#tasks-table-detail').height(120 + (height * 0.20));
$('#hosts-table-detail').height(150 + (height * 0.70));
}
// 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));
function flashPlayTip() {
setTimeout(function(){
$('#play-help').popover('show');
},500);
setTimeout(function() {
$('#play-help').popover('hide');
}, 5000);
}
scope.selectPlay = function(id) {
if (scope.liveEventProcessing && !scope.pauseLiveEvents) {
scope.pauseLiveEvents = true;
flashPlayTip();
}
SelectPlay({
scope: scope,
id: id
});
};
scope.selectTask = function(id) {
if (scope.liveEventProcessing && !scope.pauseLiveEvents) {
scope.pauseLiveEvents = true;
flashPlayTip();
}
SelectTask({
scope: scope,
id: id
});
};
scope.togglePlayButton = function() {
if (scope.pauseLiveEvents) {
scope.pauseLiveEvents = false;
scope.$emit('LoadJob');
}
};
scope.toggleSummary = function(hide) {
var docw, doch, height = $('#job-detail-container').height(), slide_width;
if (!hide) {
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': 1090,
'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.toggleLessStatus = function() {
if (!scope.lessStatus) {
$('#job-status-form .toggle-show').hide(400);
scope.lessStatus = true;
}
else {
$('#job-status-form .toggle-show').show(400);
scope.lessStatus = false;
}
};
scope.filterPlayStatus = function() {
scope.search_play_status = (scope.search_play_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadPlays({
scope: scope
});
}
};
scope.searchPlays = function() {
if (scope.search_play_name) {
scope.searchPlaysEnabled = false;
}
else {
scope.searchPlaysEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadPlays({
scope: scope
});
}
};
scope.searchPlaysKeyPress = function(e) {
if (e.keyCode === 13) {
scope.searchPlays();
e.stopPropagation();
}
};
scope.searchTasks = function() {
if (scope.search_task_name) {
scope.searchTasksEnabled = false;
}
else {
scope.searchTasksEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadTasks({
scope: scope
});
}
};
scope.searchTasksKeyPress = function(e) {
if (e.keyCode === 13) {
scope.searchTasks();
e.stopPropagation();
}
};
scope.searchHosts = function() {
if (scope.search_host_name) {
scope.searchHostsEnabled = false;
}
else {
scope.searchHostsEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadHosts({
scope: scope
});
}
};
scope.searchHostsKeyPress = function(e) {
if (e.keyCode === 13) {
scope.searchHosts();
e.stopPropagation();
}
};
scope.searchHostSummary = function() {
if (scope.search_host_summary_name) {
scope.searchHostSummaryEnabled = false;
}
else {
scope.searchHostSummaryEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
ReloadHostSummaryList({
scope: scope
});
}
};
scope.searchHostSummaryKeyPress = function(e) {
if (e.keyCode === 13) {
scope.searchHostSummary();
e.stopPropagation();
}
};
scope.filterTaskStatus = function() {
scope.search_task_status = (scope.search_task_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadTasks({
scope: scope
});
}
};
scope.filterHostStatus = function() {
scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadHosts({
scope: scope
});
}
};
scope.filterHostSummaryStatus = function() {
scope.search_host_summary_status = (scope.search_host_summary_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
ReloadHostSummaryList({
scope: scope
});
}
};
scope.viewHostResults = function(id) {
EventViewer({
scope: scope,
url: scope.job.related.job_events,
parent_id: scope.selectedTask,
event_id: id,
index: this.$index,
title: 'Host Event'
});
};
if (scope.removeDeleteFinished) {
scope.removeDeleteFinished();
}
scope.removeDeleteFinished = scope.$on('DeleteFinished', function(e, action) {
Wait('stop');
if (action !== 'cancel') {
Wait('stop');
$location.url('/jobs');
}
});
scope.deleteJob = function() {
DeleteJob({
scope: scope,
id: scope.job.id,
job: scope.job,
callback: 'DeleteFinished'
});
};
scope.relaunchJob = function() {
PlaybookRun({
scope: scope,
id: scope.job.id
});
};
scope.playsScrollDown = function() {
// check for more plays when user scrolls to bottom of play list...
if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_plays) {
$('#playsMoreRows').fadeIn();
scope.playsLoading = true;
Rest.setUrl(scope.next_plays);
Rest.get()
.success( function(data) {
scope.next_plays = data.next;
data.results.forEach(function(event, idx) {
var status, status_text, start, end, elapsed, ok, changed, failed, skipped;
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK';
start = event.started;
if (idx < data.results.length - 1) {
// end date = starting date of the next event
end = data.results[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.push({
id: event.id,
name: event.play,
created: start,
finished: end,
status: status,
status_text: status_text,
status_tip: "Event ID: " + event.id + "<br />Status: " + status_text,
elapsed: elapsed,
hostCount: 0,
fistTask: null,
playActiveClass: '',
unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0,
});
ok = (event.ok_count) ? event.ok_count : 0;
changed = (event.changed_count) ? event.changed_count : 0;
failed = (event.failed_count) ? event.failed_count : 0;
skipped = (event.skipped_count) ? event.skipped_count : 0;
scope.plays[scope.plays.length - 1].hostCount = ok + changed + failed + skipped;
scope.playsLoading = false;
});
$('#playsMoreRows').fadeOut(400);
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + scope.next_plays + '. GET returned: ' + status });
});
}
};
scope.tasksScrollDown = function() {
// check for more tasks when user scrolls to bottom of task list...
if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_tasks) {
$('#tasksMoreRows').fadeIn();
scope.tasksLoading = true;
Rest.setUrl(scope.next_tasks);
Rest.get()
.success(function(data) {
scope.next_tasks = data.next;
data.results.forEach(function(event, idx) {
var end, elapsed, status, status_text;
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
scope.plays.every(function(p, j) {
if (p.id === scope.selectedPlay) {
end = scope.plays[j].finished;
return false;
}
return true;
});
}
if (end) {
elapsed = GetElapsed({
start: event.created,
end: end
});
}
else {
elapsed = '00:00:00';
}
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
status_text = (event.failed) ? 'Failed' : (event.changed) ? 'Changed' : 'OK';
scope.tasks.push({
id: event.id,
play_id: scope.selectedPlay,
name: event.name,
status: status,
status_text: status_text,
status_tip: "Event ID: " + event.id + "<br />Status: " + status_text,
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[scope.tasks.length - 1]
});
});
$('#tasksMoreRows').fadeOut(400);
scope.tasksLoading = false;
})
.error(function(data, status) {
$('#tasksMoreRows').fadeOut(400);
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + scope.next_tasks + '. GET returned: ' + status });
});
}
};
scope.hostResultsScrollDown = function() {
// check for more hosts when user scrolls to bottom of host results list...
if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_host_results) {
$('#hostResultsMoreRows').fadeIn();
scope.hostResultsLoading = true;
Rest.setUrl(scope.next_host_results);
Rest.get()
.success(function(data) {
scope.next_host_results = data.next;
data.results.forEach(function(row) {
var status, status_text, item, msg;
if (row.event === "runner_on_skipped") {
status = 'skipped';
}
else if (row.event === "runner_on_unreachable") {
status = 'unreachable';
}
else {
status = (row.failed) ? 'failed' : (row.changed) ? 'changed' : 'successful';
}
switch(status) {
case "successful":
status_text = 'OK';
break;
case "changed":
status_text = "Changed";
break;
case "failed":
status_text = "Failed";
break;
case "unreachable":
status_text = "Unreachable";
break;
case "skipped":
status_text = "Skipped";
}
if (row.event_data && row.event_data.res) {
item = row.event_data.res.item;
if (typeof item === "object") {
item = JSON.stringify(item);
}
}
msg = '';
if (row.event_data && row.event_data.res) {
if (typeof row.event_data.res === 'object') {
msg = row.event_data.res.msg;
} else {
msg = row.event_data.res;
}
}
scope.hostResults.push({
id: row.id,
status: status,
status_text: status_text,
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 : '',
item: item
});
scope.hostResultsLoading = false;
});
$('#hostResultsMoreRows').fadeOut(400);
})
.error(function(data, status) {
$('#hostResultsMoreRows').fadeOut(400);
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + scope.next_host_results + '. GET returned: ' + status });
});
}
};
scope.hostSummariesScrollDown = function() {
// check for more hosts when user scrolls to bottom of host summaries list...
if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_host_summaries) {
scope.hostSummariesLoading = true;
Rest.setUrl(scope.next_host_summaries);
Rest.get()
.success(function(data) {
scope.next_host_summaries = data.next;
data.results.forEach(function(row) {
var name;
if (row.host_name) {
name = row.host_name;
}
else {
name = "<deleted host>";
}
scope.hosts.push({
id: row.id,
name: name,
ok: row.ok,
changed: row.changed,
unreachable: row.dark,
failed: row.failures
});
});
$('#hostSummariesMoreRows').fadeOut();
scope.hostSummariesLoading = false;
})
.error(function(data, status) {
$('#hostSummariesMoreRows').fadeOut();
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + scope.next_host_summaries + '. GET returned: ' + status });
});
}
};
scope.hostEventsViewer = function(id, name, status) {
HostEventsViewer({
scope: scope,
id: id,
name: name,
url: scope.job.related.job_events,
job_id: scope.job.id,
status: status
});
};
scope.refresh = function(){
$scope.$emit('LoadJob');
};
scope.editHost = function(id) {
HostsEdit({
host_scope: scope,
group_scope: null,
host_id: id,
inventory_id: scope.job.inventory,
mode: 'edit', // 'add' or 'edit'
selected_group_id: null
});
};
}
JobDetailController.$inject = [ '$location', '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath',
'Wait', 'Rest', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'DrawGraph', 'LoadHostSummary', 'ReloadHostSummaryList',
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'EventViewer', 'DeleteJob', 'PlaybookRun', 'HostEventsViewer', 'LoadPlays', 'LoadTasks',
'LoadHosts', 'HostsEdit', 'ParseVariableString'
];