diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 4bbaf8ede5..4521970a7d 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -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', diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index e300e407d4..0b163602c6 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -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; }); }; }]) diff --git a/awx/ui/static/js/helpers/PaginationHelpers.js b/awx/ui/static/js/helpers/PaginationHelpers.js index 745cc2c670..22022c16b4 100644 --- a/awx/ui/static/js/helpers/PaginationHelpers.js +++ b/awx/ui/static/js/helpers/PaginationHelpers.js @@ -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']); }; } ]) diff --git a/awx/ui/static/less/job-details.less b/awx/ui/static/less/job-details.less index a5deb52591..1b89b8d346 100644 --- a/awx/ui/static/less/job-details.less +++ b/awx/ui/static/less/job-details.less @@ -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; + } +} diff --git a/awx/ui/static/lib/d3js/.bower.json b/awx/ui/static/lib/d3js/.bower.json new file mode 100644 index 0000000000..6e2680e72d --- /dev/null +++ b/awx/ui/static/lib/d3js/.bower.json @@ -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 +} \ No newline at end of file diff --git a/awx/ui/static/lib/d3js/.gitignore b/awx/ui/static/lib/d3js/.gitignore new file mode 100644 index 0000000000..a72b52ebe8 --- /dev/null +++ b/awx/ui/static/lib/d3js/.gitignore @@ -0,0 +1,15 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log +node_modules diff --git a/awx/ui/static/lib/d3js/LICENSE b/awx/ui/static/lib/d3js/LICENSE new file mode 100644 index 0000000000..63e9564f3a --- /dev/null +++ b/awx/ui/static/lib/d3js/LICENSE @@ -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. diff --git a/awx/ui/static/lib/d3js/README.md b/awx/ui/static/lib/d3js/README.md new file mode 100644 index 0000000000..9ad31ded1c --- /dev/null +++ b/awx/ui/static/lib/d3js/README.md @@ -0,0 +1,2 @@ +d3js +==== diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index 102ef54703..0efeab2496 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -1,168 +1,136 @@ -
+
+
- + +
-
-
-
Job
-
- +
+ +
+
    +
  • Status {{ job_status.status }}
  • +
  • Start {{ job_status.started | date:'MM/dd/yy HH:mm:ss' }}
  • +
  • Finish {{ job_status.finished | date:'MM/dd/yy HH:mm:ss' }}
  • +
  • Elapsed {{ job_status.elapsed }}
  • +
+
+ +
+
+
Plays
+
+ + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + +
Name
Job Template{{ job_template_name }}
Project{{ project_name }}
Inventory{{ inventory_name }}
Playbook{{ playbook }}
Run Type{{ job_type }}
Machine Credential{{ credential_name }}
Cloud Credential{{ cloud_credential_name }}
Forks{{ forks }}
Limit{{ limit }}
Verbosity{{ verbosity }}
Job Tags{{ job_tags }}
+ {{ play.name }}
-
-
-
Plays
-
- - - - - +
+
Tasks
+
{{ play.name }}
+ + + + + + + + + + + + + +
NameHost Status
+ {{ task.role }} {{ task.name }} + +
+
{{ task.successfulCount }}
+
{{ task.changedCount }}
+
{{ task.skippedCount }}
+
{{ task.failedCount }}
+
+
-
-
+
-
-
Tasks
- +
+
Hosts
+
+
+ +
+
+
+
+ + + + + + + +
+
+
+
Host Summary
+
- - - - + + + + - - - - - - + + + + + + +
StartedNameStatus + HostOKChangedDarkFailed
{{ task.created | date: 'HH:mm:ss' }}{{ task.name }} -
-
-
-
-
-
{{ host.name }}{{ host.ok }}{{ host.changed }}{{ host.unreachable }}{{ host.failed }}
+
+
-
-
Hosts
-
- - - - - - - -
{{ result.host_name }}
-
-
-
-
- -
-
-
Summary
-
- -
- - - - - - - -
Status {{ job_status }}
Started{{ started | date:'MM/dd/yy HH:mm:ss' }}
Finished{{ finished | date:'MM/dd/yy HH:mm:ss' }}
Elapsed{{ elapsed }} seconds
-
- -
- - - - - - - - - - - - - - - - - - - -
HostOKChangedUnreachableFailed
{{ host.name }}{{ host.ok }}{{ host.changed }}{{ host.unreachable }}{{ host.failed }}
-
- -
-
-
- diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index fbe3a1cbf1..8c48dd1633 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -399,6 +399,132 @@ + +