mirror of
https://github.com/ansible/awx.git
synced 2026-03-13 23:17:32 -02:30
responsive job details & extend GetBaseUrl with /api/v1/job_events endpoint. resolves #1263
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user