diff --git a/awx/ui/client/features/jobs/_index.less b/awx/ui/client/features/jobs/_index.less index 78e1de6b33..1ebec50483 100644 --- a/awx/ui/client/features/jobs/_index.less +++ b/awx/ui/client/features/jobs/_index.less @@ -11,10 +11,28 @@ &-menuBottom { color: @at-gray-dark-4x; - border: 1px solid @at-gray-dark-2x; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-top: none; + font-size: 10px; + text-transform: uppercase; + font-weight: bold; + position: absolute; + right: 60px; + bottom: 24px; + cursor: pointer; + } + + &-menuIconGroup { + & > p { + margin: 0; + } + + & > p:first-child { + font-size: 20px; + margin-right: 8px; + } + + & > p:last-child { + margin-top: 9px; + } } &-menuIcon { @@ -26,25 +44,24 @@ &-toggle { color: @at-gray-dark-4x; background-color: @at-gray-light; - font-size: 12px; + font-size: 18px; + line-height: 12px; & > i { cursor: pointer; } - padding: 0 20px 0 10px; + padding: 0 10px 0 10px; user-select: none; } &-line { - color: @at-gray-dark-4x; + color: @at-gray-dark-6x; background-color: @at-gray-light; - text-align: right; - vertical-align: top; padding-right: 5px; - border-right: 1px solid @at-gray-dark; + border-right: 1px solid @at-gray-dark-2x; user-select: none; } @@ -64,16 +81,25 @@ text-align: right; user-select: none; width: 11ch; + + & > span { + background-color: white; + border-radius: 4px; + padding: 1px 2px; + } } &-container { max-height: 80vh; - font-size: 14px; + font-size: 15px; border: 1px solid @at-gray-dark-2x; - background-color: @at-gray-light-3x; + background-color: @at-gray-light-2x; + color: @at-gray-dark-6x; padding: 0; margin: 0; border-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; & > table { table-layout: fixed; diff --git a/awx/ui/client/features/output/index.controller.js b/awx/ui/client/features/output/index.controller.js index c94660d298..0926846e42 100644 --- a/awx/ui/client/features/output/index.controller.js +++ b/awx/ui/client/features/output/index.controller.js @@ -5,13 +5,16 @@ let vm; let ansi; let job; let jobEvent; +let container; let $timeout; let $sce; let $compile; let $scope; const record = {}; -const meta = {}; +const meta = { + scroll: {} +}; const EVENT_START_TASK = 'playbook_on_task_start'; const EVENT_START_PLAY = 'playbook_on_play_start'; @@ -52,10 +55,13 @@ function JobsIndexController (_job_, JobEventModel, _$sce_, _$timeout_, _$scope_ vm.showHostDetails = showHostDetails; vm.menu = { - scroll, + scroll: { + display: false, + to: scrollTo + }, top: { expand, - isExpanded: false + isExpanded: true }, bottom: { next @@ -64,9 +70,15 @@ function JobsIndexController (_job_, JobEventModel, _$sce_, _$timeout_, _$scope_ $timeout(() => { const table = $('#result-table'); + container = $('.at-Stdout-container'); table.html($sce.getTrustedHtml(html)); $compile(table.contents())($scope); + + meta.scroll.height = container[0].scrollHeight; + meta.scroll.buffer = 100; + + container.scroll(onScroll); }); } @@ -78,16 +90,14 @@ function next () { } function expand () { - vm.toggle(meta.parent); + vm.toggle(meta.parent, true); } -function scroll (direction) { - const container = $('.at-Stdout-container')[0]; - +function scrollTo (direction) { if (direction === 'top') { - container.scrollTop = 0; + container[0].scrollTop = 0; } else { - container.scrollTop = container.scrollHeight; + container[0].scrollTop = container[0].scrollHeight; } } @@ -222,7 +232,7 @@ function createRow (current, ln, content) { if (current) { if (current.isParent && current.line === ln) { id = current.uuid; - tdToggle = ``; + tdToggle = ``; } if (current.isHost) { @@ -230,7 +240,7 @@ function createRow (current, ln, content) { } if (current.time && current.line === ln) { - timestamp = current.time; + timestamp = `${current.time}`; } if (current.parents) { @@ -261,8 +271,11 @@ function createRow (current, ln, content) { function getTime (created) { const date = new Date(created); + const hour = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours(); + const minute = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes(); + const second = date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds(); - return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; + return `${hour}:${minute}:${second}`; } function showHostDetails (id) { @@ -279,27 +292,64 @@ function showHostDetails (id) { }); } -function toggle (uuid) { +function toggle (uuid, menu) { const lines = $(`.child-of-${uuid}`); let icon = $(`#${uuid} .at-Stdout-toggle > i`); + if (menu || record[uuid].level === 1) { + vm.menu.top.isExpanded = !vm.menu.top.isExpanded; + } + if (record[uuid].children) { icon = icon.add($(`#${record[uuid].children.join(', #')}`).find('.at-Stdout-toggle > i')); } - if (icon.hasClass('fa-chevron-down')) { - icon.addClass('fa-chevron-right'); - icon.removeClass('fa-chevron-down'); + if (icon.hasClass('fa-angle-down')) { + icon.addClass('fa-angle-right'); + icon.removeClass('fa-angle-down'); lines.addClass('hidden'); } else { - icon.addClass('fa-chevron-down'); - icon.removeClass('fa-chevron-right'); + icon.addClass('fa-angle-down'); + icon.removeClass('fa-angle-right'); lines.removeClass('hidden'); } } +function onScroll () { + if (meta.scroll.inProgress) { + return; + } + + meta.scroll.inProgress = true; + + $timeout(() => { + const top = container[0].scrollTop; + const bottom = top + meta.scroll.buffer + container[0].offsetHeight; + + meta.scroll.inProgress = false; + + if (top === 0) { + vm.menu.scroll.display = false; + + return; + } + + vm.menu.scroll.display = true; + + if (bottom >= meta.scroll.height) { + // fetch more lines + } + }, 500); +} + +/* + *function approximateLineNumber () { + * + *} + */ + JobsIndexController.$inject = ['job', 'JobEventModel', '$sce', '$timeout', '$scope', '$compile']; module.exports = JobsIndexController; diff --git a/awx/ui/client/features/output/index.js b/awx/ui/client/features/output/index.js index 6ff616c6ba..62e7309115 100644 --- a/awx/ui/client/features/output/index.js +++ b/awx/ui/client/features/output/index.js @@ -35,7 +35,7 @@ function JobsRun ($stateExtender, strings) { return new Jobs('get', id) .then(job => job.extend('job_events', { params: { - page_size: 10, + page_size: 1000, order_by: 'start_line' } })); diff --git a/awx/ui/client/features/output/index.view.html b/awx/ui/client/features/output/index.view.html index 89862f9cba..e45303ccec 100644 --- a/awx/ui/client/features/output/index.view.html +++ b/awx/ui/client/features/output/index.view.html @@ -13,7 +13,7 @@ ng-class="{ 'fa-minus': vm.menu.top.isExpanded, 'fa-plus': !vm.menu.top.isExpanded }"> -
+
@@ -22,13 +22,10 @@
 
-
-
- -
- -
- +
+
+

+

Back to Top

diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index cfd84c6a1f..0efa02b329 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -346,21 +346,53 @@ function extend (related, config) { return Promise.reject(new Error(`No related property, ${related}, exists`)); } -function next (related) { - related = related || this.resource; +function next (related, config = {}) { + const url = this.get(`related.${related}.next`); - if (!this.has(`related.${related}.next`)) { + if (!url) { return Promise.resolve(null); } const req = { method: 'GET', - url: this.get(`related.${related}.next`) + url }; return $http(req) .then(({ data }) => { - console.log(data); + const results = this.get(`related.${related}.results`) || []; + + data.results = results.concat(data.results); + this.set('get', `related.${related}`, data); + + if (config.limit < results.length) { + console.log(results); + } + }); +} + +function prev (related, config = {}) { + const url = this.get(`related.${related}.previous`); + + if (!url) { + return Promise.resolve(null); + } + + const req = { + method: 'GET', + url + }; + + return $http(req) + .then(({ data }) => { + const results = this.get(`related.${related}.results`) || []; + + data.results = data.results.concat(results); + this.set('get', `related.${related}`, data); + + if (config.limit < results.length) { + console.log(results); + } }); } @@ -545,6 +577,7 @@ function BaseModel (resource, settings) { this.normalizePath = normalizePath; this.options = options; this.parseRequestConfig = parseRequestConfig; + this.prev = prev; this.request = request; this.requestWithCache = requestWithCache; this.search = search;