mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 02:31:03 -03:30
Job detail page
Added dialog for viewing host results and a bunch of formatting. Linked host summary to job_host_summaries page (yes, that still exists). Host results now shows 'skipped' hosts with a blue icon rather than green.
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest,
|
function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest,
|
||||||
ProcessErrors, SelectPlay, SelectTask, Socket, GetElapsed, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList,
|
ProcessErrors, SelectPlay, SelectTask, Socket, GetElapsed, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList,
|
||||||
JobIsFinished, SetTaskStyles, DigestEvent, UpdateDOM) {
|
JobIsFinished, SetTaskStyles, DigestEvent, UpdateDOM, ViewHostResults) {
|
||||||
|
|
||||||
ClearScope();
|
ClearScope();
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
|
|||||||
event = data.results[idx];
|
event = data.results[idx];
|
||||||
task.hostResults[event.id] = {
|
task.hostResults[event.id] = {
|
||||||
id: 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,
|
host_id: event.host,
|
||||||
task_id: event.parent,
|
task_id: event.parent,
|
||||||
name: event.event_data.host,
|
name: event.event_data.host,
|
||||||
@@ -1044,13 +1044,16 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.viewEvent = function(event_id) {
|
scope.viewHostResults = function(id) {
|
||||||
$log.debug(event_id);
|
ViewHostResults({
|
||||||
|
scope: scope,
|
||||||
|
id: id
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JobDetailController.$inject = [ '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath',
|
JobDetailController.$inject = [ '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath',
|
||||||
'Wait', 'Rest', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'FilterAllByHostName', 'DrawGraph',
|
'Wait', 'Rest', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'FilterAllByHostName', 'DrawGraph',
|
||||||
'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM'
|
'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'ViewHostResults'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
|
angular.module('JobDetailHelper', ['Utilities', 'RestServices', 'ModalDialog'])
|
||||||
|
|
||||||
.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult',
|
.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult',
|
||||||
'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', 'AddNewTask',
|
'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', 'AddNewTask',
|
||||||
@@ -716,7 +716,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
data.results.forEach(function(event) {
|
data.results.forEach(function(event) {
|
||||||
scope.hostResults.push({
|
scope.hostResults.push({
|
||||||
id: 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,
|
host_id: event.host,
|
||||||
task_id: event.parent,
|
task_id: event.parent,
|
||||||
name: event.event_data.host,
|
name: event.event_data.host,
|
||||||
@@ -1089,4 +1089,160 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
msg: 'Call to ' + url + '. GET returned: ' + status });
|
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 += "<table class=\"object-list\">\n";
|
||||||
|
html += "<tbody>\n";
|
||||||
|
keys = Object.keys(obj).sort();
|
||||||
|
keys.forEach(function(key) {
|
||||||
|
if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
||||||
|
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value";
|
||||||
|
html += (key === "results" || key === "stdout" || key === "stderr") ? " mono-space" : "";
|
||||||
|
html += "\">";
|
||||||
|
html += (key === "status") ? "<i class=\"fa icon-job-" + obj[key] + "\"></i> " + obj[key] : obj[key];
|
||||||
|
html += "</td></tr>\n";
|
||||||
|
}
|
||||||
|
else if (obj[key] === null || obj[key] === undefined) {
|
||||||
|
// html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">null</td></tr>\n";
|
||||||
|
}
|
||||||
|
else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
|
||||||
|
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value";
|
||||||
|
html += (key === "results" || key === "stdout" || key === "stderr") ? " mono-space" : "";
|
||||||
|
html += "\">";
|
||||||
|
obj[key].forEach(function(row) {
|
||||||
|
html += "<p>" + row + "</p>";
|
||||||
|
});
|
||||||
|
html += "</td></tr>\n";
|
||||||
|
}
|
||||||
|
else if (typeof obj[key] === "object") {
|
||||||
|
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"nested-table\">\n" + parseJSON(obj[key]) + "</td></tr>\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html += "</tbody>\n";
|
||||||
|
html += "</table>\n";
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function parseJSON(obj) {
|
||||||
|
var html="", key;
|
||||||
|
html += "<ul class=\"object-list\">\n";
|
||||||
|
for(key in obj) {
|
||||||
|
if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
||||||
|
html += "<li><div class=\"key\">" + key + ":</div><div class=\"value\">" + obj[key] + "</div></li>\n";
|
||||||
|
}
|
||||||
|
if (obj[key] === null || obj[key] === undefined) {
|
||||||
|
html += "<li><div class=\"key\">" + key + ":</div><div class=\"value\">null</div></li>\n";
|
||||||
|
}
|
||||||
|
if (typeof obj[key] === "object") {
|
||||||
|
html += "<li><div class=\"key\">" + key + ":</div>" + parseJSON(obj[key]) + "</li>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html += "</ul>\n";
|
||||||
|
return html;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*function parseJSON(obj) {
|
||||||
|
var html="", key;
|
||||||
|
for(key in obj) {
|
||||||
|
html += "<div class=\"row object-list\">\n";
|
||||||
|
if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
||||||
|
html += "<div class=\"col-lg-4 col-md-4 key\">" + key + ":</div><div class=\"col-lg-8 col-md-8 value\">" + obj[key] + "</div>\n";
|
||||||
|
}
|
||||||
|
if (obj[key] === null || obj[key] === undefined) {
|
||||||
|
html += "<div class=\"col-lg-4 col-md-4 key\">" + key + ":</div><div class=\"col-lg-8 col-md-8 value\">null</div>\n";
|
||||||
|
}
|
||||||
|
if (typeof obj[key] === "object") {
|
||||||
|
html += "<div class=\"col-lg-4 col-md-4 key\">" + key + ":</div><div class=\"col-lg-8 col-md-8\">" + parseJSON(obj[key]) + "</div>\n";
|
||||||
|
}
|
||||||
|
html += "</div>\n";
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (my_scope.removeDataReady) {
|
||||||
|
my_scope.removeDataReady();
|
||||||
|
}
|
||||||
|
my_scope.removeDataReady = my_scope.$on('DataReady', function(e, event_data, host) {
|
||||||
|
//var html = "<div class=\"title-section\">\n";
|
||||||
|
//html += "<h4>" + host.name + "</h4>\n";
|
||||||
|
//html += (host.description && host.description !== "imported") ? "<h5>" + host.description + "</h5>" : "";
|
||||||
|
//html += "<p>Event " + id + " details:</p>\n";
|
||||||
|
//html += "</div>\n";
|
||||||
|
var html = "<div class=\"results\">\n";
|
||||||
|
event_data.host = host.name;
|
||||||
|
html += parseJSON(event_data);
|
||||||
|
html += "<div class=\"spacer\"></div>\n";
|
||||||
|
html += "</div>\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();
|
||||||
|
};
|
||||||
|
};
|
||||||
}]);
|
}]);
|
||||||
@@ -975,7 +975,8 @@ input[type="checkbox"].checkbox-no-label {
|
|||||||
.icon-job-success:before,
|
.icon-job-success:before,
|
||||||
.icon-job-successful:before,
|
.icon-job-successful:before,
|
||||||
.icon-job-changed:before,
|
.icon-job-changed:before,
|
||||||
.icon-job-ok:before {
|
.icon-job-ok:before,
|
||||||
|
.icon-job-skipped:before {
|
||||||
content: "\f111";
|
content: "\f111";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,7 +992,6 @@ input[type="checkbox"].checkbox-no-label {
|
|||||||
.icon-job-waiting:before,
|
.icon-job-waiting:before,
|
||||||
.icon-job-new:before,
|
.icon-job-new:before,
|
||||||
.icon-job-none:before,
|
.icon-job-none:before,
|
||||||
.icon-job-skipped:before,
|
|
||||||
.icon-job-no-matching-hosts:before {
|
.icon-job-no-matching-hosts:before {
|
||||||
content: "\f10c";
|
content: "\f10c";
|
||||||
}
|
}
|
||||||
@@ -1004,6 +1004,10 @@ input[type="checkbox"].checkbox-no-label {
|
|||||||
color: @green;
|
color: @green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-job-skipped {
|
||||||
|
color: @skipped;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-job-running {
|
.icon-job-running {
|
||||||
.pulsate();
|
.pulsate();
|
||||||
}
|
}
|
||||||
@@ -1025,7 +1029,6 @@ input[type="checkbox"].checkbox-no-label {
|
|||||||
.icon-job-pending,
|
.icon-job-pending,
|
||||||
.icon-job-waiting,
|
.icon-job-waiting,
|
||||||
.icon-job-new,
|
.icon-job-new,
|
||||||
.icon-job-skipped,
|
|
||||||
.icon-job-no-matching-hosts {
|
.icon-job-no-matching-hosts {
|
||||||
color: @grey;
|
color: @grey;
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
|
|||||||
@@ -387,6 +387,60 @@ svg text.percent{
|
|||||||
font-weight: bold;
|
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) {
|
@media (max-width: 767px) {
|
||||||
#job-detail-container {
|
#job-detail-container {
|
||||||
#job-status-form {
|
#job-status-form {
|
||||||
|
|||||||
@@ -151,10 +151,9 @@
|
|||||||
<div id="hosts-table-detail" aw-custom-scroll data-on-total-scroll="HostDetailOnTotalScroll"
|
<div id="hosts-table-detail" aw-custom-scroll data-on-total-scroll="HostDetailOnTotalScroll"
|
||||||
data-on-total-scroll-back="HostDetailOnTotalScrollBack" class="table-detail">
|
data-on-total-scroll-back="HostDetailOnTotalScrollBack" class="table-detail">
|
||||||
<div id="hosts-table-detail-inner">
|
<div id="hosts-table-detail-inner">
|
||||||
<div class="row" ng-repeat="result in results = (hostResults | filter:{ status : searchAllStatus}) track by $index">
|
<div class="row cursor-pointer" ng-repeat="result in results = (hostResults | filter:{ status : searchAllStatus}) track by $index" ng-click="viewHostResults(result.id)">
|
||||||
<!-- ng-repeat="result in results = (hostResults | filter:{ status : searchAllStatus} })" -->
|
|
||||||
<div class="col-lg-7 col-md-7 col-sm-7 col-xs-7 status-column">
|
<div class="col-lg-7 col-md-7 col-sm-7 col-xs-7 status-column">
|
||||||
<a href="" ng-click="viewEvent(result.id)" aw-tool-tip="Event Id: {{ result.id }} Status: {{ result.status }}. Click for details" data-placement="top"><i class="fa icon-job-{{ result.status }}"></i> {{ result.name }}</a>
|
<a href="" aw-tool-tip="Event Id: {{ result.id }} Status: {{ result.status }}. Click for details" data-placement="top"><i class="fa icon-job-{{ result.status }}"></i> {{ result.name }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-5 col-md-5 col-sm-5 col-xs-5">
|
<div class="col-lg-5 col-md-5 col-sm-5 col-xs-5">
|
||||||
{{ result.msg }}
|
{{ result.msg }}
|
||||||
@@ -226,8 +225,8 @@
|
|||||||
<div id="hosts-summary-table" class="table-detail" aw-custom-scroll data-on-total-scroll="HostSummaryOnTotalScroll"
|
<div id="hosts-summary-table" class="table-detail" aw-custom-scroll data-on-total-scroll="HostSummaryOnTotalScroll"
|
||||||
data-on-total-scroll-back="HostSummaryOnTotalScrollBack">
|
data-on-total-scroll-back="HostSummaryOnTotalScrollBack">
|
||||||
<div class="row" ng-repeat="host in summaryList = (hosts | filter:{ status : searchAllStatus}) track by $index" id="{{ host.id }}">
|
<div class="row" ng-repeat="host in summaryList = (hosts | filter:{ status : searchAllStatus}) track by $index" id="{{ host.id }}">
|
||||||
<div class="name col-lg-6 col-md-6 col-sm-6 col-xs-6"><a href="/#/home/hosts/?id={{ host.id }}"
|
<div class="name col-lg-6 col-md-6 col-sm-6 col-xs-6"><a ng-href="/#/job_events/{{ job.id }}/?host={{ host.name }}" target="_blank"
|
||||||
aw-tool-tip="Click to edit host" data-placement="top">{{ host.name }}</a>
|
aw-tool-tip="View all events for this host.<br />Opens in new tab or window." data-placement="top">{{ host.name }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 badge-column">
|
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 badge-column">
|
||||||
<a href="" aw-tool-tip="OK" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
<a href="" aw-tool-tip="OK" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
||||||
@@ -257,4 +256,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="event-viewer-dialog" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user