responsive job details & extend GetBaseUrl with /api/v1/job_events endpoint. resolves #1263

This commit is contained in:
Leigh Johnson
2016-04-09 11:05:18 -04:00
parent 2a5a97fc35
commit 833f68dd7f
11 changed files with 67 additions and 87 deletions

View File

@@ -75,7 +75,7 @@ export default
scope.viewJobDetails = function(job) {
var goToJobDetails = function(state) {
$state.go(state, {id: job.id});
$state.go(state, {id: job.id}, {reload:true});
}
switch(job.type) {

View File

@@ -1,4 +1,4 @@
<div id="HostEvent" class="HostEvent modal fade" role="dialog">
<div id="HostEvent" class="HostEvent modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<!-- modal body -->

View File

@@ -10,6 +10,7 @@
function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){
$scope.processEventStatus = JobDetailService.processEventStatus;
$scope.hostResults = [];
// Avoid rendering objects in the details fieldset
// ng-if="processResults(value)" via host-event-details.partial.html
$scope.processResults = function(value){
@@ -18,8 +19,8 @@
};
var codeMirror = function(el, json){
var el = $(el)[0];
var editor = CodeMirror.fromTextArea(el, {
var container = $(el)[0];
var editor = CodeMirror.fromTextArea(container, {
lineNumbers: true,
mode: {name: "javascript", json: true}
});
@@ -54,17 +55,17 @@
};
var init = function(){
console.log(event)
$scope.event = event.data.results[0];
$scope.event.created = moment($scope.event.created).format();
$scope.hostResults = $stateParams.hostResults;
JobDetailService.getJobEventChildren($stateParams.taskId).success(function(res){
$scope.hostResults = res.results;
});
$scope.json = JobDetailService.processJson($scope.event);
if ($state.current.name == 'jobDetail.host-event.json'){
codeMirror('#HostEvent-json', $scope.json);
}
try {
$scope.stdout = JobDetailService.processJson($scope.event.event_data.res)
console.log($scope.stdout)
if ($state.current.name == 'jobDetail.host-event.stdout'){
codeMirror('#HostEvent-stdout', $scope.stdout);
}
@@ -72,9 +73,6 @@
catch(err){
$scope.sdout = null;
}
console.log($scope)
$('#HostEvent').modal('show');
};
init();

View File

@@ -8,14 +8,8 @@
var hostEventModal = {
name: 'jobDetail.host-event',
url: '/host-event/:eventId',
url: '/task/:taskId/host-event/:eventId',
controller: 'HostEventController',
params:{
hostResults: {
value: null,
squash: false,
}
},
templateUrl: templateUrl('job-detail/host-event/host-event-modal'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
@@ -23,12 +17,12 @@ var hostEventModal = {
}],
event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
return JobDetailService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId
}).success(function(res){ return res.results[0]})
id: $stateParams.eventId,
}).success(function(res){ return res;})
}]
},
onExit: function($state){
// close the modal
// close the modal
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
$('#HostEvent').modal('hide');
// hacky way to handle user browsing away via URL bar
@@ -48,12 +42,7 @@ var hostEventModal = {
name: 'jobDetail.host-event.json',
url: '/json',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-json'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
return FeaturesService.get();
}]
}
templateUrl: templateUrl('job-detail/host-event/host-event-json')
};
var hostEventStdout = {

View File

@@ -1,6 +1,9 @@
@import "awx/ui/client/src/shared/branding/colors.less";
@import "awx/ui/client/src/shared/branding/colors.default.less";
.CodeMirror{
border: none;
}
.HostEvents .modal-footer{
border: 0;
margin-top: 0px;

View File

@@ -14,9 +14,10 @@
$scope.search = null;
var buildTooltips = function(hosts){
// status waterfall: unreachable > failed > changed > ok > skipped
var count = {
ok : _.filter(hosts, function(o){
return o.changed === 0 && o.ok > 0;
return o.failures === 0 && o.changed === 0 && o.ok > 0;
}),
skipped : _.filter(hosts, function(o){
return o.skipped > 0;
@@ -43,16 +44,13 @@
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
jobSocket.on('summary_complete', function(data) {
// discard socket msgs we don't care about in this context
if ($stateParams.id === data['unified_job_id']){
JobDetailService.getJobHostSummaries($stateParams.id, {page_size: page_size})
.success(function(res){
init()
});
if ($stateParams.id == data['unified_job_id']){
init()
}
});
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
jobSocket.on('status_changed', function(data) {
if ($stateParams.id === data['unified_job_id']){
if ($stateParams.id == data['unified_job_id']){
$scope.status = data['status'];
}
});

View File

@@ -2,28 +2,26 @@
@import '../shared/branding/colors.less';
@import '../shared/branding/colors.default.less';
@import '../shared/layouts/one-plus-one.less';
@breakpoint-md: 1200px;
@breakpoint-sm: 420px;
.JobDetail{
display: flex;
flex-direction: row;
.OnePlusOne-container(100%, @breakpoint-md);
}
.JobDetail-leftSide{
flex: 1 0 auto;
width: 50%;
padding-right: 20px;
.OnePlusOne-panel--left(100%, @breakpoint-md);
}
.JobDetail-rightSide{
flex: 1 0 auto;
width: 50%;
.OnePlusOne-panel--right(100%, @breakpoint-md);
}
.JobDetail-panelHeader{
display: flex;
height: 30px;
}
.JobDetail-expandContainer{
flex: 1;
margin: 0px;
@@ -62,11 +60,17 @@
flex-wrap: wrap;
flex-direction: row;
padding-top: 25px;
@media screen and(max-width: @breakpoint-sm){
flex-direction: column;
}
}
.JobDetail-resultRow{
width: 50%;
display: flex;
@media screen and(max-width: @breakpoint-sm){
width: 100%;
}
}
.JobDetail-resultRowLabel{
@@ -78,6 +82,9 @@
font-size: 14px;
font-weight: normal!important;
flex: 1 0 auto;
@media screen and(max-width: @breakpoint-md){
flex: 2.5 0 auto;
}
}
.JobDetail-resultRow--variables{
@@ -109,10 +116,16 @@
flex-direction: row;
height: 50px;
margin-top: 25px;
@media screen and(max-width: @breakpoint-sm){
height: auto;
}
}
.JobDetail-searchContainer{
flex: 1 0 auto;
flex: 2 0 auto;
@media screen and(max-width: @breakpoint-sm){
margin-bottom: 0px;
}
}
.JobDetail-tableToggleContainer{

View File

@@ -272,7 +272,7 @@ export default
$rootScope.removeJobSummaryComplete = $rootScope.$on('JobSummaryComplete', function() {
// the job host summary should now be available from the API
$log.debug('Trigging reload of job_host_summaries');
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
});
if (scope.removeInitialLoadComplete) {
@@ -315,42 +315,6 @@ export default
if (scope.removeLoadHostSummaries) {
scope.removeLoadHostSummaries();
}
scope.removeHostSummaries = scope.$on('LoadHostSummaries', function() {
if(scope.job){
var params = {
page_size: scope.hostSummariesMaxRows,
order: 'host_name'
};
JobDetailService.getJobHostSummaries(scope.job.id, params)
.success(function(data) {
scope.next_host_summaries = data.next;
if (data.results.length > 0) {
// only dump what's in memory when job_host_summaries is available.
scope.jobData.hostSummaries = {};
}
data.results.forEach(function(event) {
var name;
if (event.host_name) {
name = event.host_name;
}
else {
name = "<deleted host>";
}
scope.jobData.hostSummaries[event.host] = {
id: event.host,
name: name,
ok: event.ok,
changed: event.changed,
unreachable: event.dark,
failed: event.failures,
status: (event.failed) ? 'failed' : 'successful'
};
});
scope.$emit('InitialLoadComplete');
});
}
});
if (scope.removeLoadHosts) {
scope.removeLoadHosts();
@@ -376,13 +340,13 @@ export default
}
scope.next_host_results = data.next;
task.hostResults = JobDetailService.processHostEvents(data.results);
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
});
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
});
@@ -491,10 +455,10 @@ export default
msg: 'Call to ' + url + '. GET returned: ' + status });
});
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
});

View File

@@ -1,6 +1,6 @@
<div class="tab-pane" id="jobs-detail">
<div ng-cloak id="htmlTemplate" class="JobDetail">
<div ui-view></div>
<div ui-view class-""></div>
<!--beginning of job-detail-container (left side) -->
<div id="job-detail-container" class="JobDetail-leftSide" ng-class="{'JobDetail-stdoutActionButton--active': stdoutFullScreen}">
<!--beginning of results-->
@@ -344,7 +344,7 @@
<tbody>
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column">
<a ui-sref="jobDetail.host-event.details({eventId: result.id, hostResults: hostResults})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
<a ui-sref="jobDetail.host-event.details({eventId: result.id, taskId: selectedTask})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
</td>
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3 item-column">{{ result.item }}</td>
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>

View File

@@ -145,6 +145,19 @@ export default
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJobEventChildren: function(id){
var url = GetBasePath('job_events');
url = url + id + '/children/';
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job host summaries related to a job run
// e.g. ?page_size=200&order=host_name
getJobHostSummaries: function(id, params){

View File

@@ -35,6 +35,8 @@ angular.module('ApiLoader', ['Utilities'])
.success(function (data) {
data.base = base;
$rootScope.defaultUrls = data;
// tiny hack to side-step api/v1/job_events not being a visible endpoint @ GET api/v1/
$rootScope.defaultUrls['job_events'] = '/api/v1/job_events/';
Store('api', data);
})
.error(function (data, status) {