1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 01:21:21 +03:00

Latest job_detail page changes.

This commit is contained in:
Chris Houseknecht 2014-04-17 18:52:18 +00:00
parent 647001546a
commit ca42694a13
5 changed files with 349 additions and 223 deletions

View File

@ -59,6 +59,39 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
} }
}); });
if ($scope.removeGetCredentialNames) {
$scope.removeGetCredentialNames();
}
$scope.removeGetCredentialNames = $scope.$on('GetCredentialNames', function(e, data) {
var url;
if (data.credential) {
url = GetBasePath('credentials') + data.credential + '/';
Rest.setUrl(url);
Rest.get()
.success( function(data) {
$scope.credential_name = data.name;
})
.error( function(data, status) {
$scope.credential_name = '';
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
if (data.cloud_credential) {
url = GetBasePath('credentials') + data.credential + '/';
Rest.setUrl(url);
Rest.get()
.success( function(data) {
$scope.cloud_credential_name = data.name;
})
.error( function(data, status) {
$scope.credential_name = '';
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
});
// Load the job record // Load the job record
Rest.setUrl(GetBasePath('jobs') + job_id + '/'); Rest.setUrl(GetBasePath('jobs') + job_id + '/');
Rest.get() Rest.get()
@ -78,7 +111,12 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
$scope.limit = data.limit; $scope.limit = data.limit;
$scope.verbosity = data.verbosity; $scope.verbosity = data.verbosity;
$scope.job_tags = data.job_tags; $scope.job_tags = data.job_tags;
$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('JobReady', data.related.job_events + '?page_size=50&order_by=id');
$scope.$emit('GetCredentialNames', data);
}) })
.error(function(data, status) { .error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', ProcessErrors($scope, data, status, null, { hdr: 'Error!',

View File

@ -39,11 +39,13 @@
angular.module('JobDetailHelper', ['Utilities', 'RestServices']) angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'MakeLastRowActive', .factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'SelectPlay', 'SelectTask',
function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, MakeLastRowActive) { function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
events = params.events; events = params.events;
events.forEach(function(event) { events.forEach(function(event) {
if (event.event === 'playbook_on_play_start') { if (event.event === 'playbook_on_play_start') {
scope.plays.push({ scope.plays.push({
@ -52,8 +54,10 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
status: (event.changed) ? 'changed' : (event.failed) ? 'failed' : 'none', status: (event.changed) ? 'changed' : (event.failed) ? 'failed' : 'none',
children: [] children: []
}); });
scope.activePlay = event.id; SelectPlay({
MakeLastRowActive({ scope: scope, list: scope.plays, field: 'playActiveClass', set: 'activePlay' }); scope: scope,
id: event.id
});
} }
if (event.event === 'playbook_on_setup') { if (event.event === 'playbook_on_setup') {
scope.tasks.push({ scope.tasks.push({
@ -82,7 +86,10 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
failed: event.failed, failed: event.failed,
changed: event.changed changed: event.changed
}); });
MakeLastRowActive({ scope: scope, list: scope.tasks, field: 'taskActiveClass', set: 'activeTask' }); SelectTask({
scope: scope,
id: event.id
});
} }
/*if (event.event === 'playbook_on_no_hosts_matched') { /*if (event.event === 'playbook_on_no_hosts_matched') {
UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent }); UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent });
@ -94,7 +101,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
UpdateHostStatus({ UpdateHostStatus({
scope: scope, scope: scope,
name: event.event_data.host, name: event.event_data.host,
host_id: event.host_id, host_id: event.host,
task_id: event.parent, task_id: event.parent,
status: (event.changed) ? 'changed' : 'ok', status: (event.changed) ? 'changed' : 'ok',
results: (event.res && event.res.results) ? event.res.results : '' results: (event.res && event.res.results) ? event.res.results : ''
@ -169,7 +176,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
id = params.play_id; id = params.play_id;
scope.plays.every(function(play,idx) { scope.plays.every(function(play,idx) {
if (play.id === id) { if (play.id === id) {
scope.plays[idx].status = (changed) ? 'changed' : (failed) ? 'failed' : 'none'; scope.plays[idx].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful';
return false; return false;
} }
return true; return true;
@ -190,7 +197,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
scope: scope, scope: scope,
failed: failed, failed: failed,
changed: changed, changed: changed,
play_id: task.parent play_id: task.play_id
}); });
} }
}); });
@ -293,34 +300,80 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla
}; };
}]) }])
.factory('SelectPlay', [ function() { .factory('SelectPlay', ['SelectTask', function(SelectTask) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
id = params.id; id = params.id,
max_task_id = 0;
scope.plays.forEach(function(play, idx) { scope.plays.forEach(function(play, idx) {
if (play.id === id) { if (play.id === id) {
scope.plays[idx].playActiveClass = 'active'; scope.plays[idx].playActiveClass = 'active';
scope.activePlay = id;
scope.activePlayName = play.name;
} }
else { else {
scope.plays[idx].playActiveClass = ''; scope.plays[idx].playActiveClass = '';
} }
}); });
scope.activePlay = id;
// Select the last task
scope.tasks.forEach(function(task) {
if (task.play_id === scope.activePlay && task.id > max_task_id) {
max_task_id = task.id;
}
});
scope.activeTask = max_task_id;
SelectTask({
scope: scope,
id: max_task_id,
callback: function() {
setTimeout(function() {
var inner_height = $('#job_tasks .job-detail-table').height();
$('#job_tasks').scrollTop(inner_height);
}, 100);
}
});
}; };
}]) }])
.factory('SelectTask', [ function() { .factory('SelectTask', ['SelectHost', function(SelectHost) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
id = params.id; id = params.id,
callback = params.callback;
scope.tasks.forEach(function(task, idx) { scope.tasks.forEach(function(task, idx) {
if (task.id === id) { if (task.id === id) {
scope.tasks[idx].taskActiveClass = 'active'; scope.tasks[idx].taskActiveClass = 'active';
scope.activeTask = id;
scope.activeTaskName = task.name;
} }
else { else {
scope.tasks[idx].taskActiveClass = ''; scope.tasks[idx].taskActiveClass = '';
} }
}); });
scope.activeTask = id; if (callback) {
callback();
}
SelectHost();
};
}])
.factory('SelectHost', [ function() {
return function() {
setTimeout(function() {
var inner_height = $('#host_details .job-detail-table').height();
$('#host_details').scrollTop(inner_height);
}, 100);
}; };
}]); }]);

View File

@ -34,6 +34,7 @@
@import "codemirror.less"; @import "codemirror.less";
@import "angular-scheduler.less"; @import "angular-scheduler.less";
@import "log-viewer.less"; @import "log-viewer.less";
@import "job-details.less";
html, body { height: 100%; } html, body { height: 100%; }
@ -854,7 +855,7 @@ input[type="checkbox"].checkbox-no-label {
/* Display list actions next to search widget */ /* Display list actions next to search widget */
.list-actions { .list-actions {
text-align: right; text-align: right;
margin-bottom: 25px; margin-bottom: 10px;
button { button {
margin-left: 4px; margin-left: 4px;
@ -1278,9 +1279,14 @@ input[type="checkbox"].checkbox-no-label {
/* Inventory Edit */ /* Inventory Edit */
#groups-container .well,
#hosts-container .well {
padding: 8px;
}
#inventories_table i[class*="icon-job-"], #inventories_table i[class*="icon-job-"],
#home_groups_table i[class*="icon-job-"] { #home_groups_table i[class*="icon-job-"] {
margin-left: 8px; margin-left: 10px;
} }
.selected { .selected {
@ -1579,93 +1585,7 @@ tr td button i {
} }
} }
/* New job detail page */
.relative-position {
position: relative;
}
.job-detail-tables, .job_options {
.table {
margin-bottom: 0;
}
.table>tbody>tr>td {
border-top-color: @well;
padding: 0;
}
.table>thead>tr>th {
border-bottom-color: @well;
padding: 0;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li ul {
margin-left: 20px;
}
}
.job_well {
padding: 8px;
background-color: @well;
border: 1px solid @well-border;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
.job_options {
height: 100px;
overflow-y: auto;
overflow-x: none;
}
.job-list li.active {
background-color: @active-color;
}
.job_plays, .job_tasks, .job_hosts {
height: 150px;
overflow-y: auto;
overflow-x: none;
}
.host_details {
pre {
border: none;
max-height: 100px;
overflow: auto;
margin: 0;
padding: 0;
}
}
.scroll-up-indicator {
position: absolute;
right: 18px;
font-size: 14px;
bottom: -2px;
padding: 0;
}
.scroll-down-indicator {
position: absolute;
right: 18px;
bottom: -2px;
font-size: 14px;
padding: 0;
}
.scroll-up-indicator,
.scroll-up-indicator:hover,
.scroll-up-indicator:visited,
.scroll-down-indicator,
.scroll-down-indicator:hover,
.scroll-down-indicator:visited {
color: @grey;
}
/* ng-cloak directive */ /* ng-cloak directive */

View File

@ -0,0 +1,99 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* job-details.less
*
* Styles for job details page
*
*/
.job-detail-tables {
.table {
margin-bottom: 0;
}
.table>tbody>tr>td {
border-top-color: @well;
padding: 0;
}
.table>thead>tr>th {
border-bottom-color: @well;
padding: 0;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li ul {
margin-left: 20px;
}
.status-column {
width: 25px;
font-size: 12px;
text-align: center;
i {
margin-top: 4px;
}
}
.table>tbody>tr.active>td {
background-color: @active-color;
}
}
.section {
margin-top: 20px;
h5 {
margin-top: 0;
margin-bottom: 5px;
}
.small-title {
font-weight: normal;
}
}
.job_summary, .job_status {
.table {
margin-bottom: 0;
}
.table>tbody>tr>td {
border-top-color: @well;
}
.table>thead>tr>th {
border-bottom-color: @well;
}
}
.job_status {
margin-bottom: 25px;
.label_column {
width: 80px;
}
.table>tbody>tr>td {
padding-bottom: 3px;
}
.status-column i {
font-size: 12px;
}
}
.job_well {
padding: 8px;
background-color: @well;
border: 1px solid @well-border;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
#job_options {
height: 100px;
overflow-y: auto;
overflow-x: none;
}
#job_plays, #job_tasks, #host_details {
height: 150px;
overflow-y: auto;
overflow-x: none;
}

View File

@ -5,10 +5,13 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-7">
<h5>Options</h5>
<div class="job_options job_well" id="job_options"> <div class="job-detail-tables">
<table class="table table-condensed"> <div class="section">
<h5>Job</h5>
<div id="job_options" class="job_well">
<table class="table table-condensed job-detail-table">
<tbody> <tbody>
<tr ng-show="job_template_url"> <tr ng-show="job_template_url">
<td class="col-md-3 col-sm-2">Job Template</td> <td class="col-md-3 col-sm-2">Job Template</td>
@ -32,7 +35,11 @@
</tr> </tr>
<tr ng-show="credential"> <tr ng-show="credential">
<td class="col-md-3 col-sm-2">Machine Credential</td> <td class="col-md-3 col-sm-2">Machine Credential</td>
<td>{{ 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>
<tr ng-show="forks"> <tr ng-show="forks">
<td class="col-md-3 col-sm-2">Forks</td> <td class="col-md-3 col-sm-2">Forks</td>
@ -52,62 +59,74 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div><!-- section -->
</div> </div>
<!-- <a id="job_options_scroll_down_indicator" href="" aw-scroll-down="job_options" class="scroll-down-indicator">more <i class="fa fa-chevron-circle-down"></i></a>
<a id="job_options_scroll_up_indicator" href="" aw-scroll-up="job_options" class="scroll-up-indicator">more <i class="fa fa-chevron-circle-up"></i></a> -->
<div class="job-detail-tables">
<div class="section">
<h5>Plays</h5> <h5>Plays</h5>
<div class="job_plays job_well"> <div id="job_plays" class="job_well">
<ul class="job-list">
<li ng-repeat="play in plays" ng-class="play.playActiveClass" ng-click="selectPlay(play.id)" class="cursor-pointer">
<i class="fa icon-job-{{ play.status }}"></i> {{play.name }}
<ul ng-show="play.children.length > 0">
<li ng-repeat="child in play.children">
<i class="fa icon-job-{{ child.status }}"></i> {{child.name }}
</li>
</ul>
</li>
</ul>
</div>
<h5>Tasks</h5>
<div class="job_tasks job_well">
<ul class="job-list">
<li ng-repeat="task in tasks | filter:{ play_id: activePlay }" ng-class="task.taskActiveClass" ng-click="selectTask(task.id)"><i class="fa icon-job-{{ task.status }}"></i> {{ task.name }}</li>
</ul>
</div>
<h5>Host Details</h5>
<div class="host_details job_well">
<p>{{ activeTaskName }}</p>
<table class="table table-condensed job-detail-table"> <table class="table table-condensed job-detail-table">
<thead>
<tr>
<th class="col-md-4">Host</th>
<th class="col-md-8">Results</th>
</tr>
</thead>
<tbody> <tbody>
<tr ng-repeat="result in hostResults | filter:{ task_id: activeTask }"> <tr ng-repeat="play in plays" ng-class="play.playActiveClass" ng-click="selectPlay(play.id)" class="cursor-pointer">
<td><i class="fa icon-job-{{ result.status }}"></i> {{ result.host_name }}</td> <td class="status-column"><i class="fa icon-job-{{ play.status }}"></i></td>
<td><pre>{{ result.results }}</pre></td> <td><span aw-tool-tip="Event: {{ play.id }}" data-placement="top">{{ play.name }}</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div><!-- section -->
<div class="section">
<h5>Tasks for play: <span class="small-title">{{ activePlayName }}</span></h5>
<div id="job_tasks" class="job_well">
<table class="table table-condensed job-detail-table">
<tbody>
<tr ng-repeat="task in tasks | filter:{ play_id: activePlay }" ng-class="task.taskActiveClass" ng-click="selectTask(task.id)" class="cursor-pointer">
<td class="status-column"><i class="fa icon-job-{{ task.status }}"></i></td>
<td><span aw-tool-tip="Event: {{ task.id }}" data-placement="top">{{ task.name }}</span></td>
</tr>
</tbody>
</table>
</div> </div>
</div><!-- section -->
<div class="section">
<h5>Hosts in task: <span class="small-title">{{ activeTaskName }}</span></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><!-- col-md-8 -->
<div class="col-md-4"> <div class="col-md-5">
<h5>Hosts Summary</h5> <div class="section">
<div class="job_hosts job_well"> <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"> <table class="table table-condensed job-detail-table">
<thead> <thead>
<tr> <tr>
<th class="col-md-8">Name</th> <th class="col-md-8">Host</th>
<th class="text-center col-md-1">OK</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">Changed</th>
<th class="text-center col-md-1">Unreachable</th> <th class="text-center col-md-1">Unreachable</th>
@ -116,7 +135,7 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="host in hosts"> <tr ng-repeat="host in hosts">
<td>{{ host.name }}</td> <td><a href="/#/home/hosts/?id={{ host.id }}">{{ host.name }}</a></td>
<td class="text-center">{{ host.ok }}</td> <td class="text-center">{{ host.ok }}</td>
<td class="text-center">{{ host.changed }}</td> <td class="text-center">{{ host.changed }}</td>
<td class="text-center">{{ host.unreachable }}</td> <td class="text-center">{{ host.unreachable }}</td>
@ -125,12 +144,9 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!--
<div class="job_well">
<p>Active Play: {{ activePlay }}</p>
<p>Active Task: {{ activeTask }}</p>
</div> </div>
--> </div><!-- section -->
</div> </div>
</div> </div>