diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index 2b6bc1d9df..ff0329a453 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -85,6 +85,24 @@ color: @default-interface-txt; } +.JobResultsStdOut-followButton.is-engaged { + background-color: @default-link; + color: @default-bg; +} + +.JobResultsStdOut-followButton.is-engaged .JobResultsStdOut-followIcon { + color: @default-bg; +} + +.JobResultsStdOut-followButton.is-engaged:hover { + background-color: @default-icon; +} + +.JobResultsStdOut-followButton.is-engaged:hover .JobResultsStdOut-followIcon, +.JobResultsStdOut-followButton.is-engaged .JobResultsStdOut-followIcon:hover { + color: @default-border; +} + .JobResultsStdOut-stdoutContainer { height: ~"calc(100% - 48px)"; background-color: @default-no-items-bord; @@ -167,3 +185,10 @@ width: 70px; height: 100%; } + +.foo { + margin-right: auto; + margin-top: 8px; + margin-left: 20px; + font-weight: bold; +} diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js index dd7366fc11..be0c08fc93 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js @@ -5,14 +5,132 @@ *************************************************/ // import hostStatusBarController from './host-status-bar.controller'; -export default [ 'templateUrl', '$timeout', - function(templateUrl, $timeout) { +export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', '$log', + function(templateUrl, $timeout, $location, $anchorScroll, $log) { return { scope: false, templateUrl: templateUrl('job-results/job-results-stdout/job-results-stdout'), restrict: 'E', - link: function(scope, element, attrs) { + link: function(scope, element) { + + var findTopLines = function() { + scope.visibleItems = ""; + var $container = $('.JobResultsStdOut-stdoutContainer'); + var visItem, + parentItem; + $container.find('.JobResultsStdOut-aLineOfStdOut').each( function () { + var $this = $(this); + if ( $this.position().top + $this.height() > $container.position().top && + $this.position().top < ($container.height() + $container.position().top) ) { + visItem = parseInt($($this.children()[0]).text()); + + var $head, + classList, + header; + if ($this.hasClass("header_play") || $this.hasClass("header_task")) { + classList = $this.attr("class") + .split(" "); + header = classList + .filter(n => n.indexOf("header_task_") > -1)[0]; + if (!header) { + header = classList + .filter(n => n.indexOf("header_play_") > -1)[0]; + } + $head = $(".actual_header." + header); + } else { + classList = $this.attr("class") + .split(" "); + header = classList + .filter(n => n.indexOf("task_") > -1)[0]; + if (!header) { + header = classList + .filter(n => n.indexOf("play_") > -1)[0]; + } + + $head = $(".actual_header.header_" + header); + } + parentItem = parseInt($($head.children()[0]).text()); + return false; + } + }); + scope.visLine = visItem; + scope.parentVisLine = parentItem; + scope.visibleItems = "First visible line: " + visItem + ", Parent line: " + parentItem; + }; + + var lastScrollTop = 0; + $(".JobResultsStdOut-stdoutContainer").on('scroll', function() { + var st = $(this).scrollTop(); + if (st < lastScrollTop){ + // user up scrolled, so disengage follow + scope.followEngaged = false; + } + + if($(this).scrollTop() + $(this).innerHeight() >= + $(this)[0].scrollHeight) { + // user scrolled all the way to bottom, so engage + // follow + scope.followEngaged = true; + } + + lastScrollTop = st; + }); + + scope.followScroll = function() { + $(".JobResultsStdOut-followAnchor") + .appendTo(".JobResultsStdOut-stdoutContainer"); + + $location.hash('followAnchor'); + $anchorScroll(); + }; + + scope.topLineAnchor = function() { + $location.hash('topLineAnchor'); + $anchorScroll(); + } + + // follow button for completed job should specify that the + // button will jump to the bottom of the standard out pane, + // not follow lines as they come in + if (scope.jobFinished) { + scope.followTooltip = "Jump to last line"; + } + + // if following becomes active, go ahead and get to the bottom + // of the standard out pane + scope.$watch('followEngaged', function(val) { + if (val) { + scope.followScroll(); + } + + if (!scope.jobFinished) { + if (val) { + scope.followTooltip = "Follow standard out"; + } else { + scope.followTooltip = "Unfollow standard out"; + } + } + }); + + scope.followToggleClicked = function() { + if (scope.jobFinished) { + scope.followScroll(); + } else { + scope.followEngaged = !scope.followEngaged; + } + }; + scope.toggleAllStdout = function(type) { + findTopLines(); + + if (type === 'expand') { + $(".line_num_" + scope.visLine) + .prepend($("#topLineAnchor")); + } else { + $(".line_num_" + scope.parentVisLine) + .prepend($("#topLineAnchor")); + } + var expandClass; if (type === 'expand') { expandClass = "fa-caret-right"; @@ -24,6 +142,7 @@ export default [ 'templateUrl', '$timeout', .each((i, val) => { $timeout(function(){ angular.element(val).trigger('click'); + scope.topLineAnchor(); }); }); @@ -38,6 +157,7 @@ export default [ 'templateUrl', '$timeout', $timeout(function(){ angular.element(val) .trigger('click'); + scope.topLineAnchor(); }); } }); diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index c0f13597c4..08a3f4ac17 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -17,16 +17,32 @@