mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 23:51:09 +03:00
Merge pull request #1284 from leigh-johnson/JobDetailModalRestyle
Remove HostEventsViewer and refactor into job-details/host-events module
This commit is contained in:
commit
817ea4861b
@ -8,5 +8,10 @@ export default {
|
||||
ncyBreadcrumb: {
|
||||
label: "ABOUT"
|
||||
},
|
||||
onExit: function(){
|
||||
// hacky way to handle user browsing away via URL bar
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open');
|
||||
},
|
||||
templateUrl: templateUrl('about/about')
|
||||
};
|
||||
|
@ -181,7 +181,6 @@ var tower = angular.module('Tower', [
|
||||
'StandardOutHelper',
|
||||
'LogViewerOptionsDefinition',
|
||||
'EventViewerHelper',
|
||||
'HostEventsViewerHelper',
|
||||
'JobDetailHelper',
|
||||
'SocketIO',
|
||||
'lrInfiniteScroll',
|
||||
|
@ -12,7 +12,6 @@ import Credentials from "./helpers/Credentials";
|
||||
import EventViewer from "./helpers/EventViewer";
|
||||
import Events from "./helpers/Events";
|
||||
import Groups from "./helpers/Groups";
|
||||
import HostEventsViewer from "./helpers/HostEventsViewer";
|
||||
import Hosts from "./helpers/Hosts";
|
||||
import JobDetail from "./helpers/JobDetail";
|
||||
import JobSubmission from "./helpers/JobSubmission";
|
||||
@ -46,7 +45,6 @@ export
|
||||
EventViewer,
|
||||
Events,
|
||||
Groups,
|
||||
HostEventsViewer,
|
||||
Hosts,
|
||||
JobDetail,
|
||||
JobSubmission,
|
||||
|
@ -1,287 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name helpers.function:HostEventsViewer
|
||||
* @description view a list of events for a given job and host
|
||||
*/
|
||||
|
||||
export default
|
||||
angular.module('HostEventsViewerHelper', ['ModalDialog', 'Utilities', 'EventViewerHelper'])
|
||||
|
||||
.factory('HostEventsViewer', ['$log', '$compile', 'CreateDialog', 'Wait', 'GetBasePath', 'Empty', 'GetEvents', 'EventViewer',
|
||||
function($log, $compile, CreateDialog, Wait, GetBasePath, Empty, GetEvents, EventViewer) {
|
||||
return function(params) {
|
||||
var parent_scope = params.scope,
|
||||
scope = parent_scope.$new(true),
|
||||
job_id = params.job_id,
|
||||
url = params.url,
|
||||
title = params.title, //optional
|
||||
fixHeight, buildTable,
|
||||
lastID, setStatus, buildRow, status;
|
||||
|
||||
// initialize the status dropdown
|
||||
scope.host_events_status_options = [
|
||||
{ value: "all", name: "All" },
|
||||
{ value: "changed", name: "Changed" },
|
||||
{ value: "failed", name: "Failed" },
|
||||
{ value: "ok", name: "OK" },
|
||||
{ value: "unreachable", name: "Unreachable" }
|
||||
];
|
||||
scope.host_events_search_name = params.name;
|
||||
status = (params.status) ? params.status : 'all';
|
||||
scope.host_events_status_options.every(function(opt, idx) {
|
||||
if (opt.value === status) {
|
||||
scope.host_events_search_status = scope.host_events_status_options[idx];
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!scope.host_events_search_status) {
|
||||
scope.host_events_search_status = scope.host_events_status_options[0];
|
||||
}
|
||||
|
||||
$log.debug('job_id: ' + job_id + ' url: ' + url + ' title: ' + title + ' name: ' + name + ' status: ' + status);
|
||||
|
||||
scope.eventsSearchActive = (scope.host_events_search_name) ? true : false;
|
||||
|
||||
if (scope.removeModalReady) {
|
||||
scope.removeModalReady();
|
||||
}
|
||||
scope.removeModalReady = scope.$on('ModalReady', function() {
|
||||
scope.hostViewSearching = false;
|
||||
$('#host-events-modal-dialog').dialog('open');
|
||||
});
|
||||
|
||||
if (scope.removeJobReady) {
|
||||
scope.removeJobReady();
|
||||
}
|
||||
scope.removeEventReady = scope.$on('EventsReady', function(e, data, maxID) {
|
||||
var elem, html;
|
||||
|
||||
lastID = maxID;
|
||||
html = buildTable(data);
|
||||
$('#host-events').html(html);
|
||||
elem = angular.element(document.getElementById('host-events-modal-dialog'));
|
||||
$compile(elem)(scope);
|
||||
|
||||
CreateDialog({
|
||||
scope: scope,
|
||||
width: 675,
|
||||
height: 600,
|
||||
minWidth: 450,
|
||||
callback: 'ModalReady',
|
||||
id: 'host-events-modal-dialog',
|
||||
onResizeStop: fixHeight,
|
||||
title: ( (title) ? title : 'Host Events' ),
|
||||
onClose: function() {
|
||||
try {
|
||||
scope.$destroy();
|
||||
}
|
||||
catch(e) {
|
||||
//ignore
|
||||
}
|
||||
},
|
||||
onOpen: function() {
|
||||
fixHeight();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (scope.removeRefreshHTML) {
|
||||
scope.removeRefreshHTML();
|
||||
}
|
||||
scope.removeRefreshHTML = scope.$on('RefreshHTML', function(e, data) {
|
||||
var elem, html = buildTable(data);
|
||||
$('#host-events').html(html);
|
||||
scope.hostViewSearching = false;
|
||||
elem = angular.element(document.getElementById('host-events'));
|
||||
$compile(elem)(scope);
|
||||
});
|
||||
|
||||
setStatus = function(result) {
|
||||
var msg = '', status = 'ok', status_text = 'OK';
|
||||
if (!result.task && result.event_data && result.event_data.res && result.event_data.res.ansible_facts) {
|
||||
result.task = "Gathering Facts";
|
||||
}
|
||||
if (result.event === "runner_on_no_hosts") {
|
||||
msg = "No hosts remaining";
|
||||
}
|
||||
if (result.event === 'runner_on_unreachable') {
|
||||
status = 'unreachable';
|
||||
status_text = 'Unreachable';
|
||||
}
|
||||
else if (result.failed) {
|
||||
status = 'failed';
|
||||
status_text = 'Failed';
|
||||
}
|
||||
else if (result.changed) {
|
||||
status = 'changed';
|
||||
status_text = 'Changed';
|
||||
}
|
||||
if (result.event_data.res && result.event_data.res.msg) {
|
||||
msg = result.event_data.res.msg;
|
||||
}
|
||||
result.msg = msg;
|
||||
result.status = status;
|
||||
result.status_text = status_text;
|
||||
return result;
|
||||
};
|
||||
|
||||
buildRow = function(res) {
|
||||
var html = '';
|
||||
html += "<tr>\n";
|
||||
html += "<td class=\"col-md-3\"><a href=\"\" ng-click=\"showDetails(" + res.id + ")\" aw-tool-tip=\"Click to view details\" data-placement=\"top\"><i class=\"fa icon-job-" + res.status + "\"></i> " + res.status_text + "</a></td>\n";
|
||||
html += "<td class=\"col-md=3\" ng-non-bindable>" + res.host_name + "</td>\n";
|
||||
html += "<td class=\"col-md-3\" ng-non-bindable>" + res.play + "</td>\n";
|
||||
html += "<td class=\"col-md-3\" ng-non-bindable>" + res.task + "</td>\n";
|
||||
html += "</tr>";
|
||||
return html;
|
||||
};
|
||||
|
||||
buildTable = function(data) {
|
||||
var html = "<table class=\"table\">\n";
|
||||
html += "<tbody>\n";
|
||||
data.results.forEach(function(result) {
|
||||
var res = setStatus(result);
|
||||
html += buildRow(res);
|
||||
});
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
return html;
|
||||
};
|
||||
|
||||
fixHeight = function() {
|
||||
var available_height = $('#host-events-modal-dialog').height() - $('#host-events-modal-dialog #search-form').height() - $('#host-events-modal-dialog #fixed-table-header').height();
|
||||
$('#host-events').height(available_height);
|
||||
$log.debug('set height to: ' + available_height);
|
||||
// Check width and reset search fields
|
||||
if ($('#host-events-modal-dialog').width() <= 450) {
|
||||
$('#host-events-modal-dialog #status-field').css({'margin-left': '7px'});
|
||||
}
|
||||
else {
|
||||
$('#host-events-modal-dialog #status-field').css({'margin-left': '15px'});
|
||||
}
|
||||
};
|
||||
|
||||
GetEvents({
|
||||
url: url,
|
||||
scope: scope,
|
||||
callback: 'EventsReady'
|
||||
});
|
||||
|
||||
scope.modalOK = function() {
|
||||
$('#host-events-modal-dialog').dialog('close');
|
||||
scope.$destroy();
|
||||
};
|
||||
|
||||
scope.searchEvents = function() {
|
||||
scope.eventsSearchActive = (scope.host_events_search_name) ? true : false;
|
||||
GetEvents({
|
||||
scope: scope,
|
||||
url: url,
|
||||
callback: 'RefreshHTML'
|
||||
});
|
||||
};
|
||||
|
||||
scope.searchEventKeyPress = function(e) {
|
||||
if (e.keyCode === 13) {
|
||||
scope.searchEvents();
|
||||
}
|
||||
};
|
||||
|
||||
scope.showDetails = function(id) {
|
||||
EventViewer({
|
||||
scope: parent_scope,
|
||||
url: GetBasePath('jobs') + job_id + '/job_events/?id=' + id,
|
||||
});
|
||||
};
|
||||
|
||||
if (scope.removeEventsScrollDownBuild) {
|
||||
scope.removeEventsScrollDownBuild();
|
||||
}
|
||||
scope.removeEventsScrollDownBuild = scope.$on('EventScrollDownBuild', function(e, data, maxID) {
|
||||
var elem, html = '';
|
||||
lastID = maxID;
|
||||
data.results.forEach(function(result) {
|
||||
var res = setStatus(result);
|
||||
html += buildRow(res);
|
||||
});
|
||||
if (html) {
|
||||
$('#host-events table tbody').append(html);
|
||||
elem = angular.element(document.getElementById('host-events'));
|
||||
$compile(elem)(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scope.hostEventsScrollDown = function() {
|
||||
GetEvents({
|
||||
scope: scope,
|
||||
url: url,
|
||||
gt: lastID,
|
||||
callback: 'EventScrollDownBuild'
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('GetEvents', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) {
|
||||
return function(params) {
|
||||
var url = params.url,
|
||||
scope = params.scope,
|
||||
gt = params.gt,
|
||||
callback = params.callback;
|
||||
|
||||
if (scope.host_events_search_name) {
|
||||
url += '?host_name=' + scope.host_events_search_name;
|
||||
}
|
||||
else {
|
||||
url += '?host_name__isnull=false';
|
||||
}
|
||||
|
||||
if (scope.host_events_search_status.value === 'changed') {
|
||||
url += '&event__icontains=runner&changed=true';
|
||||
}
|
||||
else if (scope.host_events_search_status.value === 'failed') {
|
||||
url += '&event__icontains=runner&failed=true';
|
||||
}
|
||||
else if (scope.host_events_search_status.value === 'ok') {
|
||||
url += '&event=runner_on_ok&changed=false';
|
||||
}
|
||||
else if (scope.host_events_search_status.value === 'unreachable') {
|
||||
url += '&event=runner_on_unreachable';
|
||||
}
|
||||
else if (scope.host_events_search_status.value === 'all') {
|
||||
url += '&event__icontains=runner¬__event=runner_on_skipped';
|
||||
}
|
||||
|
||||
if (gt) {
|
||||
// used for endless scroll
|
||||
url += '&id__gt=' + gt;
|
||||
}
|
||||
|
||||
url += '&page_size=50&order=id';
|
||||
|
||||
scope.hostViewSearching = true;
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success(function(data) {
|
||||
var lastID;
|
||||
scope.hostViewSearching = false;
|
||||
if (data.results.length > 0) {
|
||||
lastID = data.results[data.results.length - 1].id;
|
||||
}
|
||||
scope.$emit(callback, data, lastID);
|
||||
})
|
||||
.error(function(data, status) {
|
||||
scope.hostViewSearching = false;
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get events ' + url + '. GET returned: ' + status });
|
||||
});
|
||||
};
|
||||
}]);
|
@ -0,0 +1,82 @@
|
||||
@import "awx/ui/client/src/shared/branding/colors.less";
|
||||
@import "awx/ui/client/src/shared/branding/colors.default.less";
|
||||
|
||||
.HostEvents .modal-footer{
|
||||
border: 0;
|
||||
margin-top: 0px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.HostEvents-status--ok{
|
||||
color: @green;
|
||||
}
|
||||
.HostEvents-status--unreachable{
|
||||
color: @unreachable;
|
||||
}
|
||||
.HostEvents-status--changed{
|
||||
color: @changed;
|
||||
}
|
||||
.HostEvents-status--failed{
|
||||
color: @warning;
|
||||
}
|
||||
.HostEvents-status--skipped{
|
||||
color: @skipped;
|
||||
}
|
||||
.HostEvents-search--form{
|
||||
max-width: 420px;
|
||||
display: inline-block;
|
||||
}
|
||||
.HostEvents-close{
|
||||
width: 70px;
|
||||
}
|
||||
.HostEvents-filter--form{
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
float: right;
|
||||
display: inline-block;
|
||||
}
|
||||
.HostEvents .modal-body{
|
||||
padding: 20px;
|
||||
}
|
||||
.HostEvents .select2-container{
|
||||
text-transform: capitalize;
|
||||
max-width: 220px;
|
||||
float: right;
|
||||
}
|
||||
.HostEvents-form--container{
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.HostEvents-title{
|
||||
color: @default-interface-txt;
|
||||
font-weight: 600;
|
||||
}
|
||||
.HostEvents-status i {
|
||||
padding-right: 10px;
|
||||
}
|
||||
.HostEvents-table--header {
|
||||
height: 30px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
color: @default-interface-txt;
|
||||
background-color: @default-list-header-bg;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-bottom-width: 0px;
|
||||
}
|
||||
.HostEvents-table--header:first-of-type{
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
.HostEvents-table--header:last-of-type{
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
.HostEvents-table--row{
|
||||
color: @default-data-txt;
|
||||
border: 0 !important;
|
||||
}
|
||||
.HostEvents-table--row:nth-child(odd){
|
||||
background: @default-tertiary-bg;
|
||||
}
|
||||
.HostEvents-table--cell{
|
||||
border: 0 !important;
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
|
||||
'JobDetailService', 'CreateSelect2',
|
||||
function($stateParams, $scope, $rootScope, $state, Wait,
|
||||
JobDetailService, CreateSelect2){
|
||||
|
||||
// pagination not implemented yet, but it'll depend on this
|
||||
$scope.page_size = $stateParams.page_size;
|
||||
|
||||
$scope.activeFilter = $stateParams.filter || null;
|
||||
|
||||
$scope.search = function(){
|
||||
Wait('start');
|
||||
if ($scope.searchStr == undefined){
|
||||
return
|
||||
}
|
||||
//http://docs.ansible.com/ansible-tower/latest/html/towerapi/intro.html#filtering
|
||||
// SELECT WHERE host_name LIKE str OR WHERE play LIKE str OR WHERE task LIKE str AND host_name NOT ""
|
||||
// selecting non-empty host_name fields prevents us from displaying non-runner events, like playbook_on_task_start
|
||||
JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
or__host_name__icontains: $scope.searchStr,
|
||||
or__play__icontains: $scope.searchStr,
|
||||
or__task__icontains: $scope.searchStr,
|
||||
not__host_name: "" ,
|
||||
page_size: $scope.pageSize})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop')
|
||||
});
|
||||
};
|
||||
|
||||
$scope.filters = ['all', 'changed', 'failed', 'ok', 'unreachable', 'skipped'];
|
||||
|
||||
var filter = function(filter){
|
||||
Wait('start');
|
||||
if (filter == 'all'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
page_size: $scope.pageSize})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
// handle runner cases
|
||||
if (filter == 'skipped'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
event: 'runner_on_skipped'})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
if (filter == 'unreachable'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
event: 'runner_on_unreachable'})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
if (filter == 'ok'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
event: 'runner_on_ok',
|
||||
changed: false
|
||||
})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
// handle convience properties .changed .failed
|
||||
if (filter == 'changed'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
changed: true})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
if (filter == 'failed'){
|
||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||
host_name: $stateParams.hostName,
|
||||
failed: true})
|
||||
.success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// watch select2 for changes
|
||||
$('.HostEvents-select').on("select2:select", function (e) {
|
||||
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(){
|
||||
// create filter dropdown
|
||||
CreateSelect2({
|
||||
element: '.HostEvents-select',
|
||||
multiple: false
|
||||
});
|
||||
// process the filter if one was passed
|
||||
if ($stateParams.filter){
|
||||
filter($stateParams.filter).success(function(res){
|
||||
$scope.results = res.results;
|
||||
Wait('stop');
|
||||
$('#HostEvents').modal('show');
|
||||
});;
|
||||
}
|
||||
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.goBack = function(){
|
||||
// go back to the job details state
|
||||
// we're leaning on $stateProvider's onExit to close the modal
|
||||
$state.go('jobDetail');
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
}];
|
@ -0,0 +1,64 @@
|
||||
<div id="HostEvents" class="HostEvents modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="HostEvents-header">
|
||||
<span class="HostEvents-title">HOST EVENTS</span>
|
||||
<!-- Close -->
|
||||
<button ng-click="goBack()" type="button" class="close">
|
||||
<i class="fa fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="HostEvents-form--container">
|
||||
<form ng-submit="search()" class="form-inline HostEvents-search--form">
|
||||
<!-- Search -->
|
||||
<div class="form-group" >
|
||||
<div class="input-group">
|
||||
<input type="text" ng-model="searchStr" class="form-control" placeholder="SEARCH">
|
||||
<span ng-click="search()" type="submit" class="input-group-addon btn btn-default"><i class="fa fa-search"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<select class="HostEvents-select">
|
||||
<option ng-selected="filter == activeFilter" class="HostEvents-select--option" value="{{filter}}" ng-repeat="filter in filters">{{filter}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- event results table -->
|
||||
<div class="table-responsive">
|
||||
<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 -->
|
||||
<tr class="HostEvents-table--row" ng-repeat="event in results track by $index" modal-paginate="event in results | page_size: page_size">
|
||||
<td class=HostEvents-table--cell>
|
||||
<!-- status circles -->
|
||||
<a class="HostEvents-status">
|
||||
<i class="fa fa-circle" ng-class="processStatus(event, $index)"></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>
|
||||
<tr ng-show="results.length == 0" class="HostEvents-table--row">
|
||||
<td class=HostEvents-table--cell>
|
||||
No results were found.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<!-- pagination -->
|
||||
<!-- close -->
|
||||
<button ng-click="goBack()" class="btn btn-default pull-right HostEvents-close">OK</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,30 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'jobDetail.host-events',
|
||||
url: '/host-events/:hostName?:filter',
|
||||
controller: 'HostEventsController',
|
||||
params: {
|
||||
page_size: 10
|
||||
},
|
||||
templateUrl: templateUrl('job-detail/host-events/host-events'),
|
||||
onExit: function(){
|
||||
// close the modal
|
||||
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
|
||||
$('#HostEvents').modal('hide');
|
||||
// hacky way to handle user browsing away via URL bar
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open');
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}]
|
||||
}
|
||||
};
|
15
awx/ui/client/src/job-detail/host-events/main.js
Normal file
15
awx/ui/client/src/job-detail/host-events/main.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import route from './host-events.route';
|
||||
import controller from './host-events.controller';
|
||||
|
||||
export default
|
||||
angular.module('jobDetail.hostEvents', [])
|
||||
.controller('HostEventsController', controller)
|
||||
.run(['$stateExtender', function($stateExtender){
|
||||
$stateExtender.addState(route)
|
||||
}]);
|
@ -15,7 +15,7 @@ export default
|
||||
'$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait',
|
||||
'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed',
|
||||
'DrawGraph', 'LoadHostSummary', 'ReloadHostSummaryList',
|
||||
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun', 'HostEventsViewer',
|
||||
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
|
||||
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit',
|
||||
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
|
||||
'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EventViewer',
|
||||
@ -25,7 +25,7 @@ export default
|
||||
SelectPlay, SelectTask, Socket, GetElapsed, DrawGraph,
|
||||
LoadHostSummary, ReloadHostSummaryList, JobIsFinished,
|
||||
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
|
||||
PlaybookRun, HostEventsViewer, LoadPlays, LoadTasks, LoadHosts,
|
||||
PlaybookRun, LoadPlays, LoadTasks, LoadHosts,
|
||||
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
|
||||
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, EventViewer
|
||||
) {
|
||||
@ -43,7 +43,7 @@ export default
|
||||
scope.parseType = 'yaml';
|
||||
scope.previousTaskFailed = false;
|
||||
$scope.stdoutFullScreen = false;
|
||||
|
||||
|
||||
scope.$watch('job_status', function(job_status) {
|
||||
if (job_status && job_status.explanation && job_status.explanation.split(":")[0] === "Previous Task Failed") {
|
||||
scope.previousTaskFailed = true;
|
||||
@ -1400,17 +1400,6 @@ export default
|
||||
}
|
||||
};
|
||||
|
||||
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');
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
<div class="tab-pane" id="jobs-detail">
|
||||
<div ng-cloak id="htmlTemplate" class="JobDetail">
|
||||
|
||||
<div ui-view></div>
|
||||
<!--beginning of job-detail-container (left side) -->
|
||||
<div id="job-detail-container" class="JobDetail-leftSide" ng-class="{'JobDetail-stdoutActionButton--active': stdoutFullScreen}">
|
||||
|
||||
<!--beginning of results-->
|
||||
<div id="job-results-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen">
|
||||
<div class="JobDetail-panelHeader">
|
||||
@ -423,13 +422,13 @@
|
||||
<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 href="" ng-click="hostEventsViewer(host.id, host.name)" aw-tool-tip="View events" data-placement="top">{{ host.name }}</a>
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id})" 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 href="" ng-click="hostEventsViewer(host.id, host.name, '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>
|
||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'changed')" aw-tool-tip="{{ host.changedTip }}" data-tip-watch="host.changedTip" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
|
||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'unreachable')" aw-tool-tip="{{ host.unreachableTip }}" data-tip-watch="host.unreachableTip" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
|
||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'failed')" aw-tool-tip="{{ host.failedTip }}" data-tip-watch="host.failedTip" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a>
|
||||
<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>
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'changed'})" aw-tool-tip="{{ host.changedTip }}" data-tip-watch="host.changedTip" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'unreachable'})" aw-tool-tip="{{ host.unreachableTip }}" data-tip-watch="host.unreachableTip" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
|
||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'failed'})" aw-tool-tip="{{ host.failedTip }}" data-tip-watch="host.failedTip" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="summaryList.length === 0 && waiting">
|
||||
@ -483,40 +482,6 @@
|
||||
|
||||
<div ng-include="'/static/partials/eventviewer.html'"></div>
|
||||
|
||||
<div id="host-events-modal-dialog" style="display:none;">
|
||||
<div id="search-form" class="form-inline">
|
||||
<div class="form-group" style="position:relative;">
|
||||
<label>Search</label>
|
||||
<div class="search-name" style="display:inline-block; position:relative;">
|
||||
<input type="text" class="form-control input-sm" id="host-events-search-name" ng-model="host_events_search_name" placeholder="Host name" ng-keypress="searchEventKeyPress($event)" >
|
||||
<div id="search-all-input-icons">
|
||||
<a class="search-icon" ng-show="!eventsSearchActive" ng-click="searchEvents()"><i class="fa fa-search"></i></a>
|
||||
<a class="search-icon" ng-show="eventsSearchActive" ng-click="host_events_search_name=''; searchEvents()"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="status-field">
|
||||
<label>Status</label>
|
||||
<select id="host-events-search-status" class="form-control input-sm" ng-model="host_events_search_status" name="host-events-search-name" ng-change="searchEvents()"
|
||||
ng-options="opt.name for opt in host_events_status_options track by opt.value"></select>
|
||||
</div>
|
||||
<div class="form-group" id="search-indicator" ng-show="hostViewSearching"><i class="fa fa-lg fa-spin fa-cog"></i></div>
|
||||
</div>
|
||||
<!-- lr-infinite-scroll="hostEventsTable" scroll-threshold="10" time-threshold="500" -->
|
||||
<div id="host-events-table">
|
||||
<table id="fixed-table-header" class="table">
|
||||
<thead>
|
||||
<tr><th class="col-md-3">Status</th>
|
||||
<th class="col-md-3">Host</th>
|
||||
<th class="col-md-3">Play</th>
|
||||
<th class="col-md-3">Task</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div id="host-events" lr-infinite-scroll="hostEventsScrollDown" scroll-threshold="10" time-threshold="500"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div>
|
||||
|
||||
<div ng-include="'/static/partials/schedule_dialog.html'"></div>
|
||||
|
@ -7,9 +7,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';
|
||||
|
||||
export default
|
||||
angular.module('jobDetail', [])
|
||||
angular.module('jobDetail', [
|
||||
hostEvents.name
|
||||
])
|
||||
.controller('JobDetailController', controller)
|
||||
.service('JobDetailService', service)
|
||||
.run(['$stateExtender', function($stateExtender) {
|
||||
|
@ -11,7 +11,9 @@ export default function($stateProvider){
|
||||
resolve: state.resolve,
|
||||
params: state.params,
|
||||
data: state.data,
|
||||
ncyBreadcrumb: state.ncyBreadcrumb
|
||||
ncyBreadcrumb: state.ncyBreadcrumb,
|
||||
onEnter: state.onEnter,
|
||||
onExit: state.onExit
|
||||
});
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user