mirror of
https://github.com/ansible/awx.git
synced 2026-03-09 13:39:27 -02:30
[WIP] Move page-related functionality into separate service
This commit is contained in:
committed by
Jake McDermott
parent
a5bd905f18
commit
df84f822f6
@@ -5,6 +5,7 @@ let vm;
|
|||||||
let ansi;
|
let ansi;
|
||||||
let model;
|
let model;
|
||||||
let resource;
|
let resource;
|
||||||
|
let page;
|
||||||
let container;
|
let container;
|
||||||
let $timeout;
|
let $timeout;
|
||||||
let $sce;
|
let $sce;
|
||||||
@@ -41,7 +42,7 @@ const TIME_EVENTS = [
|
|||||||
|
|
||||||
function JobsIndexController (
|
function JobsIndexController (
|
||||||
_resource_,
|
_resource_,
|
||||||
webSocketNamespace,
|
_page_,
|
||||||
_$sce_,
|
_$sce_,
|
||||||
_$timeout_,
|
_$timeout_,
|
||||||
_$scope_,
|
_$scope_,
|
||||||
@@ -56,6 +57,7 @@ function JobsIndexController (
|
|||||||
$scope = _$scope_;
|
$scope = _$scope_;
|
||||||
$q = _$q_;
|
$q = _$q_;
|
||||||
resource = _resource_;
|
resource = _resource_;
|
||||||
|
page = _page_;
|
||||||
model = resource.model;
|
model = resource.model;
|
||||||
|
|
||||||
ansi = new Ansi();
|
ansi = new Ansi();
|
||||||
@@ -64,7 +66,9 @@ function JobsIndexController (
|
|||||||
const parsed = parseEvents(events);
|
const parsed = parseEvents(events);
|
||||||
const html = $sce.trustAsHtml(parsed.html);
|
const html = $sce.trustAsHtml(parsed.html);
|
||||||
|
|
||||||
cache.push({ page: 1, lines: parsed.lines });
|
page.init(resource);
|
||||||
|
|
||||||
|
page.add({ number: 1, lines: parsed.lines });
|
||||||
|
|
||||||
// Development helper(s)
|
// Development helper(s)
|
||||||
vm.clear = devClear;
|
vm.clear = devClear;
|
||||||
@@ -86,10 +90,12 @@ function JobsIndexController (
|
|||||||
vm.isExpanded = true;
|
vm.isExpanded = true;
|
||||||
|
|
||||||
// Real-time (active between JOB_START and JOB_END events only)
|
// Real-time (active between JOB_START and JOB_END events only)
|
||||||
$scope.$on(webSocketNamespace, processWebSocketEvents);
|
$scope.$on(resource.ws.namespace, processWebSocketEvents);
|
||||||
vm.stream = {
|
vm.stream = {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isRendering: false,
|
isRendering: false,
|
||||||
|
isPaused: false,
|
||||||
|
buffered: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
page: 1
|
page: 1
|
||||||
};
|
};
|
||||||
@@ -105,7 +111,13 @@ function JobsIndexController (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Determine how to manage buffered events (store in page cache vs. separate)
|
||||||
|
// Leaning towards keeping separate (same as they come in over WS). On resume of scroll,
|
||||||
|
// Clear/reset cache, append buffered events, then back to normal render cycle
|
||||||
|
|
||||||
function processWebSocketEvents (scope, data) {
|
function processWebSocketEvents (scope, data) {
|
||||||
|
let done;
|
||||||
|
|
||||||
if (data.event === JOB_START) {
|
if (data.event === JOB_START) {
|
||||||
vm.scroll.isActive = true;
|
vm.scroll.isActive = true;
|
||||||
vm.stream.isActive = true;
|
vm.stream.isActive = true;
|
||||||
@@ -114,37 +126,28 @@ function processWebSocketEvents (scope, data) {
|
|||||||
vm.stream.isActive = false;
|
vm.stream.isActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Determine how to manage buffered events (store in page cache vs. separate)
|
const pageAdded = page.addToBuffer(data);
|
||||||
// Leaning towards keeping separate (same as they come in over WS). On resume of scroll,
|
|
||||||
// Clear/reset cache, append buffered events, then back to normal render cycle
|
|
||||||
|
|
||||||
if (vm.stream.count % resource.page.size === 0) {
|
if (pageAdded && !vm.scroll.isLocked) {
|
||||||
cache.push({ page: vm.stream.page });
|
vm.stream.isPaused = true;
|
||||||
|
|
||||||
vm.stream.page++;
|
|
||||||
|
|
||||||
if (buffer.length > (resource.page.resultLimit - resource.page.size)) {
|
|
||||||
buffer.splice(0, (buffer.length - resource.page.resultLimit) + resource.page.size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.stream.count++;
|
if (vm.stream.isPaused && vm.scroll.isLocked) {
|
||||||
buffer.push(data);
|
vm.stream.isPaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (vm.stream.isRendering || !vm.scroll.isLocked) {
|
if (vm.stream.isRendering || vm.stream.isPaused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.stream.isRendering = true;
|
const events = page.emptyBuffer();
|
||||||
|
|
||||||
const events = buffer.slice(0, buffer.length);
|
|
||||||
|
|
||||||
buffer = [];
|
|
||||||
|
|
||||||
return render(events);
|
return render(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
function render (events) {
|
function render (events) {
|
||||||
|
vm.stream.isRendering = true;
|
||||||
|
|
||||||
return shift()
|
return shift()
|
||||||
.then(() => append(events))
|
.then(() => append(events))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -154,16 +157,15 @@ function render (events) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!vm.stream.isActive) {
|
if (!vm.stream.isActive) {
|
||||||
if (buffer.length) {
|
const buffer = page.emptyBuffer();
|
||||||
events = buffer.slice(0, buffer.length);
|
|
||||||
buffer = [];
|
|
||||||
|
|
||||||
return render(events);
|
if (buffer.length) {
|
||||||
} else {
|
return render(buffer);
|
||||||
vm.stream.isRendering = false;
|
|
||||||
vm.scroll.isLocked = false;
|
|
||||||
vm.scroll.isActive = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm.stream.isRendering = false;
|
||||||
|
vm.scroll.isLocked = false;
|
||||||
|
vm.scroll.isActive = false;
|
||||||
} else {
|
} else {
|
||||||
vm.stream.isRendering = false;
|
vm.stream.isRendering = false;
|
||||||
}
|
}
|
||||||
@@ -172,13 +174,14 @@ function render (events) {
|
|||||||
|
|
||||||
function devClear () {
|
function devClear () {
|
||||||
cache = [];
|
cache = [];
|
||||||
|
page.init(resource);
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function next () {
|
function next () {
|
||||||
const config = {
|
const config = {
|
||||||
related: resource.related,
|
related: resource.related,
|
||||||
page: cache[cache.length - 1].page + 1,
|
page: vm.scroll.lastPage + 1,
|
||||||
params: {
|
params: {
|
||||||
order_by: 'start_line'
|
order_by: 'start_line'
|
||||||
}
|
}
|
||||||
@@ -191,9 +194,9 @@ function next () {
|
|||||||
return $q.resolve();
|
return $q.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.push({
|
cache.push({ page: data.page, events: [] });
|
||||||
page: data.page
|
|
||||||
});
|
vm.scroll.lastPage = data.page;
|
||||||
|
|
||||||
return shift()
|
return shift()
|
||||||
.then(() => append(data.results));
|
.then(() => append(data.results));
|
||||||
@@ -205,12 +208,13 @@ function prev () {
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
related: resource.related,
|
related: resource.related,
|
||||||
page: cache[0].page - 1,
|
page: vm.scroll.firstPage - 1,
|
||||||
params: {
|
params: {
|
||||||
order_by: 'start_line'
|
order_by: 'start_line'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(cache);
|
||||||
// console.log('[2] getting previous page', config.page, cache);
|
// console.log('[2] getting previous page', config.page, cache);
|
||||||
return model.goToPage(config)
|
return model.goToPage(config)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -218,12 +222,13 @@ function prev () {
|
|||||||
return $q.resolve();
|
return $q.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.unshift({
|
cache.unshift({ page: data.page, events: [] });
|
||||||
page: data.page
|
|
||||||
});
|
vm.scroll.firstPage = data.page;
|
||||||
|
|
||||||
const previousHeight = container.scrollHeight;
|
const previousHeight = container.scrollHeight;
|
||||||
|
|
||||||
|
console.log(cache);
|
||||||
return pop()
|
return pop()
|
||||||
.then(() => prepend(data.results))
|
.then(() => prepend(data.results))
|
||||||
.then(lines => {
|
.then(lines => {
|
||||||
@@ -241,13 +246,8 @@ function append (events) {
|
|||||||
const parsed = parseEvents(events);
|
const parsed = parseEvents(events);
|
||||||
const rows = $($sce.getTrustedHtml($sce.trustAsHtml(parsed.html)));
|
const rows = $($sce.getTrustedHtml($sce.trustAsHtml(parsed.html)));
|
||||||
const table = $(ELEMENT_TBODY);
|
const table = $(ELEMENT_TBODY);
|
||||||
const index = cache.length - 1;
|
|
||||||
|
|
||||||
if (cache[index].lines) {
|
page.updateLineCount('current', parsed.lines);
|
||||||
cache[index].lines += parsed.lines;
|
|
||||||
} else {
|
|
||||||
cache[index].lines = parsed.lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.append(rows);
|
table.append(rows);
|
||||||
$compile(rows.contents())($scope);
|
$compile(rows.contents())($scope);
|
||||||
@@ -289,6 +289,8 @@ function pop () {
|
|||||||
// console.log('[3.1] popping', ejected);
|
// console.log('[3.1] popping', ejected);
|
||||||
const rows = $(ELEMENT_TBODY).children().slice(-ejected.lines);
|
const rows = $(ELEMENT_TBODY).children().slice(-ejected.lines);
|
||||||
|
|
||||||
|
vm.scroll.firstPage = cache[0].page;
|
||||||
|
|
||||||
rows.empty();
|
rows.empty();
|
||||||
rows.remove();
|
rows.remove();
|
||||||
|
|
||||||
@@ -298,17 +300,19 @@ function pop () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shift () {
|
function shift () {
|
||||||
console.log('[3] shifting old page', cache.length);
|
// console.log('[3] shifting old page', cache.length);
|
||||||
return $q(resolve => {
|
return $q(resolve => {
|
||||||
if (cache.length <= resource.page.pageLimit) {
|
if (!page.isOverCapacity()) {
|
||||||
// console.log('[3.1] nothing to shift');
|
// console.log('[3.1] nothing to shift');
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
const ejected = cache.shift();
|
const lines = page.trim();
|
||||||
console.log('[3.1] shifting', ejected);
|
//console.log('[3.1] shifting', lines);
|
||||||
const rows = $(ELEMENT_TBODY).children().slice(0, ejected.lines);
|
const rows = $(ELEMENT_TBODY).children().slice(0, lines);
|
||||||
|
|
||||||
|
vm.scroll.firstPage = page.getPageNumber('first');
|
||||||
|
|
||||||
rows.empty();
|
rows.empty();
|
||||||
rows.remove();
|
rows.remove();
|
||||||
@@ -637,17 +641,20 @@ function scrollHome () {
|
|||||||
|
|
||||||
function scrollEnd () {
|
function scrollEnd () {
|
||||||
if (vm.scroll.isLocked) {
|
if (vm.scroll.isLocked) {
|
||||||
|
// Make note of current page when unlocked -- keep buffered events for that page for
|
||||||
|
// continuity
|
||||||
|
|
||||||
|
vm.scroll.firstPage = cache[0].page;
|
||||||
|
vm.scroll.lastPage = cache[cache.length - 1].page;
|
||||||
vm.scroll.isLocked = false;
|
vm.scroll.isLocked = false;
|
||||||
vm.scroll.isActive = false;
|
vm.scroll.isActive = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (!vm.scroll.isLocked && vm.stream.isActive) {
|
} else if (!vm.scroll.isLocked && vm.stream.isActive) {
|
||||||
vm.scroll.isActive = true;
|
vm.scroll.isActive = true;
|
||||||
|
vm.scroll.isLocked = true;
|
||||||
|
|
||||||
return clear()
|
return;
|
||||||
.then(() => {
|
|
||||||
vm.scroll.isLocked = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@@ -698,7 +705,7 @@ function scrollPageDown () {
|
|||||||
|
|
||||||
JobsIndexController.$inject = [
|
JobsIndexController.$inject = [
|
||||||
'resource',
|
'resource',
|
||||||
'webSocketNamespace',
|
'JobPageService',
|
||||||
'$sce',
|
'$sce',
|
||||||
'$timeout',
|
'$timeout',
|
||||||
'$scope',
|
'$scope',
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ import IndexController from '~features/output/index.controller';
|
|||||||
import atLibModels from '~models';
|
import atLibModels from '~models';
|
||||||
import atLibComponents from '~components';
|
import atLibComponents from '~components';
|
||||||
|
|
||||||
import JobsStrings from '~features/output/jobs.strings';
|
import Strings from '~features/output/jobs.strings';
|
||||||
import IndexController from '~features/output/index.controller';
|
import Controller from '~features/output/index.controller';
|
||||||
|
import PageService from '~features/output/page.service';
|
||||||
|
|
||||||
const indexTemplate = require('~features/output/index.view.html');
|
const Template = require('~features/output/index.view.html');
|
||||||
|
|
||||||
const MODULE_NAME = 'at.features.output';
|
const MODULE_NAME = 'at.features.output';
|
||||||
const PAGE_CACHE = true;
|
const PAGE_CACHE = true;
|
||||||
const PAGE_LIMIT = 3;
|
const PAGE_LIMIT = 3;
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 100;
|
||||||
|
const WS_PREFIX = 'ws';
|
||||||
|
|
||||||
function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams) {
|
function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams) {
|
||||||
const { id, type } = $stateParams;
|
const { id, type } = $stateParams;
|
||||||
@@ -56,20 +58,20 @@ function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJ
|
|||||||
type,
|
type,
|
||||||
model,
|
model,
|
||||||
related,
|
related,
|
||||||
ws: getWebSocketResource(type),
|
ws: {
|
||||||
|
namespace: `${WS_PREFIX}-${getWebSocketResource(type).key}-${id}`
|
||||||
|
},
|
||||||
page: {
|
page: {
|
||||||
cache: PAGE_CACHE,
|
cache: PAGE_CACHE,
|
||||||
size: PAGE_SIZE,
|
size: PAGE_SIZE,
|
||||||
pageLimit: PAGE_LIMIT,
|
pageLimit: PAGE_LIMIT
|
||||||
resultLimit: PAGE_SIZE * PAGE_LIMIT
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWebSocket (SocketService, $stateParams) {
|
function resolveWebSocketConnection (SocketService, $stateParams) {
|
||||||
const { type, id } = $stateParams;
|
const { type, id } = $stateParams;
|
||||||
const prefix = 'ws';
|
|
||||||
const resource = getWebSocketResource(type);
|
const resource = getWebSocketResource(type);
|
||||||
|
|
||||||
let name;
|
let name;
|
||||||
@@ -87,8 +89,6 @@ function resolveWebSocket (SocketService, $stateParams) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SocketService.addStateResolve(state, id);
|
SocketService.addStateResolve(state, id);
|
||||||
|
|
||||||
return `${prefix}-${resource.key}-${id}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveBreadcrumb (strings) {
|
function resolveBreadcrumb (strings) {
|
||||||
@@ -139,8 +139,8 @@ function JobsRun ($stateRegistry) {
|
|||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
'@': {
|
'@': {
|
||||||
templateUrl: indexTemplate,
|
templateUrl: Template,
|
||||||
controller: IndexController,
|
controller: Controller,
|
||||||
controllerAs: 'vm'
|
controllerAs: 'vm'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -155,13 +155,13 @@ function JobsRun ($stateRegistry) {
|
|||||||
resolveResource
|
resolveResource
|
||||||
],
|
],
|
||||||
ncyBreadcrumb: [
|
ncyBreadcrumb: [
|
||||||
'JobsStrings',
|
'JobStrings',
|
||||||
resolveBreadcrumb
|
resolveBreadcrumb
|
||||||
],
|
],
|
||||||
webSocketNamespace: [
|
webSocketConnection: [
|
||||||
'SocketService',
|
'SocketService',
|
||||||
'$stateParams',
|
'$stateParams',
|
||||||
resolveWebSocket
|
resolveWebSocketConnection
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -176,8 +176,8 @@ angular
|
|||||||
atLibModels,
|
atLibModels,
|
||||||
atLibComponents
|
atLibComponents
|
||||||
])
|
])
|
||||||
.controller('indexController', IndexController)
|
.service('JobStrings', Strings)
|
||||||
.service('JobsStrings', JobsStrings)
|
.service('JobPageService', PageService)
|
||||||
.run(JobsRun);
|
.run(JobsRun);
|
||||||
|
|
||||||
export default MODULE_NAME;
|
export default MODULE_NAME;
|
||||||
|
|||||||
140
awx/ui/client/features/output/page.service.js
Normal file
140
awx/ui/client/features/output/page.service.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
function JobPageService () {
|
||||||
|
this.page = null;
|
||||||
|
this.resource = null;
|
||||||
|
this.result = null;
|
||||||
|
this.buffer = null;
|
||||||
|
this.cache = null;
|
||||||
|
|
||||||
|
this.init = resource => {
|
||||||
|
this.resource = resource;
|
||||||
|
|
||||||
|
this.page = {
|
||||||
|
limit: resource.page.pageLimit,
|
||||||
|
size: resource.page.size,
|
||||||
|
current: 0,
|
||||||
|
index: -1,
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.result = {
|
||||||
|
limit: this.page.limit * this.page.size,
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.buffer = {
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cache = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
this.add = (page, position) => {
|
||||||
|
page.events = page.events || [];
|
||||||
|
page.lines = page.lines || 0;
|
||||||
|
|
||||||
|
if (!position) {
|
||||||
|
this.cache.push(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.page.count++;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addToBuffer = event => {
|
||||||
|
let pageAdded = false;
|
||||||
|
|
||||||
|
if (this.result.count % this.page.size === 0) {
|
||||||
|
pageAdded = true;
|
||||||
|
|
||||||
|
this.add({ number: this.page.count + 1, events: [event] });
|
||||||
|
|
||||||
|
this.trimBuffer();
|
||||||
|
} else {
|
||||||
|
this.cache[this.cache.length - 1].events.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer.count++;
|
||||||
|
this.result.count++;
|
||||||
|
|
||||||
|
return pageAdded;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.trimBuffer = () => {
|
||||||
|
const diff = this.cache.length - this.page.limit;
|
||||||
|
|
||||||
|
if (diff <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < diff; i++) {
|
||||||
|
if (this.cache[i].events) {
|
||||||
|
this.buffer.count -= this.cache[i].events.length;
|
||||||
|
this.cache[i].events = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.emptyBuffer = () => {
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.cache.length; i++) {
|
||||||
|
const events = this.cache[i].events;
|
||||||
|
|
||||||
|
if (events.length > 0) {
|
||||||
|
this.buffer.count -= events.length;
|
||||||
|
data = data.concat(this.cache[i].events.splice(0, events.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isOverCapacity = () => {
|
||||||
|
return (this.cache.length - this.page.limit) > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.trim = () => {
|
||||||
|
const count = this.cache.length - this.page.limit;
|
||||||
|
const ejected = this.cache.splice(0, count);
|
||||||
|
const linesRemoved = ejected.reduce((total, page) => total + page.lines, 0);
|
||||||
|
|
||||||
|
return linesRemoved;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPageNumber = (page) => {
|
||||||
|
let index;
|
||||||
|
|
||||||
|
if (page === 'first') {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cache[index].number;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateLineCount = (page, lines) => {
|
||||||
|
let index;
|
||||||
|
|
||||||
|
if (page === 'current') {
|
||||||
|
index = this.cache.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cache[index].lines) {
|
||||||
|
this.cache[index].lines += lines;
|
||||||
|
} else {
|
||||||
|
this.cache[index].lines = lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.next = () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.prev = () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.current = () => {
|
||||||
|
return this.resource.model.get(`related.${this.resource.related}.results`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobPageService;
|
||||||
0
awx/ui/client/features/output/render.service.js
Normal file
0
awx/ui/client/features/output/render.service.js
Normal file
Reference in New Issue
Block a user