dashboard: fix CSS, escape special HTML chars, clean up code (#17167)
* dashboard: fix CSS, escape special HTML chars, clean up code * dashboard: change 0 to 1 * dashboard: add escape-html npm package
This commit is contained in:
parent
db5e403afe
commit
eb7f901289
File diff suppressed because it is too large
Load Diff
|
@ -27,7 +27,7 @@ const styles = {
|
||||||
body: {
|
body: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '92%',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,16 @@ import Icon from 'material-ui/Icon';
|
||||||
import MenuIcon from 'material-ui-icons/Menu';
|
import MenuIcon from 'material-ui-icons/Menu';
|
||||||
import Typography from 'material-ui/Typography';
|
import Typography from 'material-ui/Typography';
|
||||||
|
|
||||||
|
// styles contains the constant styles of the component.
|
||||||
|
const styles = {
|
||||||
|
header: {
|
||||||
|
height: '8%',
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// themeStyles returns the styles generated from the theme for the component.
|
// themeStyles returns the styles generated from the theme for the component.
|
||||||
const themeStyles = (theme: Object) => ({
|
const themeStyles = (theme: Object) => ({
|
||||||
header: {
|
header: {
|
||||||
|
@ -54,8 +64,8 @@ class Header extends Component<Props> {
|
||||||
const {classes} = this.props;
|
const {classes} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position='static' className={classes.header}>
|
<AppBar position='static' className={classes.header} style={styles.header}>
|
||||||
<Toolbar className={classes.toolbar}>
|
<Toolbar className={classes.toolbar} style={styles.toolbar}>
|
||||||
<IconButton onClick={this.props.switchSideBar}>
|
<IconButton onClick={this.props.switchSideBar}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
import List, {ListItem} from 'material-ui/List';
|
import List, {ListItem} from 'material-ui/List';
|
||||||
|
import escapeHtml from 'escape-html';
|
||||||
import type {Record, Content, LogsMessage, Logs as LogsType} from '../types/content';
|
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.
|
// 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}`;
|
content += `<span style="color:${color}">${lvl}</span>[${month}-${date}|${hours}:${minutes}:${seconds}] ${msg}`;
|
||||||
|
|
||||||
for (let i = 0; i < ctx.length; i += 2) {
|
for (let i = 0; i < ctx.length; i += 2) {
|
||||||
const key = ctx[i];
|
const key = escapeHtml(ctx[i]);
|
||||||
const val = ctx[i + 1];
|
const val = escapeHtml(ctx[i + 1]);
|
||||||
let padding = fieldPadding.get(key);
|
let padding = fieldPadding.get(key);
|
||||||
if (typeof padding !== 'number' || padding < val.length) {
|
if (typeof padding !== 'number' || padding < val.length) {
|
||||||
padding = val.length;
|
padding = val.length;
|
||||||
|
@ -101,11 +102,17 @@ const createChunk = (records: Array<Record>) => {
|
||||||
return content;
|
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.
|
// 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.
|
// 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) => {
|
export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType) => {
|
||||||
prev.topChanged = 0;
|
prev.topChanged = SAME;
|
||||||
prev.bottomChanged = 0;
|
prev.bottomChanged = SAME;
|
||||||
if (!Array.isArray(update.chunk) || update.chunk.length < 1) {
|
if (!Array.isArray(update.chunk) || update.chunk.length < 1) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
@ -123,7 +130,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
return [{content, name: '00000000000000.log'}];
|
return [{content, name: '00000000000000.log'}];
|
||||||
}
|
}
|
||||||
prev.chunks[prev.chunks.length - 1].content += content;
|
prev.chunks[prev.chunks.length - 1].content += content;
|
||||||
prev.bottomChanged = 1;
|
prev.bottomChanged = ADDED;
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
const chunk = {
|
const chunk = {
|
||||||
|
@ -137,10 +144,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
if (prev.chunks.length >= limit) {
|
if (prev.chunks.length >= limit) {
|
||||||
prev.endBottom = false;
|
prev.endBottom = false;
|
||||||
prev.chunks.splice(limit - 1, prev.chunks.length - limit + 1);
|
prev.chunks.splice(limit - 1, prev.chunks.length - limit + 1);
|
||||||
prev.bottomChanged = -1;
|
prev.bottomChanged = REMOVED;
|
||||||
}
|
}
|
||||||
prev.chunks = [chunk, ...prev.chunks];
|
prev.chunks = [chunk, ...prev.chunks];
|
||||||
prev.topChanged = 1;
|
prev.topChanged = ADDED;
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
if (update.source.last) {
|
if (update.source.last) {
|
||||||
|
@ -149,10 +156,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
if (prev.chunks.length >= limit) {
|
if (prev.chunks.length >= limit) {
|
||||||
prev.endTop = false;
|
prev.endTop = false;
|
||||||
prev.chunks.splice(0, prev.chunks.length - limit + 1);
|
prev.chunks.splice(0, prev.chunks.length - limit + 1);
|
||||||
prev.topChanged = -1;
|
prev.topChanged = REMOVED;
|
||||||
}
|
}
|
||||||
prev.chunks = [...prev.chunks, chunk];
|
prev.chunks = [...prev.chunks, chunk];
|
||||||
prev.bottomChanged = 1;
|
prev.bottomChanged = ADDED;
|
||||||
return prev;
|
return prev;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -160,6 +167,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
const styles = {
|
const styles = {
|
||||||
logListItem: {
|
logListItem: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
lineHeight: 1.231,
|
||||||
},
|
},
|
||||||
logChunk: {
|
logChunk: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
|
@ -167,6 +175,11 @@ const styles = {
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
width: 0,
|
width: 0,
|
||||||
},
|
},
|
||||||
|
waitMsg: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'white',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
@ -192,7 +205,17 @@ class Logs extends Component<Props, State> {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {container} = this.props;
|
const {container} = this.props;
|
||||||
|
if (typeof container === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
container.scrollTop = container.scrollHeight - container.clientHeight;
|
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
|
// 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) {
|
if (logs.chunks.length < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.atTop()) {
|
if (this.atTop() && !logs.endTop) {
|
||||||
if (!logs.endTop) {
|
this.sendRequest(logs.chunks[0].name, true);
|
||||||
this.setState({requestAllowed: false});
|
} else if (this.atBottom() && !logs.endBottom) {
|
||||||
this.props.send(JSON.stringify({
|
this.sendRequest(logs.chunks[logs.chunks.length - 1].name, false);
|
||||||
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,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 checks if the scroll position it at the top of the container.
|
||||||
atTop = () => this.props.container.scrollTop <= this.props.container.scrollHeight * requestBand;
|
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.
|
// and the height of the first log chunk, which can be deleted during the insertion.
|
||||||
beforeUpdate = () => {
|
beforeUpdate = () => {
|
||||||
let firstHeight = 0;
|
let firstHeight = 0;
|
||||||
if (this.content && this.content.children[0] && this.content.children[0].children[0]) {
|
let chunkList = this.content.children[1];
|
||||||
firstHeight = this.content.children[0].children[0].clientHeight;
|
if (chunkList && chunkList.children[0]) {
|
||||||
|
firstHeight = chunkList.children[0].clientHeight;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
scrollTop: this.props.container.scrollTop,
|
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
|
// 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
|
// visible part of the container isn't full, and resets the scroll position in order to avoid jumping when a
|
||||||
// chunk is inserted.
|
// chunk is inserted or removed.
|
||||||
didUpdate = (prevProps, prevState, snapshot) => {
|
didUpdate = (prevProps, prevState, snapshot) => {
|
||||||
if (typeof this.props.shouldUpdate.logs === 'undefined' || typeof this.content === 'undefined' || snapshot === null) {
|
if (typeof this.props.shouldUpdate.logs === 'undefined' || typeof this.content === 'undefined' || snapshot === null) {
|
||||||
return;
|
return;
|
||||||
|
@ -264,27 +282,21 @@ class Logs extends Component<Props, State> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.content.clientHeight < container.clientHeight) {
|
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.
|
// and the scroll bar doesn't appear.
|
||||||
if (!logs.endTop) {
|
if (!logs.endTop) {
|
||||||
this.setState({requestAllowed: false});
|
this.sendRequest(logs.chunks[0].name, true);
|
||||||
this.props.send(JSON.stringify({
|
|
||||||
Logs: {
|
|
||||||
Name: logs.chunks[0].name,
|
|
||||||
Past: true,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const chunks = this.content.children[0].children;
|
|
||||||
let {scrollTop} = snapshot;
|
let {scrollTop} = snapshot;
|
||||||
if (logs.topChanged > 0) {
|
if (logs.topChanged === ADDED) {
|
||||||
scrollTop += chunks[0].clientHeight;
|
// It would be safer to use a ref to the list, but ref doesn't work well with HOCs.
|
||||||
} else if (logs.bottomChanged > 0) {
|
scrollTop += this.content.children[1].children[0].clientHeight;
|
||||||
if (logs.topChanged < 0) {
|
} else if (logs.bottomChanged === ADDED) {
|
||||||
|
if (logs.topChanged === REMOVED) {
|
||||||
scrollTop -= snapshot.firstHeight;
|
scrollTop -= snapshot.firstHeight;
|
||||||
} else if (logs.endBottom && this.atBottom()) {
|
} else if (this.atBottom() && logs.endBottom) {
|
||||||
scrollTop = container.scrollHeight - container.clientHeight;
|
scrollTop = container.scrollHeight - container.clientHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,6 +307,9 @@ class Logs extends Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div ref={(ref) => { this.content = ref; }}>
|
<div ref={(ref) => { this.content = ref; }}>
|
||||||
|
<div style={styles.waitMsg}>
|
||||||
|
{this.props.content.logs.endTop ? 'No more logs.' : 'Waiting for server...'}
|
||||||
|
</div>
|
||||||
<List>
|
<List>
|
||||||
{this.props.content.logs.chunks.map((c, index) => (
|
{this.props.content.logs.chunks.map((c, index) => (
|
||||||
<ListItem style={styles.logListItem} key={index}>
|
<ListItem style={styles.logListItem} key={index}>
|
||||||
|
@ -302,6 +317,7 @@ class Logs extends Component<Props, State> {
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
{this.props.content.logs.endBottom || <div style={styles.waitMsg}>Waiting for server...</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"css-loader": "^0.28.9",
|
"css-loader": "^0.28.9",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
"eslint": "^4.16.0",
|
"eslint": "^4.16.0",
|
||||||
"eslint-config-airbnb": "^16.1.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"eslint-loader": "^2.0.0",
|
"eslint-loader": "^2.0.0",
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "NODE_ENV=production webpack",
|
"build": "NODE_ENV=production webpack",
|
||||||
"stats": "webpack --profile --json > stats.json",
|
"stats": "webpack --profile --json > stats.json",
|
||||||
"dev": "webpack-dev-server --port 8081",
|
"dev": "webpack-dev-server --port 8081",
|
||||||
"flow": "flow-typed install"
|
"flow": "flow-typed install"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2248,7 +2248,7 @@ es6-weak-map@^2.0.1:
|
||||||
es6-iterator "^2.0.1"
|
es6-iterator "^2.0.1"
|
||||||
es6-symbol "^3.1.1"
|
es6-symbol "^3.1.1"
|
||||||
|
|
||||||
escape-html@~1.0.3:
|
escape-html@^1.0.3, escape-html@~1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue