Fixed Loading message and Pagination widget fouc. Start of new job detail page.

This commit is contained in:
Chris Houseknecht
2014-04-11 02:02:23 -04:00
parent 2e572e51da
commit 69c1aed22d
12 changed files with 378 additions and 23 deletions

View File

@@ -6,7 +6,7 @@
*/
var urlPrefix = $basePath;
angular.module('ansible', [
angular.module('Tower', [
'ngRoute',
'ngSanitize',
'ngCookies',
@@ -97,7 +97,8 @@ angular.module('ansible', [
'JobsListDefinition',
'LogViewerStatusDefinition',
'LogViewerHelper',
'LogViewerOptionsDefinition'
'LogViewerOptionsDefinition',
'JobDetailHelper'
])
.constant('AngularScheduler.partials', $basePath + 'lib/angular-scheduler/lib/')
@@ -107,11 +108,18 @@ angular.module('ansible', [
.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/jobs', {
templateUrl: urlPrefix + 'partials/jobs.html',
controller: 'JobsListController'
}).
when('/jobs/:id', {
templateUrl: urlPrefix + 'partials/job_detail.html',
controller: 'JobDetailController'
}).
when('/job_events/:id', {
templateUrl: urlPrefix + 'partials/job_events.html',
@@ -405,7 +413,6 @@ angular.module('ansible', [
$rootScope.sessionTimer = Timer.init();
$rootScope.$on("$routeChangeStart", function (event, next) {
// Before navigating away from current tab, make sure the primary view is visible
if ($('#stream-container').is(':visible')) {
HideStream();

View File

@@ -23,7 +23,7 @@ function CredentialsList($scope, $rootScope, $location, $log, $routeParams, Rest
base = $location.path().replace(/^\//, '').split('/')[0],
mode = (base === 'credentials') ? 'edit' : 'select',
url;
view.inject(list, { mode: mode, scope: $scope });
$scope.selected = [];

View File

@@ -0,0 +1,88 @@
/************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* JobDetail.js
*
*/
'use strict';
function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, DigestEvents) {
ClearScope();
var job_id = $routeParams.id,
job;
/*LoadBreadCrumbs();
e = angular.element(document.getElementById('breadcrumbs'));
e.html(Breadcrumbs({ list: { editTitle: 'Jobs' } , mode: 'edit' }));
$compile(e)($scope);
*/
$scope.plays = [];
$scope.tasks = [];
// Apply each event to the view
if ($scope.removeEventsReady) {
$scope.removeEventsReady();
}
$scope.removeEventsReady = $scope.$on('EventsReady', function(e, events) {
DigestEvents({
scope: $scope,
events: events
});
});
// Get events, page size 50
if ($scope.removeJobReady) {
$scope.removeJobReady();
}
$scope.removeJobReady = $scope.$on('JobReady', function(e, next) {
if (next) {
Rest.setUrl(next);
Rest.get()
.success(function(data) {
$scope.$emit('EventsReady', data.results);
if (data.next) {
$scope.$emit('JobReady', data.next);
}
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve job events: ' + next + ' GET returned: ' + status });
});
}
});
// Load the job record
Rest.setUrl(GetBasePath('jobs') + job_id + '/');
Rest.get()
.success(function(data) {
job = data;
$scope.job_template_name = data.name;
$scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : '';
$scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : '';
$scope.job_template_url = '/#/job_templates/' + data.unified_job_template;
$scope.inventory_url = ($scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : '';
$scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : '';
$scope.job_type = data.job_type;
$scope.playbook = data.playbook;
$scope.credential = data.credential;
$scope.cloud_credential = data.cloud_credential;
$scope.forks = data.forks;
$scope.limit = data.limit;
$scope.verbosity = data.verbosity;
$scope.job_tags = data.job_tags;
$scope.$emit('JobReady', data.related.job_events + '?page_size=50&order_by=id');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve job: ' + $routeParams.id + '. GET returned: ' + status });
});
}
JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait',
'Rest', 'ProcessErrors', 'DigestEvents'
];

View File

@@ -209,7 +209,7 @@ function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateI
scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label;
scope[list.iterator + 'SearchSelectValue'] = { value: 1 };
}
scope.search(list.iterator);
scope.search(list.iterator, null, true);
};
}])

View File

@@ -0,0 +1,108 @@
/************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* JobDetail.js
*
* Helper moduler for JobDetails controller
*
*/
/*
# Playbook events will be structured to form the following hierarchy:
# - playbook_on_start (once for each playbook file)
# - playbook_on_vars_prompt (for each play, but before play starts, we
# currently don't handle responding to these prompts)
# - playbook_on_play_start (once for each play)
# - playbook_on_import_for_host
# - playbook_on_not_import_for_host
# - playbook_on_no_hosts_matched
# - playbook_on_no_hosts_remaining
# - playbook_on_setup
# - runner_on*
# - playbook_on_task_start (once for each task within a play)
# - runner_on_failed
# - runner_on_ok
# - runner_on_error
# - runner_on_skipped
# - runner_on_unreachable
# - runner_on_no_hosts
# - runner_on_async_poll
# - runner_on_async_ok
# - runner_on_async_failed
# - runner_on_file_diff
# - playbook_on_notify (once for each notification from the play)
# - playbook_on_stats
*/
'use strict';
angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', function(UpdatePlayStatus, UpdatePlayNoHostsMatched) {
return function(params) {
var scope = params.scope,
events = params.events;
events.forEach(function(event) {
if (event.event === 'playbook_on_play_start') {
scope.plays.push({
id: event.id,
name: event.play,
status: (event.failed) ? 'failed' : 'successful'
});
}
if (event.event === 'playbook_on_task_start') {
scope.tasks.push({
id: event.id,
name: event.task,
play_id: event.parent,
status: (event.failed) ? 'failed' : 'successful'
});
UpdatePlayStatus({ scope: scope, play_id: event.parent, status: event.status });
}
if (event.event === 'playbook_on_no_hosts_matched') {
UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent });
}
if (event.event === 'runner_on_failed') {
}
if (event.event === 'playbook_on_stats') {
}
});
};
}])
// Update the status of a play
.factory('UpdatePlayStatus', [ function() {
return function(params) {
var scope = params.scope,
status = params.status,
id = params.play_id;
scope.plays.every(function(play,idx) {
if (play.id === id) {
scope.plays[idx].status = (status) ? 'failed' : 'successful';
return false;
}
return true;
});
};
}])
.factory('UpdatePlayNoHostsMatched', [ function() {
return function(params) {
var scope = params.scope,
id = params.play_id;
scope.plays.every(function(play,idx) {
if (play.id === id) {
scope.plays[idx].status = 'none';
return false;
}
return true;
});
};
}]);

View File

@@ -86,10 +86,15 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job
list = scope.queued_jobs;
}
job = Find({ list: list, key: 'id', val: id });
LogViewer({
scope: scope,
url: job.url
});
if (job.type === 'job') {
$location.url('/jobs/' + job.id);
}
else {
LogViewer({
scope: scope,
url: job.url
});
}
}
};
};

View File

@@ -26,7 +26,7 @@ angular.module('RefreshHelper', ['RestServices', 'Utilities', 'PaginationHelpers
iterator = params.iterator,
url = params.url;
scope[iterator + 'Loading'] = true;
//scope[iterator + 'Loading'] = true;
scope.current_url = url;
Rest.setUrl(url);
Rest.get()
@@ -45,6 +45,7 @@ angular.module('RefreshHelper', ['RestServices', 'Utilities', 'PaginationHelpers
}
scope[set] = data.results;
scope[iterator + 'Loading'] = false;
scope[iterator + "HidePaginator"] = false;
Wait('stop');
scope.$emit('PostRefresh');
})

View File

@@ -1569,17 +1569,17 @@ tr td button i {
}
}
/*
.activity-btn {
padding-left: 2px;
padding-right: 2px;
padding-bottom: 2px;
img {
width: 16px;
height: 16px;
/* New job detail page */
.job-detail-tables {
.table>tbody>tr>td {
border-top-color: #fff;
}
}
*/
.table>thead>tr>th {
border-bottom-color: #fff;
}
}
/* ng-cloak directive */

View File

@@ -792,7 +792,7 @@ angular.module('GeneratorHelpers', [])
html += "<!-- Paginate Widget -->\n";
html += "<div class=\"row page-row\">\n";
html += "<div class=\"col-lg-8 col-md-8\">\n";
html += "<ul class=\"pagination\" ng-hide=\"" + iterator + "Loading || " + iterator + "_num_pages <= 1\">\n";
html += "<ul class=\"pagination\" ng-hide=\"" + iterator + "HidePaginator || " + iterator + "_num_pages <= 1\">\n";
html += "<li ng-hide=\"" + iterator + "_page -5 <= 1 \"><a href ng-click=\"getPage(1,'" + set + "','" + iterator + "')\">" +
"<i class=\"fa fa-angle-double-left\"></i></a></li>\n";
html += "<li ng-hide=\"" + iterator + "_page -1 <= 0\"><a href " +

View File

@@ -82,6 +82,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
// Reset the scope to prevent displaying old data from our last visit to this list
//this.scope[list.name] = null;
this.scope[list.iterator] = [];
this.scope[list.iterator + "HidePaginator"] = true;
// Remove any lingering tooltip and popover <div> elements
$('.tooltip').each(function() {

View File

@@ -0,0 +1,143 @@
<div class="tab-pane" id="jobs">
<div ng-cloak id="htmlTemplate">
<div class="row">
<div class="col-md-12" id="breadcrumbs"></div>
</div>
<div class="row">
<div class="col-md-6">
<h4>Job Options</h4>
<div class="job_options">
<table class="table table-condensed">
<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>{{ credential }}</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>
</tbody>
</table>
</div>
</div>
</div><!-- row -->
<div class="row">
<div class="col-md-6 job-detail-tables">
<h4>Plays</h4>
<div class="job_plays">
<table class="table table-condensed job-detail-table">
<thead>
<tr>
<th class="col-md-1">Status</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="play in plays">
<td><i class="fa icon-job-{{ play.status }}"></i></td>
<td>{{ play.name }}</td>
<td>{{ play.state }}</td>
</tr>
</tbody>
</table>
</div>
<h4>Tasks</h4>
<div class="job_tasks">
<table class="table table-condensed job-detail-table">
<thead>
<tr>
<th class="col-md-1">Status</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="task in tasks">
<td><i class="fa icon-job-{{ task.status }}"></i></td>
<td>{{ task.name }}</td>
<td>{{ task.state }}</td>
</tr>
</tbody>
</table>
</div>
<h4>Hosts</h4>
<div class="job_hosts">
<table class="table table-condensed job-detail-table">
<thead>
<tr>
<th class="col-md-1">Status</th>
<th>Name</th>
<th>OK</th>
<th>Changed</th>
<th>Unreachable</th>
<th>Failed</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="host in hosts">
<td><i class="fa icon-job-{{ task.status }}"></i></td>
<td>{{ host.name }}</td>
<td>{{ host.ok }}</td>
<td>{{ host.changed }}</td>
<td>{{ host.unreachable }}</td>
<td>{{ host.failed }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" ng-app="ansible">
<html lang="en" ng-app="Tower">
<head>
<meta charset="utf-8">
<title>Ansible Tower</title>
@@ -74,6 +74,7 @@
<script src="{{ STATIC_URL }}js/controllers/JobTemplates.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Projects.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Jobs.js"></script>
<script src="{{ STATIC_URL }}js/controllers/JobDetail.js"></script>
<script src="{{ STATIC_URL }}js/controllers/JobEvents.js"></script>
<script src="{{ STATIC_URL }}js/controllers/JobHosts.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Permissions.js"></script>
@@ -150,6 +151,7 @@
<script src="{{ STATIC_URL }}js/helpers/Variables.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Schedules.js"></script>
<script src="{{ STATIC_URL }}js/helpers/LogViewer.js"></script>
<script src="{{ STATIC_URL }}js/helpers/JobDetail.js"></script>
<script src="{{ STATIC_URL }}js/widgets/JobStatus.js"></script>
<script src="{{ STATIC_URL }}js/widgets/InventorySyncStatus.js"></script>
<script src="{{ STATIC_URL }}js/widgets/SCMSyncStatus.js"></script>
@@ -355,7 +357,7 @@
<div id="push"></div>
</div>
<div class="navbar navbar-inverse site-footer">
<div class="navbar navbar-inverse site-footer fade-in">
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 text-left help">