Skip to content

Commit eb7f901

Browse files
kurkomisikaralabe
authored andcommitted
dashboard: fix CSS, escape special HTML chars, clean up code (ethereum#17167)
* dashboard: fix CSS, escape special HTML chars, clean up code * dashboard: change 0 to 1 * dashboard: add escape-html npm package
1 parent db5e403 commit eb7f901

File tree

6 files changed

+427
-350
lines changed

6 files changed

+427
-350
lines changed

dashboard/assets.go

+347-297
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/assets/components/Body.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const styles = {
2727
body: {
2828
display: 'flex',
2929
width: '100%',
30-
height: '100%',
30+
height: '92%',
3131
},
3232
};
3333

dashboard/assets/components/Header.jsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ import Icon from 'material-ui/Icon';
2626
import MenuIcon from 'material-ui-icons/Menu';
2727
import Typography from 'material-ui/Typography';
2828

29+
// styles contains the constant styles of the component.
30+
const styles = {
31+
header: {
32+
height: '8%',
33+
},
34+
toolbar: {
35+
height: '100%',
36+
},
37+
};
38+
2939
// themeStyles returns the styles generated from the theme for the component.
3040
const themeStyles = (theme: Object) => ({
3141
header: {
@@ -54,8 +64,8 @@ class Header extends Component<Props> {
5464
const {classes} = this.props;
5565

5666
return (
57-
<AppBar position='static' className={classes.header}>
58-
<Toolbar className={classes.toolbar}>
67+
<AppBar position='static' className={classes.header} style={styles.header}>
68+
<Toolbar className={classes.toolbar} style={styles.toolbar}>
5969
<IconButton onClick={this.props.switchSideBar}>
6070
<Icon>
6171
<MenuIcon />

dashboard/assets/components/Logs.jsx

+63-47
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import React, {Component} from 'react';
2020

2121
import List, {ListItem} from 'material-ui/List';
22+
import escapeHtml from 'escape-html';
2223
import type {Record, Content, LogsMessage, Logs as LogsType} from '../types/content';
2324

2425
// 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>) => {
8384
content += `<span style="color:${color}">${lvl}</span>[${month}-${date}|${hours}:${minutes}:${seconds}] ${msg}`;
8485

8586
for (let i = 0; i < ctx.length; i += 2) {
86-
const key = ctx[i];
87-
const val = ctx[i + 1];
87+
const key = escapeHtml(ctx[i]);
88+
const val = escapeHtml(ctx[i + 1]);
8889
let padding = fieldPadding.get(key);
8990
if (typeof padding !== 'number' || padding < val.length) {
9091
padding = val.length;
@@ -101,11 +102,17 @@ const createChunk = (records: Array<Record>) => {
101102
return content;
102103
};
103104

105+
// ADDED, SAME and REMOVED are used to track the change of the log chunk array.
106+
// The scroll position is set using these values.
107+
const ADDED = 1;
108+
const SAME = 0;
109+
const REMOVED = -1;
110+
104111
// inserter is a state updater function for the main component, which inserts the new log chunk into the chunk array.
105112
// limit is the maximum length of the chunk array, used in order to prevent the browser from OOM.
106113
export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType) => {
107-
prev.topChanged = 0;
108-
prev.bottomChanged = 0;
114+
prev.topChanged = SAME;
115+
prev.bottomChanged = SAME;
109116
if (!Array.isArray(update.chunk) || update.chunk.length < 1) {
110117
return prev;
111118
}
@@ -123,7 +130,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
123130
return [{content, name: '00000000000000.log'}];
124131
}
125132
prev.chunks[prev.chunks.length - 1].content += content;
126-
prev.bottomChanged = 1;
133+
prev.bottomChanged = ADDED;
127134
return prev;
128135
}
129136
const chunk = {
@@ -137,10 +144,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
137144
if (prev.chunks.length >= limit) {
138145
prev.endBottom = false;
139146
prev.chunks.splice(limit - 1, prev.chunks.length - limit + 1);
140-
prev.bottomChanged = -1;
147+
prev.bottomChanged = REMOVED;
141148
}
142149
prev.chunks = [chunk, ...prev.chunks];
143-
prev.topChanged = 1;
150+
prev.topChanged = ADDED;
144151
return prev;
145152
}
146153
if (update.source.last) {
@@ -149,24 +156,30 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
149156
if (prev.chunks.length >= limit) {
150157
prev.endTop = false;
151158
prev.chunks.splice(0, prev.chunks.length - limit + 1);
152-
prev.topChanged = -1;
159+
prev.topChanged = REMOVED;
153160
}
154161
prev.chunks = [...prev.chunks, chunk];
155-
prev.bottomChanged = 1;
162+
prev.bottomChanged = ADDED;
156163
return prev;
157164
};
158165

159166
// styles contains the constant styles of the component.
160167
const styles = {
161168
logListItem: {
162169
padding: 0,
170+
lineHeight: 1.231,
163171
},
164172
logChunk: {
165173
color: 'white',
166174
fontFamily: 'monospace',
167175
whiteSpace: 'nowrap',
168176
width: 0,
169177
},
178+
waitMsg: {
179+
textAlign: 'center',
180+
color: 'white',
181+
fontFamily: 'monospace',
182+
},
170183
};
171184

172185
export type Props = {
@@ -192,7 +205,17 @@ class Logs extends Component<Props, State> {
192205

193206
componentDidMount() {
194207
const {container} = this.props;
208+
if (typeof container === 'undefined') {
209+
return;
210+
}
195211
container.scrollTop = container.scrollHeight - container.clientHeight;
212+
const {logs} = this.props.content;
213+
if (typeof this.content === 'undefined' || logs.chunks.length < 1) {
214+
return;
215+
}
216+
if (this.content.clientHeight < container.clientHeight && !logs.endTop) {
217+
this.sendRequest(logs.chunks[0].name, true);
218+
}
196219
}
197220

198221
// 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> {
205228
if (logs.chunks.length < 1) {
206229
return;
207230
}
208-
if (this.atTop()) {
209-
if (!logs.endTop) {
210-
this.setState({requestAllowed: false});
211-
this.props.send(JSON.stringify({
212-
Logs: {
213-
Name: logs.chunks[0].name,
214-
Past: true,
215-
},
216-
}));
217-
}
218-
} else if (this.atBottom()) {
219-
if (!logs.endBottom) {
220-
this.setState({requestAllowed: false});
221-
this.props.send(JSON.stringify({
222-
Logs: {
223-
Name: logs.chunks[logs.chunks.length - 1].name,
224-
Past: false,
225-
},
226-
}));
227-
}
231+
if (this.atTop() && !logs.endTop) {
232+
this.sendRequest(logs.chunks[0].name, true);
233+
} else if (this.atBottom() && !logs.endBottom) {
234+
this.sendRequest(logs.chunks[logs.chunks.length - 1].name, false);
228235
}
229236
};
230237

238+
sendRequest = (name: string, past: boolean) => {
239+
this.setState({requestAllowed: false});
240+
this.props.send(JSON.stringify({
241+
Logs: {
242+
Name: name,
243+
Past: past,
244+
},
245+
}));
246+
};
247+
231248
// atTop checks if the scroll position it at the top of the container.
232249
atTop = () => this.props.container.scrollTop <= this.props.container.scrollHeight * requestBand;
233250

@@ -242,8 +259,9 @@ class Logs extends Component<Props, State> {
242259
// and the height of the first log chunk, which can be deleted during the insertion.
243260
beforeUpdate = () => {
244261
let firstHeight = 0;
245-
if (this.content && this.content.children[0] && this.content.children[0].children[0]) {
246-
firstHeight = this.content.children[0].children[0].clientHeight;
262+
let chunkList = this.content.children[1];
263+
if (chunkList && chunkList.children[0]) {
264+
firstHeight = chunkList.children[0].clientHeight;
247265
}
248266
return {
249267
scrollTop: this.props.container.scrollTop,
@@ -252,8 +270,8 @@ class Logs extends Component<Props, State> {
252270
};
253271

254272
// didUpdate is called by the parent component, which provides the container. Sends the first request if the
255-
// visible part of the container isn't full, and resets the scroll position in order to avoid jumping when new
256-
// chunk is inserted.
273+
// visible part of the container isn't full, and resets the scroll position in order to avoid jumping when a
274+
// chunk is inserted or removed.
257275
didUpdate = (prevProps, prevState, snapshot) => {
258276
if (typeof this.props.shouldUpdate.logs === 'undefined' || typeof this.content === 'undefined' || snapshot === null) {
259277
return;
@@ -264,27 +282,21 @@ class Logs extends Component<Props, State> {
264282
return;
265283
}
266284
if (this.content.clientHeight < container.clientHeight) {
267-
// Only enters here at the beginning, when there isn't enough log to fill the container
285+
// Only enters here at the beginning, when there aren't enough logs to fill the container
268286
// and the scroll bar doesn't appear.
269287
if (!logs.endTop) {
270-
this.setState({requestAllowed: false});
271-
this.props.send(JSON.stringify({
272-
Logs: {
273-
Name: logs.chunks[0].name,
274-
Past: true,
275-
},
276-
}));
288+
this.sendRequest(logs.chunks[0].name, true);
277289
}
278290
return;
279291
}
280-
const chunks = this.content.children[0].children;
281292
let {scrollTop} = snapshot;
282-
if (logs.topChanged > 0) {
283-
scrollTop += chunks[0].clientHeight;
284-
} else if (logs.bottomChanged > 0) {
285-
if (logs.topChanged < 0) {
293+
if (logs.topChanged === ADDED) {
294+
// It would be safer to use a ref to the list, but ref doesn't work well with HOCs.
295+
scrollTop += this.content.children[1].children[0].clientHeight;
296+
} else if (logs.bottomChanged === ADDED) {
297+
if (logs.topChanged === REMOVED) {
286298
scrollTop -= snapshot.firstHeight;
287-
} else if (logs.endBottom && this.atBottom()) {
299+
} else if (this.atBottom() && logs.endBottom) {
288300
scrollTop = container.scrollHeight - container.clientHeight;
289301
}
290302
}
@@ -295,13 +307,17 @@ class Logs extends Component<Props, State> {
295307
render() {
296308
return (
297309
<div ref={(ref) => { this.content = ref; }}>
310+
<div style={styles.waitMsg}>
311+
{this.props.content.logs.endTop ? 'No more logs.' : 'Waiting for server...'}
312+
</div>
298313
<List>
299314
{this.props.content.logs.chunks.map((c, index) => (
300315
<ListItem style={styles.logListItem} key={index}>
301316
<div style={styles.logChunk} dangerouslySetInnerHTML={{__html: c.content}} />
302317
</ListItem>
303318
))}
304319
</List>
320+
{this.props.content.logs.endBottom || <div style={styles.waitMsg}>Waiting for server...</div>}
305321
</div>
306322
);
307323
}

dashboard/assets/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"babel-runtime": "^6.26.0",
1414
"classnames": "^2.2.5",
1515
"css-loader": "^0.28.9",
16+
"escape-html": "^1.0.3",
1617
"eslint": "^4.16.0",
1718
"eslint-config-airbnb": "^16.1.0",
1819
"eslint-loader": "^2.0.0",
@@ -41,7 +42,7 @@
4142
"scripts": {
4243
"build": "NODE_ENV=production webpack",
4344
"stats": "webpack --profile --json > stats.json",
44-
"dev": "webpack-dev-server --port 8081",
45-
"flow": "flow-typed install"
45+
"dev": "webpack-dev-server --port 8081",
46+
"flow": "flow-typed install"
4647
}
4748
}

dashboard/assets/yarn.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -2248,7 +2248,7 @@ es6-weak-map@^2.0.1:
22482248
es6-iterator "^2.0.1"
22492249
es6-symbol "^3.1.1"
22502250

2251-
escape-html@~1.0.3:
2251+
escape-html@^1.0.3, escape-html@~1.0.3:
22522252
version "1.0.3"
22532253
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
22542254

0 commit comments

Comments
 (0)