dashboard, cmd: minor UI fix, include commit hash

This commit is contained in:
Kurkó Mihály 2018-01-11 23:15:03 +02:00
parent 011ab3665a
commit 24880501bb
9 changed files with 93 additions and 145 deletions

View file

@ -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)

View file

@ -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)
})
}

View file

@ -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);

View file

@ -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 = <T>(limit: number) => (prev: Array<T>, update: Array<T>) => [...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 = <T>(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<Props, State> {
@ -171,6 +164,7 @@ class Dashboard extends Component<Props, State> {
server.onmessage = (event) => {
const msg: $Shape<Content> = JSON.parse(event.data);
if (!msg) {
console.error(`Incoming message is ${msg}`);
return;
}
this.update(msg);
@ -183,8 +177,8 @@ class Dashboard extends Component<Props, State> {
// update updates the content corresponding to the incoming message.
update = (msg: $Shape<Content>) => {
this.setState(prevState => ({
content: deepUpdate(prevState.content, msg, handlers),
shouldUpdate: shouldUpdate(msg, handlers),
content: deepUpdate(prevState.content, msg, updaters),
shouldUpdate: shouldUpdate(msg, updaters),
}));
};

View file

@ -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<Props> {
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) => (
<IconButton onClick={this.changeSideBar}>
<ChevronLeftIcon
style={{
...arrowDefault,
...arrowTransition[transitionState],
}}
/>
</IconButton>
);
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 (
<AppBar position="static" className={classes.header}>
<Toolbar className={classes.toolbar}>
<Transition mountOnEnter in={opened} timeout={{enter: DURATION}}>
{this.arrowButton}
</Transition>
<Typography type="title" color="inherit" noWrap className={classes.mainText}>
<Typography type="title" color="inherit" className={classes.mainText}>
{this.props.general.version}
</Typography>
<Typography type="title" color="inherit" className={classes.mainText}>
{this.props.general.gitCommit}
</Typography>
</Toolbar>
</AppBar>
);

View file

@ -28,6 +28,7 @@ export type Content = {
export type General = {
version: string,
gitCommit: string,
};
export type Home = {

View file

@ -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,

View file

@ -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 {

BIN
geth Executable file

Binary file not shown.