diff --git a/awx/ui/client/features/jobs/_index.less b/awx/ui/client/features/jobs/_index.less
index 9144f3c93c..50597f1b1a 100644
--- a/awx/ui/client/features/jobs/_index.less
+++ b/awx/ui/client/features/jobs/_index.less
@@ -1,12 +1,18 @@
.at-Stdout {
- tr {
- & > td:first-child {
- padding-right: 5px;
- border-right: 1px solid gray;
- }
+ &-expand {
+ padding-right: 10px;
+ }
- & > td:last-child {
- padding-left: 5px;
- }
+ &-lineNumber {
+ padding-right: 5px;
+ border-right: 1px solid gray;
+ }
+
+ &-content {
+ padding-left: 5px;
+ }
+
+ &-timestamp {
+ padding-left: 20px;
}
}
diff --git a/awx/ui/client/features/output/index.controller.js b/awx/ui/client/features/output/index.controller.js
index 71c4bcf408..d94971ce7f 100644
--- a/awx/ui/client/features/output/index.controller.js
+++ b/awx/ui/client/features/output/index.controller.js
@@ -1,40 +1,102 @@
import Ansi from 'ansi-to-html';
import hasAnsi from 'has-ansi';
+let ansi;
+
+const EVENT_START_TASK = 'playbook_on_task_start';
+const EVENT_START_PLAY = 'playbook_on_play_start';
+const EVENT_STATS_PLAY = 'playbook_on_stats';
+
+const EVENT_GROUPS = [
+ EVENT_START_TASK,
+ EVENT_START_PLAY
+];
+
+const TIME_EVENTS = [
+ EVENT_START_TASK,
+ EVENT_START_PLAY,
+ EVENT_STATS_PLAY
+];
+
function JobsIndexController (job, $sce) {
const vm = this || {};
- const results = job.get('related.job_events.results');
- const ansi = new Ansi({});
+ const events = job.get('related.job_events.results');
- /*
- * const colors = [];
- *
- * for (let i = 0; i < 255; i++) {
- * colors.push('#ababab');
- * }
- *
- */
+ ansi = new Ansi();
- let html = '';
- results.forEach((line, i) => {
- if (!line.stdout) {
- return;
- }
-
- let output;
-
- if (hasAnsi(line.stdout)) {
- output = ansi.toHtml(line.stdout);
- } else {
- output = line.stdout; // .replace(/(\n|\r)/g, '');
- }
-
- html += `
| ${i} | ${output} |
`;
- });
+ const html = parseEvents(events);
vm.html = $sce.trustAsHtml(html);
}
+function parseEvents (events) {
+ events.sort((a, b) => a.start_line > b.start_line);
+
+ return events.reduce((html, event) => `${html}${parseLine(event)}`, '');
+}
+
+function parseLine (event) {
+ if (!event || !event.stdout) {
+ return '';
+ }
+
+ const { stdout } = event;
+ const lines = stdout.split('\r\n');
+
+ let ln = event.start_line;
+
+ return lines.reduce((html, line, i) => {
+ ln++;
+
+ const time = getTime(event, i);
+ const group = getGroup(event, i);
+
+ return `${html}${createRow(ln, line, time, group)}`;
+ }, '');
+}
+
+function createRow (ln, content, time, group) {
+ content = hasAnsi(content) ? ansi.toHtml(content) : content;
+
+ let expand = '';
+ if (group.parent) {
+ expand = '';
+ }
+
+ return `
+
+ | ${expand} |
+ ${ln} |
+ ${content} |
+ ${time} |
+
`;
+}
+
+function getGroup (event, i) {
+ const group = {};
+
+ if (EVENT_GROUPS.includes(event.event) && i === 1) {
+ group.parent = true;
+ group.classList = `parent parent-${event.event_level}`;
+ } else {
+ group.classList = '';
+ }
+
+ group.level = event.event_level;
+
+ return group;
+}
+
+function getTime (event, i) {
+ if (!TIME_EVENTS.includes(event.event) || i !== 1) {
+ return '';
+ }
+
+ const date = new Date(event.created);
+
+ return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
+}
+
JobsIndexController.$inject = ['job', '$sce'];
module.exports = JobsIndexController;
diff --git a/awx/ui/client/features/output/index.js b/awx/ui/client/features/output/index.js
index c5899b7964..558a1e4714 100644
--- a/awx/ui/client/features/output/index.js
+++ b/awx/ui/client/features/output/index.js
@@ -8,7 +8,7 @@ const MODULE_NAME = 'at.features.output';
function JobsRun ($stateExtender, strings) {
$stateExtender.addState({
name: 'jobz',
- route: '/jobz',
+ route: '/jobz/:id',
ncyBreadcrumb: {
label: strings.get('state.TITLE')
},
@@ -24,9 +24,12 @@ function JobsRun ($stateExtender, strings) {
}
},
resolve: {
- job: ['JobsModel', Jobs => new Jobs('get', 1002)
- .then(job => job.extend('job_events'))
- ]
+ job: ['JobsModel', '$stateParams', (Jobs, $stateParams) => {
+ const { id } = $stateParams;
+
+ return new Jobs('get', id)
+ .then(job => job.extend('job_events'));
+ }]
}
});
}
diff --git a/awx/ui/package.json b/awx/ui/package.json
index 2d9f32f488..5f8ff0ad5c 100644
--- a/awx/ui/package.json
+++ b/awx/ui/package.json
@@ -107,12 +107,14 @@
"angular-sanitize": "~1.6.6",
"angular-scheduler": "git+https://git@github.com/ansible/angular-scheduler#v0.3.2",
"angular-tz-extensions": "git+https://git@github.com/ansible/angular-tz-extensions#v0.5.2",
+ "ansi-to-html": "^0.6.3",
"babel-polyfill": "^6.26.0",
"bootstrap": "^3.3.7",
"bootstrap-datepicker": "^1.7.1",
"codemirror": "^5.17.0",
"components-font-awesome": "^4.6.1",
"d3": "~3.3.13",
+ "has-ansi": "^3.0.0",
"javascript-detect-element-resize": "^0.5.3",
"jquery": "~2.2.4",
"jquery-ui": "^1.12.1",