diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutput.js b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
index f59d53a429..5d4e06807c 100644
--- a/awx/ui/src/screens/Job/JobOutput/JobOutput.js
+++ b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
@@ -9,9 +9,6 @@ import {
InfiniteLoader,
List,
} from 'react-virtualized';
-import Ansi from 'ansi-to-html';
-import hasAnsi from 'has-ansi';
-import { encode } from 'html-entities';
import { Button } from '@patternfly/react-core';
import AlertModal from 'components/AlertModal';
@@ -33,110 +30,12 @@ import HostEventModal from './HostEventModal';
import JobOutputSearch from './JobOutputSearch';
import { HostStatusBar, OutputToolbar } from './shared';
import getRowRangePageSize from './shared/jobOutputUtils';
+import getLineTextHtml from './getLineTextHtml';
const QS_CONFIG = getQSConfig('job_output', {
order_by: 'counter',
});
-const EVENT_START_TASK = 'playbook_on_task_start';
-const EVENT_START_PLAY = 'playbook_on_play_start';
-const EVENT_STATS_PLAY = 'playbook_on_stats';
-const TIME_EVENTS = [EVENT_START_TASK, EVENT_START_PLAY, EVENT_STATS_PLAY];
-
-const ansi = new Ansi({
- stream: true,
- colors: {
- 0: '#000',
- 1: '#A30000',
- 2: '#486B00',
- 3: '#795600',
- 4: '#00A',
- 5: '#A0A',
- 6: '#004368',
- 7: '#AAA',
- 8: '#555',
- 9: '#F55',
- 10: '#5F5',
- 11: '#FF5',
- 12: '#55F',
- 13: '#F5F',
- 14: '#5FF',
- 15: '#FFF',
- },
-});
-
-function getTimestamp({ created }) {
- const date = new Date(created);
-
- const dateHours = date.getHours();
- const dateMinutes = date.getMinutes();
- const dateSeconds = date.getSeconds();
-
- const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours;
- const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
- const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
-
- return `${stampHours}:${stampMinutes}:${stampSeconds}`;
-}
-
-const styleAttrPattern = new RegExp('style="[^"]*"', 'g');
-
-function createStyleAttrHash(styleAttr) {
- let hash = 0;
- for (let i = 0; i < styleAttr.length; i++) {
- hash = (hash << 5) - hash; // eslint-disable-line no-bitwise
- hash += styleAttr.charCodeAt(i);
- hash &= hash; // eslint-disable-line no-bitwise
- }
- return `${hash}`;
-}
-
-function replaceStyleAttrs(html) {
- const allStyleAttrs = [...new Set(html.match(styleAttrPattern))];
- const cssMap = {};
- let result = html;
- for (let i = 0; i < allStyleAttrs.length; i++) {
- const styleAttr = allStyleAttrs[i];
- const cssClassName = `output-${createStyleAttrHash(styleAttr)}`;
-
- cssMap[cssClassName] = styleAttr.replace('style="', '').slice(0, -1);
- result = result.split(styleAttr).join(`class="${cssClassName}"`);
- }
- return { cssMap, result };
-}
-
-function getLineTextHtml({ created, event, start_line, stdout }) {
- const sanitized = encode(stdout);
- let lineCssMap = {};
- const lineTextHtml = [];
-
- sanitized.split('\r\n').forEach((lineText, index) => {
- let html;
- if (hasAnsi(lineText)) {
- const { cssMap, result } = replaceStyleAttrs(ansi.toHtml(lineText));
- html = result;
- lineCssMap = { ...lineCssMap, ...cssMap };
- } else {
- html = lineText;
- }
-
- if (index === 1 && TIME_EVENTS.includes(event)) {
- const time = getTimestamp({ created });
- html += `${time}`;
- }
-
- lineTextHtml.push({
- lineNumber: start_line + index,
- html,
- });
- });
-
- return {
- lineCssMap,
- lineTextHtml,
- };
-}
-
const CardBody = styled(_CardBody)`
display: flex;
flex-flow: column;
diff --git a/awx/ui/src/screens/Job/JobOutput/getLineTextHtml.js b/awx/ui/src/screens/Job/JobOutput/getLineTextHtml.js
new file mode 100644
index 0000000000..1ac82bd0c4
--- /dev/null
+++ b/awx/ui/src/screens/Job/JobOutput/getLineTextHtml.js
@@ -0,0 +1,107 @@
+import Ansi from 'ansi-to-html';
+import hasAnsi from 'has-ansi';
+import { encode } from 'html-entities';
+
+const EVENT_START_TASK = 'playbook_on_task_start';
+const EVENT_START_PLAY = 'playbook_on_play_start';
+const EVENT_STATS_PLAY = 'playbook_on_stats';
+const TIME_EVENTS = [EVENT_START_TASK, EVENT_START_PLAY, EVENT_STATS_PLAY];
+
+const ansi = new Ansi({
+ stream: true,
+ colors: {
+ 0: '#000',
+ 1: '#A30000',
+ 2: '#486B00',
+ 3: '#795600',
+ 4: '#00A',
+ 5: '#A0A',
+ 6: '#004368',
+ 7: '#AAA',
+ 8: '#555',
+ 9: '#F55',
+ 10: '#5F5',
+ 11: '#FF5',
+ 12: '#55F',
+ 13: '#F5F',
+ 14: '#5FF',
+ 15: '#FFF',
+ },
+});
+
+function getTimestamp({ created }) {
+ const date = new Date(created);
+
+ const dateHours = date.getHours();
+ const dateMinutes = date.getMinutes();
+ const dateSeconds = date.getSeconds();
+
+ const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours;
+ const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
+ const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
+
+ return `${stampHours}:${stampMinutes}:${stampSeconds}`;
+}
+
+function createStyleAttrHash(styleAttr) {
+ let hash = 0;
+ for (let i = 0; i < styleAttr.length; i++) {
+ hash = (hash << 5) - hash; // eslint-disable-line no-bitwise
+ hash += styleAttr.charCodeAt(i);
+ hash &= hash; // eslint-disable-line no-bitwise
+ }
+ return `${hash}`;
+}
+
+const styleAttrPattern = new RegExp('style="[^"]*"', 'g');
+
+function replaceStyleAttrs(html) {
+ const allStyleAttrs = [...new Set(html.match(styleAttrPattern))];
+ const cssMap = {};
+ let result = html;
+ for (let i = 0; i < allStyleAttrs.length; i++) {
+ const styleAttr = allStyleAttrs[i];
+ const cssClassName = `output-${createStyleAttrHash(styleAttr)}`;
+
+ cssMap[cssClassName] = styleAttr.replace('style="', '').slice(0, -1);
+ result = result.split(styleAttr).join(`class="${cssClassName}"`);
+ }
+ return { cssMap, result };
+}
+
+export default function getLineTextHtml({
+ created,
+ event,
+ start_line,
+ stdout,
+}) {
+ const sanitized = encode(stdout);
+ let lineCssMap = {};
+ const lineTextHtml = [];
+
+ sanitized.split('\r\n').forEach((lineText, index) => {
+ let html;
+ if (hasAnsi(lineText)) {
+ const { cssMap, result } = replaceStyleAttrs(ansi.toHtml(lineText));
+ html = result;
+ lineCssMap = { ...lineCssMap, ...cssMap };
+ } else {
+ html = lineText;
+ }
+
+ if (index === 1 && TIME_EVENTS.includes(event)) {
+ const time = getTimestamp({ created });
+ html += `${time}`;
+ }
+
+ lineTextHtml.push({
+ lineNumber: start_line + index,
+ html,
+ });
+ });
+
+ return {
+ lineCssMap,
+ lineTextHtml,
+ };
+}