Fixed jshint linting errors. Lates job detail page changes.

This commit is contained in:
Chris Houseknecht 2014-04-21 10:18:37 -04:00
parent 29349d0a5a
commit 25c117782c
11 changed files with 623 additions and 245 deletions

View File

@ -26,7 +26,9 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
$scope.tasks = [];
$scope.hosts = [];
$scope.hostResults = [];
$scope.job_status = {};
$scope.job_id = job_id;
// Apply each event to the view
if ($scope.removeEventsReady) {
$scope.removeEventsReady();
@ -116,10 +118,10 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
$scope.limit = data.limit;
$scope.verbosity = data.verbosity;
$scope.job_tags = data.job_tags;
$scope.started = data.started;
$scope.finished = data.finished;
$scope.elapsed = data.elapsed;
$scope.job_status = data.status;
//$scope.started = data.started;
//$scope.finished = data.finished;
//$scope.elapsed = data.elapsed;
//$scope.job_status = data.status;
$scope.$emit('JobReady', data.related.job_events + '?page_size=50&order_by=id');
$scope.$emit('GetCredentialNames', data);
})
@ -141,6 +143,17 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
id: id
});
};
$( "#hosts-slider-vertical" ).slider({
orientation: "vertical",
range: "min",
min: 0,
max: 100,
value: 60,
slide: function( event, ui ) {
$( "#amount" ).val( ui.value );
}
});
}
JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait',

View File

@ -40,7 +40,8 @@
angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'SelectPlay', 'SelectTask',
function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask) {
'GetHostCount', 'GetElapsed',
function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed) {
return function(params) {
var scope = params.scope,
@ -48,10 +49,17 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
events.forEach(function(event) {
var hostCount;
if (event.event === 'playbook_on_start') {
scope.job_status.started = event.created;
scope.job_status.status = 'running';
}
if (event.event === 'playbook_on_play_start') {
scope.plays.push({
id: event.id,
name: event.play,
created: event.created,
status: (event.changed) ? 'changed' : (event.failed) ? 'failed' : 'none',
children: []
});
@ -61,24 +69,30 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
});
}
if (event.event === 'playbook_on_setup') {
hostCount = (scope.tasks.length > 0) ? scope.tasks[scope.tasks.length - 1].hostCount : 0;
hostCount = GetHostCount({
scope: scope,
play_id: event.parent
});
scope.tasks.push({
id: event.id,
name: event.event_display,
play_id: event.parent,
status: (event.failed) ? 'failed' : 'successful',
created: event.created,
modified: event.modified,
hostCount: hostCount,
failedCount: 0,
changedCount: 0,
successfulCount: 0,
skippedCount: 0
skippedCount: 0,
reportedHosts: 0
});
UpdatePlayStatus({
scope: scope,
play_id: event.parent,
failed: event.failed,
changed: event.changed
changed: event.changed,
modified: event.modified
});
SelectTask({
scope: scope,
@ -86,7 +100,10 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
});
}
if (event.event === 'playbook_on_task_start') {
hostCount = (scope.tasks.length > 0) ? scope.tasks[scope.tasks.length - 1].hostCount : 0;
hostCount = GetHostCount({
scope: scope,
play_id: event.parent
});
scope.tasks.push({
id: event.id,
name: event.task,
@ -94,11 +111,13 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
status: ( (event.changed) ? 'changed' : (event.failed) ? 'failed' : 'successful' ),
role: event.role,
created: event.created,
modified: event.modified,
hostCount: hostCount,
failedCount: 0,
changedCount: 0,
successfulCount: 0,
skippedCount: 0
skippedCount: 0,
reportedHosts: 0
});
if (event.role) {
scope.hasRoles = true;
@ -107,7 +126,8 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
scope: scope,
play_id: event.parent,
failed: event.failed,
changed: event.changed
changed: event.changed,
modified: event.modified
});
SelectTask({
scope: scope,
@ -125,7 +145,9 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
host_id: event.host,
task_id: event.parent,
status: 'unreachable',
event_id: event.id
event_id: event.id,
created: event.created,
modified: event.modified
});
}
@ -136,7 +158,9 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
host_id: event.host,
task_id: event.parent,
status: 'failed',
event_id: event.id
event_id: event.id,
created: event.created,
modified: event.modified
});
}
if (event.event === 'runner_on_skipped') {
@ -146,7 +170,9 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
host_id: event.host,
task_id: event.parent,
status: 'skipped',
event_id: event.id
event_id: event.id,
created: event.created,
modified: event.modified
});
}
if (event.event === 'runner_on_ok') {
@ -155,17 +181,72 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
name: event.event_data.host,
host_id: event.host,
task_id: event.parent,
status: (event.changed) ? 'changed' : 'ok',
event_id: event.id
status: ( (event.changed) ? 'changed' : (event.failed) ? 'failed' : 'successful' ),
event_id: event.id,
created: event.created,
modified: event.modified
});
}
if (event.event === '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) ? 'error' : 'successful';
}
});
};
}])
.factory('GetHostCount', function() {
return function(params) {
var scope = params.scope,
play_id = params.play_id,
tasks = [];
// Get the known set of tasks for a given play
if (scope.tasks.length > 0) {
scope.tasks.forEach(function(task) {
if (task.play_id === play_id) {
tasks.push(task);
}
});
// sort by ascending event.id
if (tasks.length > 0) {
tasks.sort(function(a, b) {
return a.id - b.id;
});
return tasks[0].hostCount;
}
}
return 0;
};
})
.factory('FindFirstTaskofPlay', function() {
return function(params) {
var scope = params.scope,
play_id = params.play_id,
tasks = [];
// Get the known set of tasks for a given play
if (scope.tasks.length > 0) {
scope.tasks.forEach(function(task) {
if (task.play_id === play_id) {
tasks.push(task);
}
});
// sort by ascending event.id
if (tasks.length > 0) {
tasks.sort(function(a, b) {
return a.id - b.id;
});
return tasks[0].id;
}
}
return 0;
};
})
.factory('MakeLastRowActive', [ function() {
return function(params) {
var scope = params.scope,
@ -215,16 +296,51 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
};
}])
.factory('GetElapsed', [ function() {
return function(params) {
var start = params.start,
end = params.end,
dt1, dt2, sec, hours, min;
dt1 = new Date(start);
dt2 = new Date(end);
if ( dt2.getTime() !== dt1.getTime() ) {
sec = Math.floor( (dt2.getTime() - dt1.getTime()) / 1000 );
hours = Math.floor(sec / 3600);
sec = sec - (hours * 3600);
if (('' + hours).length < 2) {
hours = ('00' + hours).substr(-2, 2);
}
min = Math.floor(sec / 60);
sec = sec - (min * 60);
min = ('00' + min).substr(-2,2);
sec = ('00' + sec).substr(-2,2);
return hours + ':' + min + ':' + sec;
}
else {
return '00:00:00';
}
};
}])
// Update the status of a play
.factory('UpdatePlayStatus', [ function() {
.factory('UpdatePlayStatus', ['GetElapsed', function(GetElapsed) {
return function(params) {
var scope = params.scope,
failed = params.failed,
changed = params.changed,
id = params.play_id;
id = params.play_id,
modified = params.modified;
scope.plays.every(function(play,idx) {
if (play.id === id) {
scope.plays[idx].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful';
if (play.status !== 'changed' && play.status !== 'failed') {
// once the status becomes 'changed' or 'failed' don't modify it
scope.plays[idx].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful';
}
scope.plays[idx].finished = modified;
scope.plays[idx].elapsed = GetElapsed({
start: play.created,
end: modified
});
return false;
}
return true;
@ -232,22 +348,34 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
};
}])
.factory('UpdateTaskStatus', ['UpdatePlayStatus', function(UpdatePlayStatus) {
.factory('UpdateTaskStatus', ['UpdatePlayStatus', 'GetElapsed', function(UpdatePlayStatus, GetElapsed) {
return function(params) {
var scope = params.scope,
failed = params.failed,
changed = params.changed,
id = params.task_id;
id = params.task_id,
modified = params.modified;
scope.tasks.every(function (task, i) {
if (task.id === id) {
scope.tasks[i].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful';
if (task.status !== 'changed' && task.status !== 'failed') {
// once the status becomes 'changed' or 'failed' don't modify it
scope.tasks[i].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful';
}
scope.tasks[i].finished = params.modified;
scope.tasks[i].elapsed = GetElapsed({
start: task.created,
end: modified
});
UpdatePlayStatus({
scope: scope,
failed: failed,
changed: changed,
play_id: task.play_id
play_id: task.play_id,
modified: modified
});
return false;
}
return true;
});
};
}])
@ -270,16 +398,18 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
.factory('UpdateHostStatus', ['UpdateTaskStatus', 'AddHostResult', function(UpdateTaskStatus, AddHostResult) {
return function(params) {
var scope = params.scope,
status = params.status, // ok, changed, unreachable, failed
status = params.status, // successful, changed, unreachable, failed, skipped
name = params.name,
event_id = params.event_id,
host_id = params.host_id,
task_id = params.task_id,
modified = params.modified,
created = params.created,
host_found = false;
scope.hosts.every(function(host, i) {
if (host.id === host_id) {
scope.hosts[i].ok += (status === 'ok' || status === 'changed') ? 1 : 0;
scope.hosts[i].ok += (status === 'successful') ? 1 : 0;
scope.hosts[i].changed += (status === 'changed') ? 1 : 0;
scope.hosts[i].unreachable += (status === 'unreachable') ? 1 : 0;
scope.hosts[i].failed += (status === 'failed') ? 1 : 0;
@ -293,7 +423,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
scope.hosts.push({
id: host_id,
name: name,
ok: (status === 'ok' || status === 'changed') ? 1 : 0,
ok: (status === 'successful') ? 1 : 0,
changed: (status === 'changed') ? 1 : 0,
unreachable: (status === 'unreachable') ? 1 : 0,
failed: (status === 'failed') ? 1 : 0
@ -304,49 +434,78 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
scope: scope,
task_id: task_id,
failed: (status === 'failed' || status === 'unreachable') ? true :false,
changed: (status === 'changed') ? true : false
changed: (status === 'changed') ? true : false,
modified: modified
});
AddHostResult({
scope: scope,
task_id: task_id,
host_id: host_id,
event_id: event_id
event_id: event_id,
status: status,
name: name,
created: created
});
};
}])
// Add a new host result
.factory('AddHostResult', [ function() {
.factory('AddHostResult', ['FindFirstTaskofPlay', function(FindFirstTaskofPlay) {
return function(params) {
var scope = params.scope,
task_id = params.task_id,
host_id = params.host_id,
event_id = params.event_id,
status = params.status;
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
host_id = event.host;
status = params.status,
created = params.created,
name = params.name,
play_id, first;
scope.hostResults.push({
id: event_id,
status: status,
host_id: host_id,
task_id: event.parent
task_id: task_id,
name: name,
created: created
});
scope.tasks.forEach(function(task, idx) {
scope.tasks.every(function(task) {
if (task.id === task_id) {
scope.tasks[idx].hostCount += (idx === 0) ? 1 : 0; // we only need to count hosts for the first task in a play
play_id = task.play_id;
return false;
}
return true;
});
first = FindFirstTaskofPlay({
scope: scope,
play_id: play_id
});
scope.tasks.every(function(task, idx) {
if (task.id === task_id) {
scope.tasks[idx].hostCount += (task.id === first) ? 1 : 0; // we only need to count hosts for the first task in a play
scope.tasks[idx].reportedHosts++;
scope.tasks[idx].failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0;
scope.tasks[idx].changedCount += (status === 'changed') ? 1 : 0;
scope.tasks[idx].successfulCount += (status === 'successful' || status === 'changed') ? 1 : 0;
scope.tasks[idx].successfulCount += (status === 'successful') ? 1 : 0;
scope.tasks[idx].skippedCount += (status === 'skipped') ? 1 : 0;
scope.tasks[idx].failedPct = 100 * Math.round(scope.tasks[idx].failedCount / scope.tasks[idx].hostCount);
scope.tasks[idx].changedPct = 100 * (scope.tasks[idx].successfulCount) ? Math.round(scope.tasks[idx].changedCount / scope.tasks[idx].successfulCount) : 0;
scope.tasks[idx].skippedPct = 100 * Math.round(scope.tasks[idx].skippedCount / scope.tasks[idx].hostCount);
scope.tasks[idx].successfulPct = 100 * Math.round(scope.tasks[idx].successfulCount / scope.tasks[idx].hostCount);
scope.tasks[idx].failedPct = (scope.tasks[idx].hostCount > 0) ? 100 * Math.round(scope.tasks[idx].failedCount / scope.tasks[idx].hostCount) : 0;
scope.tasks[idx].changedPct = (scope.tasks[idx].hostCount > 0) ? 100 * Math.round(scope.tasks[idx].changedCount / scope.tasks[idx].hostCount) : 0;
scope.tasks[idx].skippedPct = (scope.tasks[idx].hostCount > 0) ? 100 * Math.round(scope.tasks[idx].skippedCount / scope.tasks[idx].hostCount) : 0;
scope.tasks[idx].successfulPct = (scope.tasks[idx].hostCount > 0) ? 100 * Math.round(scope.tasks[idx].successfulCount / scope.tasks[idx].hostCount) : 0;
scope.tasks[idx].successfulStyle = (scope.tasks[idx].successfulPct > 0) ? { width: scope.tasks[idx].successfulPct + '%' } : { display: 'none' };
scope.tasks[idx].changedStyle = (scope.tasks[idx].changedPct > 0) ? { width: scope.tasks[idx].changedPct + '%' } : { display: 'none' };
scope.tasks[idx].skippedStyle = (scope.tasks[idx].skippedPct > 0) ? { width: scope.tasks[idx].skippedPct + '%' } : { display: 'none' };
scope.tasks[idx].failedStyle = (scope.tasks[idx].failedPct > 0) ? { width: scope.tasks[idx].failedPct + '%' } : { display: 'none' };
return false;
}
return true;
});
};
}])

View File

@ -49,11 +49,6 @@ angular.module('PaginationHelpers', ['Utilities', 'RefreshHelper', 'RefreshRelat
for (i = first; i <= last; i++) {
scope[iterator + '_page_range'].push(i);
}
console.log('first: ' + first);
console.log('last: ' + last);
console.log('range: ');
console.log(scope[iterator + '_page_range']);
console.log('num_pages: ' + scope[iterator + '_num_pages']);
};
}
])

View File

@ -7,34 +7,68 @@
*
*/
#jobs-detail {
.nav-path {
margin-bottom: 20px;
}
}
.inline-block {
display: inline-block;
vertical-align: top;
}
#job-status {
margin: 8px 0 15px 0;
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
display: inline-block;
margin-right: 15px;
}
i {
font-size: 12px;
}
.label {
font-size: 12px;
color: @black;
padding-left: 0;
}
}
.job-detail-table {
margin-bottom: 0;
border: 1px solid @well;
/**
The thing that makes the table body scrollable:
http://kamlekar.wordpress.com/2013/06/17/table-tbody-scroll-cross-browser/comment-page-1/
**/
thead {
display: table;
float: left;
width: 100%;
}
thead tr {
display: table-row;
width: 100%;
border: 1px solid @grey;
background-color: @white;
/* http://stackoverflow.com/questions/21168521/scrollable-table-with-fixed-header-in-bootstrap */
width: 100%;
thead, tbody, tr, td, th { display: block; }
tr:after {
content: ' ';
display: block;
visibility: hidden;
clear: both;
}
tbody {
display: block;
height: 122px;
width: 100%;
overflow-y: scroll;
overflow-y: auto;
height: 150px;
}
thead {
/* fallback */
}
thead>tr>th {
height: 22px;
}
tbody td, thead th {
height: auto;
float: left;
}
tbody tr {
display: table;
width: 100%;
}
tbody>tr>td {
border-top-color: @well;
padding-top: 0;
@ -45,70 +79,75 @@
padding-bottom: 0;
font-size: 14px;
}
td.status-column {
text-align: center;
font-size: 12px;
i {
margin-top: 4px;
}
tbody>tr.active, tbody>tr.active>td {
background-color: #EDF2F2;
}
tbody>tr.active>td {
background-color: @active-color;
.status-column i {
font-size: 12px;
}
}
.section {
margin-top: 20px;
margin-bottom: 20px;
h5 {
margin-top: 0;
margin-bottom: 5px;
}
.small-title {
font-weight: normal;
margin-bottom: 12px;
}
}
.job_summary, .job_status {
.section:last-child {
margin-bottom: 0;
}
.job_summary {
.table {
margin-bottom: 0;
border: 1px solid @grey;
background-color: @white;
}
.table>tbody>tr>td {
border-top-color: @well;
padding-bottom: 0;
}
.table>thead>tr>th {
border-bottom-color: @well;
padding-bottom: 0;
height: 22px;
}
}
.job_status {
margin-bottom: 25px;
.label_column {
width: 80px;
}
.table>tbody>tr>td {
padding-bottom: 3px;
}
.status-column i {
font-size: 12px;
}
}
.status-bar {
display: inline-block;
height: 15px;
height: 16px;
overflow: hidden;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
width: 98%;
margin-top: 2px;
}
.inner-bar {
display: inline-block;
overflow: hidden;
height: 16px;
text-align: center;
font-size: 12px;
font-weight: bold;
line-height: normal;
}
.failed-hosts {
background-color: @red;
background-color: #DA4D49;
}
.successful-hosts {
background-color: @green;
background-color: #9ED89E;
}
.changed-hosts {
background-color: @warning;
background-color: #FFC773;
}
.skipped-hosts {
background-color: @grey;
background-color: #D4D4D4;
}
.job_well {
@ -126,8 +165,34 @@
overflow-x: none;
}
#job_plays, #job_tasks, #host_details {
#job_plays, #job_tasks {
height: 150px;
overflow-y: auto;
overflow-x: none;
}
#hosts-section {
border: 1px solid @grey;
border-top: 2px solid #ddd;
padding: 5px;
height: 150px;
background-color: @white;
}
#host-details {
ul {
list-style: none;
margin: 0;
padding: 0;
i {
font-size: 12px;
}
}
li {
border-bottom: 1px solid @well;
padding-bottom: 2px;
padding-top: 0;
margin: 0;
line-height: normal;
}
}

View File

@ -0,0 +1,14 @@
{
"name": "d3js",
"homepage": "https://github.com/henrytao-me/d3js",
"_release": "2745db14e3",
"_resolution": {
"type": "branch",
"branch": "master",
"commit": "2745db14e37540fc5a9c4ac3b44152bfd7f92e9a"
},
"_source": "git://github.com/henrytao-me/d3js.git",
"_target": "*",
"_originalSource": "d3js",
"_direct": true
}

15
awx/ui/static/lib/d3js/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Henry Tao
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,2 @@
d3js
====

View File

@ -1,168 +1,136 @@
<div class="tab-pane" id="jobs">
<div class="tab-pane" id="jobs-detail">
<div ng-cloak id="htmlTemplate">
<div class="row">
<div class="col-md-12" id="breadcrumbs"></div>
<div class="col-md-12">
<div class="nav-path">
<ul class="breadcrumb" id="breadcrumb-list">
<li><strong>{{ job_id }}</strong> - <a href="{{ job_template_url }}">{{ job_template_name }}</a></li>
<li><a href="{{ project_url }}">{{ project_name }}</a></li>
<li><a href="{{ inventory_url }}">{{ inventory_name }}</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-7">
<div class="job-detail-tables">
<div class="section">
<h5>Job</h5>
<div id="job_options" class="job_well">
<table class="table table-condensed job-detail-table">
<div class="job_well">
<div id="job-status">
<ul>
<li><span class="label">Status</span> <i class="fa icon-job-{{ job_status.status }}"></i> {{ job_status.status }}</li>
<li><span class="label">Start</span> {{ job_status.started | date:'MM/dd/yy HH:mm:ss' }}</li>
<li><span class="label">Finish</span> {{ job_status.finished | date:'MM/dd/yy HH:mm:ss' }}<li>
<li><span class="label">Elapsed</span> {{ job_status.elapsed }}</li>
</ul>
</div>
<div class="job-detail-tables">
<div class="section">
<h5>Plays</h5>
<table class="table job-detail-table">
<thead>
<tr>
<th class="col-lg-1 col-md-1 col-sm-1 hidden-xs">Started</th>
<th class="col-lg-10 col-md-10 col-sm-10 col-xs-12">Name</th>
<th class="col-lg-1 col-md-1 col-sm-1 hidden-xs">Elapsed</th>
</tr>
</thead>
<tbody>
<tr ng-show="job_template_url">
<td class="col-md-3 col-sm-2">Job Template</td>
<td><a ng-href="{{ job_template_url }}">{{ job_template_name }}</a></td>
</tr>
<tr ng-show="project_url">
<td class="col-md-3 col-sm-2">Project</td>
<td><a ng-href="{{ project_url }}">{{ project_name }}</a></td>
</tr>
<tr ng-show="inventory_url">
<td class="col-md-3 col-sm-2">Inventory</td>
<td><a ng-href="{{ inventory_url }}">{{ inventory_name }}</a></td>
</tr>
<tr ng-show="playbook">
<td class="col-md-3 col-sm-2">Playbook</td>
<td>{{ playbook }}</td>
</tr>
<tr ng-show="job_type">
<td class="col-md-3 col-sm-2">Run Type</td>
<td>{{ job_type }}</td>
</tr>
<tr ng-show="credential">
<td class="col-md-3 col-sm-2">Machine Credential</td>
<td><a href="/#/credentials/{{ credential }}">{{ credential_name }}</a></td>
</tr>
<tr ng-show="cloud_credential">
<td class="col-md-3 col-sm-2">Cloud Credential</td>
<td><a href="/#/credentials/{{ credential }}">{{ cloud_credential_name }}</a></td>
</tr>
<tr ng-show="forks">
<td class="col-md-3 col-sm-2">Forks</td>
<td>{{ forks }}</td>
</tr>
<tr ng-show="limit">
<td class="col-md-3 col-sm-2">Limit</td>
<td>{{ limit }}</td>
</tr>
<tr ng-show="verbosity !== undefined">
<td class="col-md-3 col-sm-2">Verbosity</td>
<td>{{ verbosity }}</td>
</tr>
<tr ng-show="job_tags">
<td class="col-md-3 col-sm-2">Job Tags</td>
<td>{{ job_tags }}</td>
<tr ng-repeat="play in plays" ng-class="play.playActiveClass" ng-click="selectPlay(play.id)" class="cursor-pointer">
<td class="col-lg-1 col-md-1 col-sm-1 hidden-xs">{{ play.created | date: 'HH:mm:ss' }}</td>
<td class="col-lg-10 col-md-9 col-sm-10 col-xs-12 status-column">
<i class="fa icon-job-{{ play.status }}"></i> {{ play.name }}</span></td>
<td class="col-lg-1 col-md-1 col-sm-1 hidden-xs" aw-tool-tip="Completed at {{ play.finished | date:'HH:mm:ss' }}"
data-placement="top">{{ play.elapsed }}</td>
</tr>
</tbody>
</table>
</div><!-- section -->
</div>
<div class="section">
<h5>Plays</h5>
<div id="job_plays" class="job_well">
<table class="table table-condensed job-detail-table">
<tbody>
<tr ng-repeat="play in plays" ng-class="play.playActiveClass" ng-click="selectPlay(play.id)" class="cursor-pointer">
<td class="status-column"><i class="fa icon-job-{{ play.status }}"></i></td>
<td><span aw-tool-tip="Event: {{ play.id }}" data-placement="top">{{ play.name }}</span></td>
<div class="section">
<h5>Tasks</h5>
<table class="table job-detail-table">
<thead>
<tr>
<th class="col-lg-1 col-md-1 col-sm-1 hidden-xs">Started</th>
<th class="col-lg-6 col-md-6 col-sm-6 col-xs-7">Name</th>
<th class="col-lg-4 col-md-4 col-sm-4 col-xs-5">Host Status</th>
<th class="col-lg-1 col-md-1 col-sm-1 hidden-xs">Elapsed</th>
</tr>
</thead>
<tbody id="task-table-body">
<tr ng-repeat="task in tasks | filter:{ play_id: activePlay }" ng-class="task.taskActiveClass" ng-click="selectTask(task.id)" class="cursor-pointer">
<td class="col-lg-1 col-md-1 col-sm-1 hidden-xs">{{ task.created | date: 'HH:mm:ss' }}</td>
<td class="col-lg-6 col-md-6 col-sm-6 col-xs-7 status-column">
<i class="fa icon-job-{{ task.status }}"></i><span ng-show="hasRoles"> {{ task.role }} </span>{{ task.name }}
</td>
<td class="col-lg-4 col-md-4 col-sm-4 col-xs-5">
<div class="status-bar">
<div class="successful-hosts inner-bar" aw-tool-tip="{{ task.successfulCount}} hosts OK" aw-tip-watch="task.successfulCount" data-placement="top" ng-style="{{ task.successfulStyle }}">{{ task.successfulCount }}</div>
<div class="changed-hosts inner-bar" aw-tool-tip="{{ task.changedCount}} hosts changed" aw-tip-watch="task.changedCount" data-placement="top" ng-style="{{ task.changedStyle }}">{{ task.changedCount }}</div>
<div class="skipped-hosts inner-bar" aw-tool-tip="{{ task.skippedCount}} hosts skipped" aw-tip-watch="task.skippedCount" data-placement="top" ng-style="{{ task.skippedStyle }}">{{ task.skippedCount }}</div>
<div class="failed-hosts inner-bar" aw-tool-tip="{{ task.failedCount}} hosts failed" aw-tip-watch="task.failedCount" data-placement="top" ng-style="{{ task.failedStyle }}">{{ task.failedCount }}</div>
</div>
</td>
<td class="col-lg-1 col-md-1 col-sm-1 hidden-xs" aw-tool-tip="Completed at {{ task.finished | date:'HH:mm:ss' }}"
data-placement="top">{{ task.elapsed }}</td>
</tr>
</tbody>
</table>
</div>
</div><!-- section -->
</div><!-- section -->
<div class="section">
<h5>Tasks</h5>
<table class="table table-condensed table-striped job-detail-table">
<div class="section" style="position: relative;">
<h5>Hosts</h5>
<div id="hosts-section">
<div id="host-details">
<ul>
<li ng-repeat="result in hostResults | filter:{ task_id: activeTask }">
<div class="status-column inline-block"><i class="fa icon-job-{{ result.status }}"></i> <a href="" ng-click="doSomething()" aw-tool-tip="Click to view results" data-placement="top">{{ result.name }}</a></div>
</li>
</ul>
</div>
</div>
<div id="hosts-slider-vertical" style="position:absolute; top:27px; right: 0; width:9px; height:150px;"></div>
</div><!-- section -->
</div><!-- job-detail-tables -->
</div><!-- well -->
</div><!-- col-md-7 -->
<div class="col-md-5">
<div class="job_well">
<div class="section job_summary">
<h5>Host Summary</h5>
<table class="table table-condensed">
<thead>
<tr>
<th class="col-md-1 col-sm-1 col-xs-3">Started</th>
<th class="col-md-2 col-sm-2 hidden-xs" ng-show="hasRoles">Role</th>
<th class="col-md-5 col-sm-5 col-xs-5">Name</th>
<th class="col-md-4 col-sm-4 col-xs-5">Status</td>
<th class="col-lg-8 col-md-8 col-sm-8 col-xs-4">Host</th>
<th class="col-lg-1 col-md-1 col-sm-1 col-xs-2">OK</th>
<th class="col-lg-1 col-md-1 col-sm-1 col-xs-2">Changed</th>
<th class="col-lg-1 col-md-1 col-sm-1 col-xs-2">Dark</th>
<th class="col-lg-1 col-md-1 col-sm-1 col-xs-2">Failed</th>
</tr>
</thead>
<tbody id="task-table-body">
<tr ng-repeat="task in tasks | filter:{ play_id: activePlay }" ng-class="task.taskActiveClass" ng-click="selectTask(task.id)" class="cursor-pointer">
<td class="col-md-1 col-sm-1 col-xs-3">{{ task.created | date: 'HH:mm:ss' }}</td>
<td class="col-md-2 col-sm-2 hidden-xs" ng-show="hasRoles">{{ task.role }}</td>
<td class="col-md-5 col-sm-5 col-xs-5">{{ task.name }}</td>
<td class="col-md-4 col-sm-4 col-xs-5">
<div class="successful-hosts status-bar" style="width:{{ task.successfulPct || 0 }}%">
<div class="changed-hosts status-bar" style="width:{{ task.changedPct || 0 }}%"></div>
</div>
<div class="skipped-hosts status-bar" style="width:{{ task.skippedPct || 0 }}%"></div>
<div class="failed-hosts status-bar" style="width:{{ task.failedPct || 0 }}%"></div>
</td>
<tbody >
<tr ng-repeat="host in hosts">
<td class="col-lg-8 col-md-8 col-sm-8 col-xs-4"><a href="/#/home/hosts/?id={{ host.id }}">{{ host.name }}</a></td>
<td class="col-lg-1 col-md-1 col-sm-1 col-xs-2">{{ host.ok }}</td>
<td class="col-lg-1 col-md-1 col-sm-1 col-xs-2">{{ host.changed }}</td>
<td class="col-lg-1 col-md-1 col-sm-1 col-xs-2">{{ host.unreachable }}</td>
<td class="col-lg-1 col-md-1 col-sm-1 col-xs-2">{{ host.failed }}</td>
</tr>
</tbody>
</table>
</div><!-- section -->
</div>
</div><!-- col-md-5 -->
<div class="section">
<h5>Hosts</h5>
<div id="host_details" class="job_well">
<table class="table table-condensed job-detail-table">
<tbody>
<tr ng-repeat="result in hostResults | filter:{ task_id: activeTask }">
<td class="status-column"><i class="fa icon-job-{{ result.status }}"></i></td>
<td><a href="" ng-click="doSomething()" aw-tool-tip="Click to view results" data-placement="top">{{ result.host_name }} <i class="fa fa-external-link"></i></a></td>
</tr>
</tbody>
</table>
</div>
</div><!-- section -->
</div><!-- job-detail-tables -->
</div><!-- col-md-8 -->
<div class="col-md-5">
<div class="section">
<h5>Summary</h5>
<div class="job_well">
<div class="job_status">
<table class="table table-condensed job-detail-table">
<tbody>
<tr><td class="label_column">Status</td><td class="status-column"><i class="fa icon-job-{{ job_status }}"></i> {{ job_status }}</td></tr>
<tr><td class="label_column">Started</td><td>{{ started | date:'MM/dd/yy HH:mm:ss' }}</td></tr>
<tr><td class="label_column">Finished</td><td>{{ finished | date:'MM/dd/yy HH:mm:ss' }}</td></tr>
<tr><td class="label_column">Elapsed</td><td>{{ elapsed }} seconds</td></tr>
</tbody>
</table>
</div>
<div class="job_summary">
<table class="table table-condensed job-detail-table">
<thead>
<tr>
<th class="col-md-8">Host</th>
<th class="text-center col-md-1">OK</th>
<th class="text-center col-md-1">Changed</th>
<th class="text-center col-md-1">Unreachable</th>
<th class="text-center col-md-1">Failed</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="host in hosts">
<td><a href="/#/home/hosts/?id={{ host.id }}">{{ host.name }}</a></td>
<td class="text-center">{{ host.ok }}</td>
<td class="text-center">{{ host.changed }}</td>
<td class="text-center">{{ host.unreachable }}</td>
<td class="text-center">{{ host.failed }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div><!-- section -->
</div>
</div>
</div>
</div>

View File

@ -399,6 +399,132 @@
<script src="{{ STATIC_URL }}lib/codemirror/addon/selection/active-line.js"></script>
<script src="{{ STATIC_URL }}lib/scrollto/lib/jquery-scrollto.js"></script>
<script src="{{ STATIC_URL }}lib/socket.io-client/dist/socket.io.min.js"></script>
<script src="{{ STATIC_URL }}lib/lib/d3js/build/d3.v3.min.js"></script>
<script>
!function(){
var Donut3D={};
function pieTop(d, rx, ry, ir ){
if(d.endAngle - d.startAngle == 0 ) return "M 0 0";
var sx = rx*Math.cos(d.startAngle),
sy = ry*Math.sin(d.startAngle),
ex = rx*Math.cos(d.endAngle),
ey = ry*Math.sin(d.endAngle);
var ret =[];
ret.push("M",sx,sy,"A",rx,ry,"0",(d.endAngle-d.startAngle > Math.PI? 1: 0),"1",ex,ey,"L",ir*ex,ir*ey);
ret.push("A",ir*rx,ir*ry,"0",(d.endAngle-d.startAngle > Math.PI? 1: 0), "0",ir*sx,ir*sy,"z");
return ret.join(" ");
}
function pieOuter(d, rx, ry, h ){
var startAngle = (d.startAngle > Math.PI ? Math.PI : d.startAngle);
var endAngle = (d.endAngle > Math.PI ? Math.PI : d.endAngle);
var sx = rx*Math.cos(startAngle),
sy = ry*Math.sin(startAngle),
ex = rx*Math.cos(endAngle),
ey = ry*Math.sin(endAngle);
var ret =[];
ret.push("M",sx,h+sy,"A",rx,ry,"0 0 1",ex,h+ey,"L",ex,ey,"A",rx,ry,"0 0 0",sx,sy,"z");
return ret.join(" ");
}
function pieInner(d, rx, ry, h, ir ){
var startAngle = (d.startAngle < Math.PI ? Math.PI : d.startAngle);
var endAngle = (d.endAngle < Math.PI ? Math.PI : d.endAngle);
var sx = ir*rx*Math.cos(startAngle),
sy = ir*ry*Math.sin(startAngle),
ex = ir*rx*Math.cos(endAngle),
ey = ir*ry*Math.sin(endAngle);
var ret =[];
ret.push("M",sx, sy,"A",ir*rx,ir*ry,"0 0 1",ex,ey, "L",ex,h+ey,"A",ir*rx, ir*ry,"0 0 0",sx,h+sy,"z");
return ret.join(" ");
}
function getPercent(d){
return (d.endAngle-d.startAngle > 0.2 ?
Math.round(1000*(d.endAngle-d.startAngle)/(Math.PI*2))/10+'%' : '');
}
Donut3D.transition = function(id, data, rx, ry, h, ir){
function arcTweenInner(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieInner(i(t), rx+0.5, ry+0.5, h, ir); };
}
function arcTweenTop(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieTop(i(t), rx, ry, ir); };
}
function arcTweenOuter(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieOuter(i(t), rx-.5, ry-.5, h); };
}
function textTweenX(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return 0.6*rx*Math.cos(0.5*(i(t).startAngle+i(t).endAngle)); };
}
function textTweenY(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return 0.6*rx*Math.sin(0.5*(i(t).startAngle+i(t).endAngle)); };
}
var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);
d3.select("#"+id).selectAll(".innerSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenInner);
d3.select("#"+id).selectAll(".topSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenTop);
d3.select("#"+id).selectAll(".outerSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenOuter);
d3.select("#"+id).selectAll(".percent").data(_data).transition().duration(750)
.attrTween("x",textTweenX).attrTween("y",textTweenY).text(getPercent);
}
Donut3D.draw=function(id, data, x /*center x*/, y/*center y*/,
rx/*radius x*/, ry/*radius y*/, h/*height*/, ir/*inner radius*/){
var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);
var slices = d3.select("#"+id).append("g").attr("transform", "translate(" + x + "," + y + ")")
.attr("class", "slices");
slices.selectAll(".innerSlice").data(_data).enter().append("path").attr("class", "innerSlice")
.style("fill", function(d) { return d3.hsl(d.data.color).darker(0.7); })
.attr("d",function(d){ return pieInner(d, rx+0.5,ry+0.5, h, ir);})
.each(function(d){this._current=d;});
slices.selectAll(".topSlice").data(_data).enter().append("path").attr("class", "topSlice")
.style("fill", function(d) { return d.data.color; })
.style("stroke", function(d) { return d.data.color; })
.attr("d",function(d){ return pieTop(d, rx, ry, ir);})
.each(function(d){this._current=d;});
slices.selectAll(".outerSlice").data(_data).enter().append("path").attr("class", "outerSlice")
.style("fill", function(d) { return d3.hsl(d.data.color).darker(0.7); })
.attr("d",function(d){ return pieOuter(d, rx-.5,ry-.5, h);})
.each(function(d){this._current=d;});
slices.selectAll(".percent").data(_data).enter().append("text").attr("class", "percent")
.attr("x",function(d){ return 0.6*rx*Math.cos(0.5*(d.startAngle+d.endAngle));})
.attr("y",function(d){ return 0.6*ry*Math.sin(0.5*(d.startAngle+d.endAngle));})
.text(getPercent).each(function(d){this._current=d;});
}
this.Donut3D = Donut3D;
}();
</script>
<script>
// When user clicks on main tab, fire the matching Angular route

View File

@ -20,7 +20,8 @@
"components-font-awesome": "~4.0.3",
"less.js": "~1.6.3",
"select2": "~3.4.5",
"sizzle": "1.10.16"
"sizzle": "1.10.16",
"d3js": "*"
},
"resolutions": {
"angular": "1.2.15-build.2398+sha.4bab3d8"