mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 23:51:09 +03:00
Merge pull request #1311 from leigh-johnson/HostEventModal
Refactor HostViewer into job-details/host-event module
This commit is contained in:
commit
1095ecc392
@ -180,7 +180,6 @@ var tower = angular.module('Tower', [
|
||||
'LogViewerStatusDefinition',
|
||||
'StandardOutHelper',
|
||||
'LogViewerOptionsDefinition',
|
||||
'EventViewerHelper',
|
||||
'JobDetailHelper',
|
||||
'SocketIO',
|
||||
'lrInfiniteScroll',
|
||||
@ -211,6 +210,8 @@ var tower = angular.module('Tower', [
|
||||
templateUrl: urlPrefix + 'partials/breadcrumb.html'
|
||||
});
|
||||
|
||||
// route to the details pane of /job/:id/host-event/:eventId if no other child specified
|
||||
$urlRouterProvider.when('/jobs/*/host-event/*', '/jobs/*/host-event/*/details')
|
||||
// $urlRouterProvider.otherwise("/home");
|
||||
$urlRouterProvider.otherwise(function($injector){
|
||||
var $state = $injector.get("$state");
|
||||
|
@ -9,7 +9,6 @@ import './lists';
|
||||
|
||||
import Children from "./helpers/Children";
|
||||
import Credentials from "./helpers/Credentials";
|
||||
import EventViewer from "./helpers/EventViewer";
|
||||
import Events from "./helpers/Events";
|
||||
import Groups from "./helpers/Groups";
|
||||
import Hosts from "./helpers/Hosts";
|
||||
@ -42,7 +41,6 @@ import ActivityStreamHelper from "./helpers/ActivityStream";
|
||||
export
|
||||
{ Children,
|
||||
Credentials,
|
||||
EventViewer,
|
||||
Events,
|
||||
Groups,
|
||||
Hosts,
|
||||
|
@ -1,568 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name helpers.function:EventViewer
|
||||
* @description eventviewerhelper
|
||||
*/
|
||||
|
||||
export default
|
||||
angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition', 'HostsHelper'])
|
||||
|
||||
.factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'Empty', 'EventAddPreFormattedText',
|
||||
function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, Empty, EventAddPreFormattedText) {
|
||||
return function(params) {
|
||||
var parent_scope = params.scope,
|
||||
url = params.url,
|
||||
event_id = params.event_id,
|
||||
parent_id = params.parent_id,
|
||||
title = params.title, //optional
|
||||
scope = parent_scope.$new(true),
|
||||
index = params.index,
|
||||
page,
|
||||
current_event;
|
||||
|
||||
if (scope.removeShowNextEvent) {
|
||||
scope.removeShowNextEvent();
|
||||
}
|
||||
scope.removeShowNextEvent = scope.$on('ShowNextEvent', function(e, data, show_event) {
|
||||
scope.events = data;
|
||||
$('#event-next-spinner').slideUp(200);
|
||||
if (show_event === 'prev') {
|
||||
showEvent(scope.events.length - 1);
|
||||
}
|
||||
else if (show_event === 'next') {
|
||||
showEvent(0);
|
||||
}
|
||||
});
|
||||
|
||||
// show scope.events[idx]
|
||||
function showEvent(idx) {
|
||||
var show_tabs = false, elem, data;
|
||||
|
||||
if (idx > scope.events.length - 1) {
|
||||
GetEvent({
|
||||
scope: scope,
|
||||
url: scope.next_event_set,
|
||||
show_event: 'next'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx < 0) {
|
||||
GetEvent({
|
||||
scope: scope,
|
||||
url: scope.prev_event_set,
|
||||
show_event: 'prev'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
data = scope.events[idx];
|
||||
current_event = idx;
|
||||
|
||||
$('#status-form-container').empty();
|
||||
$('#results-form-container').empty();
|
||||
$('#timing-form-container').empty();
|
||||
$('#stdout-form-container').empty();
|
||||
$('#stderr-form-container').empty();
|
||||
$('#traceback-form-container').empty();
|
||||
$('#json-form-container').empty();
|
||||
$('#eventview-tabs li:eq(1)').hide();
|
||||
$('#eventview-tabs li:eq(2)').hide();
|
||||
$('#eventview-tabs li:eq(3)').hide();
|
||||
$('#eventview-tabs li:eq(4)').hide();
|
||||
$('#eventview-tabs li:eq(5)').hide();
|
||||
$('#eventview-tabs li:eq(6)').hide();
|
||||
|
||||
EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' });
|
||||
|
||||
if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) {
|
||||
show_tabs = true;
|
||||
$('#eventview-tabs li:eq(1)').show();
|
||||
}
|
||||
|
||||
if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) {
|
||||
show_tabs = true;
|
||||
$('#eventview-tabs li:eq(2)').show();
|
||||
}
|
||||
|
||||
if (data.stdout) {
|
||||
show_tabs = true;
|
||||
$('#eventview-tabs li:eq(3)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'stdout-form-container',
|
||||
val: data.stdout
|
||||
});
|
||||
}
|
||||
|
||||
if (data.stderr) {
|
||||
show_tabs = true;
|
||||
$('#eventview-tabs li:eq(4)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'stderr-form-container',
|
||||
val: data.stderr
|
||||
});
|
||||
}
|
||||
|
||||
if (data.traceback) {
|
||||
show_tabs = true;
|
||||
$('#eventview-tabs li:eq(5)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'traceback-form-container',
|
||||
val: data.traceback
|
||||
});
|
||||
}
|
||||
|
||||
show_tabs = true;
|
||||
$('#eventview-tabs li:eq(6)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'json-form-container',
|
||||
val: JSON.stringify(data, null, 2)
|
||||
});
|
||||
|
||||
if (!show_tabs) {
|
||||
$('#eventview-tabs').hide();
|
||||
}
|
||||
|
||||
elem = angular.element(document.getElementById('eventviewer-modal-dialog'));
|
||||
$compile(elem)(scope);
|
||||
}
|
||||
|
||||
function setButtonMargin() {
|
||||
var width = ($('.ui-dialog[aria-describedby="eventviewer-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#events-next-button').outerWidth() - 73;
|
||||
$('#events-next-button').css({'margin-right': width + 'px'});
|
||||
}
|
||||
|
||||
function addSpinner() {
|
||||
var position;
|
||||
if ($('#event-next-spinner').length > 0) {
|
||||
$('#event-next-spinner').remove();
|
||||
}
|
||||
position = $('#events-next-button').position();
|
||||
$('#events-next-button').after('<i class="fa fa-cog fa-spin" id="event-next-spinner" style="display:none; position:absolute; top:' + (position.top + 15) + 'px; left:' + (position.left + 75) + 'px;"></i>');
|
||||
}
|
||||
|
||||
if (scope.removeModalReady) {
|
||||
scope.removeModalReady();
|
||||
}
|
||||
scope.removeModalReady = scope.$on('ModalReady', function() {
|
||||
Wait('stop');
|
||||
$('#eventviewer-modal-dialog').dialog('open');
|
||||
});
|
||||
|
||||
if (scope.removeJobReady) {
|
||||
scope.removeJobReady();
|
||||
}
|
||||
scope.removeEventReady = scope.$on('EventReady', function(e, data) {
|
||||
var btns;
|
||||
scope.events = data;
|
||||
if (event_id) {
|
||||
// find and show the selected event
|
||||
data.every(function(row, idx) {
|
||||
if (parseInt(row.id,10) === parseInt(event_id,10)) {
|
||||
current_event = idx;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
else {
|
||||
current_event = 0;
|
||||
}
|
||||
showEvent(current_event);
|
||||
|
||||
btns = [];
|
||||
if (scope.events.length > 1) {
|
||||
btns.push({
|
||||
label: "Prev",
|
||||
onClick: function () {
|
||||
if (current_event - 1 === 0 && !scope.prev_event_set) {
|
||||
$('#events-prev-button').prop('disabled', true);
|
||||
}
|
||||
if (current_event - 1 < scope.events.length - 1) {
|
||||
$('#events-next-button').prop('disabled', false);
|
||||
}
|
||||
showEvent(current_event - 1);
|
||||
},
|
||||
icon: "fa-chevron-left",
|
||||
"class": "btn btn-primary",
|
||||
id: "events-prev-button"
|
||||
});
|
||||
btns.push({
|
||||
label: "Next",
|
||||
onClick: function() {
|
||||
if (current_event + 1 > 0) {
|
||||
$('#events-prev-button').prop('disabled', false);
|
||||
}
|
||||
if (current_event + 1 >= scope.events.length - 1 && !scope.next_event_set) {
|
||||
$('#events-next-button').prop('disabled', true);
|
||||
}
|
||||
showEvent(current_event + 1);
|
||||
},
|
||||
icon: "fa-chevron-right",
|
||||
"class": "btn btn-primary",
|
||||
id: "events-next-button"
|
||||
});
|
||||
}
|
||||
btns.push({
|
||||
label: "OK",
|
||||
onClick: function() {
|
||||
scope.modalOK();
|
||||
},
|
||||
icon: "",
|
||||
"class": "btn btn-primary",
|
||||
id: "dialog-ok-button"
|
||||
});
|
||||
|
||||
CreateDialog({
|
||||
scope: scope,
|
||||
width: 675,
|
||||
height: 600,
|
||||
minWidth: 450,
|
||||
callback: 'ModalReady',
|
||||
id: 'eventviewer-modal-dialog',
|
||||
// onResizeStop: resizeText,
|
||||
title: ( (title) ? title : 'Host Event' ),
|
||||
buttons: btns,
|
||||
closeOnEscape: true,
|
||||
onResizeStop: function() {
|
||||
setButtonMargin();
|
||||
addSpinner();
|
||||
},
|
||||
onClose: function() {
|
||||
try {
|
||||
scope.$destroy();
|
||||
}
|
||||
catch(e) {
|
||||
//ignore
|
||||
}
|
||||
},
|
||||
onOpen: function() {
|
||||
$('#eventview-tabs a:first').tab('show');
|
||||
$('#dialog-ok-button').focus();
|
||||
if (scope.events.length > 1 && current_event === 0 && !scope.prev_event_set) {
|
||||
$('#events-prev-button').prop('disabled', true);
|
||||
}
|
||||
if ((current_event === scope.events.length - 1) && !scope.next_event_set) {
|
||||
$('#events-next-button').prop('disabled', true);
|
||||
}
|
||||
if (scope.events.length > 1) {
|
||||
setButtonMargin();
|
||||
addSpinner();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
page = (index) ? Math.ceil((index+1)/50) : 1;
|
||||
url += (/\/$/.test(url)) ? '?' : '&';
|
||||
url += (parent_id) ? 'page='+page +'&parent=' + parent_id + '&page_size=50&order=host_name,counter' : 'page_size=50&order=host_name,counter';
|
||||
|
||||
GetEvent({
|
||||
url: url,
|
||||
scope: scope
|
||||
});
|
||||
|
||||
scope.modalOK = function() {
|
||||
$('#eventviewer-modal-dialog').dialog('close');
|
||||
scope.$destroy();
|
||||
};
|
||||
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('GetEvent', ['Wait', 'Rest', 'ProcessErrors',
|
||||
function(Wait, Rest, ProcessErrors) {
|
||||
return function(params) {
|
||||
var url = params.url,
|
||||
scope = params.scope,
|
||||
show_event = params.show_event,
|
||||
results= [];
|
||||
|
||||
if (show_event) {
|
||||
$('#event-next-spinner').show();
|
||||
}
|
||||
else {
|
||||
Wait('start');
|
||||
}
|
||||
|
||||
function getStatus(e) {
|
||||
return (e.event === "runner_on_unreachable") ? "unreachable" : (e.event === "runner_on_skipped") ? 'skipped' : (e.failed) ? 'failed' :
|
||||
(e.changed) ? 'changed' : 'ok';
|
||||
}
|
||||
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success( function(data) {
|
||||
|
||||
if(jQuery.isEmptyObject(data)) {
|
||||
Wait('stop');
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get event ' + url + '. ' });
|
||||
|
||||
}
|
||||
else {
|
||||
scope.next_event_set = data.next;
|
||||
scope.prev_event_set = data.previous;
|
||||
data.results.forEach(function(event) {
|
||||
var msg, key, event_data = {};
|
||||
if (event.event_data.res) {
|
||||
if (typeof event.event_data.res !== 'object') {
|
||||
// turn event_data.res into an object
|
||||
msg = event.event_data.res;
|
||||
event.event_data.res = {};
|
||||
event.event_data.res.msg = msg;
|
||||
}
|
||||
for (key in event.event_data) {
|
||||
if (key !== "res") {
|
||||
event.event_data.res[key] = event.event_data[key];
|
||||
}
|
||||
}
|
||||
if (event.event_data.res.ansible_facts) {
|
||||
// don't show fact gathering results
|
||||
event.event_data.res.task = "Gathering Facts";
|
||||
delete event.event_data.res.ansible_facts;
|
||||
}
|
||||
event.event_data.res.status = getStatus(event);
|
||||
event_data = event.event_data.res;
|
||||
}
|
||||
else {
|
||||
event.event_data.status = getStatus(event);
|
||||
event_data = event.event_data;
|
||||
}
|
||||
// convert results to stdout
|
||||
if (event_data.results && typeof event_data.results === "object" && Array.isArray(event_data.results)) {
|
||||
event_data.stdout = "";
|
||||
event_data.results.forEach(function(row) {
|
||||
event_data.stdout += row + "\n";
|
||||
});
|
||||
delete event_data.results;
|
||||
}
|
||||
if (event_data.invocation) {
|
||||
for (key in event_data.invocation) {
|
||||
event_data[key] = event_data.invocation[key];
|
||||
}
|
||||
delete event_data.invocation;
|
||||
}
|
||||
event_data.play = event.play;
|
||||
if (event.task) {
|
||||
event_data.task = event.task;
|
||||
}
|
||||
event_data.created = event.created;
|
||||
event_data.role = event.role;
|
||||
event_data.host_id = event.host;
|
||||
event_data.host_name = event.host_name;
|
||||
if (event_data.host) {
|
||||
delete event_data.host;
|
||||
}
|
||||
event_data.id = event.id;
|
||||
event_data.parent = event.parent;
|
||||
event_data.event = (event.event_display) ? event.event_display : event.event;
|
||||
results.push(event_data);
|
||||
});
|
||||
if (show_event) {
|
||||
scope.$emit('ShowNextEvent', results, show_event);
|
||||
}
|
||||
else {
|
||||
scope.$emit('EventReady', results);
|
||||
}
|
||||
} //else statement
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get event ' + url + '. GET returned: ' + status });
|
||||
});
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
id = params.id,
|
||||
event = params.event,
|
||||
section = params.section,
|
||||
html = '', e;
|
||||
|
||||
function parseObject(obj) {
|
||||
// parse nested JSON objects. a mini version of parseJSON without references to the event form object.
|
||||
var i, key, html = '';
|
||||
for (key in obj) {
|
||||
if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
||||
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">" + obj[key] + "</td></tr>";
|
||||
}
|
||||
else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
|
||||
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">[";
|
||||
for (i = 0; i < obj[key].length; i++) {
|
||||
html += obj[key][i] + ",";
|
||||
}
|
||||
html = html.replace(/,$/,'');
|
||||
html += "]</td></tr>\n";
|
||||
}
|
||||
else if (typeof obj[key] === "object") {
|
||||
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"nested-table\"><table>\n<tbody>\n" + parseObject(obj[key]) + "</tbody>\n</table>\n</td></tr>\n";
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function parseItem(itm, key, label) {
|
||||
var i, html = '';
|
||||
if (Empty(itm)) {
|
||||
// exclude empty items
|
||||
}
|
||||
else if (typeof itm === "boolean" || typeof itm === "number" || typeof itm === "string") {
|
||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"value\">";
|
||||
if (key === "status") {
|
||||
html += "<i class=\"fa icon-job-" + itm + "\"></i> " + itm;
|
||||
}
|
||||
else if (key === "start" || key === "end" || key === "created") {
|
||||
if (!/Z$/.test(itm)) {
|
||||
itm = itm.replace(/\ /,'T') + 'Z';
|
||||
html += $filter('longDate')(itm);
|
||||
}
|
||||
else {
|
||||
html += $filter('longDate')(itm);
|
||||
}
|
||||
}
|
||||
else if (key === "host_name" && event.host_id) {
|
||||
html += "<a href=\"/#/home/hosts/?id=" + event.host_id + "\" target=\"_blank\" " +
|
||||
"aw-tool-tip=\"View host. Opens in new tab or window.\" data-placement=\"top\" " +
|
||||
">" + itm + "</a>";
|
||||
}
|
||||
else {
|
||||
if( typeof itm === "string"){
|
||||
if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){
|
||||
itm = $filter('sanitize')(itm);
|
||||
}
|
||||
}
|
||||
html += "<span ng-non-bindable>" + itm + "</span>";
|
||||
}
|
||||
|
||||
html += "</td></tr>\n";
|
||||
}
|
||||
else if (typeof itm === "object" && Array.isArray(itm)) {
|
||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"value\">[";
|
||||
for (i = 0; i < itm.length; i++) {
|
||||
html += itm[i] + ",";
|
||||
}
|
||||
html = html.replace(/,$/,'');
|
||||
html += "]</td></tr>\n";
|
||||
}
|
||||
else if (typeof itm === "object") {
|
||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"nested-table\"><table>\n<tbody>\n" + parseObject(itm) + "</tbody>\n</table>\n</td></tr>\n";
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function parseJSON(obj) {
|
||||
var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = "";
|
||||
if (typeof obj === "object") {
|
||||
html += "<table class=\"table eventviewer-status\">\n";
|
||||
html += "<tbody>\n";
|
||||
keys = [];
|
||||
for (key in EventsViewerForm.fields) {
|
||||
if (EventsViewerForm.fields[key].section === section) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
keys.forEach(function(key) {
|
||||
var h, label;
|
||||
label = EventsViewerForm.fields[key].label;
|
||||
h = parseItem(obj[key], key, label);
|
||||
if (h) {
|
||||
html += h;
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if (section === 'Results') {
|
||||
// Add to result fields that might not be found in the form object.
|
||||
for (key in obj) {
|
||||
h = '';
|
||||
if (key !== 'host_id' && key !== 'parent' && key !== 'event' && key !== 'src' && key !== 'md5sum' &&
|
||||
key !== 'stdout' && key !== 'traceback' && key !== 'stderr' && key !== 'cmd' && key !=='changed' && key !== "verbose_override" &&
|
||||
key !== 'feature_result' && key !== 'warnings') {
|
||||
if (!EventsViewerForm.fields[key]) {
|
||||
h = parseItem(obj[key], key, key);
|
||||
if (h) {
|
||||
html += h;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
} else if (key === 'cmd') {
|
||||
// only show cmd if it's a cmd that was run
|
||||
if (!EventsViewerForm.fields[key] && obj[key].length > 0) {
|
||||
// include the label head Shell Command instead of CMD in the modal
|
||||
if(typeof(obj[key]) === 'string'){
|
||||
obj[key] = [obj[key]];
|
||||
}
|
||||
string_cmd += obj[key].join(" ");
|
||||
h = parseItem(string_cmd, key, "Shell Command");
|
||||
if (h) {
|
||||
html += h;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
} else if (key === 'warnings') {
|
||||
if (!EventsViewerForm.fields[key] && obj[key].length > 0) {
|
||||
if(typeof(obj[key]) === 'string'){
|
||||
obj[key] = [obj[key]];
|
||||
}
|
||||
string_warnings += obj[key].join(" ");
|
||||
h = parseItem(string_warnings, key, "Warnings");
|
||||
if (h) {
|
||||
html += h;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
}
|
||||
return (found) ? html : '';
|
||||
}
|
||||
html = parseJSON(event);
|
||||
|
||||
e = angular.element(document.getElementById(id));
|
||||
e.empty();
|
||||
if (html) {
|
||||
e.html(html);
|
||||
$compile(e)(scope);
|
||||
}
|
||||
return (html) ? true : false;
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('EventAddTextarea', [ function() {
|
||||
return function(params) {
|
||||
var container_id = params.container_id,
|
||||
val = params.val,
|
||||
fld_id = params.fld_id,
|
||||
html;
|
||||
html = "<div class=\"form-group\">\n" +
|
||||
"<textarea ng-non-bindable id=\"" + fld_id + "\" class=\"form-control mono-space\" rows=\"12\" readonly>" + val + "</textarea>" +
|
||||
"</div>\n";
|
||||
$('#' + container_id).empty().html(html);
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('EventAddPreFormattedText', ['$filter', function($filter) {
|
||||
return function(params) {
|
||||
var id = params.id,
|
||||
val = params.val,
|
||||
html;
|
||||
if( typeof val === "string"){
|
||||
if(val.indexOf('<') > -1 || val.indexOf('>') > -1){
|
||||
val = $filter('sanitize')(val);
|
||||
}
|
||||
}
|
||||
html = "<pre ng-non-bindable>" + val + "</pre>\n";
|
||||
$('#' + id).empty().html(html);
|
||||
};
|
||||
}]);
|
@ -0,0 +1,49 @@
|
||||
<div class="HostEvent-details--left">
|
||||
<div class="HostEvent-field">
|
||||
<div class="HostEvent-title">EVENT</div>
|
||||
<span class="HostEvent-field--content"></span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">HOST</span>
|
||||
<span class="HostEvent-field--content">
|
||||
<a ui-sref="jobDetail.host-events({hostName: event.host_name})">{{event.host_name || "No result found"}}</a></span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">STATUS</span>
|
||||
<span class="HostEvent-field--content">
|
||||
<a class="HostEvents-status">
|
||||
<i class="fa fa-circle" ng-class="processEventStatus"></i>
|
||||
</a>
|
||||
{{event.status || "No result found"}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">ID</span>
|
||||
<span class="HostEvent-field--content">{{event.id || "No result found"}}</span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">CREATED</span>
|
||||
<span class="HostEvent-field--content">{{event.created || "No result found"}}</span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">PLAY</span>
|
||||
<span class="HostEvent-field--content">{{event.play || "No result found"}}</span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">TASK</span>
|
||||
<span class="HostEvent-field--content">{{event.task || "No result found"}}</span>
|
||||
</div>
|
||||
<div class="HostEvent-field">
|
||||
<span class="HostEvent-field--label">MODULE</span>
|
||||
<span class="HostEvent-field--content">{{event.event_data.res.invocation.module_name || "No result found"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="HostEvent-details--right" ng-show="event.event_data.res">
|
||||
<div class="HostEvent-title">RESULTS</div>
|
||||
<!-- discard any objects in the ansible response until we decide to flatten them -->
|
||||
<div class="HostEvent-field" ng-repeat="(key, value) in results = event.event_data.res track by $index" ng-if="processResults(value)">
|
||||
<span class="HostEvent-field--label">{{key}}</span>
|
||||
<span class="HostEvent-field--content">{{value}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,2 @@
|
||||
<textarea id="HostEvent-json" class="HostEvent-json">
|
||||
</textarea>
|
@ -0,0 +1,36 @@
|
||||
<div id="HostEvent" class="HostEvent modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<!-- modal body -->
|
||||
<div class="modal-body">
|
||||
<div class="HostEvent-header">
|
||||
<span class="HostEvent-title">HOST EVENT</span>
|
||||
<!-- close -->
|
||||
<button ui-sref="jobDetail" type="button" class="close">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="HostEvent-nav">
|
||||
<!-- view navigation buttons -->
|
||||
<button ui-sref="jobDetail.host-event.details" type="button" class="btn btn-sm btn-default" >Details</button>
|
||||
<button ui-sref="jobDetail.host-event.json" type="button" class="btn btn-sm btn-default ">JSON</button>
|
||||
<button ng-show="event.stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default ">Standard Out</button>
|
||||
<button ng-show="event.timing" ui-sref="jobDetail.host-event.timing" type="button" class="btn btn-sm btn-default ">Timing</button>
|
||||
|
||||
</div>
|
||||
<div class="HostEvent-body">
|
||||
<!-- views -->
|
||||
<div ui-view></div>
|
||||
</div>
|
||||
|
||||
<!-- controls -->
|
||||
<div class="HostEvent-controls">
|
||||
<button ng-show="showPrev()" ng-click="goPrev()"
|
||||
class="btn btn-sm btn-default">Prev</button>
|
||||
<button ng-show="showNext()"ng-click="goNext()" class="btn btn-sm btn-default">Next</button>
|
||||
<button ui-sref="jobDetail" class="btn btn-sm btn-default" ng-show="true" >Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
<div class="EventHost-stdoutPanel Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText">STANDARD OUT</div>
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
|
||||
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
|
||||
<i class="fa fa-download"></i>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<standard-out-log stdout-endpoint="event._stdout"></standard-out-log>
|
||||
</div>
|
@ -0,0 +1 @@
|
||||
<div>timing</div>
|
@ -0,0 +1,69 @@
|
||||
@import "awx/ui/client/src/shared/branding/colors.less";
|
||||
@import "awx/ui/client/src/shared/branding/colors.default.less";
|
||||
@import "awx/ui/client/src/shared/layouts/one-plus-two.less";
|
||||
|
||||
.HostEvent .modal-footer{
|
||||
border: 0;
|
||||
margin-top: 0px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.HostEvent-controls{
|
||||
float: right;
|
||||
button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.HostEvent-status--ok{
|
||||
color: @green;
|
||||
}
|
||||
.HostEvent-status--unreachable{
|
||||
color: @unreachable;
|
||||
}
|
||||
.HostEvent-status--changed{
|
||||
color: @changed;
|
||||
}
|
||||
.HostEvent-status--failed{
|
||||
color: @default-err;
|
||||
}
|
||||
.HostEvent-status--skipped{
|
||||
color: @skipped;
|
||||
}
|
||||
.HostEvent-title{
|
||||
color: @default-interface-txt;
|
||||
font-weight: 600;
|
||||
}
|
||||
.HostEvent .modal-body{
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.HostEvent-nav{
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.HostEvent-field{
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.HostEvent-field--label{
|
||||
.OnePlusTwo-left--detailsLabel;
|
||||
width: 80px;
|
||||
margin-right: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.HostEvent-field{
|
||||
.OnePlusTwo-left--detailsRow;
|
||||
}
|
||||
.HostEvent-field--content{
|
||||
.OnePlusTwo-left--detailsContent;
|
||||
}
|
||||
.HostEvent-details--left, .HostEvent-details--right{
|
||||
vertical-align:top;
|
||||
width:270px;
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
.HostEvent-details--right{
|
||||
.HostEvent-field--label{
|
||||
width: 170px;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event',
|
||||
function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){
|
||||
// Avoid rendering objects in the details fieldset
|
||||
// ng-if="processResults(value)" via host-event-details.partial.html
|
||||
$scope.processResults = function(value){
|
||||
if (typeof value == 'object'){return false}
|
||||
else {return true}
|
||||
};
|
||||
|
||||
var codeMirror = function(){
|
||||
var el = $('#HostEvent-json')[0];
|
||||
var editor = CodeMirror.fromTextArea(el, {
|
||||
lineNumbers: true,
|
||||
mode: {name: "javascript", json: true}
|
||||
});
|
||||
editor.getDoc().setValue(JSON.stringify($scope.json, null, 4));
|
||||
};
|
||||
|
||||
$scope.getActiveHostIndex = function(){
|
||||
var result = $scope.hostResults.filter(function( obj ) {
|
||||
return obj.id == $scope.event.id;
|
||||
});
|
||||
return $scope.hostResults.indexOf(result[0])
|
||||
};
|
||||
|
||||
$scope.showPrev = function(){
|
||||
return $scope.getActiveHostIndex() != 0
|
||||
};
|
||||
|
||||
$scope.showNext = function(){
|
||||
return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1])
|
||||
};
|
||||
|
||||
$scope.goNext = function(){
|
||||
var index = $scope.getActiveHostIndex() + 1;
|
||||
var id = $scope.hostResults[index].id;
|
||||
$state.go('jobDetail.host-event.details', {eventId: id})
|
||||
};
|
||||
|
||||
$scope.goPrev = function(){
|
||||
var index = $scope.getActiveHostIndex() - 1;
|
||||
var id = $scope.hostResults[index].id;
|
||||
$state.go('jobDetail.host-event.details', {eventId: id})
|
||||
};
|
||||
|
||||
var init = function(){
|
||||
$scope.event = event.data.results[0];
|
||||
$scope.event.created = moment($scope.event.created).format();
|
||||
$scope.processEventStatus = JobDetailService.processEventStatus($scope.event);
|
||||
$scope.hostResults = $stateParams.hostResults;
|
||||
$scope.json = JobDetailService.processJson($scope.event);
|
||||
if ($state.current.name == 'jobDetail.host-event.json'){
|
||||
codeMirror();
|
||||
}
|
||||
try {
|
||||
$scope.stdout = $scope.event.event_data.res.stdout
|
||||
}
|
||||
catch(err){
|
||||
$scope.sdout = null;
|
||||
}
|
||||
$('#HostEvent').modal('show');
|
||||
};
|
||||
init();
|
||||
}];
|
86
awx/ui/client/src/job-detail/host-event/host-event.route.js
Normal file
86
awx/ui/client/src/job-detail/host-event/host-event.route.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
var hostEventModal = {
|
||||
name: 'jobDetail.host-event',
|
||||
url: '/host-event/:eventId',
|
||||
controller: 'HostEventController',
|
||||
params:{
|
||||
hostResults: {
|
||||
value: null,
|
||||
squash: false,
|
||||
}
|
||||
},
|
||||
templateUrl: templateUrl('job-detail/host-event/host-event-modal'),
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService){
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
id: $stateParams.eventId
|
||||
}).success(function(res){ return res.results[0]})
|
||||
}]
|
||||
},
|
||||
onExit: function($state){
|
||||
// close the modal
|
||||
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
|
||||
$('#HostEvent').modal('hide');
|
||||
// hacky way to handle user browsing away via URL bar
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open');
|
||||
}
|
||||
}
|
||||
|
||||
var hostEventDetails = {
|
||||
name: 'jobDetail.host-event.details',
|
||||
url: '/details',
|
||||
controller: 'HostEventController',
|
||||
templateUrl: templateUrl('job-detail/host-event/host-event-details'),
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService){
|
||||
return FeaturesService.get();
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
var hostEventJson = {
|
||||
name: 'jobDetail.host-event.json',
|
||||
url: '/json',
|
||||
controller: 'HostEventController',
|
||||
templateUrl: templateUrl('job-detail/host-event/host-event-json'),
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService){
|
||||
return FeaturesService.get();
|
||||
}]
|
||||
}
|
||||
};
|
||||
var hostEventTiming = {
|
||||
name: 'jobDetail.host-event.timing',
|
||||
url: '/timing',
|
||||
controller: 'HostEventController',
|
||||
templateUrl: templateUrl('job-detail/host-event/host-event-timing'),
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService){
|
||||
return FeaturesService.get();
|
||||
}]
|
||||
}
|
||||
};
|
||||
var hostEventStdout = {
|
||||
name: 'jobDetail.host-event.stdout',
|
||||
url: '/stdout',
|
||||
controller: 'HostEventController',
|
||||
templateUrl: templateUrl('job-detail/host-event/host-event-stdout'),
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService){
|
||||
return FeaturesService.get();
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
export {hostEventDetails, hostEventJson, hostEventTiming, hostEventStdout, hostEventModal}
|
21
awx/ui/client/src/job-detail/host-event/main.js
Normal file
21
awx/ui/client/src/job-detail/host-event/main.js
Normal file
@ -0,0 +1,21 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {hostEventModal, hostEventDetails, hostEventTiming,
|
||||
hostEventJson, hostEventStdout} from './host-event.route';
|
||||
import controller from './host-event.controller';
|
||||
|
||||
export default
|
||||
angular.module('jobDetail.hostEvent', [])
|
||||
.controller('HostEventController', controller)
|
||||
|
||||
.run(['$stateExtender', function($stateExtender){
|
||||
$stateExtender.addState(hostEventModal);
|
||||
$stateExtender.addState(hostEventDetails);
|
||||
$stateExtender.addState(hostEventTiming);
|
||||
$stateExtender.addState(hostEventJson);
|
||||
$stateExtender.addState(hostEventStdout);
|
||||
}]);
|
@ -16,7 +16,7 @@
|
||||
color: @changed;
|
||||
}
|
||||
.HostEvents-status--failed{
|
||||
color: @warning;
|
||||
color: @default-err;
|
||||
}
|
||||
.HostEvents-status--skipped{
|
||||
color: @skipped;
|
||||
@ -47,6 +47,7 @@
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.HostEvents-title{
|
||||
text-transform: uppercase;
|
||||
color: @default-interface-txt;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -6,13 +6,14 @@
|
||||
|
||||
export default
|
||||
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
|
||||
'JobDetailService', 'CreateSelect2',
|
||||
'JobDetailService', 'CreateSelect2', 'hosts',
|
||||
function($stateParams, $scope, $rootScope, $state, Wait,
|
||||
JobDetailService, CreateSelect2){
|
||||
JobDetailService, CreateSelect2, hosts){
|
||||
|
||||
// pagination not implemented yet, but it'll depend on this
|
||||
$scope.page_size = $stateParams.page_size;
|
||||
|
||||
$scope.processEventStatus = JobDetailService.processEventStatus;
|
||||
$scope.activeFilter = $stateParams.filter || null;
|
||||
|
||||
$scope.search = function(){
|
||||
@ -39,6 +40,7 @@
|
||||
|
||||
var filter = function(filter){
|
||||
Wait('start');
|
||||
|
||||
if (filter == 'all'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
@ -104,47 +106,17 @@
|
||||
filter($('.HostEvents-select').val());
|
||||
});
|
||||
|
||||
$scope.processStatus = function(event, $index){
|
||||
// the stack for which status to display is
|
||||
// unreachable > failed > changed > ok
|
||||
// uses the API's runner events and convenience properties .failed .changed to determine status.
|
||||
// see: job_event_callback.py
|
||||
if (event.event == 'runner_on_unreachable'){
|
||||
$scope.results[$index].status = 'Unreachable';
|
||||
return 'HostEvents-status--unreachable'
|
||||
}
|
||||
// equiv to 'runner_on_error' && 'runner on failed'
|
||||
if (event.failed){
|
||||
$scope.results[$index].status = 'Failed';
|
||||
return 'HostEvents-status--failed'
|
||||
}
|
||||
// catch the changed case before ok, because both can be true
|
||||
if (event.changed){
|
||||
$scope.results[$index].status = 'Changed';
|
||||
return 'HostEvents-status--changed'
|
||||
}
|
||||
if (event.event == 'runner_on_ok'){
|
||||
$scope.results[$index].status = 'OK';
|
||||
return 'HostEvents-status--ok'
|
||||
}
|
||||
if (event.event == 'runner_on_skipped'){
|
||||
$scope.results[$index].status = 'Skipped';
|
||||
return 'HostEvents-status--skipped'
|
||||
}
|
||||
else{
|
||||
// study a case where none of these apply
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var init = function(){
|
||||
$scope.hostName = $stateParams.hostName;
|
||||
// create filter dropdown
|
||||
console.log($stateParams)
|
||||
CreateSelect2({
|
||||
element: '.HostEvents-select',
|
||||
multiple: false
|
||||
});
|
||||
// process the filter if one was passed
|
||||
if ($stateParams.filter){
|
||||
Wait('start');
|
||||
filter($stateParams.filter).success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
@ -152,25 +124,11 @@
|
||||
});;
|
||||
}
|
||||
else{
|
||||
Wait('start');
|
||||
JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
page_size: $stateParams.page_size})
|
||||
.success(function(res){
|
||||
$scope.pagination = res;
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
$('#HostEvents').modal('show');
|
||||
|
||||
});
|
||||
$scope.results = hosts.data.results;
|
||||
$('#HostEvents').modal('show');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.goBack = function(){
|
||||
// go back to the job details state
|
||||
// we're leaning on $stateProvider's onExit to close the modal
|
||||
$state.go('jobDetail');
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
|
@ -3,9 +3,9 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="HostEvents-header">
|
||||
<span class="HostEvents-title">HOST EVENTS</span>
|
||||
<span class="HostEvents-title">HOST EVENTS | {{hostName}}</span>
|
||||
<!-- Close -->
|
||||
<button ng-click="goBack()" type="button" class="close">
|
||||
<button ui-sref="jobDetail" type="button" class="close">
|
||||
<i class="fa fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -29,7 +29,6 @@
|
||||
<table class="table">
|
||||
<!-- column labels -->
|
||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">STATUS</th>
|
||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">HOST</th>
|
||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">PLAY</th>
|
||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">TASK</th>
|
||||
<!-- result rows -->
|
||||
@ -37,11 +36,10 @@
|
||||
<td class=HostEvents-table--cell>
|
||||
<!-- status circles -->
|
||||
<a class="HostEvents-status">
|
||||
<i class="fa fa-circle" ng-class="processStatus(event, $index)"></i>
|
||||
<i class="fa fa-circle" ng-class="processEventStatus(event)"></i>
|
||||
</a>
|
||||
{{event.status}}
|
||||
</td>
|
||||
<td class=HostEvents-table--cell>{{event.host_name}}</td>
|
||||
<td class=HostEvents-table--cell>{{event.play}}</td>
|
||||
<td class=HostEvents-table--cell>{{event.task}}</td>
|
||||
</tr>
|
||||
@ -56,7 +54,7 @@
|
||||
<div class="modal-footer">
|
||||
<!-- pagination -->
|
||||
<!-- close -->
|
||||
<button ng-click="goBack()" class="btn btn-default pull-right HostEvents-close">OK</button>
|
||||
<button ui-sref="jobDetail" class="btn btn-default pull-right HostEvents-close">OK</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
@ -25,6 +25,11 @@ export default {
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}]
|
||||
}],
|
||||
hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName
|
||||
}).success(function(res){ return res.results[0]})
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ export default
|
||||
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
|
||||
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit',
|
||||
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
|
||||
'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EventViewer',
|
||||
'EditSchedule', 'ParseTypeChange', 'JobDetailService',
|
||||
function(
|
||||
$location, $rootScope, $filter, $scope, $compile, $stateParams,
|
||||
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
|
||||
@ -27,7 +27,7 @@ export default
|
||||
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
|
||||
PlaybookRun, LoadPlays, LoadTasks, LoadHosts,
|
||||
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
|
||||
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, EventViewer
|
||||
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
|
||||
) {
|
||||
ClearScope();
|
||||
|
||||
@ -1119,17 +1119,6 @@ export default
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -343,7 +343,9 @@
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
|
||||
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column"><a ng-click="viewHostResults(result.id)" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a></td>
|
||||
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column">
|
||||
<a ui-sref="jobDetail.host-event.details({eventId: result.id, hostResults: hostResults})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
|
||||
</td>
|
||||
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3 item-column">{{ result.item }}</td>
|
||||
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>
|
||||
</tr>
|
||||
@ -422,7 +424,7 @@
|
||||
<tbody>
|
||||
<tr class="List-tableRow" ng-repeat="host in summaryList = (hosts) track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
|
||||
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id})" aw-tool-tip="View events" data-placement="top">{{ host.name }}</a>
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name})" aw-tool-tip="View events" data-placement="top">{{ host.name }}</a>
|
||||
</td>
|
||||
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'ok'})" aw-tool-tip="{{ host.okTip }}" data-tip-watch="host.okTip" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
||||
@ -480,8 +482,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include="'/static/partials/eventviewer.html'"></div>
|
||||
|
||||
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div>
|
||||
|
||||
<div ng-include="'/static/partials/schedule_dialog.html'"></div>
|
||||
|
@ -2,13 +2,85 @@ export default
|
||||
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
|
||||
return {
|
||||
|
||||
/*
|
||||
/*
|
||||
For ES6
|
||||
it might be useful to set some default params here, e.g.
|
||||
getJobHostSummaries: function(id, page_size=200, order='host_name'){}
|
||||
without ES6, we'd have to supply defaults like this:
|
||||
this.page_size = params.page_size ? params.page_size : 200;
|
||||
*/
|
||||
*/
|
||||
|
||||
// the the API passes through Ansible's event_data response
|
||||
// we need to massage away the verbose and redundant properties
|
||||
|
||||
processJson: function(data){
|
||||
// a deep copy
|
||||
var result = $.extend(true, {}, data);
|
||||
// configure fields to ignore
|
||||
var ignored = [
|
||||
'event_data',
|
||||
'related',
|
||||
'summary_fields',
|
||||
'url',
|
||||
'ansible_facts',
|
||||
];
|
||||
|
||||
// remove ignored properties
|
||||
Object.keys(result).forEach(function(key, index){
|
||||
if (ignored.indexOf(key) > -1) {
|
||||
delete result[key]
|
||||
}
|
||||
});
|
||||
|
||||
// flatten Ansible's passed-through response
|
||||
try{
|
||||
result.event_data = {};
|
||||
Object.keys(data.event_data.res).forEach(function(key, index){
|
||||
if (ignored.indexOf(key) > -1) {
|
||||
return
|
||||
}
|
||||
else{
|
||||
result.event_data[key] = data.event_data.res[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(err){result.event_data = null;}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
processEventStatus: function(event){
|
||||
// Generate a helper class for job_event statuses
|
||||
// the stack for which status to display is
|
||||
// unreachable > failed > changed > ok
|
||||
// uses the API's runner events and convenience properties .failed .changed to determine status.
|
||||
// see: job_event_callback.py
|
||||
if (event.event == 'runner_on_unreachable'){
|
||||
event.status = 'Unreachable';
|
||||
return 'HostEvents-status--unreachable'
|
||||
}
|
||||
// equiv to 'runner_on_error' && 'runner on failed'
|
||||
if (event.failed){
|
||||
event.status = 'Failed';
|
||||
return 'HostEvents-status--failed'
|
||||
}
|
||||
// catch the changed case before ok, because both can be true
|
||||
if (event.changed){
|
||||
event.status = 'Changed';
|
||||
return 'HostEvents-status--changed'
|
||||
}
|
||||
if (event.event == 'runner_on_ok'){
|
||||
event.status = 'OK';
|
||||
return 'HostEvents-status--ok'
|
||||
}
|
||||
if (event.event == 'runner_on_skipped'){
|
||||
event.status = 'Skipped';
|
||||
return 'HostEvents-status--skipped'
|
||||
}
|
||||
else{
|
||||
// study a case where none of these apply
|
||||
}
|
||||
},
|
||||
|
||||
// GET events related to a job run
|
||||
// e.g.
|
||||
|
@ -8,10 +8,12 @@ import route from './job-detail.route';
|
||||
import controller from './job-detail.controller';
|
||||
import service from './job-detail.service';
|
||||
import hostEvents from './host-events/main';
|
||||
import hostEvent from './host-event/main';
|
||||
|
||||
export default
|
||||
angular.module('jobDetail', [
|
||||
hostEvents.name
|
||||
hostEvents.name,
|
||||
hostEvent.name
|
||||
])
|
||||
.controller('JobDetailController', controller)
|
||||
.service('JobDetailService', service)
|
||||
|
@ -23,7 +23,8 @@
|
||||
set: function(data){
|
||||
var defaultUrl = GetBasePath('job_templates');
|
||||
Rest.setUrl(defaultUrl);
|
||||
data.results[0].name = data.results[0].name + ' ' + moment().format('h:mm:ss a'); // 2:49:11 pm
|
||||
var name = this.buildName(data.results[0].name)
|
||||
data.results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm
|
||||
return Rest.post(data.results[0])
|
||||
.success(function(res){
|
||||
return res
|
||||
@ -32,6 +33,10 @@
|
||||
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
|
||||
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
|
||||
});
|
||||
},
|
||||
buildName: function(name){
|
||||
var result = name.split('@')[0];
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
<div id="eventviewer-modal-dialog" style="display: none;">
|
||||
<ul id="eventview-tabs" class="nav nav-tabs">
|
||||
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'eventview-tabs')">Event</a></li>
|
||||
<li><a href="#results" id="results-link" data-toggle="tab" ng-click="toggleTab($event, 'results-link', 'eventview-tabs')">Results</a></li>
|
||||
<li><a href="#timing" id="timing-link" data-toggle="tab" ng-click="toggleTab($event, 'timing-link', 'eventview-tabs')">Timing</a></li>
|
||||
<li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'eventview-tabs')">Standard Out</a></li>
|
||||
<li><a href="#stderr" id="stderr-link" data-toggle="tab" ng-click="toggleTab($event, 'stderr-link', 'eventview-tabs')">Standard Error</a></li>
|
||||
<li><a href="#traceback" id="traceback-link" data-toggle="tab" ng-click="toggleTab($event, 'traceback-link', 'eventview-tabs')">Traceback</a></li>
|
||||
<li><a href="#json" id="json-link" data-toggle="tab" ng-click="toggleTab($event, 'json-link', 'eventview-tabs')">JSON</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="status">
|
||||
<div id="status-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="results">
|
||||
<div id="results-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="timing">
|
||||
<div id="timing-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="stdout">
|
||||
<div id="stdout-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="stderr">
|
||||
<div id="stderr-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="traceback">
|
||||
<div id="traceback-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="json">
|
||||
<div id="json-form-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -61,7 +61,8 @@
|
||||
}
|
||||
|
||||
.OnePlusTwo-left--detailsLabel {
|
||||
width: 140px;
|
||||
word-wrap: break-word;
|
||||
width: 170px;
|
||||
display: inline-block;
|
||||
color: @default-interface-txt;
|
||||
text-transform: uppercase;
|
||||
|
Loading…
Reference in New Issue
Block a user