Skip to content

Commit 704840a

Browse files
kurkomisikaralabe
authored andcommitted
cmd, dashboard: use webpack dev server, remove custom assets (#16263)
* cmd, dashboard: remove custom assets, webpack dev server * dashboard: yarn commands, small fixes
1 parent 3ec1b9a commit 704840a

20 files changed

+8971
-10095
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ profile.cov
4242
/dashboard/assets/node_modules
4343
/dashboard/assets/stats.json
4444
/dashboard/assets/bundle.js
45+
/dashboard/assets/package-lock.json
46+
47+
**/yarn-error.log

cmd/geth/main.go

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ var (
6565
utils.DashboardAddrFlag,
6666
utils.DashboardPortFlag,
6767
utils.DashboardRefreshFlag,
68-
utils.DashboardAssetsFlag,
6968
utils.EthashCacheDirFlag,
7069
utils.EthashCachesInMemoryFlag,
7170
utils.EthashCachesOnDiskFlag,

cmd/utils/flags.go

-6
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,6 @@ var (
209209
Usage: "Dashboard metrics collection refresh rate",
210210
Value: dashboard.DefaultConfig.Refresh,
211211
}
212-
DashboardAssetsFlag = cli.StringFlag{
213-
Name: "dashboard.assets",
214-
Usage: "Developer flag to serve the dashboard from the local file system",
215-
Value: dashboard.DefaultConfig.Assets,
216-
}
217212
// Ethash settings
218213
EthashCacheDirFlag = DirectoryFlag{
219214
Name: "ethash.cachedir",
@@ -1120,7 +1115,6 @@ func SetDashboardConfig(ctx *cli.Context, cfg *dashboard.Config) {
11201115
cfg.Host = ctx.GlobalString(DashboardAddrFlag.Name)
11211116
cfg.Port = ctx.GlobalInt(DashboardPortFlag.Name)
11221117
cfg.Refresh = ctx.GlobalDuration(DashboardRefreshFlag.Name)
1123-
cfg.Assets = ctx.GlobalString(DashboardAssetsFlag.Name)
11241118
}
11251119

11261120
// RegisterEthService adds an Ethereum client to the stack.

dashboard/README.md

+7-8
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,35 @@ The client's UI uses [React][React] with JSX syntax, which is validated by the [
1212
As the dashboard depends on certain NPM packages (which are not included in the `go-ethereum` repo), these need to be installed first:
1313

1414
```
15-
$ (cd dashboard/assets && npm install)
16-
$ (cd dashboard/assets && ./node_modules/.bin/flow-typed install)
15+
$ (cd dashboard/assets && yarn install && yarn flow)
1716
```
1817

19-
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources:
18+
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `yarn dev` to watch for file system changes and refresh the browser automatically.
2019

2120
```
22-
$ (cd dashboard/assets && ./node_modules/.bin/webpack --watch)
23-
$ geth --dashboard --dashboard.assets=dashboard/assets --vmodule=dashboard=5
21+
$ geth --dashboard --vmodule=dashboard=5
22+
$ (cd dashboard/assets && yarn dev)
2423
```
2524

2625
To bundle up the final UI into Geth, run `go generate`:
2726

2827
```
29-
$ go generate ./dashboard
28+
$ (cd dashboard && go generate)
3029
```
3130

3231
### Static type checking
3332

3433
Since JavaScript doesn't provide type safety, [Flow][Flow] is used to check types. These are only useful during development, so at the end of the process Babel will strip them.
3534

36-
To take advantage of static type checking, your IDE needs to be prepared for it. In case of [Atom][Atom] a configuration guide can be found [here][Atom config]: Install the [Nuclide][Nuclide] package for Flow support, making sure it installs all of its support packages by enabling `Install Recommended Packages on Startup`, and set the path of the `flow-bin` which were installed previously by `npm`.
35+
To take advantage of static type checking, your IDE needs to be prepared for it. In case of [Atom][Atom] a configuration guide can be found [here][Atom config]: Install the [Nuclide][Nuclide] package for Flow support, making sure it installs all of its support packages by enabling `Install Recommended Packages on Startup`, and set the path of the `flow-bin` which were installed previously by `yarn`.
3736

3837
For more IDE support install the `linter-eslint` package too, which finds the `.eslintrc` file, and provides real-time linting. Atom warns, that these two packages are incompatible, but they seem to work well together. For third-party library errors and auto-completion [flow-typed][flow-typed] is used.
3938

4039
### Have fun
4140

4241
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
4342

44-
* Generate the bundle's profile running `webpack --profile --json > stats.json`
43+
* Generate the bundle's profile running `yarn stats`
4544
* For the _dependency tree_ go to [Webpack Analyze][WA], and import `stats.json`
4645
* For the _space usage_ go to [Webpack Visualizer][WV], and import `stats.json`
4746

dashboard/assets.go

+2,238-2,203
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/assets/components/CustomTooltip.jsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ export const percentPlotter = <T>(text: string, mapper: (T => T) = multiplier(1)
3838
};
3939

4040
// unit contains the units for the bytePlotter.
41-
const unit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
41+
const unit = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
4242

4343
// simplifyBytes returns the simplified version of the given value followed by the unit.
4444
const simplifyBytes = (x: number) => {
4545
let i = 0;
46-
for (; x > 1024 && i < 5; i++) {
46+
for (; x > 1024 && i < 8; i++) {
4747
x /= 1024;
4848
}
49-
return x.toFixed(2).toString().concat(' ', unit[i]);
49+
return x.toFixed(2).toString().concat(' ', unit[i], 'B');
5050
};
5151

5252
// bytePlotter renders a tooltip, which displays the payload as a byte value.

dashboard/assets/components/Dashboard.jsx

+14-14
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ const defaultContent: Content = {
8181
version: null,
8282
commit: null,
8383
},
84-
home: {
84+
home: {},
85+
chain: {},
86+
txpool: {},
87+
network: {},
88+
system: {
8589
activeMemory: [],
8690
virtualMemory: [],
8791
networkIngress: [],
@@ -91,10 +95,6 @@ const defaultContent: Content = {
9195
diskRead: [],
9296
diskWrite: [],
9397
},
94-
chain: {},
95-
txpool: {},
96-
network: {},
97-
system: {},
9898
logs: {
9999
log: [],
100100
},
@@ -108,7 +108,11 @@ const updaters = {
108108
version: replacer,
109109
commit: replacer,
110110
},
111-
home: {
111+
home: null,
112+
chain: null,
113+
txpool: null,
114+
network: null,
115+
system: {
112116
activeMemory: appender(200),
113117
virtualMemory: appender(200),
114118
networkIngress: appender(200),
@@ -118,11 +122,7 @@ const updaters = {
118122
diskRead: appender(200),
119123
diskWrite: appender(200),
120124
},
121-
chain: null,
122-
txpool: null,
123-
network: null,
124-
system: null,
125-
logs: {
125+
logs: {
126126
log: appender(200),
127127
},
128128
};
@@ -136,7 +136,7 @@ const styles = {
136136
height: '100%',
137137
zIndex: 1,
138138
overflow: 'hidden',
139-
}
139+
},
140140
};
141141

142142
// themeStyles returns the styles generated from the theme for the component.
@@ -178,7 +178,8 @@ class Dashboard extends Component<Props, State> {
178178
// reconnect establishes a websocket connection with the server, listens for incoming messages
179179
// and tries to reconnect on connection loss.
180180
reconnect = () => {
181-
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://') + window.location.host}/api`);
181+
// PROD is defined by webpack.
182+
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://')}${PROD ? window.location.host : 'localhost:8080'}/api`);
182183
server.onopen = () => {
183184
this.setState({content: defaultContent, shouldUpdate: {}});
184185
};
@@ -217,7 +218,6 @@ class Dashboard extends Component<Props, State> {
217218
return (
218219
<div className={this.props.classes.dashboard} style={styles.dashboard}>
219220
<Header
220-
opened={this.state.sideBar}
221221
switchSideBar={this.switchSideBar}
222222
/>
223223
<Body

dashboard/assets/components/Footer.jsx

+82-76
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ import {ResponsiveContainer, AreaChart, Area, Tooltip} from 'recharts';
2626
import ChartRow from './ChartRow';
2727
import CustomTooltip, {bytePlotter, bytePerSecPlotter, percentPlotter, multiplier} from './CustomTooltip';
2828
import {styles as commonStyles} from '../common';
29-
import type {Content} from '../types/content';
29+
import type {General, System} from '../types/content';
30+
31+
const FOOTER_SYNC_ID = 'footerSyncId';
32+
33+
const CPU = 'cpu';
34+
const MEMORY = 'memory';
35+
const DISK = 'disk';
36+
const TRAFFIC = 'traffic';
37+
38+
const TOP = 'Top';
39+
const BOTTOM = 'Bottom';
3040

3141
// styles contains the constant styles of the component.
3242
const styles = {
@@ -40,17 +50,16 @@ const styles = {
4050
padding: 0,
4151
},
4252
doubleChartWrapper: {
43-
height: '100%',
44-
width: '99%',
45-
paddingTop: 5,
53+
height: '100%',
54+
width: '99%',
4655
},
4756
};
4857

4958
// themeStyles returns the styles generated from the theme for the component.
5059
const themeStyles: Object = (theme: Object) => ({
5160
footer: {
52-
backgroundColor: theme.palette.background.appBar,
53-
color: theme.palette.getContrastText(theme.palette.background.appBar),
61+
backgroundColor: theme.palette.grey[900],
62+
color: theme.palette.getContrastText(theme.palette.grey[900]),
5463
zIndex: theme.zIndex.appBar,
5564
height: theme.spacing.unit * 10,
5665
},
@@ -59,111 +68,108 @@ const themeStyles: Object = (theme: Object) => ({
5968
export type Props = {
6069
classes: Object, // injected by withStyles()
6170
theme: Object,
62-
content: Content,
71+
general: General,
72+
system: System,
6373
shouldUpdate: Object,
6474
};
6575

6676
// Footer renders the footer of the dashboard.
6777
class Footer extends Component<Props> {
6878
shouldComponentUpdate(nextProps) {
69-
return typeof nextProps.shouldUpdate.home !== 'undefined';
79+
return typeof nextProps.shouldUpdate.general !== 'undefined' || typeof nextProps.shouldUpdate.system !== 'undefined';
7080
}
7181

72-
// info renders a label with the given values.
73-
info = (about: string, value: ?string) => (value ? (
74-
<Typography type='caption' color='inherit'>
75-
<span style={commonStyles.light}>{about}</span> {value}
76-
</Typography>
77-
) : null);
82+
// halfHeightChart renders an area chart with half of the height of its parent.
83+
halfHeightChart = (chartProps, tooltip, areaProps) => (
84+
<ResponsiveContainer width='100%' height='50%'>
85+
<AreaChart {...chartProps} >
86+
{!tooltip || (<Tooltip cursor={false} content={<CustomTooltip tooltip={tooltip} />} />)}
87+
<Area isAnimationActive={false} type='monotone' {...areaProps} />
88+
</AreaChart>
89+
</ResponsiveContainer>
90+
);
7891

7992
// doubleChart renders a pair of charts separated by the baseline.
80-
doubleChart = (syncId, topChart, bottomChart) => {
81-
const topKey = 'topKey';
82-
const bottomKey = 'bottomKey';
83-
const topDefault = topChart.default ? topChart.default : 0;
84-
const bottomDefault = bottomChart.default ? bottomChart.default : 0;
85-
const topTooltip = topChart.tooltip ? (
86-
<Tooltip cursor={false} content={<CustomTooltip tooltip={topChart.tooltip} />} />
87-
) : null;
88-
const bottomTooltip = bottomChart.tooltip ? (
89-
<Tooltip cursor={false} content={<CustomTooltip tooltip={bottomChart.tooltip} />} />
90-
) : null;
93+
doubleChart = (syncId, chartKey, topChart, bottomChart) => {
94+
if (!Array.isArray(topChart.data) || !Array.isArray(bottomChart.data)) {
95+
return null;
96+
}
97+
const topDefault = topChart.default || 0;
98+
const bottomDefault = bottomChart.default || 0;
99+
const topKey = `${chartKey}${TOP}`;
100+
const bottomKey = `${chartKey}${BOTTOM}`;
91101
const topColor = '#8884d8';
92102
const bottomColor = '#82ca9d';
93103

94-
// Put the samples of the two charts into the same array in order to avoid problems
95-
// at the synchronized area charts. If one of the two arrays doesn't have value at
96-
// a given position, give it a 0 default value.
97-
let data = [...topChart.data.map(({value}) => {
98-
const d = {};
99-
d[topKey] = value || topDefault;
100-
return d;
101-
})];
102-
for (let i = 0; i < data.length && i < bottomChart.data.length; i++) {
103-
// The value needs to be negative in order to plot it upside down.
104-
const d = bottomChart.data[i];
105-
data[i][bottomKey] = d && d.value ? -d.value : bottomDefault;
106-
}
107-
data = [...data, ...bottomChart.data.slice(data.length).map(({value}) => {
108-
const d = {};
109-
d[topKey] = topDefault;
110-
d[bottomKey] = -value || bottomDefault;
111-
return d;
112-
})];
113-
114104
return (
115105
<div style={styles.doubleChartWrapper}>
116-
<ResponsiveContainer width='100%' height='50%'>
117-
<AreaChart data={data} syncId={syncId} >
118-
{topTooltip}
119-
<Area type='monotone' dataKey={topKey} stroke={topColor} fill={topColor} />
120-
</AreaChart>
121-
</ResponsiveContainer>
122-
<div style={{marginTop: -10, width: '100%', height: '50%'}}>
123-
<ResponsiveContainer width='100%' height='100%'>
124-
<AreaChart data={data} syncId={syncId} >
125-
{bottomTooltip}
126-
<Area type='monotone' dataKey={bottomKey} stroke={bottomColor} fill={bottomColor} />
127-
</AreaChart>
128-
</ResponsiveContainer>
129-
</div>
106+
{this.halfHeightChart(
107+
{
108+
syncId,
109+
data: topChart.data.map(({value}) => ({[topKey]: value || topDefault})),
110+
margin: {top: 5, right: 5, bottom: 0, left: 5},
111+
},
112+
topChart.tooltip,
113+
{dataKey: topKey, stroke: topColor, fill: topColor},
114+
)}
115+
{this.halfHeightChart(
116+
{
117+
syncId,
118+
data: bottomChart.data.map(({value}) => ({[bottomKey]: -value || -bottomDefault})),
119+
margin: {top: 0, right: 5, bottom: 5, left: 5},
120+
},
121+
bottomChart.tooltip,
122+
{dataKey: bottomKey, stroke: bottomColor, fill: bottomColor},
123+
)}
130124
</div>
131125
);
132-
}
126+
};
133127

134128
render() {
135-
const {content} = this.props;
136-
const {general, home} = content;
129+
const {general, system} = this.props;
137130

138131
return (
139132
<Grid container className={this.props.classes.footer} direction='row' alignItems='center' style={styles.footer}>
140133
<Grid item xs style={styles.chartRowWrapper}>
141134
<ChartRow>
142135
{this.doubleChart(
143-
'all',
144-
{data: home.processCPU, tooltip: percentPlotter('Process')},
145-
{data: home.systemCPU, tooltip: percentPlotter('System', multiplier(-1))},
136+
FOOTER_SYNC_ID,
137+
CPU,
138+
{data: system.processCPU, tooltip: percentPlotter('Process load')},
139+
{data: system.systemCPU, tooltip: percentPlotter('System load', multiplier(-1))},
146140
)}
147141
{this.doubleChart(
148-
'all',
149-
{data: home.activeMemory, tooltip: bytePlotter('Active')},
150-
{data: home.virtualMemory, tooltip: bytePlotter('Virtual', multiplier(-1))},
142+
FOOTER_SYNC_ID,
143+
MEMORY,
144+
{data: system.activeMemory, tooltip: bytePlotter('Active memory')},
145+
{data: system.virtualMemory, tooltip: bytePlotter('Virtual memory', multiplier(-1))},
151146
)}
152147
{this.doubleChart(
153-
'all',
154-
{data: home.diskRead, tooltip: bytePerSecPlotter('Disk Read')},
155-
{data: home.diskWrite, tooltip: bytePerSecPlotter('Disk Write', multiplier(-1))},
148+
FOOTER_SYNC_ID,
149+
DISK,
150+
{data: system.diskRead, tooltip: bytePerSecPlotter('Disk read')},
151+
{data: system.diskWrite, tooltip: bytePerSecPlotter('Disk write', multiplier(-1))},
156152
)}
157153
{this.doubleChart(
158-
'all',
159-
{data: home.networkIngress, tooltip: bytePerSecPlotter('Download')},
160-
{data: home.networkEgress, tooltip: bytePerSecPlotter('Upload', multiplier(-1))},
154+
FOOTER_SYNC_ID,
155+
TRAFFIC,
156+
{data: system.networkIngress, tooltip: bytePerSecPlotter('Download')},
157+
{data: system.networkEgress, tooltip: bytePerSecPlotter('Upload', multiplier(-1))},
161158
)}
162159
</ChartRow>
163160
</Grid>
164161
<Grid item >
165-
{this.info('Geth', general.version)}
166-
{this.info('Commit', general.commit ? general.commit.substring(0, 7) : null)}
162+
<Typography type='caption' color='inherit'>
163+
<span style={commonStyles.light}>Geth</span> {general.version}
164+
</Typography>
165+
{general.commit && (
166+
<Typography type='caption' color='inherit'>
167+
<span style={commonStyles.light}>{'Commit '}</span>
168+
<a href={`https://github.com/ethereum/go-ethereum/commit/${general.commit}`} target='_blank' style={{color: 'inherit', textDecoration: 'none'}} >
169+
{general.commit.substring(0, 8)}
170+
</a>
171+
</Typography>
172+
)}
167173
</Grid>
168174
</Grid>
169175
);

0 commit comments

Comments
 (0)