Cleanup the Status View (#5317)

* Better view of Settings and Mining Settings

* Cleanup Status view

* Node Logs refactoring

* Cleanup Status

* Move RPC Calls files

* Basic Peers view

* Add Peers table

* style table header
This commit is contained in:
Nicolas Gotchac
2017-03-29 14:38:07 +02:00
committed by Gav Wood
parent 8930f510fc
commit 5fa088114c
101 changed files with 771 additions and 869 deletions

View File

@@ -16,5 +16,4 @@
import { createAction } from 'redux-actions';
export const error = createAction('error');
export const syncRpcStateFromLocalStorage = createAction('sync rpcStateFromLocalStorage');

View File

@@ -14,6 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createAction } from 'redux-actions';
import rpc from './rpc';
import logger from './logger';
export const initAppAction = createAction('init app');
export {
rpc,
logger
};

View File

@@ -43,6 +43,7 @@
.log {
font-family: monospace;
font-size: 0.9em;
white-space: pre-line;
word-wrap: break-word;
color: #aaa;

View File

@@ -14,30 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { Container } from '~/ui';
import { PauseIcon, PlayIcon, ReorderIcon, ReplayIcon } from '~/ui/Icons';
import DebugStore from './store';
import styles from './debug.css';
@observer
export default class Debug extends Component {
static propTypes = {
actions: PropTypes.shape({
clearStatusLogs: PropTypes.func.isRequired,
toggleStatusLogs: PropTypes.func.isRequired
}).isRequired,
nodeStatus: PropTypes.object.isRequired
}
static contextTypes = {
api: PropTypes.object.isRequired
};
state = {
reversed: true
debugStore = new DebugStore(this.context.api);
componentWillUnmount () {
this.debugStore.stopPolling();
}
render () {
const { nodeStatus } = this.props;
const { devLogsLevels } = nodeStatus;
const { logsLevels } = this.debugStore;
return (
<Container
@@ -50,7 +50,7 @@ export default class Debug extends Component {
>
{ this.renderActions() }
<h2 className={ styles.subheader }>
{ devLogsLevels || '-' }
{ logsLevels || '-' }
</h2>
{ this.renderToggle() }
{ this.renderLogs() }
@@ -59,9 +59,9 @@ export default class Debug extends Component {
}
renderToggle () {
const { devLogsEnabled } = this.props.nodeStatus;
const { logsEnabled } = this.debugStore;
if (devLogsEnabled) {
if (logsEnabled) {
return null;
}
@@ -76,36 +76,18 @@ export default class Debug extends Component {
}
renderLogs () {
const { nodeStatus } = this.props;
const { reversed } = this.state;
const { devLogs } = nodeStatus;
const { logs } = this.debugStore;
const dateRegex = /^(\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2})(.*)$/i;
if (!devLogs) {
if (logs.length === 0) {
return null;
}
const logs = reversed
? [].concat(devLogs).reverse()
: [].concat(devLogs);
const text = logs
.map((log, index) => {
const logDate = dateRegex.exec(log);
if (!logDate) {
return (
<p key={ index } className={ styles.log }>
{ log }
</p>
);
}
return (
<p key={ index } className={ styles.log }>
<span className={ styles.logDate }>{ logDate[1] }</span>
<span className={ styles.logText }>{ logDate[2] }</span>
<span className={ styles.logDate }>[{ log.date.toLocaleString() }]</span>
<span className={ styles.logText }>{ log.log }</span>
</p>
);
});
@@ -118,8 +100,8 @@ export default class Debug extends Component {
}
renderActions () {
const { devLogsEnabled } = this.props.nodeStatus;
const toggleButton = devLogsEnabled
const { logsEnabled } = this.debugStore;
const toggleButton = logsEnabled
? <PauseIcon />
: <PlayIcon />;
@@ -143,21 +125,14 @@ export default class Debug extends Component {
}
clear = () => {
const { clearStatusLogs } = this.props.actions;
clearStatusLogs();
}
this.debugStore.clearLogs();
};
toggle = () => {
const { devLogsEnabled } = this.props.nodeStatus;
const { toggleStatusLogs } = this.props.actions;
toggleStatusLogs(!devLogsEnabled);
}
this.debugStore.toggle();
};
reverse = () => {
const { reversed } = this.state;
this.setState({ reversed: !reversed });
}
this.debugStore.reverse();
};
}

View File

@@ -0,0 +1,128 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable, transaction } from 'mobx';
const LOG_DATE_REGEX = /^(\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2})(.*)$/i;
const MAX_LOGS = 25;
export default class DebugStore {
@observable logs = [];
@observable logsLevels = null;
@observable logsEnabled = false;
@observable reversed = false;
api = null;
_lastLogAdded = null;
_timeoutId = null;
constructor (api) {
this.api = api;
}
@action clearLogs () {
this.logs = [];
}
@action setLogs (logs, logsLevels) {
let newLogs = [];
if (this._lastLogAdded) {
const sliceIndex = logs.findIndex((log) => log === this._lastLogAdded);
newLogs = logs.slice(0, sliceIndex);
} else {
newLogs = logs.slice();
}
this._lastLogAdded = logs[0];
const parsedLogs = newLogs
.map((log) => {
const logDate = LOG_DATE_REGEX.exec(log);
if (!logDate) {
return null;
}
return {
date: new Date(logDate[1]),
log: logDate[2]
};
})
.filter((log) => log);
transaction(() => {
if (!this.reversed) {
this.logs = [].concat(parsedLogs, this.logs.slice()).slice(0, MAX_LOGS);
} else {
parsedLogs.reverse();
this.logs = [].concat(this.logs.slice(), parsedLogs).slice(-1 * MAX_LOGS);
}
this.logsLevels = logsLevels;
});
}
@action toggle () {
this.logsEnabled = !this.logsEnabled;
if (this.logsEnabled) {
this.initPolling();
} else {
this.stopPolling();
}
}
@action reverse () {
transaction(() => {
this.reversed = !this.reversed;
this.logs = this.logs.reverse();
});
}
initPolling () {
this._pollLogs();
}
stopPolling () {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
}
}
_pollLogs = () => {
const nextTimeout = (timeout = 1000) => {
this.stopPolling();
this._timeoutId = setTimeout(this._pollLogs, timeout);
};
return Promise
.all([
this.api.parity.devLogs(),
this.api.parity.devLogsLevels()
])
.then(([ devLogs, devLogsLevels ]) => {
this.setLogs(devLogs, devLogsLevels);
})
.catch((error) => {
console.error('_pollLogs', error);
})
.then(() => {
return nextTimeout();
});
}
}

View File

@@ -14,11 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import formatNumber from 'format-number';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import formatNumber from 'format-number';
import { ContainerTitle, Input } from '~/ui';
import { ContainerTitle, Input, TypedInput } from '~/ui';
import { numberFromString } from './numberFromString';
import { decodeExtraData } from './decodeExtraData';
@@ -28,21 +28,23 @@ const toNiceNumber = formatNumber();
export default class MiningSettings extends Component {
static contextTypes = {
api: PropTypes.object
}
};
static propTypes = {
nodeStatus: PropTypes.object
}
coinbase: PropTypes.string,
defaultExtraData: PropTypes.string,
extraData: PropTypes.string,
gasFloorTarget: PropTypes.object,
minGasPrice: PropTypes.object,
onUpdateSetting: PropTypes.func.isRequired
};
render () {
const { nodeStatus } = this.props;
const { coinbase, defaultExtraData, extraData, gasFloorTarget, minGasPrice } = nodeStatus;
const extradata = extraData
const { coinbase, defaultExtraData, extraData, gasFloorTarget, minGasPrice } = this.props;
const decodedExtraData = extraData
? decodeExtraData(extraData)
: '';
const defaultExtradata = defaultExtraData
const decodedDefaultExtraData = defaultExtraData
? decodeExtraData(defaultExtraData)
: '';
@@ -56,7 +58,7 @@ export default class MiningSettings extends Component {
/>
}
/>
<Input
<TypedInput
label={
<FormattedMessage
id='status.miningSettings.input.author.label'
@@ -69,14 +71,15 @@ export default class MiningSettings extends Component {
defaultMessage='the mining author'
/>
}
param='address'
value={ coinbase }
onSubmit={ this.onAuthorChange }
onChange={ this.onAuthorChange }
allowCopy
floatCopy
{ ...this._test('author') }
/>
<Input
defaultValue={ decodedDefaultExtraData }
escape='default'
label={
<FormattedMessage
id='status.miningSettings.input.extradata.label'
@@ -89,12 +92,9 @@ export default class MiningSettings extends Component {
defaultMessage='extra data for mined blocks'
/>
}
value={ extradata }
value={ decodedExtraData }
onSubmit={ this.onExtraDataChange }
defaultValue={ defaultExtradata }
allowCopy
floatCopy
{ ...this._test('extra-data') }
/>
<Input
@@ -113,8 +113,6 @@ export default class MiningSettings extends Component {
value={ toNiceNumber(minGasPrice) }
onSubmit={ this.onMinGasPriceChange }
allowCopy={ minGasPrice.toString() }
floatCopy
{ ...this._test('min-gas-price') }
/>
<Input
@@ -133,8 +131,6 @@ export default class MiningSettings extends Component {
value={ toNiceNumber(gasFloorTarget) }
onSubmit={ this.onGasFloorTargetChange }
allowCopy={ gasFloorTarget.toString() }
floatCopy
{ ...this._test('gas-floor-target') }
/>
</div>
);
@@ -143,29 +139,36 @@ export default class MiningSettings extends Component {
onMinGasPriceChange = (newVal) => {
const { api } = this.context;
api.parity.setMinGasPrice(numberFromString(newVal));
api.parity
.setMinGasPrice(numberFromString(newVal))
.then(() => this.updateMiningSettings());
};
onExtraDataChange = (newVal, isResetToDefault) => {
onExtraDataChange = (value) => {
const { api } = this.context;
const { nodeStatus } = this.props;
// In case of resetting to default we are just using raw bytes from defaultExtraData
// When user sets new value we can safely send a string that will be converted to hex by formatter.
const val = isResetToDefault ? nodeStatus.defaultExtraData : newVal;
api.parity.setExtraData(val);
api.parity
.setExtraData(value)
.then(() => this.updateMiningSettings());
};
onAuthorChange = (newVal) => {
const { api } = this.context;
api.parity.setAuthor(newVal);
api.parity
.setAuthor(newVal)
.then(() => this.updateMiningSettings());
};
onGasFloorTargetChange = (newVal) => {
const { api } = this.context;
api.parity.setGasFloorTarget(numberFromString(newVal));
api.parity
.setGasFloorTarget(numberFromString(newVal))
.then(() => this.updateMiningSettings());
};
updateMiningSettings () {
this.props.onUpdateSetting();
}
}

View File

@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './statusPage';
export default from './peers';

View File

@@ -0,0 +1,47 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.peers {
margin-top: 0.5em;
overflow: auto;
width: 100%;
table {
border-collapse: collapse;
width: 100%;
}
th {
padding: 0.5em;
text-align: left;
white-space: nowrap;
}
}
.peer {
&:nth-child(odd) {
background-color: rgba(200, 200, 200, 0.1);
}
td {
border-top: 1px solid #333;
font-size: 0.9em;
overflow: hidden;
padding: 0.5em 0.25em;
white-space: nowrap;
}
}

View File

@@ -0,0 +1,163 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { Container, ContainerTitle, ScrollableText, ShortenedHash } from '~/ui';
import styles from './peers.css';
class Peers extends Component {
static propTypes = {
peers: PropTypes.array.isRequired
};
render () {
const { peers } = this.props;
return (
<Container>
<ContainerTitle
title={
<FormattedMessage
id='status.peers.title'
defaultMessage='network peers'
/>
}
/>
<div className={ styles.peers }>
<table>
<thead>
<tr>
<th />
<th>
<FormattedMessage
id='status.peers.table.header.id'
defaultMessage='ID'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.remoteAddress'
defaultMessage='Remote Address'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.name'
defaultMessage='Name'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.ethHeader'
defaultMessage='Header (ETH)'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.ethDiff'
defaultMessage='Difficulty (ETH)'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.caps'
defaultMessage='Capabilities'
/>
</th>
</tr>
</thead>
<tbody>
{ this.renderPeers(peers) }
</tbody>
</table>
</div>
</Container>
);
}
renderPeers (peers) {
return peers.map((peer, index) => this.renderPeer(peer, index));
}
renderPeer (peer, index) {
const { caps, id, name, network, protocols } = peer;
return (
<tr
className={ styles.peer }
key={ id }
>
<td>
{ index + 1 }
</td>
<td>
<ScrollableText small text={ id } />
</td>
<td>
{ network.remoteAddress }
</td>
<td>
{ name }
</td>
<td>
{
protocols.eth
? <ShortenedHash data={ protocols.eth.head } />
: null
}
</td>
<td>
{
protocols.eth && protocols.eth.difficulty.gt(0)
? protocols.eth.difficulty.toExponential(16)
: null
}
</td>
<td>
{
caps && caps.length > 0
? caps.join(' - ')
: null
}
</td>
</tr>
);
}
}
function mapStateToProps (state) {
const handshakeRegex = /handshake/i;
const { netPeers } = state.nodeStatus;
const { peers = [] } = netPeers;
const realPeers = peers
.filter((peer) => peer.id)
.filter((peer) => !handshakeRegex.test(peer.network.remoteAddress))
.filter((peer) => peer.protocols.eth && peer.protocols.eth.head)
.sort((peerA, peerB) => {
const idComp = peerA.id.localeCompare(peerB.id);
return idComp;
});
return { peers: realPeers };
}
export default connect(mapStateToProps)(Peers);

View File

@@ -18,28 +18,46 @@ import bytes from 'bytes';
import moment from 'moment';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { Container, ContainerTitle, Input } from '~/ui';
import MiningSettings from '../MiningSettings';
import StatusStore from './store';
import styles from './status.css';
export default class Status extends Component {
class Status extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
};
static propTypes = {
nodeStatus: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
blockNumber: PropTypes.object,
blockTimestamp: PropTypes.object,
netChain: PropTypes.string,
netPeers: PropTypes.object
};
statusStore = new StatusStore(this.context.api);
componentWillMount () {
this.statusStore.startPolling();
}
componentWillUnmount () {
this.statusStore.stopPolling();
}
render () {
const { nodeStatus } = this.props;
const { netPeers } = nodeStatus;
const { blockNumber, blockTimestamp, netPeers } = this.props;
const { hashrate } = this.statusStore;
if (!netPeers || !nodeStatus.blockNumber) {
if (!netPeers || !blockNumber) {
return null;
}
const hashrate = bytes(nodeStatus.hashrate.toNumber()) || 0;
const hashrateValue = bytes(hashrate.toNumber()) || 0;
const peers = `${netPeers.active}/${netPeers.connected}/${netPeers.max}`;
return (
@@ -56,11 +74,11 @@ export default class Status extends Component {
/>
}
/>
<div { ...this._test('best-block') } className={ styles.blockInfo }>
#{ nodeStatus.blockNumber.toFormat() }
<div className={ styles.blockInfo }>
#{ blockNumber.toFormat() }
</div>
<div className={ styles.blockByline }>
{ moment(nodeStatus.blockTimestamp).calendar() }
{ moment(blockTimestamp).calendar() }
</div>
</div>
<div className={ `${styles.col12} ${styles.padBottom}` }>
@@ -72,7 +90,7 @@ export default class Status extends Component {
/>
}
/>
<div { ...this._test('peers') } className={ styles.blockInfo }>
<div className={ styles.blockInfo }>
{ peers }
</div>
</div>
@@ -85,23 +103,19 @@ export default class Status extends Component {
/>
}
/>
<div { ...this._test('hashrate') } className={ styles.blockInfo }>
<div className={ styles.blockInfo }>
<FormattedMessage
id='status.status.hashrate'
defaultMessage='{hashrate} H/s'
values={ {
hashrate
hashrate: hashrateValue
} }
/>
</div>
</div>
</div>
<div className={ styles.col4_5 }>
<MiningSettings
{ ...this._test('mining') }
nodeStatus={ nodeStatus }
actions={ this.props.actions }
/>
{ this.renderMiningSettings() }
</div>
<div className={ styles.col4_5 }>
{ this.renderSettings() }
@@ -112,12 +126,27 @@ export default class Status extends Component {
);
}
renderMiningSettings () {
const { coinbase, defaultExtraData, extraData, gasFloorTarget, minGasPrice } = this.statusStore;
return (
<MiningSettings
coinbase={ coinbase }
defaultExtraData={ defaultExtraData }
extraData={ extraData }
gasFloorTarget={ gasFloorTarget }
minGasPrice={ minGasPrice }
onUpdateSetting={ this.statusStore.handleUpdateSetting }
/>
);
}
renderNodeName () {
const { nodeStatus } = this.props;
const { nodeName } = this.statusStore;
return (
<span>
{ nodeStatus.nodeName || (
{ nodeName || (
<FormattedMessage
id='status.status.title.node'
defaultMessage='Node'
@@ -128,9 +157,8 @@ export default class Status extends Component {
}
renderSettings () {
const { nodeStatus } = this.props;
const { rpcSettings, netPeers, netPort = '' } = nodeStatus;
const peers = `${netPeers.active}/${netPeers.connected}/${netPeers.max}`;
const { netChain } = this.props;
const { enode, rpcSettings, netPort = '' } = this.statusStore;
if (!rpcSettings) {
return null;
@@ -139,7 +167,7 @@ export default class Status extends Component {
const rpcPort = rpcSettings.port || '';
return (
<div { ...this._test('settings') }>
<div>
<ContainerTitle
title={
<FormattedMessage
@@ -157,8 +185,7 @@ export default class Status extends Component {
defaultMessage='chain'
/>
}
value={ nodeStatus.netChain }
{ ...this._test('chain') }
value={ netChain }
/>
<div className={ styles.row }>
<div className={ styles.col6 }>
@@ -167,12 +194,25 @@ export default class Status extends Component {
readOnly
label={
<FormattedMessage
id='status.status.input.peers'
defaultMessage='peers'
id='status.status.input.rpcEnabled'
defaultMessage='rpc enabled'
/>
}
value={ peers }
{ ...this._test('peers') }
value={
rpcSettings.enabled
? (
<FormattedMessage
id='status.status.input.yes'
defaultMessage='yes'
/>
)
: (
<FormattedMessage
id='status.status.input.no'
defaultMessage='no'
/>
)
}
/>
</div>
<div className={ styles.col6 }>
@@ -186,37 +226,10 @@ export default class Status extends Component {
/>
}
value={ netPort.toString() }
{ ...this._test('network-port') }
/>
</div>
</div>
<Input
allowCopy
readOnly
label={
<FormattedMessage
id='status.status.input.rpcEnabled'
defaultMessage='rpc enabled'
/>
}
value={
rpcSettings.enabled
? (
<FormattedMessage
id='status.status.input.yes'
defaultMessage='yes'
/>
)
: (
<FormattedMessage
id='status.status.input.no'
defaultMessage='no'
/>
)
}
{ ...this._test('rpc-enabled') }
/>
<div className={ styles.row }>
<div className={ styles.col6 }>
<Input
@@ -229,7 +242,6 @@ export default class Status extends Component {
/>
}
value={ rpcSettings.interface }
{ ...this._test('rpc-interface') }
/>
</div>
<div className={ styles.col6 }>
@@ -243,7 +255,6 @@ export default class Status extends Component {
/>
}
value={ rpcPort.toString() }
{ ...this._test('rpc-port') }
/>
</div>
</div>
@@ -259,8 +270,7 @@ export default class Status extends Component {
defaultMessage='enode'
/>
}
value={ nodeStatus.enode }
{ ...this._test('node-enode') }
value={ enode }
/>
</div>
</div>
@@ -268,3 +278,24 @@ export default class Status extends Component {
);
}
}
function mapStateToProps (state) {
const {
blockNumber,
blockTimestamp,
netChain,
netPeers
} = state.nodeStatus;
return {
blockNumber,
blockTimestamp,
netChain,
netPeers
};
}
export default connect(
mapStateToProps,
null
)(Status);

View File

@@ -0,0 +1,160 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { action, observable, transaction } from 'mobx';
export default class StatusStore {
@observable defaultExtraData = '';
@observable enode = '';
@observable hashrate = new BigNumber(0);
@observable netPort = new BigNumber(0);
@observable nodeName = '';
@observable rpcSettings = {};
@observable coinbase = '';
@observable extraData = '';
@observable gasFloorTarget = new BigNumber(0);
@observable minGasPrice = new BigNumber(0);
api = null;
_timeoutIds = {};
constructor (api) {
this.api = api;
}
@action setLongStatus ({ defaultExtraData, enode, netPort, rpcSettings }) {
transaction(() => {
this.defaultExtraData = defaultExtraData;
this.enode = enode;
this.netPort = netPort;
this.rpcSettings = rpcSettings;
});
}
@action setStatus ({ hashrate }) {
transaction(() => {
this.hashrate = hashrate;
});
}
@action setMinerSettings ({ coinbase, extraData, gasFloorTarget, minGasPrice }) {
transaction(() => {
this.coinbase = coinbase;
this.extraData = extraData;
this.gasFloorTarget = gasFloorTarget;
this.minGasPrice = minGasPrice;
});
}
startPolling () {
this._pollStatus();
this._pollLongStatus();
}
stopPolling () {
Object.keys(this._timeoutIds).forEach((key) => clearTimeout(this._timeoutIds[key]));
}
/**
* Miner settings should never changes unless
* Parity is restarted, or if the values are changed
* from the UI
*/
_pollMinerSettings () {
return Promise
.all([
this.api.eth.coinbase(),
this.api.parity.extraData(),
this.api.parity.gasFloorTarget(),
this.api.parity.minGasPrice()
])
.then(([
coinbase, extraData, gasFloorTarget, minGasPrice
]) => {
const minerSettings = {
coinbase,
extraData,
gasFloorTarget,
minGasPrice
};
this.setMinerSettings(minerSettings);
})
.catch((error) => {
console.error('_pollMinerSettings', error);
});
}
_pollStatus () {
const nextTimeout = (timeout = 1000) => {
clearTimeout(this._timeoutIds.short);
this._timeoutIds.short = setTimeout(() => this._pollStatus(), timeout);
};
return Promise
.all([
this.api.eth.hashrate()
])
.then(([
hashrate
]) => {
this.setStatus({
hashrate
});
})
.catch((error) => {
console.error('_pollStatus', error);
})
.then(() => {
nextTimeout();
});
}
_pollLongStatus () {
const nextTimeout = (timeout = 30000) => {
clearTimeout(this._timeoutIds.long);
this._timeoutIds.long = setTimeout(() => this._pollLongStatus(), timeout);
};
this._pollMinerSettings();
return Promise
.all([
this.api.parity.defaultExtraData(),
this.api.parity.enode().then((enode) => enode).catch(() => '-'),
this.api.parity.netPort(),
this.api.parity.rpcSettings()
])
.then(([
defaultExtraData, enode, netPort, rpcSettings
]) => {
this.setLongStatus({
defaultExtraData, enode, netPort, rpcSettings
});
})
.catch((error) => {
console.error('_pollLongStatus', error);
})
.then(() => {
nextTimeout();
});
}
handleUpdateSetting = () => {
return this._pollMinerSettings();
};
}

View File

@@ -1,22 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createAction } from 'redux-actions';
import { identity } from '../util';
import { withError } from '~/redux/util';
export const copyToClipboard = createAction('copy toClipboard', identity, withError(identity));

View File

@@ -1,23 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createAction } from 'redux-actions';
export const error = createAction('error');
export const updateDevLogs = createAction('update devLogs');
export const removeDevLogs = createAction('remove devLogs');
export const updateDevLogging = createAction('update devLogging');
export const updateDevLogsLevels = createAction('update devLogsLevels');

View File

@@ -1,24 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createAction } from 'redux-actions';
export const error = createAction('error');
export const updateAuthor = createAction('update author');
export const updateMinGasPrice = createAction('update minGasPrice');
export const updateGasFloorTarget = createAction('update gasFloorTarget');
export const updateExtraData = createAction('update extraData');
export const updateDefaultExtraData = createAction('update defaultExtraData');

View File

@@ -1,23 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createAction } from 'redux-actions';
export const modifyMinGasPrice = createAction('modify minGasPrice');
export const modifyGasFloorTarget = createAction('modify gasFloorTarget');
export const modifyAuthor = createAction('modify author');
export const modifyExtraData = createAction('modify extraData');
export const resetExtraData = createAction('reset extraData');

View File

@@ -1,29 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createAction } from 'redux-actions';
export const error = createAction('error');
export const updateHashrate = createAction('update hashrate');
export const updateBlockNumber = createAction('update blockNumber');
export const updateVersion = createAction('update version');
export const updatePeerCount = createAction('update peerCount');
export const updateNetPeers = createAction('update netPeers');
export const updateNetChain = createAction('update netChain');
export const updateNetPort = createAction('update netPort');
export const updateRpcSettings = createAction('update rpcSettings');
export const updateNodeName = createAction('update nodeName');
export const updateAccounts = createAction('update accounts');

View File

@@ -1,69 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from '~/redux/actions';
import Debug from '../../components/Debug';
import Status from '../../components/Status';
import styles from './statusPage.css';
class StatusPage extends Component {
static propTypes = {
nodeStatus: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
}
componentWillMount () {
this.props.actions.toggleStatusRefresh(true);
}
componentWillUnmount () {
this.props.actions.toggleStatusRefresh(false);
}
render () {
return (
<div className={ styles.body }>
<Status { ...this.props } />
<Debug { ...this.props } />
</div>
);
}
}
function mapStateToProps (state) {
return state;
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators({
clearStatusLogs,
toggleStatusLogs,
toggleStatusRefresh
}, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(StatusPage);

View File

@@ -1,66 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { handleActions } from 'redux-actions';
import { union } from 'lodash';
const initialState = {
levels: '',
logging: true,
logs: []
};
const maxLogs = 1024;
export const actionHandlers = {
'update devLogsLevels' (state, action) {
return {
...state,
levels: `${action.payload}`
};
},
'remove devLogs' (state, action) {
return {
...state,
logs: []
};
},
'update devLogging' (state, action) {
return {
...state,
logging: action.payload
};
},
'update devLogs' (state, action) {
if (!state.logging) {
return { ...state };
}
let newLogs = union(state.logs, action.payload.reverse());
return {
...state,
logs: newLogs.slice(newLogs.length - maxLogs)
};
}
};
export default handleActions(actionHandlers, initialState);

View File

@@ -1,31 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import status from './status';
import settings from './settings';
import mining from './mining';
import debug from './debug';
import rpc from './rpc';
import logger from './logger';
export {
status,
settings,
mining,
rpc,
logger,
debug
};

View File

@@ -1,66 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { handleActions } from 'redux-actions';
const initialState = {
author: 'loading...',
extraData: 'loading...',
defaultExtraData: '0x01',
minGasPrice: 'loading...',
gasFloorTarget: 'loading...'
};
export const actionHandlers = {
'update author' (state, action) {
return {
...state,
author: `${action.payload}`
};
},
'update minGasPrice' (state, action) {
return {
...state,
minGasPrice: `${action.payload}`
};
},
'update gasFloorTarget' (state, action) {
return {
...state,
gasFloorTarget: `${action.payload}`
};
},
'update extraData' (state, action) {
return {
...state,
extraData: `${action.payload}`
};
},
'update defaultExtraData' (state, action) {
return {
...state,
defaultExtraData: `${action.payload}`
};
}
};
export default handleActions(actionHandlers, initialState);

View File

@@ -1,60 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { handleActions } from 'redux-actions';
const initialState = {
chain: 'loading...',
networkPort: 0,
maxPeers: 0,
rpcEnabled: false,
rpcInterface: '-',
rpcPort: 0
};
export default handleActions({
'update netChain' (state, action) {
return {
...state,
chain: action.payload
};
},
'update netPort' (state, action) {
return {
...state,
networkPort: action.payload
};
},
'update netPeers' (state, action) {
return {
...state,
maxPeers: action.payload.max
};
},
'update rpcSettings' (state, action) {
const rpc = action.payload;
return {
...state,
rpcEnabled: rpc.enabled,
rpcInterface: rpc.interface,
rpcPort: rpc.port
};
}
}, initialState);

View File

@@ -1,92 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { handleActions } from 'redux-actions';
const initialState = {
error: false,
noOfErrors: 0,
name: 'My node',
bestBlock: 'loading...',
hashrate: 'loading...',
connectedPeers: 0,
activePeers: 0,
peers: 0,
accounts: [],
version: '-'
};
export default handleActions({
error (state, action) {
return {
...state,
disconnected: (action.payload.message === 'Invalid JSON RPC response: ""'),
noOfErrors: state.noOfErrors + 1
};
},
'update blockNumber' (state, action) {
return {
...resetError(state),
bestBlock: `${action.payload}`
};
},
'update hashrate' (state, action) {
return {
...resetError(state),
hashrate: `${action.payload}`
};
},
'update netPeers' (state, action) {
return {
...state,
connectedPeers: action.payload.connected,
activePeers: action.payload.active
};
},
'update version' (state, action) {
return {
...resetError(state),
version: action.payload
};
},
'update accounts' (state, action) {
return {
...resetError(state),
accounts: action.payload
};
},
'update nodeName' (state, action) {
return {
...resetError(state),
name: action.payload || ' '
};
}
}, initialState);
function resetError (state) {
return {
...state,
disconnected: false,
noOfErrors: 0
};
}

View File

@@ -14,26 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Page } from '~/ui';
import StatusPage from './containers/StatusPage';
import Debug from './Debug';
import Peers from './Peers';
import Status from './Status';
export default class Status extends Component {
render () {
return (
<Page
title={
<FormattedMessage
id='status.title'
defaultMessage='Status'
/>
}
>
<StatusPage />
</Page>
);
}
}
import styles from './status.css';
export default () => (
<Page
title={
<FormattedMessage
id='status.title'
defaultMessage='Status'
/>
}
>
<div className={ styles.body }>
<Status />
<Peers />
<Debug />
</div>
</Page>
);