add onExit & onEnter hooks to $stateExtender, raze HostEventViewer and replace with host-events module, resolves #1132

This commit is contained in:
Leigh Johnson 2016-03-19 19:36:15 -04:00
parent 526a6ec7dd
commit 7c1efea037
14 changed files with 388 additions and 347 deletions

View File

@ -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')
};

View File

@ -177,7 +177,6 @@ var tower = angular.module('Tower', [
'StandardOutHelper',
'LogViewerOptionsDefinition',
'EventViewerHelper',
'HostEventsViewerHelper',
'JobDetailHelper',
'SocketIO',
'lrInfiniteScroll',

View File

@ -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,

View File

@ -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&not__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 });
});
};
}]);

View File

@ -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: #848992;
background-color: #EBEBEB;
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;
}

View File

@ -0,0 +1,184 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
'JobDetailService', 'CreateSelect2', 'PaginateInit',
function($stateParams, $scope, $rootScope, $state, Wait,
JobDetailService, CreateSelect2, PaginateInit){
$scope.search = function(){
Wait('start');
if ($scope.searchStr == undefined){
return
}
// The API treats params as AND query
// We should discuss the possibility of an OR array
// search play description
/*
JobDetailService.getRelatedJobEvents($stateParams.id, {
play: $scope.searchStr})
.success(function(res){
results.push(res.results);
});
*/
// search host name
JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $scope.searchStr})
.success(function(res){
$scope.results = res.results;
Wait('Stop')
});
// search task
/*
JobDetailService.getRelatedJobEvents($stateParams.id, {
task: $scope.searchStr})
.success(function(res){
results.push(res.results);
});
*/
};
$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})
.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'
// add param changed: false if 'ok' shouldn't display changed hosts
})
.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;
PaginateInit({ scope: $scope, list: defaultUrl });
Wait('stop');
$('#HostEvents').modal('show');
});;
}
else{
Wait('start');
JobDetailService.getRelatedJobEvents($stateParams.id, {host_name: $stateParams.hostName})
.success(function(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();
}];

View File

@ -0,0 +1,59 @@
<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-times"></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 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 class="HostEvents-table--header">STATUS</th>
<th class="HostEvents-table--header">HOST</th>
<th class="HostEvents-table--header">PLAY</th>
<th class="HostEvents-table--header">TASK</th>
<!-- result rows -->
<tr class="HostEvents-table--row" ng-repeat="event in results track by $index">
<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>
</table>
</div>
</div>
<div class="modal-footer">
<!-- pagination -->
<button ng-click="goBack()" class="btn btn-default pull-right HostEvents-close">OK</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
export default {
name: 'jobDetail.hostEvents',
url: '/host-events/:hostName?:filter',
controller: 'HostEventsController',
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();
}]
}
};

View 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)
}]);

View File

@ -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');
};

View File

@ -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.hostEvents({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.hostEvents({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.hostEvents({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.hostEvents({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.hostEvents({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>

View File

@ -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) {

View File

@ -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
});
}
};