diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 27490c4048..9c703758e0 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -158,7 +158,7 @@ func makeFullNode(ctx *cli.Context) *node.Node { utils.RegisterEthService(stack, &cfg.Eth) if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { - utils.RegisterDashboardService(stack, &cfg.Dashboard) + utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit) } // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode shhEnabled := enableWhisper(ctx) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index edf3dc2c21..5164685bc6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1103,9 +1103,9 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { } // RegisterDashboardService adds a dashboard to the stack. -func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config) { +func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config, commit string) { stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return dashboard.New(cfg) + return dashboard.New(cfg, commit) }) } diff --git a/dashboard/assets.go b/dashboard/assets.go index 4c12696ae6..b2a173107b 100644 --- a/dashboard/assets.go +++ b/dashboard/assets.go @@ -26819,37 +26819,28 @@ var _bundleJs = []byte((((((((((`!function(modules) { return protoProps && defineProperties(Constructor.prototype, protoProps), staticProps && defineProperties(Constructor, staticProps), Constructor; }; - }(), _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj) { - return typeof obj; - } : function(obj) { - return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }, _react = __webpack_require__(0), _react2 = _interopRequireDefault(_react), _withStyles = __webpack_require__(10), _withStyles2 = _interopRequireDefault(_withStyles), _Header = __webpack_require__(459), _Header2 = _interopRequireDefault(_Header), _Body = __webpack_require__(489), _Body2 = _interopRequireDefault(_Body), _Footer = __webpack_require__(805), _Footer2 = _interopRequireDefault(_Footer), _Common = __webpack_require__(77), deepCopy = function deepCopy(prev) { - var copied = Array.isArray(prev) ? [] : {}; + }(), _react = __webpack_require__(0), _react2 = _interopRequireDefault(_react), _withStyles = __webpack_require__(10), _withStyles2 = _interopRequireDefault(_withStyles), _Header = __webpack_require__(459), _Header2 = _interopRequireDefault(_Header), _Body = __webpack_require__(489), _Body2 = _interopRequireDefault(_Body), _Footer = __webpack_require__(805), _Footer2 = _interopRequireDefault(_Footer), _Common = __webpack_require__(77), deepUpdate = function deepUpdate(prev, update, updater) { + if (void 0 === update) return prev; + if ("function" == typeof updater) return updater(prev, update); + var updated = {}; return Object.keys(prev).forEach(function(key) { - var c = prev[key]; - copied[key] = "object" === (void 0 === c ? "undefined" : _typeof(c)) ? deepCopy(c) : c; - }), copied; - }, deepUpdate = function deepUpdate(prev, update, handler) { - if (void 0 === update) return deepCopy(prev); - if ("function" == typeof handler) return handler(prev, update); - var updated = Array.isArray(prev) ? [] : {}; - return Object.keys(prev).forEach(function(key) { - void 0 === update[key] ? updated[key] = deepCopy(prev[key]) : updated[key] = deepUpdate(prev[key], update[key], handler[key]); + updated[key] = deepUpdate(prev[key], update[key], updater[key]); }), updated; - }, shouldUpdate = function shouldUpdate(msg, handler) { + }, shouldUpdate = function shouldUpdate(msg, updater) { var su = {}; return Object.keys(msg).forEach(function(key) { - su[key] = "object" !== _typeof(msg[key]) || "function" == typeof handler[key] || shouldUpdate(msg[key], handler[key]); + su[key] = "function" == typeof updater[key] || shouldUpdate(msg[key], updater[key]); }), su; - }, appender200 = function(limit) { + }, appender = function(limit) { return function(prev, update) { return [].concat(_toConsumableArray(prev), _toConsumableArray(update)).slice(-limit); }; - }(200), replacer = function(prev, update) { + }, replacer = function(prev, update) { return update; }, defaultContent = { general: { - version: "-" + version: "-", + gitCommit: "-" }, home: { memory: [], @@ -26862,20 +26853,21 @@ var _bundleJs = []byte((((((((((`!function(modules) { logs: { log: [] } - }, handlers = { + }, updaters = { general: { - version: replacer + version: replacer, + gitCommit: replacer }, home: { - memory: appender200, - traffic: appender200 + memory: appender(200), + traffic: appender(200) }, chain: null, txpool: null, network: null, system: null, logs: { - log: appender200 + log: appender(200) } }, styles = function(theme) { return { @@ -26902,15 +26894,16 @@ var _bundleJs = []byte((((((((((`!function(modules) { }); }, server.onmessage = function(event) { var msg = JSON.parse(event.data); - msg && _this.update(msg); + if (!msg) return void console.error("Incoming message is " + msg); + console.log(msg), _this.update(msg); }, server.onclose = function() { setTimeout(_this.reconnect, 3e3); }; }, _this.update = function(msg) { _this.setState(function(prevState) { return { - content: deepUpdate(prevState.content, msg, handlers), - shouldUpdate: shouldUpdate(msg, handlers) + content: deepUpdate(prevState.content, msg, updaters), + shouldUpdate: shouldUpdate(msg, updaters) }; }); }, _this.changeContent = function(newActive) { @@ -38146,13 +38139,7 @@ var _bundleJs = []byte((((((((((`!function(modules) { Object.defineProperty(exports, "__esModule", { value: !0 }); - var _extends = Object.assign || function(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) Object.prototype.hasOwnProperty.call(source, key) && (target[key] = source[key]); - } - return target; - }, _createClass = function() { + var _createClass = function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; @@ -38164,13 +38151,9 @@ var _bundleJs = []byte((((((((((`!function(modules) { return protoProps && defineProperties(Constructor.prototype, protoProps), staticProps && defineProperties(Constructor, staticProps), Constructor; }; - }(), _react = __webpack_require__(0), _react2 = _interopRequireDefault(_react), _withStyles = __webpack_require__(10), _withStyles2 = _interopRequireDefault(_withStyles), _AppBar = __webpack_require__(235), _AppBar2 = _interopRequireDefault(_AppBar), _Toolbar = __webpack_require__(236), _Toolbar2 = _interopRequireDefault(_Toolbar), _Transition = __webpack_require__(76), _Transition2 = _interopRequireDefault(_Transition), _IconButton = __webpack_require__(237), _IconButton2 = _interopRequireDefault(_IconButton), _Typography = __webpack_require__(158), _Typography2 = _interopRequireDefault(_Typography), _ChevronLeft = __webpack_require__(244), _ChevronLeft2 = _interopRequireDefault(_ChevronLeft), _Common = __webpack_require__(77), arrowDefault = { - transition: "transform " + _Common.DURATION + "ms" - }, arrowTransition = { - entered: { - transform: "rotate(180deg)" - } - }, styles = function(theme) { + }(), _react = __webpack_require__(0), _react2 = _interopRequireDefault(_react), _withStyles = __webpack_require__(10), _withStyles2 = _interopRequireDefault(_withStyles), _AppBar = __webpack_require__(235), _AppBar2 = _interopRequireDefault(_AppBar), _Toolbar = __webpack_require__(236), _Toolbar2 = _interopRequireDefault(_Toolbar), _Transition = __webpack_require__(76), _IconButton = (_interopRequireDefault(_Transition), + __webpack_require__(237)), _Typography = (_interopRequireDefault(_IconButton), __webpack_require__(158)), _Typography2 = _interopRequireDefault(_Typography), _ChevronLeft = __webpack_require__(244), _Common = (_interopRequireDefault(_ChevronLeft), + __webpack_require__(77)), styles = (_Common.DURATION, function(theme) { return { header: { backgroundColor: theme.palette.background.appBar, @@ -38185,21 +38168,9 @@ var _bundleJs = []byte((((((((((`!function(modules) { paddingLeft: theme.spacing.unit } }; - }, Footer = function(_Component) { + }), Footer = function(_Component) { function Footer() { - var _ref, _temp, _this, _ret; - _classCallCheck(this, Footer); - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) args[_key] = arguments[_key]; - return _temp = _this = _possibleConstructorReturn(this, (_ref = Footer.__proto__ || Object.getPrototypeOf(Footer)).call.apply(_ref, [ this ].concat(args))), - _this.changeSideBar = function() { - _this.props.opened ? _this.props.closeSideBar() : _this.props.openSideBar(); - }, _this.arrowButton = function(transitionState) { - return _react2.default.createElement(_IconButton2.default, { - onClick: _this.changeSideBar - }, _react2.default.createElement(_ChevronLeft2.default, { - style: _extends({}, arrowDefault, arrowTransition[transitionState]) - })); - }, _ret = _temp, _possibleConstructorReturn(_this, _ret); + return _classCallCheck(this, Footer), _possibleConstructorReturn(this, (Footer.__proto__ || Object.getPrototypeOf(Footer)).apply(this, arguments)); } return _inherits(Footer, _Component), _createClass(Footer, [ { key: "shouldComponentUpdate", @@ -38209,24 +38180,21 @@ var _bundleJs = []byte((((((((((`!function(modules) { }, { key: "render", value: function() { - var _props = this.props, classes = _props.classes, opened = _props.opened; + var classes = this.props.classes; return _react2.default.createElement(_AppBar2.default, { position: "static", className: classes.header }, _react2.default.createElement(_Toolbar2.default, { className: classes.toolbar - }, _react2.default.createElement(_Transition2.default, { - mountOnEnter: !0, - in: opened, - timeout: { - enter: _Common.DURATION - } - }, this.arrowButton), _react2.default.createElement(_Typography2.default, { + }, _react2.default.createElement(_Typography2.default, { type: "title", color: "inherit", - noWrap: !0, className: classes.mainText - }, this.props.general.version))); + }, this.props.general.version), _react2.default.createElement(_Typography2.default, { + type: "title", + color: "inherit", + className: classes.mainText + }, this.props.general.gitCommit))); } } ]), Footer; }(_react.Component); diff --git a/dashboard/assets/components/Dashboard.jsx b/dashboard/assets/components/Dashboard.jsx index 5b3ab44ae1..7c58abca09 100644 --- a/dashboard/assets/components/Dashboard.jsx +++ b/dashboard/assets/components/Dashboard.jsx @@ -26,22 +26,8 @@ import Footer from './Footer'; import {MENU} from './Common'; import type {Content} from '../types/content'; -// deepCopy retrieves a copy of the given object, copying recursively in all depth. -// It is used at the state update, because React doesn't handle the object nesting well. -// References are prohibited, since the state needs to be immutable. -// NOTE: maybe there is better solution. -const deepCopy = (prev: mixed) => { - const copied = Array.isArray(prev) ? [] : {}; - Object.keys(prev).forEach((key) => { - const c: mixed = prev[key] - copied[key] = typeof c === 'object' ? deepCopy(c) : c; - }); - - return copied; -}; - // deepUpdate updates an object corresponding to the given update data, which has -// the shape of the same structure as the original object. handler 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 // updated. These functions are used to handle the update. // @@ -49,48 +35,49 @@ const deepCopy = (prev: mixed) => { // the generalization of the message handling. The only necessary thing is to set a // handler function for every path of the state in order to maximize the flexibility // of the update. -const deepUpdate = (prev: mixed, update: mixed, handler: mixed) => { +const deepUpdate = (prev: Object, update: Object, updater: Object) => { if (typeof update === 'undefined') { - return deepCopy(prev); + // TODO (kurkomisi): originally this was deep copy, investigate it. + return prev; } - if (typeof handler === 'function') { - return handler(prev, update); + if (typeof updater === 'function') { + return updater(prev, update); } - const updated = Array.isArray(prev) ? [] : {}; + const updated = {}; Object.keys(prev).forEach((key) => { - if (typeof update[key] === 'undefined') { - updated[key] = deepCopy(prev[key]); - } else { - updated[key] = deepUpdate(prev[key], update[key], handler[key]); - } + updated[key] = deepUpdate(prev[key], update[key], updater[key]); }); return updated; }; -// shouldUpdate retrieves the structure of a message. It is used to prevent unnecessary render +// shouldUpdate returns the structure of a message. It is used to prevent unnecessary render // method triggerings. In the affected component's shouldComponentUpdate method it can be checked // whether the involved data was changed or not by checking the message structure. -const shouldUpdate = (msg: mixed, handler: mixed) => { +// +// We could return the message itself too, but it's safer not to give access to it. +const shouldUpdate = (msg: Object, updater: Object) => { const su = {}; + console.log(msg); Object.keys(msg).forEach((key) => { - su[key] = typeof msg[key] === 'object' && typeof handler[key] !== 'function' ? shouldUpdate(msg[key], handler[key]) : true; + su[key] = typeof updater[key] !== 'function' ? shouldUpdate(msg[key], updater[key]) : true; }); + return su; }; // appender is a state update generalization function, which appends the update data // to the existing data. limit defines the maximum allowed size of the created array. const appender = (limit: number) => (prev: Array, update: Array) => [...prev, ...update].slice(-limit); -// appender200 is an appender function with limit 200. -const appender200 = appender(200); + // replacer is a state update generalization function, which replaces the original data. const replacer = (prev: T, update: T) => update; // defaultContent is the initial value of the state content. const defaultContent: Content = { general: { - version: '-', + version: '-', + gitCommit: '-', }, home: { memory: [], @@ -104,25 +91,28 @@ const defaultContent: Content = { log: [], }, }; -// handlers contains the state update generalization functions for each path of the state. + +// updaters contains the state update generalization functions for each path of the state. // TODO (kurkomisi): Define a tricky type which embraces the content and the handlers. -const handlers = { +const updaters = { general: { - version: replacer, + version: replacer, + gitCommit: replacer, }, home: { - memory: appender200, - traffic: appender200, + memory: appender(200), + traffic: appender(200), }, chain: null, txpool: null, network: null, system: null, logs: { - log: appender200, + log: appender(200), }, }; -// styles retrieves the styles for the Dashboard component. + +// styles returns the styles for the Dashboard component. const styles = theme => ({ dashboard: { display: 'flex', @@ -134,15 +124,18 @@ const styles = theme => ({ overflow: 'hidden', }, }); + export type Props = { classes: Object, }; + type State = { active: string, // active menu sideBar: boolean, // true if the sidebar is opened content: Content, // the visualized data shouldUpdate: Object // labels for the components, which need to rerender based on the incoming message }; + // Dashboard is the main component, which renders the whole page, makes connection with the server and // listens for messages. When there is an incoming message, updates the page's content correspondingly. class Dashboard extends Component { @@ -171,6 +164,7 @@ class Dashboard extends Component { server.onmessage = (event) => { const msg: $Shape = JSON.parse(event.data); if (!msg) { + console.error(`Incoming message is ${msg}`); return; } this.update(msg); @@ -183,8 +177,8 @@ class Dashboard extends Component { // update updates the content corresponding to the incoming message. update = (msg: $Shape) => { this.setState(prevState => ({ - content: deepUpdate(prevState.content, msg, handlers), - shouldUpdate: shouldUpdate(msg, handlers), + content: deepUpdate(prevState.content, msg, updaters), + shouldUpdate: shouldUpdate(msg, updaters), })); }; diff --git a/dashboard/assets/components/Footer.jsx b/dashboard/assets/components/Footer.jsx index cca1475829..63d280f857 100644 --- a/dashboard/assets/components/Footer.jsx +++ b/dashboard/assets/components/Footer.jsx @@ -53,9 +53,6 @@ const styles = theme => ({ }); export type Props = { classes: Object, - opened: boolean, - openSideBar: () => {}, - closeSideBar: () => {}, }; // TODO (kurkomisi): If the structure is appropriate, make an abstraction of the common parts with the Header. // Footer renders the header of the dashboard. @@ -64,39 +61,18 @@ class Footer extends Component { return typeof nextProps.shouldUpdate.logs !== 'undefined'; } - // changeSideBar opens or closes the sidebar corresponding to the previous state. - changeSideBar = () => { - if (this.props.opened) { - this.props.closeSideBar(); - } else { - this.props.openSideBar(); - } - }; - - // arrowButton is connected to the sidebar; changes its state. - arrowButton = (transitionState: string) => ( - - - - ); - render() { - const {classes, opened} = this.props; // The classes property is injected by withStyles(). + const {classes} = this.props; // The classes property is injected by withStyles(). return ( - - {this.arrowButton} - - + {this.props.general.version} + + {this.props.general.gitCommit} + ); diff --git a/dashboard/assets/types/content.jsx b/dashboard/assets/types/content.jsx index ea53b6051e..c17ea51e36 100644 --- a/dashboard/assets/types/content.jsx +++ b/dashboard/assets/types/content.jsx @@ -28,6 +28,7 @@ export type Content = { export type General = { version: string, + gitCommit: string, }; export type Home = { diff --git a/dashboard/dashboard.go b/dashboard/dashboard.go index 5cbaba10c4..622bc87117 100644 --- a/dashboard/dashboard.go +++ b/dashboard/dashboard.go @@ -52,10 +52,11 @@ var nextID uint32 // Next connection id type Dashboard struct { config *Config - listener net.Listener - conns map[uint32]*client // Currently live websocket connections - charts *HomeMessage - lock sync.RWMutex // Lock protecting the dashboard's internals + listener net.Listener + conns map[uint32]*client // Currently live websocket connections + charts *HomeMessage + gitCommit string + lock sync.RWMutex // Lock protecting the dashboard's internals quit chan chan error // Channel used for graceful exit wg sync.WaitGroup @@ -69,7 +70,7 @@ type client struct { } // New creates a new dashboard instance with the given configuration. -func New(config *Config) (*Dashboard, error) { +func New(config *Config, gitCommit string) (*Dashboard, error) { return &Dashboard{ conns: make(map[uint32]*client), config: config, @@ -78,6 +79,7 @@ func New(config *Config) (*Dashboard, error) { Memory: ChartEntries{}, Traffic: ChartEntries{}, }, + gitCommit: gitCommit, }, nil } @@ -201,10 +203,16 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) { } } }() + + versionMeta := "" + if len(params.VersionMeta) > 0 { + versionMeta = fmt.Sprintf(" (%s)", params.VersionMeta) + } // Send the past data. client.msg <- Message{ General: &GeneralMessage{ - Version: params.Version, + Version: fmt.Sprintf("Geth v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta), + GitCommit: fmt.Sprintf("Commit %s", db.gitCommit), }, Home: &HomeMessage{ Memory: db.charts.Memory, diff --git a/dashboard/message.go b/dashboard/message.go index 3a59bced72..330cd5a884 100644 --- a/dashboard/message.go +++ b/dashboard/message.go @@ -29,7 +29,8 @@ type Message struct { } type GeneralMessage struct { - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty"` + GitCommit string `json:"gitCommit,omitempty"` } type HomeMessage struct { diff --git a/geth b/geth new file mode 100755 index 0000000000..98365a6928 Binary files /dev/null and b/geth differ