Improvements to job detail page graph rendering and auto-resizing.

This commit is contained in:
chouseknecht 2014-05-15 13:49:52 -04:00
parent 70e42aff36
commit 200a528391
4 changed files with 201 additions and 244 deletions

View File

@ -8,7 +8,7 @@
'use strict';
function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, DigestEvents,
SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName) {
SelectPlay, SelectTask, Socket, GetElapsed, SelectHost, FilterAllByHostName, DrawGraph) {
ClearScope();
@ -251,6 +251,10 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
// First time. User just loaded page.
scope.$emit('LoadJob');
}
else {
// Check if we need to redraw the group
setTimeout(function() { DrawGraph({ scope: scope }); }, 500);
}
});
scope.adjustSize = function() {
@ -275,7 +279,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
"padding-right": "15px",
"z-index": 0
});
setTimeout(function() { $('#job-summary-container .job_well').height($('#job-detail-container').height() - 18); }, 500);
setTimeout(function() { $('#job-summary-container .job_well').height($('#job-detail-container').height() - 17); }, 500);
$('#job-summary-container').show();
}
// Detail table height adjusting. First, put page height back to 'normal'.
@ -304,99 +308,6 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
scope.$emit('RefreshCompleted');
};
/*function refreshHostRows() {
var url;
if (scope.activeTask) {
scope.hostResults = [];
scope.auto_scroll = true;
url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask + '&';
url += (scope.task_host_name) ? 'host__name__icontains=' + scope.task_host_name + '&' : '';
url += 'host__isnull=false&page_size=' + scope.hostTableRows + '&order_by=host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
scope.hostResults.push({
id: row.id,
status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ),
host_id: row.host,
task_id: row.parent,
name: row.event_data.host,
created: row.created,
msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' )
});
});
$('#hosts-table-detail').mCustomScrollbar("update");
setTimeout( function() { $('#hosts-table-detail').mCustomScrollbar("scrollTo", "bottom"); }, 700);
Wait('stop');
scope.$emit('RefreshCompleted');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
$('#hosts-table-detail').mCustomScrollbar("update");
scope.$emit('RefreshCompleted');
}
}
function refreshSummaryHostRows() {
if (scope.hosts.length < scope.hostSummaryTableRows) {
var url = GetBasePath('jobs') + job_id + '/job_host_summaries/?';
url += (scope.summary_host_name) ? 'host__name__icontains=' + scope.summary_host_name + '&': '';
url += '&page_size=' + scope.hostSummaryTableRows + '&order_by=host__name';
Wait('start');
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
var found = false;
scope.hosts.every(function(host) {
if (host.id === row.host) {
found = true;
return false;
}
return true;
});
if (!found) {
scope.hosts.push({
id: row.host,
name: row.summary_fields.host.name,
ok: row.ok,
changed: row.changed,
unreachable: row.dark,
failed: row.failures
});
}
});
scope.hosts.sort(function(a,b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
$('#hosts-summary-table').mCustomScrollbar("update");
setTimeout( function() { $('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom"); }, 700);
Wait('stop');
scope.$emit('RefreshCompleted');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
$('#hosts-table-detail').mCustomScrollbar("update");
scope.$emit('RefreshCompleted');
}
}*/
setTimeout(function() { scope.adjustSize(); }, 500);
// Use debounce for the underscore library to adjust after user resizes window.
@ -443,10 +354,23 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
width: $(document).width(),
height: $(document).height()
}).show();
// Adjust the summary table height
$('#job-summary-container .job_well').height(height - 18).css({
'box-shadow': '-3px 3px 5px 0 #ccc'
});
height = Math.floor($('#job-detail-container').height() * 0.5) -
$('#hosts-summary-section .header').outerHeight() -
$('#hosts-summary-section .table-header').outerHeight() -
$('#hide-summary-button').outerHeight() -
$('#summary-search-section').outerHeight() -
$('#hosts-summary-section .header').outerHeight() -
$('#hosts-summary-section .legend').outerHeight();
$('#hosts-summary-table').height(height - 50);
$('#hosts-summary-table').mCustomScrollbar("update");
$('#hide-summary-button').show();
$('#job-summary-container').css({
top: 0,
right: 0,
@ -455,6 +379,8 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
'padding-right': '15px',
'padding-left': '15px'
}).show('slide', {'direction': 'right'});
setTimeout(function() { DrawGraph({ scope: scope }); }, 500);
}
else {
$('.overlay').hide();
@ -754,5 +680,5 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
}
JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait',
'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost', 'FilterAllByHostName'
'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost', 'FilterAllByHostName', 'DrawGraph'
];

View File

@ -494,35 +494,31 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
});
if (!host_found) {
if (scope.hosts.length < 10 || name > scope.hosts[0].name) {
// This is a new host we want added to the list
scope.hosts.push({
id: host_id,
name: name,
ok: (status === 'successful') ? 1 : 0,
changed: (status === 'changed') ? 1 : 0,
unreachable: (status === 'unreachable') ? 1 : 0,
failed: (status === 'failed') ? 1 : 0
});
scope.hosts.sort(function(a,b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
// Only keep 10 hosts
if (scope.hosts.length > scope.hostSummaryTableRows) {
scope.hosts.splice(0,1);
scope.hosts.push({
id: host_id,
name: name,
ok: (status === 'successful') ? 1 : 0,
changed: (status === 'changed') ? 1 : 0,
unreachable: (status === 'unreachable') ? 1 : 0,
failed: (status === 'failed') ? 1 : 0
});
scope.hosts.sort(function(a,b) {
if (a.name < b.name) {
return -1;
}
$('#tasks-table-detail').mCustomScrollbar("update");
setTimeout( function() {
if (a.name > b.name) {
return 1;
}
return 0;
});
if (scope.hosts.length > scope.hostSummaryTableRows) {
scope.hosts.pop();
}
$('#tasks-table-detail').mCustomScrollbar("update");
/*setTimeout( function() {
scope.auto_scroll = true;
$('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom");
}, 700);
}
}, 700);*/
}
UpdateTaskStatus({
@ -774,61 +770,77 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
return function(params) {
var scope = params.scope,
dark = 0, failed = 0, changed = 0, ok = 0,
svg_height, svg_width, graph_data, svg, url;
width, height, svg_height, svg_width, svg_radius, svg, url;
svg_width = $('#graph-section').width();
svg_height = 300;
if ($('#graph-section svg').length === 0) {
svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height);
width = $('#job-summary-container .job_well').width();
height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15;
svg_radius = Math.min(width, height);
svg_width = width;
svg_height = height;
if (svg_height > 0 && svg_width > 0) {
if ($('#graph-section svg').length > 0) {
$("#completedHostsDonut").attr('id', 'completedHostsDonut_old');
svg = d3.select("#graph-section svg").attr("width", svg_width).attr("height", svg_height);
$('#completedHostsDonut').remove();
svg.append("g").attr("id","completedHostsDonut");
$('#completedHostsDonut').hide();
scope.$emit('GraphLoadData');
}
else {
svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height);
svg.append("g").attr("id","completedHostsDonut");
scope.$emit('GraphLoadData');
}
}
else {
svg = d3.select("#graph-section svg");
}
svg.append("g").attr("id","completedHostsDonutNew");
$('#completedHostsDonutNew').hide();
if (scope.removeRenderGraph) {
scope.removeRenderGraph();
}
scope.removeRenderGraph = scope.$on('RenderGraph', function() {
Donut3D.draw("completedHostsDonutNew", graph_data, Math.floor(svg_width / 2), 150, 130, 100, 15, 0.4);
$('#completedHostsDonut').remove();
$('#completedHostsDonutNew').attr('id','completedHostsDonut');
scope.removeRenderGraph = scope.$on('RenderGraph', function(e, graph_data) {
Donut3D.draw("completedHostsDonut", graph_data, Math.floor(svg_width / 2), Math.floor(svg_height / 2), Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4);
$('#completedHostsDonut_old').remove();
$('#completedHostsDonut').show();
$('#graph-section .legend').show();
});
url = GetBasePath('jobs') + scope.job_id + '/job_host_summaries/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.count) {
data.results.forEach(function(row) {
if (row.dark) {
dark ++;
}
else if (row.failures) {
failed++;
}
else if (row.changed) {
changed++;
}
else if (row.ok) {
ok++;
}
});
graph_data = [
{ label: 'OK', value: ok, color: '#9ED89E' },
{ label: 'Changed', value: changed, color: '#FFC773' },
{ label: 'Failed', value: failed, color: '#DA4D49' },
{ label: 'Unreachable', value: dark, color: '#A9A9A9' }
];
scope.$emit('RenderGraph');
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
if (scope.removeGraphLoadData) {
scope.removeGraphLoadData();
}
scope.removeGraphLoadData = scope.$on('GraphLoadData', function() {
url = GetBasePath('jobs') + scope.job_id + '/job_host_summaries/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
var graph_data;
if (data.count) {
data.results.forEach(function(row) {
if (row.dark) {
dark ++;
}
else if (row.failures) {
failed++;
}
else if (row.changed) {
changed++;
}
else if (row.ok) {
ok++;
}
});
graph_data = [
{ label: 'OK', value: ok, color: '#9ED89E' },
{ label: 'Changed', value: changed, color: '#FFC773' },
{ label: 'Failed', value: failed, color: '#DA4D49' },
{ label: 'Unreachable', value: dark, color: '#A9A9A9' }
];
scope.$emit('RenderGraph', graph_data);
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
});
};
}])

View File

@ -301,24 +301,39 @@ label.small-label {
.badge-column a {
width: 20%;
}
.legend {
margin: 8px 0;
}
}
.legend {
font-size: 12px;
i {
margin-left: 5px;
}
i:first-child {
margin-left: 0;
}
}
.mCSB_container {
margin-right: 18px;
}
#graph-section {
position: relative;
.legend {
font-size: 12px;
margin-top: 10px;
}
i {
margin-left: 5px;
}
i:first-child {
margin-left: 0;
width: 100%;
.header {
margin-top: 20px;
.legend {
text-align: center;
margin: 15px 0 0 0;
i {
margin-left: 10px
}
i:first-child {
margin-left: 0;
}
}
}
}

View File

@ -135,81 +135,85 @@
<div id="job-summary-container">
<div class="job_well">
<div id="hide-summary-button" style="display: hidden;">
<a href="" class="btn btn-xs btn-default" ng-click="toggleSummary('hide')" aw-tool-tip="Hide summary" data-placement="top"><i class="fa fa-arrow-circle-right fa-lg"></i></a>
</div>
<div id="summary-search-section" class="section">
<div class="header">
<div class="title">Filter Events</div>
<div id="summary-well-top-section">
<div id="hide-summary-button" style="display: hidden;">
<a href="" class="btn btn-xs btn-default" ng-click="toggleSummary('hide')" aw-tool-tip="Hide summary" data-placement="top"><i class="fa fa-arrow-circle-right fa-lg"></i></a>
</div>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<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)" />
<div id="search-all-input-icons">
<a class="search-icon" ng-show="searchAllHostsEnabled" ng-click="searchAllHosts()"><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>
</div>
</div>
<div id="summary-search-section" class="section">
<div class="header">
<div class="title">Filter Events</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<div style="padding-top: 5px;">
<label class="small-label">Show</label>
<div class="btn-group" aw-toggle-button>
<button class="btn btn-xs btn-default">All</button>
<button class="btn btn-xs btn-primary active">Errors</button>
</div>
</div>
</div>
</div>
<hr />
</div>
<div id="hosts-summary-section" class="section job_summary">
<div class="header">
<div class="title">Host Summary</div>
<div class="search-field">
<input type="text" ng-model="summary_host_name" placeholder="Host Name" ng-keypress="summaryHostNameKeyPress($event)" />
<a class="search-icon" ng-show="searchSummaryHostsEnabled" ng-click="searchSummaryHosts()"><i class="fa fa-search"></i></a>
<a class="search-icon" ng-show="!searchSummaryHostsEnabled" ng-click="summary_host_name=''; searchSummaryHosts()"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="table-header">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">Host</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">Events
<a href="" id="event-help-link" aw-pop-over="{{ eventsHelpText }}" aw-tool-tip="Click for help" aw-pop-over-watch="eventsHelpText" data-placement="top" data-container="body" data-title="Event Legend" class="help-link"><i class="fa fa-question-circle"></i></a>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<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)" />
<div id="search-all-input-icons">
<a class="search-icon" ng-show="searchAllHostsEnabled" ng-click="searchAllHosts()"><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>
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<div style="padding-top: 5px;">
<label class="small-label">Show</label>
<div class="btn-group" aw-toggle-button>
<button class="btn btn-xs btn-default">All</button>
<button class="btn btn-xs btn-primary active">Errors</button>
</div>
</div>
</div>
</div>
<hr />
</div>
<div id="hosts-summary-table" class="table-detail" aw-custom-scroll data-on-total-scroll="HostSummaryOnTotalScroll"
data-on-total-scroll-back="HostSummaryOnTotalScrollBack">
<div class="row" ng-repeat="host in hosts" 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 }}"
aw-tool-tip="Click to edit host" data-placement="top">{{ host.name }}</a>
</div>
<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="Changed" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a href="" aw-tool-tip="Unreachable" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
<a href="" aw-tool-tip="Failed" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a>
<div id="hosts-summary-section" class="section job_summary">
<div class="header">
<div class="title">Host Summary</div>
<div class="search-field">
<input type="text" ng-model="summary_host_name" placeholder="Host Name" ng-keypress="summaryHostNameKeyPress($event)" />
<a class="search-icon" ng-show="searchSummaryHostsEnabled" ng-click="searchSummaryHosts()"><i class="fa fa-search"></i></a>
<a class="search-icon" ng-show="!searchSummaryHostsEnabled" ng-click="summary_host_name=''; searchSummaryHosts()"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="row" ng-show="hosts.length === 0">
<div class="col-lg-12">
<div class="loading-info">No hosts matched</div>
</div>
</div>
</div>
</div><!-- section -->
<div id="graph-section">
<div class="header">
<div class="title">Host Status Overview</div>
<div class="legend"><i class="fa fa-circle successful-hosts-color"></i> Successful <i class="fa fa-circle changed-hosts-color"></i> Changed
<i class="fa fa-circle unreachable-hosts-color"></i> Unreachable <i class="fa fa-circle failed-hosts-color"></i> Failed</div>
<div class="table-header">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">Host</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">Completed Tasks
<!-- <a href="" id="event-help-link" aw-pop-over="{{ eventsHelpText }}" aw-tool-tip="Click for help" aw-pop-over-watch="eventsHelpText" data-placement="top" data-container="body" data-title="Event Legend" class="help-link"><i class="fa fa-question-circle"></i></a> -->
</div>
</div>
</div>
<div id="hosts-summary-table" class="table-detail" aw-custom-scroll data-on-total-scroll="HostSummaryOnTotalScroll"
data-on-total-scroll-back="HostSummaryOnTotalScrollBack">
<div class="row" ng-repeat="host in hosts" 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 }}"
aw-tool-tip="Click to edit host" data-placement="top">{{ host.name }}</a>
</div>
<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="Changed" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a href="" aw-tool-tip="Unreachable" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
<a href="" aw-tool-tip="Failed" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a>
</div>
</div>
<div class="row" ng-show="hosts.length === 0">
<div class="col-lg-12">
<div class="loading-info">No hosts matched</div>
</div>
</div>
</div>
</div><!-- section -->
</div><!-- summary-well-top-section -->
<div id="graph-section" class="section">
<div class="header">
<div class="title">Host Status Overview</div>
<div class="legend" style="display: none;"><i class="fa fa-circle successful-hosts-color"></i> Successful <i class="fa fa-circle changed-hosts-color"></i> Changed
<i class="fa fa-circle unreachable-hosts-color"></i> Unreachable <i class="fa fa-circle failed-hosts-color"></i> Failed</div>
</div>
</div><!-- graph section -->
</div><!-- graph section -->
</div>
</div><!-- col-md-5 -->