diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json
index a1783d92e1..7c82f572c3 100644
--- a/awx/ui_next/package-lock.json
+++ b/awx/ui_next/package-lock.json
@@ -4576,6 +4576,11 @@
}
}
},
+ "clsx": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz",
+ "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg=="
+ },
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -5429,6 +5434,14 @@
"esutils": "^2.0.2"
}
},
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
"dom-serializer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
@@ -7174,7 +7187,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -7195,12 +7209,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7215,17 +7231,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -7342,7 +7361,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -7354,6 +7374,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7368,6 +7389,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -7375,12 +7397,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -7399,6 +7423,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -7479,7 +7504,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -7491,6 +7517,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -7576,7 +7603,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -7612,6 +7640,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -7631,6 +7660,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -7674,12 +7704,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -10790,6 +10822,11 @@
"type-check": "~0.3.2"
}
},
+ "linear-layout-vector": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/linear-layout-vector/-/linear-layout-vector-0.0.1.tgz",
+ "integrity": "sha1-OYEU1zA7bsx/1rJzr3uEAdi6nHA="
+ },
"load-json-file": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -13130,8 +13167,7 @@
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
- "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
- "dev": true
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-router": {
"version": "4.3.1",
@@ -13190,6 +13226,20 @@
}
}
},
+ "react-virtualized": {
+ "version": "9.21.1",
+ "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.1.tgz",
+ "integrity": "sha512-E53vFjRRMCyUTEKuDLuGH1ld/9TFzjf/fFW816PE4HFXWZorESbSTYtiZz1oAjra0MminaUU1EnvUxoGuEFFPA==",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "clsx": "^1.0.1",
+ "dom-helpers": "^2.4.0 || ^3.0.0",
+ "linear-layout-vector": "0.0.1",
+ "loose-envify": "^1.3.0",
+ "prop-types": "^15.6.0",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json
index aab41746ae..48d0855cda 100644
--- a/awx/ui_next/package.json
+++ b/awx/ui_next/package.json
@@ -70,6 +70,7 @@
"react-codemirror2": "^6.0.0",
"react-dom": "^16.4.1",
"react-router-dom": "^4.3.1",
+ "react-virtualized": "^9.21.1",
"styled-components": "^4.2.0"
}
}
diff --git a/awx/ui_next/src/api/models/Jobs.js b/awx/ui_next/src/api/models/Jobs.js
index bd103c072a..a47b939a41 100644
--- a/awx/ui_next/src/api/models/Jobs.js
+++ b/awx/ui_next/src/api/models/Jobs.js
@@ -18,6 +18,12 @@ class Jobs extends Base {
readDetail(id, type) {
return this.http.get(`/api/v2${BASE_URLS[type]}${id}/`);
}
+
+ readJobEvents(id, params = {}) {
+ return this.http.get(`${this.baseUrl}${id}/job_events/`, {
+ params,
+ });
+ }
}
export default Jobs;
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 6e97e03269..6f70d01c78 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -1,31 +1,154 @@
import React, { Component } from 'react';
import { CardBody } from '@patternfly/react-core';
-import MenuControls from './shared/MenuControls';
import styled from 'styled-components';
+import { JobsAPI } from '@api';
+import ContentError from '@components/ContentError';
+import ContentLoading from '@components/ContentLoading';
+import MenuControls from './shared/MenuControls';
+import { List, AutoSizer } from 'react-virtualized';
const OutputToolbar = styled.div`
display: flex;
- justify-content: space-between;
+ justify-content: flex-end;
+`;
+const OutputWrapper = styled.div`
+ height: calc(100vh - 325px);
+ background-color: #fafafa;
+ margin-top: 24px;
+`;
+const OutputRow = styled.div`
+ display: flex;
`;
class JobOutput extends Component {
+ listRef = React.createRef();
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ contentError: null,
+ hasContentLoading: true,
+ results: [],
+ scrollToIndex: -1,
+ startIndex: 0,
+ stopIndex: 0,
+ };
+
+ this.loadJobEvents = this.loadJobEvents.bind(this);
+ this.renderRow = this.renderRow.bind(this);
+ this.handleScrollTop = this.handleScrollTop.bind(this);
+ this.handleScrollBottom = this.handleScrollBottom.bind(this);
+ this.handleScrollNext = this.handleScrollNext.bind(this);
+ this.handleScrollPrevious = this.handleScrollPrevious.bind(this);
+ }
+
+ componentDidMount() {
+ this.loadJobEvents();
+ }
+
+ async loadJobEvents() {
+ const { job } = this.props;
+
+ try {
+ const {
+ data: { results = [] },
+ } = await JobsAPI.readJobEvents(job.id);
+ this.setState({ results, hasContentLoading: true });
+ } catch (err) {
+ this.setState({ contentError: err });
+ } finally {
+ this.setState({ hasContentLoading: false });
+ }
+ }
+
+ renderRow({ index, key, style }) {
+ const { results } = this.state;
+ return (
+
+ {results[index].id}
+ {results[index].stdout}
+
+ );
+ }
+
+ onRowsRendered = ({ startIndex, stopIndex }) => {
+ this.setState({ startIndex, stopIndex });
+ };
+
+ handleScrollPrevious() {
+ const { startIndex, stopIndex } = this.state;
+ const index = startIndex - (stopIndex - startIndex);
+ this.setState({ scrollToIndex: Math.max(0, index) });
+ }
+
+ handleScrollNext() {
+ const { stopIndex } = this.state;
+ this.setState({ scrollToIndex: stopIndex + 1});
+ }
+
+ handleScrollTop() {
+ this.setState({ scrollToIndex: 0 });
+ }
+
+ handleScrollBottom() {
+ const { results } = this.state;
+ this.setState({ scrollToIndex: results.length - 1 });
+ }
+
render() {
const { job } = this.props;
+ const {
+ results,
+ hasContentLoading,
+ contentError,
+ scrollToIndex,
+ startIndex,
+ stopIndex
+ } = this.state;
+
+ if (hasContentLoading) {
+ return ;
+ }
+
+ if (contentError) {
+ return ;
+ }
return (
{job.name}
- {/*Heading and Job Stats */}
- {/*Host Status Bar */}
- {/* Filter and Pagination */}
- Filter placeholder
-
+
-
+
+
+ {({ width, height }) => {
+ console.log('scroll to index', scrollToIndex);
+ console.log('start index', startIndex);
+ console.log('stop index', stopIndex);
+ return (
+
+ );
+ }}
+
+
);
}
diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx
index 8d135110dd..9391ccadc6 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx
@@ -23,22 +23,32 @@ const Button = styled(PFButton)`
`;
class MenuControls extends Component {
+ constructor(props) {
+ super(props);
+ }
+
render() {
+ const {
+ onScrollTop,
+ onScrollBottom,
+ onScrollNext,
+ onScrollPrevious,
+ } = this.props;
return (
-
diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx
index ab63883778..8ac5fe4c46 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx
@@ -15,7 +15,7 @@ const findChildren = () => {
AngleDoubleDownIcon = wrapper.find('AngleDoubleDownIcon');
AngleUpIcon = wrapper.find('AngleUpIcon');
AngleDownIcon = wrapper.find('AngleDownIcon');
-}
+};
describe('MenuControls', () => {
test('should render successfully', () => {
diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/index.js b/awx/ui_next/src/screens/Job/JobOutput/shared/index.js
index 307985d03c..3a2f81055d 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/shared/index.js
+++ b/awx/ui_next/src/screens/Job/JobOutput/shared/index.js
@@ -1 +1 @@
-export { default as MenuControls } from './MenuControls';
+export { default } from './MenuControls';