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'; 'use strict';
function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, DigestEvents, 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(); ClearScope();
@@ -251,6 +251,10 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
// First time. User just loaded page. // First time. User just loaded page.
scope.$emit('LoadJob'); scope.$emit('LoadJob');
} }
else {
// Check if we need to redraw the group
setTimeout(function() { DrawGraph({ scope: scope }); }, 500);
}
}); });
scope.adjustSize = function() { scope.adjustSize = function() {
@@ -275,7 +279,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
"padding-right": "15px", "padding-right": "15px",
"z-index": 0 "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(); $('#job-summary-container').show();
} }
// Detail table height adjusting. First, put page height back to 'normal'. // 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'); 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); setTimeout(function() { scope.adjustSize(); }, 500);
// Use debounce for the underscore library to adjust after user resizes window. // 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(), width: $(document).width(),
height: $(document).height() height: $(document).height()
}).show(); }).show();
// Adjust the summary table height
$('#job-summary-container .job_well').height(height - 18).css({ $('#job-summary-container .job_well').height(height - 18).css({
'box-shadow': '-3px 3px 5px 0 #ccc' '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(); $('#hide-summary-button').show();
$('#job-summary-container').css({ $('#job-summary-container').css({
top: 0, top: 0,
right: 0, right: 0,
@@ -455,6 +379,8 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
'padding-right': '15px', 'padding-right': '15px',
'padding-left': '15px' 'padding-left': '15px'
}).show('slide', {'direction': 'right'}); }).show('slide', {'direction': 'right'});
setTimeout(function() { DrawGraph({ scope: scope }); }, 500);
} }
else { else {
$('.overlay').hide(); $('.overlay').hide();
@@ -754,5 +680,5 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
} }
JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait', 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 (!host_found) {
if (scope.hosts.length < 10 || name > scope.hosts[0].name) { scope.hosts.push({
// This is a new host we want added to the list id: host_id,
scope.hosts.push({ name: name,
id: host_id, ok: (status === 'successful') ? 1 : 0,
name: name, changed: (status === 'changed') ? 1 : 0,
ok: (status === 'successful') ? 1 : 0, unreachable: (status === 'unreachable') ? 1 : 0,
changed: (status === 'changed') ? 1 : 0, failed: (status === 'failed') ? 1 : 0
unreachable: (status === 'unreachable') ? 1 : 0, });
failed: (status === 'failed') ? 1 : 0 scope.hosts.sort(function(a,b) {
}); if (a.name < b.name) {
scope.hosts.sort(function(a,b) { return -1;
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);
} }
$('#tasks-table-detail').mCustomScrollbar("update"); if (a.name > b.name) {
setTimeout( function() { return 1;
}
return 0;
});
if (scope.hosts.length > scope.hostSummaryTableRows) {
scope.hosts.pop();
}
$('#tasks-table-detail').mCustomScrollbar("update");
/*setTimeout( function() {
scope.auto_scroll = true; scope.auto_scroll = true;
$('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom"); $('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom");
}, 700); }, 700);*/
}
} }
UpdateTaskStatus({ UpdateTaskStatus({
@@ -774,61 +770,77 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
dark = 0, failed = 0, changed = 0, ok = 0, 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(); width = $('#job-summary-container .job_well').width();
svg_height = 300; height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15;
if ($('#graph-section svg').length === 0) { svg_radius = Math.min(width, height);
svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_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) { if (scope.removeRenderGraph) {
scope.removeRenderGraph(); scope.removeRenderGraph();
} }
scope.removeRenderGraph = scope.$on('RenderGraph', function() { scope.removeRenderGraph = scope.$on('RenderGraph', function(e, graph_data) {
Donut3D.draw("completedHostsDonutNew", graph_data, Math.floor(svg_width / 2), 150, 130, 100, 15, 0.4); 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').remove(); $('#completedHostsDonut_old').remove();
$('#completedHostsDonutNew').attr('id','completedHostsDonut');
$('#completedHostsDonut').show(); $('#completedHostsDonut').show();
$('#graph-section .legend').show();
}); });
url = GetBasePath('jobs') + scope.job_id + '/job_host_summaries/'; if (scope.removeGraphLoadData) {
Rest.setUrl(url); scope.removeGraphLoadData();
Rest.get() }
.success(function(data) { scope.removeGraphLoadData = scope.$on('GraphLoadData', function() {
if (data.count) { url = GetBasePath('jobs') + scope.job_id + '/job_host_summaries/';
data.results.forEach(function(row) { Rest.setUrl(url);
if (row.dark) { Rest.get()
dark ++; .success(function(data) {
} var graph_data;
else if (row.failures) { if (data.count) {
failed++; data.results.forEach(function(row) {
} if (row.dark) {
else if (row.changed) { dark ++;
changed++; }
} else if (row.failures) {
else if (row.ok) { failed++;
ok++; }
} else if (row.changed) {
}); changed++;
graph_data = [ }
{ label: 'OK', value: ok, color: '#9ED89E' }, else if (row.ok) {
{ label: 'Changed', value: changed, color: '#FFC773' }, ok++;
{ label: 'Failed', value: failed, color: '#DA4D49' }, }
{ label: 'Unreachable', value: dark, color: '#A9A9A9' } });
]; graph_data = [
scope.$emit('RenderGraph'); { label: 'OK', value: ok, color: '#9ED89E' },
} { label: 'Changed', value: changed, color: '#FFC773' },
}) { label: 'Failed', value: failed, color: '#DA4D49' },
.error(function(data, status) { { label: 'Unreachable', value: dark, color: '#A9A9A9' }
ProcessErrors(scope, data, status, null, { hdr: 'Error!', ];
msg: 'Call to ' + url + '. GET returned: ' + status }); 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 { .badge-column a {
width: 20%; width: 20%;
} }
.legend {
margin: 8px 0;
}
}
.legend {
font-size: 12px;
i {
margin-left: 5px;
}
i:first-child {
margin-left: 0;
}
} }
.mCSB_container { .mCSB_container {
margin-right: 18px; margin-right: 18px;
} }
#graph-section { #graph-section {
position: relative; width: 100%;
.legend { .header {
font-size: 12px; margin-top: 20px;
margin-top: 10px; .legend {
} text-align: center;
i { margin: 15px 0 0 0;
margin-left: 5px; i {
} margin-left: 10px
i:first-child { }
margin-left: 0; i:first-child {
margin-left: 0;
}
}
} }
} }

View File

@@ -135,79 +135,83 @@
<div id="job-summary-container"> <div id="job-summary-container">
<div class="job_well"> <div class="job_well">
<div id="hide-summary-button" style="display: hidden;"> <div id="summary-well-top-section">
<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 id="hide-summary-button" style="display: hidden;">
</div> <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 id="summary-search-section" class="section">
<div class="header">
<div class="title">Filter Events</div>
</div> </div>
<div class="row"> <div id="summary-search-section" class="section">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6"> <div class="header">
<div style="position:relative;"> <div class="title">Filter Events</div>
<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>
<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="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">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">Events <div style="position:relative;">
<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> <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>
</div> </div>
<hr />
</div> </div>
<div id="hosts-summary-table" class="table-detail" aw-custom-scroll data-on-total-scroll="HostSummaryOnTotalScroll" <div id="hosts-summary-section" class="section job_summary">
data-on-total-scroll-back="HostSummaryOnTotalScrollBack"> <div class="header">
<div class="row" ng-repeat="host in hosts" id="{{ host.id }}"> <div class="title">Host Summary</div>
<div class="name col-lg-6 col-md-6 col-sm-6 col-xs-6"><a href="/#/home/hosts/?id={{ host.id }}" <div class="search-field">
aw-tool-tip="Click to edit host" data-placement="top">{{ host.name }}</a> <input type="text" ng-model="summary_host_name" placeholder="Host Name" ng-keypress="summaryHostNameKeyPress($event)" />
</div> <a class="search-icon" ng-show="searchSummaryHostsEnabled" ng-click="searchSummaryHosts()"><i class="fa fa-search"></i></a>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 badge-column"> <a class="search-icon" ng-show="!searchSummaryHostsEnabled" ng-click="summary_host_name=''; searchSummaryHosts()"><i class="fa fa-times"></i></a>
<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> </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 <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> <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>
</div><!-- graph section --> </div><!-- graph section -->
</div> </div>