dashboard: yarn commands, small fixes

This commit is contained in:
Kurkó Mihály 2018-03-07 18:45:52 +02:00
parent dbf5d53d58
commit 45c916790f
12 changed files with 802 additions and 810 deletions

3
.gitignore vendored
View file

@ -42,5 +42,6 @@ profile.cov
/dashboard/assets/node_modules /dashboard/assets/node_modules
/dashboard/assets/stats.json /dashboard/assets/stats.json
/dashboard/assets/bundle.js /dashboard/assets/bundle.js
/dashboard/assets/yarn-error.log
/dashboard/assets/package-lock.json /dashboard/assets/package-lock.json
**/yarn-error.log

View file

@ -12,23 +12,20 @@ The client's UI uses [React][React] with JSX syntax, which is validated by the [
As the dashboard depends on certain NPM packages (which are not included in the `go-ethereum` repo), these need to be installed first: As the dashboard depends on certain NPM packages (which are not included in the `go-ethereum` repo), these need to be installed first:
``` ```
$ (cd dashboard/assets && yarn install) $ (cd dashboard/assets && yarn install && yarn flow)
$ (cd dashboard/assets && ./node_modules/.bin/flow-typed install)
``` ```
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-dev-server` to run a `geth` independent server which automatically rebundles the UI, and uses external assets to make connection with `geth`, which this way does not rely on compiled resources: 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.
``` ```
$ geth --dashboard --vmodule=dashboard=5 $ geth --dashboard --vmodule=dashboard=5
$ (cd dashboard/assets && ./node_modules/.bin/webpack-dev-server) $ (cd dashboard/assets && yarn dev)
``` ```
The configuration of `webpack-dev-server` is in `webpack.config.js`.
To bundle up the final UI into Geth, run `go generate`: To bundle up the final UI into Geth, run `go generate`:
``` ```
$ go generate ./dashboard $ (cd dashboard && go generate)
``` ```
### Static type checking ### Static type checking
@ -43,7 +40,7 @@ For more IDE support install the `linter-eslint` package too, which finds the `.
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage. [Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
* Generate the bundle's profile running `webpack --profile --json > stats.json` * Generate the bundle's profile running `yarn stats`
* For the _dependency tree_ go to [Webpack Analyze][WA], and import `stats.json` * For the _dependency tree_ go to [Webpack Analyze][WA], and import `stats.json`
* For the _space usage_ go to [Webpack Visualizer][WV], and import `stats.json` * For the _space usage_ go to [Webpack Visualizer][WV], and import `stats.json`

File diff suppressed because one or more lines are too long

View file

@ -25,11 +25,6 @@ import Body from './Body';
import {MENU} from '../common'; import {MENU} from '../common';
import type {Content} from '../types/content'; import type {Content} from '../types/content';
// gethHost is the host of geth.
const gethHost = 'localhost';
// gethPort is the port of geth.
const gethPort = 8080;
// deepUpdate updates an object corresponding to the given update data, which has // deepUpdate updates an object corresponding to the given update data, which has
// the shape of the same structure as the original object. updater also has the same // the shape of the same structure as the original object. updater also has the same
// structure, except that it contains functions where the original data needs to be // structure, except that it contains functions where the original data needs to be
@ -82,15 +77,15 @@ const appender = <T>(limit: number, mapper = replacer) => (update: Array<T>, pre
// defaultContent is the initial value of the state content. // defaultContent is the initial value of the state content.
const defaultContent: Content = { const defaultContent: Content = {
general: {
version: null,
commit: null,
},
home: {}, home: {},
chain: {}, chain: {},
txpool: {}, txpool: {},
network: {}, network: {},
system: {}, system: {
logs: {
log: [],
},
footer: {
activeMemory: [], activeMemory: [],
virtualMemory: [], virtualMemory: [],
networkIngress: [], networkIngress: [],
@ -99,9 +94,9 @@ const defaultContent: Content = {
systemCPU: [], systemCPU: [],
diskRead: [], diskRead: [],
diskWrite: [], diskWrite: [],
},
version: null, logs: {
commit: null, log: [],
}, },
}; };
@ -109,15 +104,15 @@ const defaultContent: Content = {
// //
// TODO (kurkomisi): Define a tricky type which embraces the content and the updaters. // TODO (kurkomisi): Define a tricky type which embraces the content and the updaters.
const updaters = { const updaters = {
general: {
version: replacer,
commit: replacer,
},
home: null, home: null,
chain: null, chain: null,
txpool: null, txpool: null,
network: null, network: null,
system: null, system: {
logs: {
log: appender(200),
},
footer: {
activeMemory: appender(200), activeMemory: appender(200),
virtualMemory: appender(200), virtualMemory: appender(200),
networkIngress: appender(200), networkIngress: appender(200),
@ -126,9 +121,9 @@ const updaters = {
systemCPU: appender(200), systemCPU: appender(200),
diskRead: appender(200), diskRead: appender(200),
diskWrite: appender(200), diskWrite: appender(200),
},
version: replacer, logs: {
commit: replacer, log: appender(200),
}, },
}; };
@ -141,7 +136,7 @@ const styles = {
height: '100%', height: '100%',
zIndex: 1, zIndex: 1,
overflow: 'hidden', overflow: 'hidden',
} },
}; };
// themeStyles returns the styles generated from the theme for the component. // themeStyles returns the styles generated from the theme for the component.
@ -183,7 +178,8 @@ class Dashboard extends Component<Props, State> {
// reconnect establishes a websocket connection with the server, listens for incoming messages // reconnect establishes a websocket connection with the server, listens for incoming messages
// and tries to reconnect on connection loss. // and tries to reconnect on connection loss.
reconnect = () => { reconnect = () => {
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://')}${gethHost}:${gethPort}/api`); // PROD is defined by webpack.
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://')}${PROD ? window.location.host : 'localhost:8080'}/api`);
server.onopen = () => { server.onopen = () => {
this.setState({content: defaultContent, shouldUpdate: {}}); this.setState({content: defaultContent, shouldUpdate: {}});
}; };

View file

@ -26,13 +26,13 @@ import {ResponsiveContainer, AreaChart, Area, Tooltip} from 'recharts';
import ChartRow from './ChartRow'; import ChartRow from './ChartRow';
import CustomTooltip, {bytePlotter, bytePerSecPlotter, percentPlotter, multiplier} from './CustomTooltip'; import CustomTooltip, {bytePlotter, bytePerSecPlotter, percentPlotter, multiplier} from './CustomTooltip';
import {styles as commonStyles} from '../common'; import {styles as commonStyles} from '../common';
import type {Footer as FooterType} from '../types/content'; import type {General, System} from '../types/content';
const FOOTER_SYNC_ID = 'footerSyncId'; const FOOTER_SYNC_ID = 'footerSyncId';
const CPU = 'cpu'; const CPU = 'cpu';
const MEMORY = 'memory'; const MEMORY = 'memory';
const DISK = 'disk'; const DISK = 'disk';
const TRAFFIC = 'traffic'; const TRAFFIC = 'traffic';
const TOP = 'Top'; const TOP = 'Top';
@ -68,14 +68,15 @@ const themeStyles: Object = (theme: Object) => ({
export type Props = { export type Props = {
classes: Object, // injected by withStyles() classes: Object, // injected by withStyles()
theme: Object, theme: Object,
content: FooterType, general: General,
system: System,
shouldUpdate: Object, shouldUpdate: Object,
}; };
// Footer renders the footer of the dashboard. // Footer renders the footer of the dashboard.
class Footer extends Component<Props> { class Footer extends Component<Props> {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
return typeof nextProps.shouldUpdate.footer !== 'undefined'; return typeof nextProps.shouldUpdate.general !== 'undefined' || typeof nextProps.shouldUpdate.system !== 'undefined';
} }
// halfHeightChart renders an area chart with half of the height of its parent. // halfHeightChart renders an area chart with half of the height of its parent.
@ -125,7 +126,7 @@ class Footer extends Component<Props> {
}; };
render() { render() {
const {content} = this.props; const {general, system} = this.props;
return ( return (
<Grid container className={this.props.classes.footer} direction='row' alignItems='center' style={styles.footer}> <Grid container className={this.props.classes.footer} direction='row' alignItems='center' style={styles.footer}>
@ -134,38 +135,38 @@ class Footer extends Component<Props> {
{this.doubleChart( {this.doubleChart(
FOOTER_SYNC_ID, FOOTER_SYNC_ID,
CPU, CPU,
{data: content.processCPU, tooltip: percentPlotter('Process load')}, {data: system.processCPU, tooltip: percentPlotter('Process load')},
{data: content.systemCPU, tooltip: percentPlotter('System load', multiplier(-1))}, {data: system.systemCPU, tooltip: percentPlotter('System load', multiplier(-1))},
)} )}
{this.doubleChart( {this.doubleChart(
FOOTER_SYNC_ID, FOOTER_SYNC_ID,
MEMORY, MEMORY,
{data: content.activeMemory, tooltip: bytePlotter('Active memory')}, {data: system.activeMemory, tooltip: bytePlotter('Active memory')},
{data: content.virtualMemory, tooltip: bytePlotter('Virtual memory', multiplier(-1))}, {data: system.virtualMemory, tooltip: bytePlotter('Virtual memory', multiplier(-1))},
)} )}
{this.doubleChart( {this.doubleChart(
FOOTER_SYNC_ID, FOOTER_SYNC_ID,
DISK, DISK,
{data: content.diskRead, tooltip: bytePerSecPlotter('Disk read')}, {data: system.diskRead, tooltip: bytePerSecPlotter('Disk read')},
{data: content.diskWrite, tooltip: bytePerSecPlotter('Disk write', multiplier(-1))}, {data: system.diskWrite, tooltip: bytePerSecPlotter('Disk write', multiplier(-1))},
)} )}
{this.doubleChart( {this.doubleChart(
FOOTER_SYNC_ID, FOOTER_SYNC_ID,
TRAFFIC, TRAFFIC,
{data: content.networkIngress, tooltip: bytePerSecPlotter('Download')}, {data: system.networkIngress, tooltip: bytePerSecPlotter('Download')},
{data: content.networkEgress, tooltip: bytePerSecPlotter('Upload', multiplier(-1))}, {data: system.networkEgress, tooltip: bytePerSecPlotter('Upload', multiplier(-1))},
)} )}
</ChartRow> </ChartRow>
</Grid> </Grid>
<Grid item > <Grid item >
<Typography type='caption' color='inherit'> <Typography type='caption' color='inherit'>
<span style={commonStyles.light}>Geth</span> {content.version} <span style={commonStyles.light}>Geth</span> {general.version}
</Typography> </Typography>
{content.commit && ( {general.commit && (
<Typography type='caption' color='inherit'> <Typography type='caption' color='inherit'>
<span style={commonStyles.light}>{'Commit '}</span> <span style={commonStyles.light}>{'Commit '}</span>
<a href={`https://github.com/ethereum/go-ethereum/commit/${content.commit}`} target='_blank' style={{color: 'inherit', textDecoration: 'none'}} > <a href={`https://github.com/ethereum/go-ethereum/commit/${general.commit}`} target='_blank' style={{color: 'inherit', textDecoration: 'none'}} >
{content.commit.substring(0, 8)} {general.commit.substring(0, 8)}
</a> </a>
</Typography> </Typography>
)} )}

View file

@ -76,7 +76,8 @@ class Main extends Component<Props> {
<div style={styles.wrapper}> <div style={styles.wrapper}>
<div className={classes.content} style={styles.content}>{children}</div> <div className={classes.content} style={styles.content}>{children}</div>
<Footer <Footer
content={content.footer} general={content.general}
system={content.system}
shouldUpdate={shouldUpdate} shouldUpdate={shouldUpdate}
/> />
</div> </div>

View file

@ -37,5 +37,11 @@
"url-loader": "^0.6.2", "url-loader": "^0.6.2",
"webpack": "^3.10.0", "webpack": "^3.10.0",
"webpack-dev-server": "^2.11.1" "webpack-dev-server": "^2.11.1"
},
"scripts": {
"build": "NODE_ENV=production webpack",
"stats": "webpack --profile --json > stats.json",
"dev": "webpack-dev-server --port 8081",
"flow": "flow-typed install"
} }
} }

View file

@ -17,13 +17,13 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
export type Content = { export type Content = {
general: General,
home: Home, home: Home,
chain: Chain, chain: Chain,
txpool: TxPool, txpool: TxPool,
network: Network, network: Network,
system: System, system: System,
logs: Logs, logs: Logs,
footer: Footer,
}; };
export type ChartEntries = Array<ChartEntry>; export type ChartEntries = Array<ChartEntry>;
@ -33,6 +33,11 @@ export type ChartEntry = {
value: number, value: number,
}; };
export type General = {
version: ?string,
commit: ?string,
};
export type Home = { export type Home = {
/* TODO (kurkomisi) */ /* TODO (kurkomisi) */
}; };
@ -50,23 +55,16 @@ export type Network = {
}; };
export type System = { export type System = {
/* TODO (kurkomisi) */ activeMemory: ChartEntries,
virtualMemory: ChartEntries,
networkIngress: ChartEntries,
networkEgress: ChartEntries,
processCPU: ChartEntries,
systemCPU: ChartEntries,
diskRead: ChartEntries,
diskWrite: ChartEntries,
}; };
export type Logs = { export type Logs = {
log: Array<string>, log: Array<string>,
}; };
export type Footer = {
activeMemory: ChartEntries,
virtualMemory: ChartEntries,
networkIngress: ChartEntries,
networkEgress: ChartEntries,
processCPU: ChartEntries,
systemCPU: ChartEntries,
diskRead: ChartEntries,
diskWrite: ChartEntries,
version: ?string,
commit: ?string,
};

View file

@ -32,6 +32,9 @@ module.exports = {
mangle: false, mangle: false,
beautify: true, beautify: true,
}), }),
new webpack.DefinePlugin({
PROD: process.env.NODE_ENV === 'production',
}),
], ],
module: { module: {
rules: [ rules: [
@ -71,7 +74,4 @@ module.exports = {
}, },
], ],
}, },
devServer: {
port: 8081,
}
}; };

View file

@ -16,11 +16,11 @@
package dashboard package dashboard
//go:generate npm --prefix ./assets install //go:generate yarn --cwd ./assets install
//go:generate ./assets/node_modules/.bin/webpack --config ./assets/webpack.config.js --context ./assets //go:generate yarn --cwd ./assets build
//go:generate go-bindata -nometadata -o assets.go -prefix assets -nocompress -pkg dashboard assets/dashboard.html assets/bundle.js //go:generate go-bindata -nometadata -o assets.go -prefix assets -nocompress -pkg dashboard assets/index.html assets/bundle.js
//go:generate sh -c "sed 's#var _bundleJs#//nolint:misspell\\\n&#' assets.go > assets.go.tmp && mv assets.go.tmp assets.go" //go:generate sh -c "sed 's#var _bundleJs#//nolint:misspell\\\n&#' assets.go > assets.go.tmp && mv assets.go.tmp assets.go"
//go:generate sh -c "sed 's#var _dashboardHtml#//nolint:misspell\\\n&#' assets.go > assets.go.tmp && mv assets.go.tmp assets.go" //go:generate sh -c "sed 's#var _indexHtml#//nolint:misspell\\\n&#' assets.go > assets.go.tmp && mv assets.go.tmp assets.go"
//go:generate gofmt -w -s assets.go //go:generate gofmt -w -s assets.go
import ( import (
@ -60,7 +60,7 @@ type Dashboard struct {
listener net.Listener listener net.Listener
conns map[uint32]*client // Currently live websocket connections conns map[uint32]*client // Currently live websocket connections
charts *FooterMessage charts *SystemMessage
commit string commit string
lock sync.RWMutex // Lock protecting the dashboard's internals lock sync.RWMutex // Lock protecting the dashboard's internals
@ -82,7 +82,7 @@ func New(config *Config, commit string) (*Dashboard, error) {
conns: make(map[uint32]*client), conns: make(map[uint32]*client),
config: config, config: config,
quit: make(chan chan error), quit: make(chan chan error),
charts: &FooterMessage{ charts: &SystemMessage{
ActiveMemory: emptyChartEntries(now, activeMemorySampleLimit, config.Refresh), ActiveMemory: emptyChartEntries(now, activeMemorySampleLimit, config.Refresh),
VirtualMemory: emptyChartEntries(now, virtualMemorySampleLimit, config.Refresh), VirtualMemory: emptyChartEntries(now, virtualMemorySampleLimit, config.Refresh),
NetworkIngress: emptyChartEntries(now, networkIngressSampleLimit, config.Refresh), NetworkIngress: emptyChartEntries(now, networkIngressSampleLimit, config.Refresh),
@ -178,7 +178,7 @@ func (db *Dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
path := r.URL.String() path := r.URL.String()
if path == "/" { if path == "/" {
path = "/dashboard.html" path = "/index.html"
} }
blob, err := Asset(path[1:]) blob, err := Asset(path[1:])
if err != nil { if err != nil {
@ -224,7 +224,11 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
} }
// Send the past data. // Send the past data.
client.msg <- Message{ client.msg <- Message{
Footer: &FooterMessage{ General: &GeneralMessage{
Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
Commit: db.commit,
},
System: &SystemMessage{
ActiveMemory: db.charts.ActiveMemory, ActiveMemory: db.charts.ActiveMemory,
VirtualMemory: db.charts.VirtualMemory, VirtualMemory: db.charts.VirtualMemory,
NetworkIngress: db.charts.NetworkIngress, NetworkIngress: db.charts.NetworkIngress,
@ -233,9 +237,6 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
SystemCPU: db.charts.SystemCPU, SystemCPU: db.charts.SystemCPU,
DiskRead: db.charts.DiskRead, DiskRead: db.charts.DiskRead,
DiskWrite: db.charts.DiskWrite, DiskWrite: db.charts.DiskWrite,
Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
Commit: db.commit,
}, },
} }
// Start tracking the connection and drop at connection loss. // Start tracking the connection and drop at connection loss.
@ -350,7 +351,7 @@ func (db *Dashboard) collectData() {
db.charts.DiskWrite = append(db.charts.DiskRead[1:], diskWrite) db.charts.DiskWrite = append(db.charts.DiskRead[1:], diskWrite)
db.sendToAll(&Message{ db.sendToAll(&Message{
Footer: &FooterMessage{ System: &SystemMessage{
ActiveMemory: ChartEntries{activeMemory}, ActiveMemory: ChartEntries{activeMemory},
VirtualMemory: ChartEntries{virtualMemory}, VirtualMemory: ChartEntries{virtualMemory},
NetworkIngress: ChartEntries{networkIngress}, NetworkIngress: ChartEntries{networkIngress},

View file

@ -19,13 +19,13 @@ package dashboard
import "time" import "time"
type Message struct { type Message struct {
General *GeneralMessage `json:"general,omitempty"`
Home *HomeMessage `json:"home,omitempty"` Home *HomeMessage `json:"home,omitempty"`
Chain *ChainMessage `json:"chain,omitempty"` Chain *ChainMessage `json:"chain,omitempty"`
TxPool *TxPoolMessage `json:"txpool,omitempty"` TxPool *TxPoolMessage `json:"txpool,omitempty"`
Network *NetworkMessage `json:"network,omitempty"` Network *NetworkMessage `json:"network,omitempty"`
System *SystemMessage `json:"system,omitempty"` System *SystemMessage `json:"system,omitempty"`
Logs *LogsMessage `json:"logs,omitempty"` Logs *LogsMessage `json:"logs,omitempty"`
Footer *FooterMessage `json:"footer,omitempty"`
} }
type ChartEntries []*ChartEntry type ChartEntries []*ChartEntry
@ -35,6 +35,11 @@ type ChartEntry struct {
Value float64 `json:"value,omitempty"` Value float64 `json:"value,omitempty"`
} }
type GeneralMessage struct {
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
}
type HomeMessage struct { type HomeMessage struct {
/* TODO (kurkomisi) */ /* TODO (kurkomisi) */
} }
@ -52,14 +57,6 @@ type NetworkMessage struct {
} }
type SystemMessage struct { type SystemMessage struct {
/* TODO (kurkomisi) */
}
type LogsMessage struct {
Log []string `json:"log,omitempty"`
}
type FooterMessage struct {
ActiveMemory ChartEntries `json:"activeMemory,omitempty"` ActiveMemory ChartEntries `json:"activeMemory,omitempty"`
VirtualMemory ChartEntries `json:"virtualMemory,omitempty"` VirtualMemory ChartEntries `json:"virtualMemory,omitempty"`
NetworkIngress ChartEntries `json:"networkIngress,omitempty"` NetworkIngress ChartEntries `json:"networkIngress,omitempty"`
@ -68,7 +65,8 @@ type FooterMessage struct {
SystemCPU ChartEntries `json:"systemCPU,omitempty"` SystemCPU ChartEntries `json:"systemCPU,omitempty"`
DiskRead ChartEntries `json:"diskRead,omitempty"` DiskRead ChartEntries `json:"diskRead,omitempty"`
DiskWrite ChartEntries `json:"diskWrite,omitempty"` DiskWrite ChartEntries `json:"diskWrite,omitempty"`
}
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"` type LogsMessage struct {
Log []string `json:"log,omitempty"`
} }