aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/assets/components/Logs.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/assets/components/Logs.jsx')
-rw-r--r--dashboard/assets/components/Logs.jsx110
1 files changed, 63 insertions, 47 deletions
diff --git a/dashboard/assets/components/Logs.jsx b/dashboard/assets/components/Logs.jsx
index 203014276..57d6328d3 100644
--- a/dashboard/assets/components/Logs.jsx
+++ b/dashboard/assets/components/Logs.jsx
@@ -19,6 +19,7 @@
import React, {Component} from 'react';
import List, {ListItem} from 'material-ui/List';
+import escapeHtml from 'escape-html';
import type {Record, Content, LogsMessage, Logs as LogsType} from '../types/content';
// requestBand says how wide is the top/bottom zone, eg. 0.1 means 10% of the container height.
@@ -83,8 +84,8 @@ const createChunk = (records: Array<Record>) => {
content += `<span style="color:${color}">${lvl}</span>[${month}-${date}|${hours}:${minutes}:${seconds}] ${msg}`;
for (let i = 0; i < ctx.length; i += 2) {
- const key = ctx[i];
- const val = ctx[i + 1];
+ const key = escapeHtml(ctx[i]);
+ const val = escapeHtml(ctx[i + 1]);
let padding = fieldPadding.get(key);
if (typeof padding !== 'number' || padding < val.length) {
padding = val.length;
@@ -101,11 +102,17 @@ const createChunk = (records: Array<Record>) => {
return content;
};
+// ADDED, SAME and REMOVED are used to track the change of the log chunk array.
+// The scroll position is set using these values.
+const ADDED = 1;
+const SAME = 0;
+const REMOVED = -1;
+
// inserter is a state updater function for the main component, which inserts the new log chunk into the chunk array.
// limit is the maximum length of the chunk array, used in order to prevent the browser from OOM.
export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType) => {
- prev.topChanged = 0;
- prev.bottomChanged = 0;
+ prev.topChanged = SAME;
+ prev.bottomChanged = SAME;
if (!Array.isArray(update.chunk) || update.chunk.length < 1) {
return prev;
}
@@ -123,7 +130,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
return [{content, name: '00000000000000.log'}];
}
prev.chunks[prev.chunks.length - 1].content += content;
- prev.bottomChanged = 1;
+ prev.bottomChanged = ADDED;
return prev;
}
const chunk = {
@@ -137,10 +144,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
if (prev.chunks.length >= limit) {
prev.endBottom = false;
prev.chunks.splice(limit - 1, prev.chunks.length - limit + 1);
- prev.bottomChanged = -1;
+ prev.bottomChanged = REMOVED;
}
prev.chunks = [chunk, ...prev.chunks];
- prev.topChanged = 1;
+ prev.topChanged = ADDED;
return prev;
}
if (update.source.last) {
@@ -149,10 +156,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
if (prev.chunks.length >= limit) {
prev.endTop = false;
prev.chunks.splice(0, prev.chunks.length - limit + 1);
- prev.topChanged = -1;
+ prev.topChanged = REMOVED;
}
prev.chunks = [...prev.chunks, chunk];
- prev.bottomChanged = 1;
+ prev.bottomChanged = ADDED;
return prev;
};
@@ -160,6 +167,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
const styles = {
logListItem: {
padding: 0,
+ lineHeight: 1.231,
},
logChunk: {
color: 'white',
@@ -167,6 +175,11 @@ const styles = {
whiteSpace: 'nowrap',
width: 0,
},
+ waitMsg: {
+ textAlign: 'center',
+ color: 'white',
+ fontFamily: 'monospace',
+ },
};
export type Props = {
@@ -192,7 +205,17 @@ class Logs extends Component<Props, State> {
componentDidMount() {
const {container} = this.props;
+ if (typeof container === 'undefined') {
+ return;
+ }
container.scrollTop = container.scrollHeight - container.clientHeight;
+ const {logs} = this.props.content;
+ if (typeof this.content === 'undefined' || logs.chunks.length < 1) {
+ return;
+ }
+ if (this.content.clientHeight < container.clientHeight && !logs.endTop) {
+ this.sendRequest(logs.chunks[0].name, true);
+ }
}
// onScroll is triggered by the parent component's scroll event, and sends requests if the scroll position is
@@ -205,29 +228,23 @@ class Logs extends Component<Props, State> {
if (logs.chunks.length < 1) {
return;
}
- if (this.atTop()) {
- if (!logs.endTop) {
- this.setState({requestAllowed: false});
- this.props.send(JSON.stringify({
- Logs: {
- Name: logs.chunks[0].name,
- Past: true,
- },
- }));
- }
- } else if (this.atBottom()) {
- if (!logs.endBottom) {
- this.setState({requestAllowed: false});
- this.props.send(JSON.stringify({
- Logs: {
- Name: logs.chunks[logs.chunks.length - 1].name,
- Past: false,
- },
- }));
- }
+ if (this.atTop() && !logs.endTop) {
+ this.sendRequest(logs.chunks[0].name, true);
+ } else if (this.atBottom() && !logs.endBottom) {
+ this.sendRequest(logs.chunks[logs.chunks.length - 1].name, false);
}
};
+ sendRequest = (name: string, past: boolean) => {
+ this.setState({requestAllowed: false});
+ this.props.send(JSON.stringify({
+ Logs: {
+ Name: name,
+ Past: past,
+ },
+ }));
+ };
+
// atTop checks if the scroll position it at the top of the container.
atTop = () => this.props.container.scrollTop <= this.props.container.scrollHeight * requestBand;
@@ -242,8 +259,9 @@ class Logs extends Component<Props, State> {
// and the height of the first log chunk, which can be deleted during the insertion.
beforeUpdate = () => {
let firstHeight = 0;
- if (this.content && this.content.children[0] && this.content.children[0].children[0]) {
- firstHeight = this.content.children[0].children[0].clientHeight;
+ let chunkList = this.content.children[1];
+ if (chunkList && chunkList.children[0]) {
+ firstHeight = chunkList.children[0].clientHeight;
}
return {
scrollTop: this.props.container.scrollTop,
@@ -252,8 +270,8 @@ class Logs extends Component<Props, State> {
};
// didUpdate is called by the parent component, which provides the container. Sends the first request if the
- // visible part of the container isn't full, and resets the scroll position in order to avoid jumping when new
- // chunk is inserted.
+ // visible part of the container isn't full, and resets the scroll position in order to avoid jumping when a
+ // chunk is inserted or removed.
didUpdate = (prevProps, prevState, snapshot) => {
if (typeof this.props.shouldUpdate.logs === 'undefined' || typeof this.content === 'undefined' || snapshot === null) {
return;
@@ -264,27 +282,21 @@ class Logs extends Component<Props, State> {
return;
}
if (this.content.clientHeight < container.clientHeight) {
- // Only enters here at the beginning, when there isn't enough log to fill the container
+ // Only enters here at the beginning, when there aren't enough logs to fill the container
// and the scroll bar doesn't appear.
if (!logs.endTop) {
- this.setState({requestAllowed: false});
- this.props.send(JSON.stringify({
- Logs: {
- Name: logs.chunks[0].name,
- Past: true,
- },
- }));
+ this.sendRequest(logs.chunks[0].name, true);
}
return;
}
- const chunks = this.content.children[0].children;
let {scrollTop} = snapshot;
- if (logs.topChanged > 0) {
- scrollTop += chunks[0].clientHeight;
- } else if (logs.bottomChanged > 0) {
- if (logs.topChanged < 0) {
+ if (logs.topChanged === ADDED) {
+ // It would be safer to use a ref to the list, but ref doesn't work well with HOCs.
+ scrollTop += this.content.children[1].children[0].clientHeight;
+ } else if (logs.bottomChanged === ADDED) {
+ if (logs.topChanged === REMOVED) {
scrollTop -= snapshot.firstHeight;
- } else if (logs.endBottom && this.atBottom()) {
+ } else if (this.atBottom() && logs.endBottom) {
scrollTop = container.scrollHeight - container.clientHeight;
}
}
@@ -295,6 +307,9 @@ class Logs extends Component<Props, State> {
render() {
return (
<div ref={(ref) => { this.content = ref; }}>
+ <div style={styles.waitMsg}>
+ {this.props.content.logs.endTop ? 'No more logs.' : 'Waiting for server...'}
+ </div>
<List>
{this.props.content.logs.chunks.map((c, index) => (
<ListItem style={styles.logListItem} key={index}>
@@ -302,6 +317,7 @@ class Logs extends Component<Props, State> {
</ListItem>
))}
</List>
+ {this.props.content.logs.endBottom || <div style={styles.waitMsg}>Waiting for server...</div>}
</div>
);
}