1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 09:51:09 +03:00

Add scroll lock for real-time display

This commit is contained in:
gconsidine 2018-02-20 16:44:51 -05:00 committed by Jake McDermott
parent 83897d43a7
commit d48f69317f
4 changed files with 164 additions and 137 deletions

View File

@ -65,6 +65,15 @@
}
}
&-menuIcon--active {
font-size: 22px;
line-height: 12px;
font-weight: bold;
padding: 10px;
cursor: pointer;
color: @at-blue;
}
&-toggle {
color: @at-gray-848992;
background-color: @at-gray-eb;

View File

@ -3,8 +3,8 @@ import hasAnsi from 'has-ansi';
let vm;
let ansi;
let model;
let resource;
let related;
let container;
let $timeout;
let $sce;
@ -13,11 +13,9 @@ let $scope;
let $q;
const record = {};
const meta = {
scroll: {},
page: {}
};
const current = {};
let parent = null;
let cache = [];
const PAGE_LIMIT = 3;
const SCROLL_BUFFER = 250;
@ -27,6 +25,8 @@ const EVENT_START_PLAY = 'playbook_on_play_start';
const EVENT_STATS_PLAY = 'playbook_on_stats';
const ELEMENT_TBODY = '#atStdoutResultTable';
const ELEMENT_CONTAINER = '.at-Stdout-container';
const JOB_START = 'playbook_on_start';
const JOB_END = 'playbook_on_stats';
const EVENT_GROUPS = [
EVENT_START_TASK,
@ -48,55 +48,48 @@ function JobsIndexController (
_$compile_,
_$q_
) {
vm = this || {};
$timeout = _$timeout_;
$sce = _$sce_;
$compile = _$compile_;
$scope = _$scope_;
$q = _$q_;
resource = _resource_;
model = resource.model;
ansi = new Ansi();
related = getRelated();
const events = resource.get(`related.${related}.results`);
const events = model.get(`related.${resource.related}.results`);
const parsed = parseEvents(events);
const html = $sce.trustAsHtml(parsed.html);
vm = this || {};
cache.push({ page: 1, lines: parsed.lines });
$scope.ns = 'jobs';
$scope.jobs = {
modal: {}
};
vm.toggle = toggle;
vm.showHostDetails = showHostDetails;
// Development helper(s)
vm.clear = clear;
$scope.$on(webSocketNamespace, processWebSocketEvents);
vm.menu = {
scroll: {
display: false,
home: scrollHome,
end: scrollEnd,
down: scrollPageDown,
up: scrollPageUp
},
top: {
expand,
isExpanded: true
},
bottom: {
next
}
// Stdout Navigation
vm.scroll = {
lock: false,
display: false,
active: false,
home: scrollHome,
end: scrollEnd,
down: scrollPageDown,
up: scrollPageUp
};
meta.page.cache = [{
page: 1,
lines: parsed.lines
}];
// Expand/collapse
vm.toggle = toggle;
vm.expand = expand;
vm.isExpanded = true;
// Real-time (active between JOB_START and JOB_END events only)
$scope.$on(webSocketNamespace, processWebSocketEvents);
vm.stream = {
active: false
};
$timeout(() => {
const table = $(ELEMENT_TBODY);
@ -117,43 +110,41 @@ function clear () {
}
function processWebSocketEvents (scope, data) {
meta.scroll.inProgress = true;
vm.scroll.active = true;
if (data.event === JOB_START) {
vm.stream.active = true;
vm.scroll.lock = true;
} else if (data.event === JOB_END) {
vm.stream.active = false;
vm.scroll.lock = false;
}
console.log(data);
append([data])
.then(() => {
container[0].scrollTop = container[0].scrollHeight;
if (vm.scroll.lock) {
container[0].scrollTop = container[0].scrollHeight;
}
});
}
function getRelated () {
const name = resource.constructor.name;
switch (name) {
case 'JobModel':
return 'job_events';
default:
return 'events';
}
}
function next () {
const config = {
related,
page: meta.page.cache[meta.page.cache.length - 1].page + 1,
related: resource.related,
page: cache[cache.length - 1].page + 1,
params: {
order_by: 'start_line'
}
};
console.log('[2] getting next page', config.page, meta.page.cache);
return resource.goToPage(config)
console.log('[2] getting next page', config.page, cache);
return model.goToPage(config)
.then(data => {
if (!data || !data.results) {
return $q.resolve();
}
meta.page.cache.push({
cache.push({
page: data.page
});
@ -166,21 +157,21 @@ function prev () {
const container = $(ELEMENT_CONTAINER)[0];
const config = {
related,
page: meta.page.cache[0].page - 1,
related: resource.related,
page: cache[0].page - 1,
params: {
order_by: 'start_line'
}
};
console.log('[2] getting previous page', config.page, meta.page.cache);
return resource.goToPage(config)
console.log('[2] getting previous page', config.page, cache);
return model.goToPage(config)
.then(data => {
if (!data || !data.results) {
return $q.resolve();
}
meta.page.cache.unshift({
cache.unshift({
page: data.page
});
@ -203,9 +194,9 @@ function append (events) {
const parsed = parseEvents(events);
const rows = $($sce.getTrustedHtml($sce.trustAsHtml(parsed.html)));
const table = $(ELEMENT_TBODY);
const index = meta.page.cache.length - 1;
const index = cache.length - 1;
meta.page.cache[index].lines = parsed.lines;
cache[index].lines = parsed.lines;
table.append(rows);
$compile(rows.contents())($scope);
@ -223,7 +214,7 @@ function prepend (events) {
const rows = $($sce.getTrustedHtml($sce.trustAsHtml(parsed.html)));
const table = $(ELEMENT_TBODY);
meta.page.cache[0].lines = parsed.lines;
cache[0].lines = parsed.lines;
table.prepend(rows);
$compile(rows.contents())($scope);
@ -235,12 +226,12 @@ function prepend (events) {
function pop () {
console.log('[3] popping old page');
return $q(resolve => {
if (meta.page.cache.length <= PAGE_LIMIT) {
if (cache.length <= PAGE_LIMIT) {
console.log('[3.1] nothing to pop');
return resolve();
}
const ejected = meta.page.cache.pop();
const ejected = cache.pop();
console.log('[3.1] popping', ejected);
const rows = $(ELEMENT_TBODY).children().slice(-ejected.lines);
@ -254,12 +245,12 @@ function pop () {
function shift () {
console.log('[3] shifting old page');
return $q(resolve => {
if (meta.page.cache.length <= PAGE_LIMIT) {
if (cache.length <= PAGE_LIMIT) {
console.log('[3.1] nothing to shift');
return resolve();
}
const ejected = meta.page.cache.shift();
const ejected = cache.shift();
console.log('[3.1] shifting', ejected);
const rows = $(ELEMENT_TBODY).children().slice(0, ejected.lines);
@ -283,7 +274,7 @@ function clear () {
}
function expand () {
vm.toggle(meta.parent, true);
vm.toggle(parent, true);
}
function parseEvents (events) {
@ -375,7 +366,7 @@ function createRecord (ln, lines, event) {
info.isParent = true;
if (event.event_level === 1) {
meta.parent = event.uuid;
parent = event.uuid;
}
if (event.parent_uuid) {
@ -495,7 +486,7 @@ function toggle (uuid, menu) {
let icon = $(`#${uuid} .at-Stdout-toggle > i`);
if (menu || record[uuid].level === 1) {
vm.menu.top.isExpanded = !vm.menu.top.isExpanded;
vm.isExpanded = !vm.isExpanded;
}
if (record[uuid].children) {
@ -516,11 +507,11 @@ function toggle (uuid, menu) {
}
function onScroll () {
if (meta.scroll.inProgress) {
if (vm.scroll.active) {
return;
}
meta.scroll.inProgress = true;
vm.scroll.active = true;
$timeout(() => {
const top = container[0].scrollTop;
@ -528,17 +519,17 @@ function onScroll () {
if (top <= SCROLL_BUFFER) {
console.log('[1] scroll to top');
vm.menu.scroll.display = false;
vm.scroll.display = false;
prev()
.then(() => {
console.log('[5] scroll reset');
meta.scroll.inProgress = false;
vm.scroll.active = false;
});
return;
} else {
vm.menu.scroll.display = true;
vm.scroll.display = true;
if (bottom >= container[0].scrollHeight) {
console.log('[1] scroll to bottom');
@ -546,10 +537,10 @@ function onScroll () {
next()
.then(() => {
console.log('[5] scroll reset');
meta.scroll.inProgress = false;
vm.scroll.active = false;
});
} else {
meta.scroll.inProgress = false;
vm.scroll.active = false;
}
}
}, SCROLL_LOAD_DELAY);
@ -557,53 +548,59 @@ function onScroll () {
function scrollHome () {
const config = {
related,
related: resource.related,
page: 'first',
params: {
order_by: 'start_line'
}
};
meta.scroll.inProgress = true;
vm.scroll.active = true;
console.log('[2] getting first page', config.page, meta.page.cache);
return resource.goToPage(config)
console.log('[2] getting first page', config.page, cache);
return model.goToPage(config)
.then(data => {
if (!data || !data.results) {
return $q.resolve();
}
meta.page.cache = [{
cache = [{
page: data.page
}]
return clear()
.then(() => prepend(data.results))
.then(() => {
meta.scroll.inProgress = false;
vm.scroll.active = false;
});
});
}
function scrollEnd () {
if (vm.scroll.lock) {
vm.scroll.lock = false;
return;
}
const config = {
related,
related: resource.related,
page: 'last',
params: {
order_by: 'start_line'
}
};
meta.scroll.inProgress = true;
vm.scroll.active = true;
console.log('[2] getting last page', config.page, meta.page.cache);
return resource.goToPage(config)
console.log('[2] getting last page', config.page, cache);
return model.goToPage(config)
.then(data => {
if (!data || !data.results) {
return $q.resolve();
}
meta.page.cache = [{
cache = [{
page: data.page
}]
@ -613,7 +610,7 @@ function scrollEnd () {
const container = $(ELEMENT_CONTAINER)[0];
container.scrollTop = container.scrollHeight;
meta.scroll.inProgress = false;
vm.scroll.active = false;
});
});
}

View File

@ -9,10 +9,12 @@ import IndexController from '~features/output/index.controller';
const indexTemplate = require('~features/output/index.view.html');
const MODULE_NAME = 'at.features.output';
const PAGE_CACHE = true;
const PAGE_LIMIT = 3;
const PAGE_SIZE = 100;
function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams) {
const { id } = $stateParams;
const { type } = $stateParams;
const { id, type } = $stateParams;
let Resource;
let related = 'events';
@ -40,52 +42,44 @@ function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJ
}
return new Resource('get', id)
.then(resource => resource.extend(related, {
pageCache: true,
pageLimit: 3,
.then(model => model.extend(related, {
pageCache: PAGE_CACHE,
pageLimit: PAGE_LIMIT,
params: {
page_size: 100,
page_size: PAGE_SIZE,
order_by: 'start_line'
}
}));
}))
.then(model => {
return {
id,
type,
model,
related,
ws: getWebSocketResource(type),
page: {
cache: PAGE_CACHE,
limit: PAGE_LIMIT,
size: PAGE_SIZE
}
};
});
}
function resolveWebSocket (SocketService, $stateParams) {
const { type, id } = $stateParams;
const prefix = 'ws';
const resource = getWebSocketResource(type);
let name;
let events;
switch (type) {
case 'system':
name = 'system_jobs';
events = 'system_job_events';
break;
case 'project':
name = 'project_updates';
events = 'project_update_events';
break;
case 'command':
name = 'ad_hoc_commands';
events = 'ad_hoc_command_events';
break;
case 'inventory':
name = 'inventory_updates';
events = 'inventory_update_events';
break;
case 'playbook':
name = 'jobs';
events = 'job_events';
break;
}
const state = {
data: {
socket: {
groups: {
[name]: ['status_changed', 'summary'],
[events]: []
[resource.name]: ['status_changed', 'summary'],
[resource.key]: []
}
}
}
@ -93,7 +87,7 @@ function resolveWebSocket (SocketService, $stateParams) {
SocketService.addStateResolve(state, id);
return `${prefix}-${events}-${id}`;
return `${prefix}-${resource.key}-${id}`;
}
function resolveBreadcrumb (strings) {
@ -102,6 +96,37 @@ function resolveBreadcrumb (strings) {
};
}
function getWebSocketResource (type) {
let name;
let key;
switch (type) {
case 'system':
name = 'system_jobs';
key = 'system_job_events';
break;
case 'project':
name = 'project_updates';
key = 'project_update_events';
break;
case 'command':
name = 'ad_hoc_commands';
key = 'ad_hoc_command_events';
break;
case 'inventory':
name = 'inventory_updates';
key = 'inventory_update_events';
break;
case 'playbook':
name = 'jobs';
key = 'job_events';
break;
}
return { name, key };
}
function JobsRun ($stateRegistry) {
const state = {
name: 'jobz',

View File

@ -8,21 +8,22 @@
<div class="col-md-8">
<at-panel class="at-Stdout">
<div class="at-Stdout-menuTop">
<div class="pull-left" ng-click="vm.menu.top.expand()">
<div class="pull-left" ng-click="vm.expand()">
<i class="at-Stdout-menuIcon fa"
ng-class="{ 'fa-minus': vm.menu.top.isExpanded, 'fa-plus': !vm.menu.top.isExpanded }"></i>
ng-class="{ 'fa-minus': vm.isExpanded, 'fa-plus': !vm.isExpanded }"></i>
</div>
<div class="pull-right" ng-click="vm.menu.scroll.end()">
<i class="at-Stdout-menuIcon--lg fa fa-angle-double-down"></i>
<div class="pull-right" ng-click="vm.scroll.end()">
<i class="at-Stdout-menuIcon--lg fa fa-angle-double-down"
ng-class=" { 'at-Stdout-menuIcon--active': vm.scroll.lock }"></i>
</div>
<div class="pull-right" ng-click="vm.menu.scroll.home()">
<div class="pull-right" ng-click="vm.scroll.home()">
<i class="at-Stdout-menuIcon--lg fa fa-angle-double-up"></i>
</div>
<div class="pull-right" ng-click="vm.menu.scroll.down()">
<div class="pull-right" ng-click="vm.scroll.down()">
<i class="at-Stdout-menuIcon--lg fa fa-angle-down"></i>
</div>
<div class="pull-right" ng-click="vm.menu.scroll.up()">
<div class="pull-right" ng-click="vm.scroll.up()">
<i class="at-Stdout-menuIcon--lg fa fa-angle-up"></i>
</div>
@ -31,8 +32,8 @@
<pre class="at-Stdout-container"><table><thead><tr><th class="at-Stdout-toggle">&nbsp;</th><th class="at-Stdout-line"></th><th class="at-Stdout-event"></th></tr></thead><tbody id="atStdoutResultTable"></tbody></table></pre>
<div ng-show="vm.menu.scroll.display" class="at-Stdout-menuBottom">
<div class="at-Stdout-menuIconGroup" ng-click="vm.menu.scroll.home()">
<div ng-show="vm.scroll.display" class="at-Stdout-menuBottom">
<div class="at-Stdout-menuIconGroup" ng-click="vm.scroll.home()">
<p class="pull-left"><i class="fa fa-angle-double-up"></i></p>
<p class="pull-right">Back to Top</p>
</div>
@ -41,9 +42,4 @@
</div>
</at-panel>
</div>
<at-modal>
<br />
<at-code-stdout state="vm.host"></at-code-stdout>
</at-modal>
</div>