Job detail page refactor

Job tasks now include an unreachable count and %. Change unreachable color from grey to a different shade of red. While live events are happening host name filter is disabled and status filter is available. Created a custom status filter that accounts for live event processing. While live event processing is active angular filtering on status is disabled for tasks and plays, otherwise no tasks or plays would show up. However, status filtering does apply to host results and host summaries during live event processing.
This commit is contained in:
Chris Houseknecht 2014-06-30 17:34:10 -04:00
parent 9da4b8f336
commit 826f2b681f
6 changed files with 103 additions and 62 deletions

View File

@ -135,7 +135,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
}
scope.removeHostSummaries = scope.$on('LoadHostSummaries', function() {
var url = scope.job.related.job_host_summaries + '?';
url += '&page_size=' + scope.hostSummariesMaxRows + '&order_by=host__name';
url += '&host__name__isnull=false&page_size=' + scope.hostSummariesMaxRows + '&order_by=host__name';
scope.jobData.hostSummaries = {};
@ -245,7 +245,12 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
.success(function(data) {
if (data.results.length > 0) {
lastEventId = data.results[data.results.length - 1].id;
scope.activeTask = data.results[0].id;
if (scope.liveEventProcessing) {
scope.activeTask = data.results[data.results.length - 1].id;
}
else {
scope.activeTask = data.results[0].id;
}
}
data.results.forEach(function(event, idx) {
var end, elapsed, status, status_text;
@ -294,6 +299,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
failedCount: (event.failed_count) ? event.failed_count : 0,
changedCount: (event.changed_count) ? event.changed_count : 0,
skippedCount: (event.skipped_count) ? event.skipped_count : 0,
unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0,
taskActiveClass: '',
hostResults: {}
};
@ -338,7 +344,12 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
.success( function(data) {
if (data.results.length > 0) {
lastEventId = data.results[data.results.length - 1].id;
scope.activePlay = data.results[0].id;
if (scope.liveEventProcessing) {
scope.activePlay = data.results[data.results.length - 1].id;
}
else {
scope.activePlay = data.results[0].id;
}
}
data.results.forEach(function(event, idx) {
var status, status_text, start, end, elapsed, ok, changed, failed, skipped;
@ -472,9 +483,16 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.job_status.status = (data.status === 'waiting' || data.status === 'new') ? 'pending' : data.status;
scope.job_status.started = data.started;
scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : "";
scope.job_status.finished = (data.status === 'successful' || data.status === 'failed' || data.status === 'error') ? data.finished : null;
scope.job_status.explanation = data.job_explanation;
if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') {
scope.job_status.finished = data.finsished;
scope.liveEventProcessing = false;
}
else {
scope.job_status.finished = null;
}
if (data.started && data.finished) {
scope.job_status.elapsed = GetElapsed({
start: data.started,
@ -484,7 +502,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
else {
scope.job_status.elapsed = '00:00:00';
}
scope.setSearchAll('host');
//scope.setSearchAll('host');
scope.$emit('LoadPlays', data.related.job_events);
if (!scope.credential_name) {
scope.$emit('GetCredentialNames', data);
@ -604,20 +622,6 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.adjustSize();
}, 500));
scope.setSearchAll = function(search) {
if (search === 'host') {
scope.search_all_label = 'Host';
scope.searchAllDisabled = false;
scope.search_all_placeholder = 'Search all by host name';
}
else {
scope.search_all_label = 'Failures';
scope.search_all_placeholder = 'Show failed events';
scope.searchAllDisabled = true;
scope.search_all_placeholder = '';
}
};
scope.selectPlay = function(id) {
scope.auto_scroll_plays = false;
SelectPlay({
@ -1139,13 +1143,15 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.searchAllStatus = '';
nxtPlay = (scope.plays.length > 0) ? scope.plays[0].id : null;
}
SelectPlay({
scope: scope,
id: nxtPlay
});
ReloadHostSummaryList({
scope: scope
});
if (!scope.liveEventProcessing) {
SelectPlay({
scope: scope,
id: nxtPlay
});
ReloadHostSummaryList({
scope: scope
});
}
};
scope.viewHostResults = function(id) {

View File

@ -169,20 +169,6 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
modified: event.modified,
message: (event.event_data && event.event_data.res) ? event.event_data.res.msg : ''
});
break;
// We will respond to the job status change event. No need to do this 2x.
/*case 'playbook_on_stats':
scope.job_status.finished = event.modified;
scope.job_status.elapsed = GetElapsed({
start: scope.job_status.started,
end: scope.job_status.finished
});
scope.job_status.status = (event.failed) ? 'failed' : 'successful';
scope.job_status.status_class = "";
//LoadHostSummary({ scope: scope, data: event.event_data });
//DrawGraph({ scope: scope, resize: true });
break;*/
}
};
}])
@ -417,6 +403,9 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
scope.jobData.hostSummaries[host_id].changed += (status === 'changed') ? 1 : 0;
scope.jobData.hostSummaries[host_id].unreachable += (status === 'unreachable') ? 1 : 0;
scope.jobData.hostSummaries[host_id].failed += (status === 'failed') ? 1 : 0;
if (status === 'failed' || status === 'unreachable') {
scope.jobData.hostSummaries[host_id].status = 'failed';
}
}
else {
scope.jobData.hostSummaries[host_id] = {
@ -426,7 +415,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
changed: (status === 'changed') ? 1 : 0,
unreachable: (status === 'unreachable') ? 1 : 0,
failed: (status === 'failed') ? 1 : 0,
status: (status === 'failed') ? 'failed' : 'successful'
status: (status === 'failed' || status === 'unreachable') ? 'failed' : 'successful'
};
}
@ -503,7 +492,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
if (scope.jobData.plays[scope.activePlay].tasks[task_id] !== undefined) {
task = scope.jobData.plays[scope.activePlay].tasks[task_id];
if (task_id === scope.jobData.plays[scope.activePlay].firstTask && status !== 'unreachable') {
if (task_id === scope.jobData.plays[scope.activePlay].firstTask) {
scope.jobData.plays[scope.activePlay].hostCount++;
task.hostCount++;
}
@ -513,6 +502,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
task.changedCount += (status === 'changed') ? 1 : 0;
task.successfulCount += (status === 'successful') ? 1 : 0;
task.skippedCount += (status === 'skipped') ? 1 : 0;
task.unreachableCount += (status === 'unreachable') ? 1 : 0;
SetTaskStyles({
task: task
});
@ -525,14 +516,13 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
var task = params.task,
diff;
//task = scope.jobData.plays[scope.activePlay].tasks[task_id];
//task.hostCount = task.failedCount + task.changedCount + task.skippedCount + task.successfulCount;
task.failedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.failedCount / task.hostCount))) : 0;
task.changedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.changedCount / task.hostCount))) : 0;
task.skippedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.skippedCount / task.hostCount))) : 0;
task.successfulPct = (task.hostCount > 0) ? Math.ceil((100 * (task.successfulCount / task.hostCount))) : 0;
task.unreachablePct = (task.hostCount > 0) ? Math.ceil((100 * (task.unreachableCount / task.hostCount))) : 0;
diff = (task.failedPct + task.changedPct + task.skippedPct + task.successfulPct) - 100;
diff = (task.failedPct + task.changedPct + task.skippedPct + task.successfulPct + task.unreachablePct) - 100;
if (diff > 0) {
if (task.failedPct > diff) {
task.failedPct = task.failedPct - diff;
@ -546,11 +536,15 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
else if (task.successfulPct > diff) {
task.successfulPct = task.successfulPct - diff;
}
else if (task.unreachablePct > diff) {
task.unreachablePct = task.unreachablePct - diff;
}
}
task.successfulStyle = (task.successfulPct > 0) ? { 'display': 'inline-block', 'width': task.successfulPct + "%" } : { 'display': 'none' };
task.changedStyle = (task.changedPct > 0) ? { 'display': 'inline-block', 'width': task.changedPct + "%" } : { 'display': 'none' };
task.skippedStyle = (task.skippedPct > 0) ? { 'display': 'inline-block', 'width': task.skippedPct + "%" } : { 'display': 'none' };
task.failedStyle = (task.failedPct > 0) ? { 'display': 'inline-block', 'width': task.failedPct + "%" } : { 'display': 'none' };
task.unreachableStyle = (task.unreachablePct > 0) ? { 'display': 'inline-block', 'width': task.unreachablePct + "%" } : { 'display': 'none' };
};
}])
@ -667,6 +661,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
failedCount: (event.failed_count) ? event.failed_count : 0,
changedCount: (event.changed_count) ? event.changed_count : 0,
skippedCount: (event.skipped_count) ? event.skipped_count : 0,
unreachableCount: (event.unreachable_count) ? event.unreachable_count : 0,
taskActiveClass: ''
});
@ -823,7 +818,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
url = scope.job.related.job_host_summaries + '?';
url += (scope.search_all_hosts_name) ? 'host__name__icontains=' + scope.search_all_hosts_name + '&': '';
url += (scope.searchAllStatus === 'failed') ? 'failed=true&' : '';
url += 'page_size=' + scope.hostSummariesMaxRows + '&order_by=host__name';
url += '&page_size=' + scope.hostSummariesMaxRows + '&order_by=host__name';
scope.hosts = [];
@ -904,7 +899,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
graph_data.push({
label: 'Unreachable',
value: (scope.host_summary.unreachable === scope.host_summary.total) ? 1 : scope.host_summary.unreachable,
color: '#A9A9A9'
color: '#FF3366'
});
}
if (scope.host_summary.failed) {
@ -975,7 +970,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
keys = Object.keys(tasks);
keys.reverse();
newKeys = [];
for (idx=0; idx < scope.tasksMaxRows && idx < keys.length; idx++) {
for (idx=0; result.length < scope.tasksMaxRows && idx < keys.length; idx++) {
newKeys.push(keys[idx]);
}
newKeys.sort();
@ -1012,8 +1007,15 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
// a must be equal to b
return 0;
});
while (idx < keys.length && idx < scope.hostResultsMaxRows) {
result.unshift(hostResults[keys[idx]]);
while (idx < keys.length && result.length < scope.hostResultsMaxRows) {
if (scope.searchAllStatus === 'failed') {
if (hostResults[keys[idx]].status === 'failed') {
result.unshift(hostResults[keys[idx]]);
}
}
else {
result.unshift(hostResults[keys[idx]]);
}
idx++;
}
}
@ -1045,8 +1047,17 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
return 0;
});
while (idx < keys.length && idx < scope.hostSummariesMaxRows) {
result.push(hostSummaries[keys[idx]]);
console.log(hostSummaries);
while (idx < keys.length && result.length < scope.hostSummariesMaxRows) {
if (scope.searchAllStatus === 'failed') {
if (hostSummaries[keys[idx]].status === 'failed') {
result.push(hostSummaries[keys[idx]]);
}
}
else {
result.push(hostSummaries[keys[idx]]);
}
idx++;
}
}

View File

@ -18,8 +18,9 @@
@info: #d9edf7; /* alert info background color */
@info-border: #bce8f1; /* alert info border color */
@info-color: #3a87ad;
@red: #aa0000; // Ansible Unreachable
@red: #aa0000; // Ansible Failed
@red-hover: #AE3F3A;
@unreachable: #FF3366;
@changed: #FF9900; // Ansible Changed
@skipped: #00aaaa; // Ansible Skipped
@warning: #FF9900;
@ -1022,11 +1023,14 @@ input[type="checkbox"].checkbox-no-label {
.icon-job-stopped,
.icon-job-error,
.icon-job-failed,
.icon-job-canceled,
.icon-job-unreachable {
.icon-job-canceled {
color: @red;
}
.icon-job-unreachable {
color: @unreachable;
}
.icon-job-none,
.icon-job-pending,
.icon-job-waiting,

View File

@ -11,7 +11,7 @@
@successful-hosts-color: @green;
@changed-hosts-color: @changed;
@skipped-hosts-color: @skipped;
@unreachable-hosts-color: #A9A9A9;
@unreachable-hosts-color: @unreachable;
.job_summary {
.table {
@ -73,7 +73,7 @@
background-color: @unreachable-hosts-color;
}
.unreachable-hosts-color {
color: @grey;
color: @unreachable-hosts-color;
}
.job_well {

View File

@ -67,4 +67,24 @@ angular.module('AWFilters', [])
}
return input;
};
}])
.filter('FilterFailedEvents', [ function() {
return function(input, liveEventProcessing, searchAllStatus) {
var results = [];
if (liveEventProcessing) {
// while live events are happening, we don't want angular to filter out anything
return input;
}
else if (searchAllStatus === 'failed') {
// filter by failed
input.forEach(function(row) {
if (row.status === 'failed') {
results.push(row);
}
});
return results;
}
return input;
};
}]);

View File

@ -83,7 +83,7 @@
</div>
</div>
<div id="plays-table-detail" aw-custom-scroll class="table-detail">
<div class="row cursor-pointer" ng-repeat="play in playList = (plays | FilterById : search_all_plays | filter:{ status : searchAllStatus }) track by $index"
<div class="row cursor-pointer" ng-repeat="play in playList = (plays | FilterById : search_all_plays | FilterFailedEvents : liveEventProcessing : searchAllStatus) track by $index"
ng-class="play.playActiveClass" ng-click="selectPlay(play.id)">
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs">{{ play.created | date: 'HH:mm:ss' }}</div>
<div class="col-lg-1 col-md-1 hidden-sm hidden-xs" aw-tool-tip="Completed at {{ play.finished | date:'HH:mm:ss' }}"
@ -117,7 +117,7 @@
<div id="tasks-table-detail" class="table-detail" aw-custom-scroll data-on-total-scroll="TasksOnTotalScroll"
data-on-total-scroll-back="TasksOnTotalScrollBack">
<div class="row cursor-pointer"
ng-repeat="task in taskList = (tasks | FilterById : search_all_tasks | filter: { status : searchAllStatus }) track by $index"
ng-repeat="task in taskList = (tasks | FilterById : search_all_tasks | FilterFailedEvents : liveEventProcessing : searchAllStatus) track by $index"
ng-class="task.taskActiveClass" ng-click="selectTask(task.id)">
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs">{{ task.created | date: 'HH:mm:ss' }}</div>
<div class="col-lg-1 col-md-1 hidden-sm hidden-xs" aw-tool-tip="Completed at {{ task.finished | date:'HH:mm:ss' }}"
@ -128,7 +128,7 @@
<i class="fa icon-job-{{ task.status }}"></i><span ng-show="hasRoles"> {{ task.role }} </span> {{ task.name }}
</div>
<div class="col-lg-5 col-md-5 hidden-sm hidden-xs">
<div class="status-bar"><div class="successful-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-successful-bar" aw-tool-tip="Hosts OK" data-placement="top" ng-style="task.successfulStyle">{{ task.successfulCount }}</div><div class="changed-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-changed-bar" aw-tool-tip="Hosts Changed" data-placement="top" ng-style="task.changedStyle">{{ task.changedCount }}</div><div class="skipped-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-skipped-bar" aw-tool-tip="Hosts Skipped" data-placement="top" ng-style="task.skippedStyle">{{ task.skippedCount }}</div><div class="failed-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-failed-bar" aw-tool-tip="Hosts Failed" data-placement="top" ng-style="task.failedStyle">{{ task.failedCount }}</div><div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found" data-placement="top" style="width: 100%;" ng-show="task.status === 'no-m atching-hosts'">No matching hosts</div></div>
<div class="status-bar"><div class="successful-hosts inner-bar" id="{{ task.id }}-successful-bar" aw-tool-tip="Hosts OK" data-placement="top" ng-style="task.successfulStyle">{{ task.successfulCount }}</div><div class="changed-hosts inner-bar" id="{{ task.id }}-changed-bar" aw-tool-tip="Hosts Changed" data-placement="top" ng-style="task.changedStyle">{{ task.changedCount }}</div><div class="skipped-hosts inner-bar" id="{{ task.id }}-skipped-bar" aw-tool-tip="Hosts Skipped" data-placement="top" ng-style="task.skippedStyle">{{ task.skippedCount }}</div><div class="failed-hosts inner-bar" id="{{ task.id }}-failed-bar" aw-tool-tip="Hosts Failed" data-placement="top" ng-style="task.failedStyle">{{ task.failedCount }}</div><div class="unreachable-hosts inner-bar" id="{{ task.id }}-unreachable-hosts-bar" aw-tool-tip="Hosts Unreachable" data-placement="top" ng-style="task.unreachableStyle">{{ task.unreachableCount }}</div><div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found" data-placement="top" style="width: 100%;" ng-show="task.status === 'no-m atching-hosts'">No matching hosts</div></div>
</div>
</div>
<div class="row" ng-show="taskList.length == 0">
@ -153,7 +153,7 @@
<div id="hosts-table-detail-inner">
<div class="row cursor-pointer" ng-repeat="result in results = (hostResults | filter:{ status : searchAllStatus}) track by $index" ng-click="viewHostResults(result.id)">
<div class="col-lg-7 col-md-7 col-sm-7 col-xs-7 status-column">
<a href="" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. 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 }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="fa icon-job-{{ result.status }}"></i><i ng-show="result.status_text == 'Unreachable'" class="fa icon-job-unreachable"></i> {{ result.name }}</a>
</div>
<div class="col-lg-5 col-md-5 col-sm-5 col-xs-5">
{{ result.msg }}
@ -189,7 +189,7 @@
<div class="col-sm-7 col-xs-7 remove-left-padding">
<div style="position:relative;">
<input type="text" class="input-sm form-control" id="search_all_hosts_name" ng-model="search_all_hosts_name"
placeholder="Host Name" ng-disabled="searchAllDisabled" ng-keypress="allHostNameKeyPress($event)" />
placeholder="Host Name" ng-disabled="liveEventProcessing " ng-keypress="allHostNameKeyPress($event)" >
<div id="search-all-input-icons">
<a class="search-icon" ng-show="searchAllHostsEnabled" ng-click="searchAllByHost()"><i class="fa fa-search"></i></a>
<a class="search-icon" ng-show="!searchAllHostsEnabled" ng-click="search_all_hosts_name=''; searchAllByHost()"><i class="fa fa-times"></i></a>