mirror of
https://github.com/ansible/awx.git
synced 2026-03-23 20:05:03 -02:30
Job stdout page
Fixed endless scroll, both up and down. It now models the Jenkins behavior. If live events are happening, it captures and displays them, always auto-scrolling down. If user srolls up, live events continue to be captured but auto-scrolling is suspended. If user scrolls all the way back down to bottom, auto-scrolling resumes.
This commit is contained in:
@@ -16,12 +16,14 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
stdout_url,
|
stdout_url,
|
||||||
current_range,
|
current_range,
|
||||||
event_socket,
|
event_socket,
|
||||||
first_time=0,
|
first_time_up = 0,
|
||||||
|
first_time_down = 0,
|
||||||
loaded_sections = [],
|
loaded_sections = [],
|
||||||
event_queue = 0,
|
event_queue = 0,
|
||||||
auto_scroll_down=true,
|
auto_scroll_down=true, // programmatic scroll to bottom
|
||||||
live_event_processing = true,
|
live_event_processing = true,
|
||||||
should_apply_live_events = true,
|
should_apply_live_events = true,
|
||||||
|
prior_mcs,
|
||||||
page_size = 500;
|
page_size = 500;
|
||||||
|
|
||||||
event_socket = Socket({
|
event_socket = Socket({
|
||||||
@@ -45,14 +47,14 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function(e, data) {
|
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function(e, data) {
|
||||||
// if we receive a status change event for the current job indicating the job
|
// if we receive a status change event for the current job indicating the job
|
||||||
// is finished, stop event queue processing and reload
|
// is finished, stop event queue processing and reload
|
||||||
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
|
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) {
|
||||||
$scope.job.status = data.status;
|
$scope.job.status = data.status;
|
||||||
if (data.status === 'failed' || data.status === 'canceled' ||
|
if (data.status === 'failed' || data.status === 'canceled' ||
|
||||||
data.status === 'error' || data.status === 'successful') {
|
data.status === 'error' || data.status === 'successful') {
|
||||||
if ($rootScope.jobStdOutInterval) {
|
if ($rootScope.jobStdOutInterval) {
|
||||||
window.clearInterval($rootScope.jobStdOutInterval);
|
window.clearInterval($rootScope.jobStdOutInterval);
|
||||||
}
|
}
|
||||||
if (live_event_processing && should_apply_live_events) {
|
if (live_event_processing) {
|
||||||
getNextSection();
|
getNextSection();
|
||||||
}
|
}
|
||||||
live_event_processing = false;
|
live_event_processing = false;
|
||||||
@@ -61,20 +63,20 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
});
|
});
|
||||||
|
|
||||||
$rootScope.jobStdOutInterval = setInterval( function() {
|
$rootScope.jobStdOutInterval = setInterval( function() {
|
||||||
// limit the scrolling to every 5 seconds
|
if (event_queue > 0) {
|
||||||
if (event_queue > 5) {
|
// events happened since the last check
|
||||||
$log.debug('checking for stdout...');
|
$log.debug('checking for stdout...');
|
||||||
if (loaded_sections.length === 0) {
|
if (loaded_sections.length === 0) {
|
||||||
$log.debug('calling LoadStdout');
|
$log.debug('calling LoadStdout');
|
||||||
$scope.$emit('LoadStdout');
|
$scope.$emit('LoadStdout');
|
||||||
}
|
}
|
||||||
else {
|
else if (live_event_processing) {
|
||||||
$log.debug('calling getNextSection');
|
$log.debug('calling getNextSection');
|
||||||
getNextSection();
|
getNextSection();
|
||||||
}
|
}
|
||||||
event_queue = 0;
|
event_queue = 0;
|
||||||
}
|
}
|
||||||
}, 1500);
|
}, 2000);
|
||||||
|
|
||||||
if ($scope.removeLoadStdout) {
|
if ($scope.removeLoadStdout) {
|
||||||
$scope.removeLoadStdout();
|
$scope.removeLoadStdout();
|
||||||
@@ -88,7 +90,10 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
api_complete = true;
|
api_complete = true;
|
||||||
$('#pre-container-content').html(data.content);
|
$('#pre-container-content').html(data.content);
|
||||||
current_range = data.range;
|
current_range = data.range;
|
||||||
loaded_sections.push(data.range.start);
|
loaded_sections.push({
|
||||||
|
start: (data.range.start < 0) ? 0 : data.range.start,
|
||||||
|
end: data.range.end
|
||||||
|
});
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
$('#pre-container').mCustomScrollbar("scrollTo", "bottom");
|
$('#pre-container').mCustomScrollbar("scrollTo", "bottom");
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -136,22 +141,42 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
|
|
||||||
$scope.onTotalScroll = function() {
|
$scope.onTotalScroll = function() {
|
||||||
// scroll forward or into the future toward the end of the file
|
// scroll forward or into the future toward the end of the file
|
||||||
var start = current_range.end + 1, url;
|
var start, url;
|
||||||
if ((!live_event_processing) && (!auto_scroll_down) && loaded_sections.indexOf(start) < 0) {
|
if ((live_event_processing === false || (live_event_processing && should_apply_live_events === false)) &&
|
||||||
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (current_range.start + page_size);
|
auto_scroll_down === false) {
|
||||||
first_time++;
|
|
||||||
|
if (loaded_sections.length > 0) {
|
||||||
|
start = loaded_sections[loaded_sections.length - 1].end + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (start + page_size);
|
||||||
|
first_time_down++;
|
||||||
Wait('start');
|
Wait('start');
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
Rest.get()
|
Rest.get()
|
||||||
.success( function(data) {
|
.success( function(data) {
|
||||||
Wait('stop');
|
Wait('stop');
|
||||||
if (data.content) {
|
if (loaded_sections.indexOf(start) < 0) {
|
||||||
$('#pre-container-content').append(data.content);
|
if (data.content) {
|
||||||
loaded_sections.push(data.range.start);
|
$('#pre-container-content').append(data.content);
|
||||||
current_range = data.range;
|
loaded_sections.push({
|
||||||
|
start: (data.range.start < 0) ? 0 : data.range.start,
|
||||||
|
end: data.range.end
|
||||||
|
});
|
||||||
|
current_range = data.range;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//$("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0});
|
if (data.range.end === data.range.absolute_end) {
|
||||||
//$('#pre-container').mCustomScrollbar("update");
|
should_apply_live_events = true; //we're at the bottom
|
||||||
|
$log.debug('at the end. turned on live events');
|
||||||
|
}
|
||||||
|
auto_scroll_down = true;
|
||||||
|
if (first_time_down === 1) {
|
||||||
|
$('#pre-container').mCustomScrollbar("update");
|
||||||
|
}
|
||||||
|
$("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0});
|
||||||
})
|
})
|
||||||
.error(function(data, status) {
|
.error(function(data, status) {
|
||||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
@@ -165,11 +190,18 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
|
|
||||||
$scope.onTotalScrollBack = function() {
|
$scope.onTotalScrollBack = function() {
|
||||||
// scroll up or back in time toward the beginning of the file
|
// scroll up or back in time toward the beginning of the file
|
||||||
if ((!live_event_processing) && current_range.start > 0) {
|
var start, end, url;
|
||||||
//we haven't hit the top yet
|
if (loaded_sections.length > 0 && loaded_sections[0].start > 0) {
|
||||||
var start = (current_range.start < page_size) ? 0 : current_range.start - page_size,
|
start = (loaded_sections[0].start - page_size > 0) ? loaded_sections[0].start - page_size : 0;
|
||||||
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (current_range.start - 1);
|
end = loaded_sections[0].start - 1;
|
||||||
first_time++;
|
}
|
||||||
|
else if (loaded_sections.length === 0) {
|
||||||
|
start = 0;
|
||||||
|
end = page_size;
|
||||||
|
}
|
||||||
|
if (start !== undefined && end !== undefined) {
|
||||||
|
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + end;
|
||||||
|
first_time_up++;
|
||||||
Wait('start');
|
Wait('start');
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
Rest.get()
|
Rest.get()
|
||||||
@@ -178,11 +210,13 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
var oldContentHeight, heightDiff;
|
var oldContentHeight, heightDiff;
|
||||||
oldContentHeight=$("#pre-container .mCSB_container").innerHeight();
|
oldContentHeight=$("#pre-container .mCSB_container").innerHeight();
|
||||||
$('#pre-container-content').prepend(data.content);
|
$('#pre-container-content').prepend(data.content);
|
||||||
loaded_sections.unshift(data.range.start);
|
loaded_sections.unshift({
|
||||||
|
start: (data.range.start < 0) ? 0 : data.range.start,
|
||||||
|
end: data.range.end
|
||||||
|
});
|
||||||
current_range = data.range;
|
current_range = data.range;
|
||||||
heightDiff=$("#pre-container .mCSB_container").innerHeight() - oldContentHeight;
|
heightDiff=$("#pre-container .mCSB_container").innerHeight() - oldContentHeight;
|
||||||
if (first_time === 1) {
|
if (first_time_up === 1) {
|
||||||
//setTimeout(function() { $("#pre-container").mCustomScrollbar("scrollTo", heightDiff, {scrollInertia:0}); }, 300);
|
|
||||||
$('#pre-container').mCustomScrollbar("update");
|
$('#pre-container').mCustomScrollbar("update");
|
||||||
}
|
}
|
||||||
$("#pre-container").mCustomScrollbar("scrollTo", heightDiff, {scrollInertia:0});
|
$("#pre-container").mCustomScrollbar("scrollTo", heightDiff, {scrollInertia:0});
|
||||||
@@ -194,32 +228,65 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.whileScrolling = function(mcs) {
|
||||||
|
var direction;
|
||||||
|
if (prior_mcs !== undefined) {
|
||||||
|
if (mcs.topPct < prior_mcs.topPct && prior_mcs.topPct !== 100) {
|
||||||
|
direction = "up";
|
||||||
|
}
|
||||||
|
else if (mcs.topPct > prior_mcs.topPct) {
|
||||||
|
direction = "down";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
direction = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prior_mcs = mcs;
|
||||||
|
if (direction === "up") {
|
||||||
|
// user is scrollin up or back in time
|
||||||
|
$log.debug('user scrolled up. turned off live events.');
|
||||||
|
should_apply_live_events = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$scope.scrollStarted = function() {
|
$scope.scrollStarted = function() {
|
||||||
// user touched the scroll bar. stop applying live events and forcing
|
// user touched the scroll bar. stop applying live events and forcing
|
||||||
should_apply_live_events = false;
|
//if (auto_scroll_down === false) {
|
||||||
$log.debug('turned off live events');
|
// should_apply_live_events = false;
|
||||||
|
// $log.debug('turned off live events');
|
||||||
|
//}
|
||||||
|
//else {
|
||||||
|
// auto_scroll_down = false;
|
||||||
|
//}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNextSection() {
|
function getNextSection() {
|
||||||
var start = current_range.end + 1, url;
|
// get the next range of data from the API
|
||||||
should_apply_live_events = true; //user scrolled all the way to bottom. if there are live events, start applying them
|
var start = loaded_sections[loaded_sections.length - 1].end + 1, url;
|
||||||
if (loaded_sections.indexOf(start) < 0) {
|
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (start + page_size);
|
||||||
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (current_range.start + page_size);
|
Rest.setUrl(url);
|
||||||
Wait('start');
|
Rest.get()
|
||||||
Rest.setUrl(url);
|
.success( function(data) {
|
||||||
Rest.get()
|
$('#pre-container-content').append(data.content);
|
||||||
.success( function(data) {
|
loaded_sections.push({
|
||||||
Wait('stop');
|
start: (data.range.start < 0) ? 0 : data.range.start,
|
||||||
$('#pre-container-content').append(data.content);
|
end: data.range.end
|
||||||
loaded_sections.push(data.range.start);
|
|
||||||
current_range = data.range;
|
|
||||||
$("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0});
|
|
||||||
})
|
|
||||||
.error(function(data, status) {
|
|
||||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
|
||||||
msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status });
|
|
||||||
});
|
});
|
||||||
}
|
if (should_apply_live_events) {
|
||||||
|
// if user has not disabled live event view by scrolling upward, then scroll down to the new content
|
||||||
|
current_range = data.range;
|
||||||
|
auto_scroll_down = true; // prevent auto load from happening
|
||||||
|
first_time_down++;
|
||||||
|
if (first_time_down === 1) {
|
||||||
|
$('#pre-container').mCustomScrollbar("update");
|
||||||
|
}
|
||||||
|
$("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -723,7 +723,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
|
|||||||
.directive('awCustomScroll', [ function() {
|
.directive('awCustomScroll', [ function() {
|
||||||
return function(scope, element, attrs) {
|
return function(scope, element, attrs) {
|
||||||
var theme = (attrs.scrollTheme) ? attrs.scrollTheme : 'dark-thin',
|
var theme = (attrs.scrollTheme) ? attrs.scrollTheme : 'dark-thin',
|
||||||
inertia = (attrs.scrollInertia) ? parseInt(attrs.scrollInertia,10) : 500;
|
inertia = (attrs.scrollInertia) ? parseInt(attrs.scrollInertia,10) : 500,
|
||||||
|
axis = ($(element).attr('data-scroll-axis')) ? $(element).attr('data-scroll-axis') : 'y';
|
||||||
$(element).mCustomScrollbar({
|
$(element).mCustomScrollbar({
|
||||||
advanced:{
|
advanced:{
|
||||||
updateOnContentResize: true
|
updateOnContentResize: true
|
||||||
@@ -731,6 +732,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
|
|||||||
scrollButtons: {
|
scrollButtons: {
|
||||||
enable: false
|
enable: false
|
||||||
},
|
},
|
||||||
|
axis: axis,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
mouseWheel: true,
|
mouseWheel: true,
|
||||||
scrollInertia: inertia,
|
scrollInertia: inertia,
|
||||||
@@ -740,6 +742,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
|
|||||||
onTotalScrollBack: scope[attrs.onTotalScrollBack],
|
onTotalScrollBack: scope[attrs.onTotalScrollBack],
|
||||||
onTotalScrollBackOffset: attrs.onTotalScrollBackOffset,
|
onTotalScrollBackOffset: attrs.onTotalScrollBackOffset,
|
||||||
onScrollStart: scope[$(element).attr('data-on-scroll-start')],
|
onScrollStart: scope[$(element).attr('data-on-scroll-start')],
|
||||||
|
whileScrolling: scope[$(element).attr('data-while-scrolling')],
|
||||||
alwaysTriggerOffsets: false
|
alwaysTriggerOffsets: false
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div id="job-status"><label>Job Status</label> <i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}</div>
|
<div id="job-status"><label>Job Status</label> <i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}</div>
|
||||||
<div id="pre-container" class="body_background body_foreground pre mono-space" aw-custom-scroll data-scroll-theme="light-thin"
|
<div id="pre-container" class="body_background body_foreground pre mono-space" aw-custom-scroll data-scroll-theme="light-thin"
|
||||||
data-scroll-inertia="500" data-on-total-scroll="onTotalScroll" data-on-total-scroll-back="onTotalScrollBack" data-on-scroll-start="scrollStarted" data-on-total-scroll-back-offset="100">
|
data-scroll-inertia="500" data-on-total-scroll="onTotalScroll" data-on-total-scroll-back="onTotalScrollBack" data-on-scroll-start="scrollStarted"
|
||||||
|
data-while-scrolling="whileScrolling" data-on-total-scroll-back-offset="100" data-scroll-axis="yx">
|
||||||
<div id="pre-container-content"></div>
|
<div id="pre-container-content"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user