diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js
index ca1e5e0cb3..c98706f5fe 100644
--- a/awx/ui/static/js/controllers/JobDetail.js
+++ b/awx/ui/static/js/controllers/JobDetail.js
@@ -9,7 +9,7 @@
function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest,
ProcessErrors, SelectPlay, SelectTask, Socket, GetElapsed, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList,
- JobIsFinished, SetTaskStyles, DigestEvent, UpdateDOM) {
+ JobIsFinished, SetTaskStyles, DigestEvent, UpdateDOM, ViewHostResults) {
ClearScope();
@@ -182,7 +182,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
event = data.results[idx];
task.hostResults[event.id] = {
id: event.id,
- status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ),
+ status: (event.event === "runner_on_skipped") ? 'skipped' : (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful',
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
@@ -1044,13 +1044,16 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
});
};
- scope.viewEvent = function(event_id) {
- $log.debug(event_id);
+ scope.viewHostResults = function(id) {
+ ViewHostResults({
+ scope: scope,
+ id: id
+ });
};
}
JobDetailController.$inject = [ '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath',
'Wait', 'Rest', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'FilterAllByHostName', 'DrawGraph',
- 'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM'
+ 'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'ViewHostResults'
];
diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js
index 4dcb9d046a..9a9b429799 100644
--- a/awx/ui/static/js/helpers/JobDetail.js
+++ b/awx/ui/static/js/helpers/JobDetail.js
@@ -37,7 +37,7 @@
'use strict';
-angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
+angular.module('JobDetailHelper', ['Utilities', 'RestServices', 'ModalDialog'])
.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult',
'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', 'AddNewTask',
@@ -716,7 +716,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
data.results.forEach(function(event) {
scope.hostResults.push({
id: event.id,
- status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ),
+ status: (event.event === "runner_on_skipped") ? 'skipped' : (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful',
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
@@ -1089,4 +1089,160 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
msg: 'Call to ' + url + '. GET returned: ' + status });
});
};
+}])
+
+.factory('ViewHostResults', ['$log', 'CreateDialog', 'Rest', 'ProcessErrors', 'Wait', function($log, CreateDialog, Rest, ProcessErrors, Wait) {
+ return function(params) {
+ var scope = params.scope,
+ my_scope = params.scope.$new(),
+ id = params.id,
+ url;
+
+ function parseJSON(obj) {
+ var html="", keys;
+ if (typeof obj === "object") {
+ html += "
\n";
+ html += "\n";
+ keys = Object.keys(obj).sort();
+ keys.forEach(function(key) {
+ if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
+ html += "| " + key + ": | ";
+ html += (key === "status") ? " " + obj[key] : obj[key];
+ html += " |
\n";
+ }
+ else if (obj[key] === null || obj[key] === undefined) {
+ // html += "| " + key + ": | null |
\n";
+ }
+ else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
+ html += "| " + key + ": | ";
+ obj[key].forEach(function(row) {
+ html += " " + row + " ";
+ });
+ html += " |
\n";
+ }
+ else if (typeof obj[key] === "object") {
+ html += "| " + key + ": | \n" + parseJSON(obj[key]) + " |
\n";
+ }
+ });
+ html += "\n";
+ html += "
\n";
+ }
+ return html;
+ }
+
+ /*function parseJSON(obj) {
+ var html="", key;
+ html += "\n";
+ for(key in obj) {
+ if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
+ html += "" + key + ":
" + obj[key] + "
\n";
+ }
+ if (obj[key] === null || obj[key] === undefined) {
+ html += "" + key + ":
null
\n";
+ }
+ if (typeof obj[key] === "object") {
+ html += "" + key + ":
" + parseJSON(obj[key]) + " \n";
+ }
+ }
+ html += "
\n";
+ return html;
+ }*/
+
+ /*function parseJSON(obj) {
+ var html="", key;
+ for(key in obj) {
+ html += "\n";
+ if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
+ html += "
" + key + ":
" + obj[key] + "
\n";
+ }
+ if (obj[key] === null || obj[key] === undefined) {
+ html += "
" + key + ":
null
\n";
+ }
+ if (typeof obj[key] === "object") {
+ html += "
" + key + ":
" + parseJSON(obj[key]) + "
\n";
+ }
+ html += "
\n";
+ }
+ return html;
+ }*/
+
+ if (my_scope.removeDataReady) {
+ my_scope.removeDataReady();
+ }
+ my_scope.removeDataReady = my_scope.$on('DataReady', function(e, event_data, host) {
+ //var html = "\n";
+ //html += "
" + host.name + "
\n";
+ //html += (host.description && host.description !== "imported") ? "
" + host.description + "
" : "";
+ //html += "
Event " + id + " details:
\n";
+ //html += "
\n";
+ var html = "\n";
+ event_data.host = host.name;
+ html += parseJSON(event_data);
+ html += "
\n";
+ html += "
\n";
+
+ $('#event-viewer-dialog').empty().html(html);
+
+ CreateDialog({
+ scope: my_scope,
+ width: 600,
+ height: 550,
+ minWidth: 450,
+ callback: 'ModalReady',
+ id: 'event-viewer-dialog',
+ title: 'Host Results',
+ onOpen: function() {
+ $('#dialog-ok-button').focus();
+ }
+ });
+ });
+
+ if (my_scope.removeModalReady) {
+ my_scope.removeModalReady();
+ }
+ my_scope.removeModalReady = my_scope.$on('ModalReady', function() {
+ Wait('stop');
+ $('#event-viewer-dialog').dialog('open');
+ });
+
+ url = scope.job.related.job_events + "?id=" + id;
+ Wait('start');
+ Rest.setUrl(url);
+ Rest.get()
+ .success( function(data) {
+ var key;
+ Wait('stop');
+ if (data.results.length > 0 && data.results[0].event_data.res) {
+ for (key in data.results[0].event_data) {
+ if (key !== "res") {
+ data.results[0].event_data.res[key] = data.results[0].event_data[key];
+ }
+ }
+ if (data.results[0].event_data.res.ansible_facts) {
+ delete data.results[0].event_data.res.ansible_facts;
+ }
+ data.results[0].event_data.res.status = (data.results[0].event === "runner_on_skipped") ? 'skipped' : (data.results[0].failed) ? 'failed' :
+ (data.results[0].changed) ? 'changed' : 'successful';
+ my_scope.$emit('DataReady', data.results[0].event_data.res, data.results[0].summary_fields.host, data.results[0].id);
+ }
+ else {
+ data.results[0].event_data.status = (data.results[0].event === "runner_on_skipped") ? 'skipped' : (data.results[0].failed) ? 'failed' :
+ (data.results[0].changed) ? 'changed' : 'successful';
+ my_scope.$emit('DataReady', data.results[0].event_data, data.results[0].summary_fields.host, data.results[0].id);
+ }
+ })
+ .error(function(data, status) {
+ ProcessErrors(scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + '. GET returned: ' + status });
+ });
+
+ scope.modalOK = function() {
+ $('#event-viewer-dialog').dialog('close');
+ my_scope.$destroy();
+ };
+ };
}]);
\ No newline at end of file
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index d00d78101d..eb2ba148d7 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -975,7 +975,8 @@ input[type="checkbox"].checkbox-no-label {
.icon-job-success:before,
.icon-job-successful:before,
.icon-job-changed:before,
- .icon-job-ok:before {
+ .icon-job-ok:before,
+ .icon-job-skipped:before {
content: "\f111";
}
@@ -991,7 +992,6 @@ input[type="checkbox"].checkbox-no-label {
.icon-job-waiting:before,
.icon-job-new:before,
.icon-job-none:before,
- .icon-job-skipped:before,
.icon-job-no-matching-hosts:before {
content: "\f10c";
}
@@ -1004,6 +1004,10 @@ input[type="checkbox"].checkbox-no-label {
color: @green;
}
+ .icon-job-skipped {
+ color: @skipped;
+ }
+
.icon-job-running {
.pulsate();
}
@@ -1025,7 +1029,6 @@ input[type="checkbox"].checkbox-no-label {
.icon-job-pending,
.icon-job-waiting,
.icon-job-new,
- .icon-job-skipped,
.icon-job-no-matching-hosts {
color: @grey;
opacity: 0.45;
diff --git a/awx/ui/static/less/job-details.less b/awx/ui/static/less/job-details.less
index abdec4de73..fbec2bd10e 100644
--- a/awx/ui/static/less/job-details.less
+++ b/awx/ui/static/less/job-details.less
@@ -387,6 +387,60 @@ svg text.percent{
font-weight: bold;
}
+#event-viewer-dialog {
+ padding-bottom: 5px;
+ padding-top: 10px;
+ padding-left: 5px;
+ padding-right: 5px;
+ overflow: hidden;
+ .results {
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ }
+ .spacer {
+ height: 60px;
+ }
+ table {
+ border-collapse: collapse;
+ width: 100%;
+ border-bottom: 1px solid @well-border;
+ }
+ tr {
+ border-top: 1px solid @well-border;
+ /*border-bottom: 1px solid @well-border;*/
+ }
+ tr:first-child {
+ border-top: none;
+ }
+ .key {
+ vertical-align: top;
+ padding: 3px;
+ font-weight: 600;
+ }
+ .value {
+ padding: 3px;
+ i {
+ font-size: 12px;
+ }
+ }
+ .nested-table {
+ border: none;
+ padding: 0;
+ table {
+ border-top: none;
+ border-bottom: none;
+ width: auto;
+ tr:first-child {
+ border-top: none;
+ }
+ .key {
+ font-weight: 400;
+ }
+ }
+ }
+}
+
@media (max-width: 767px) {
#job-detail-container {
#job-status-form {
diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html
index 9999715852..66a837c91c 100644
--- a/awx/ui/static/partials/job_detail.html
+++ b/awx/ui/static/partials/job_detail.html
@@ -151,10 +151,9 @@
-
-
+
{{ result.msg }}
@@ -226,8 +225,8 @@