Node Health warnings (#5951)
* Health endpoint. * Asynchronous health endpoint. * Configure time api URL via CLI. * Tests for TimeChecker. * Health indication on Status page. * Adding status indication to tab titles. * Add status to ParityBar. * Fixing lints. * Add health status on SyncWarning. * Fix health URL for embed. * Nicer messages. * Fix tests. * Fixing JS tests. * NTP time sync (#5956) * use NTP to check time drift * update time module documentation * replace time_api flag with ntp_server * fix TimeChecker tests * fix ntp-server flag usage * hide status tooltip if there's no message to show * remove TimeProvider trait * use Cell in FakeNtp test trait * share fetch client and ntp client cpu pool * Add documentation to public method. * Removing peer count from status. * Remove unknown upgrade status. * Send two time requests at the time. * Revert "Send two time requests at the time." This reverts commit f7b754b1155076a5a5d8fdafa022801fae324452. * Defer reporting time synchronization issues. * Fix tests. * Fix linting.
This commit is contained in:
@@ -94,6 +94,7 @@ class FrameSecureApi extends SecureApi {
|
||||
const transport = window.secureTransport || new FakeTransport();
|
||||
const uiUrl = transport.uiUrl || 'http://127.0.0.1:8180';
|
||||
|
||||
transport.uiUrlWithProtocol = uiUrl;
|
||||
transport.uiUrl = uiUrl.replace('http://', '').replace('https://', '');
|
||||
const api = new FrameSecureApi(transport);
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ import { statusBlockNumber, statusCollection } from './statusActions';
|
||||
const log = getLogger(LOG_KEYS.Signer);
|
||||
let instance = null;
|
||||
|
||||
const STATUS_OK = 'ok';
|
||||
const STATUS_WARN = 'needsAttention';
|
||||
const STATUS_BAD = 'bad';
|
||||
|
||||
export default class Status {
|
||||
_apiStatus = {};
|
||||
_status = {};
|
||||
@@ -195,13 +199,16 @@ export default class Status {
|
||||
|
||||
const statusPromises = [
|
||||
this._api.eth.syncing(),
|
||||
this._api.parity.netPeers()
|
||||
this._api.parity.netPeers(),
|
||||
this._fetchHealth()
|
||||
];
|
||||
|
||||
return Promise
|
||||
.all(statusPromises)
|
||||
.then(([ syncing, netPeers ]) => {
|
||||
const status = { netPeers, syncing };
|
||||
.then(([ syncing, netPeers, health ]) => {
|
||||
const status = { netPeers, syncing, health };
|
||||
|
||||
health.overall = this._overallStatus(health);
|
||||
|
||||
if (!isEqual(status, this._status)) {
|
||||
this._store.dispatch(statusCollection(status));
|
||||
@@ -216,6 +223,33 @@ export default class Status {
|
||||
});
|
||||
}
|
||||
|
||||
_overallStatus = (health) => {
|
||||
const all = [health.peers, health.sync, health.time].filter(x => x);
|
||||
const statuses = all.map(x => x.status);
|
||||
const bad = statuses.find(x => x === STATUS_BAD);
|
||||
const needsAttention = statuses.find(x => x === STATUS_WARN);
|
||||
const message = all.map(x => x.message).filter(x => x);
|
||||
|
||||
if (all.length) {
|
||||
return {
|
||||
status: bad || needsAttention || STATUS_OK,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: STATUS_BAD,
|
||||
message: ['Unable to fetch node health.']
|
||||
};
|
||||
}
|
||||
|
||||
_fetchHealth = () => {
|
||||
// Support Parity-Extension.
|
||||
const uiUrl = this._api.transport.uiUrlWithProtocol || '';
|
||||
|
||||
return fetch(`${uiUrl}/api/health`).then(res => res.json());
|
||||
}
|
||||
|
||||
/**
|
||||
* The data fetched here should not change
|
||||
* unless Parity is restarted. They are thus
|
||||
|
||||
@@ -18,11 +18,28 @@ import BigNumber from 'bignumber.js';
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
const DEFAULT_NETCHAIN = '(unknown)';
|
||||
const DEFAULT_STATUS = 'needsAttention';
|
||||
const initialState = {
|
||||
blockNumber: new BigNumber(0),
|
||||
blockTimestamp: new Date(),
|
||||
clientVersion: '',
|
||||
gasLimit: new BigNumber(0),
|
||||
health: {
|
||||
peers: {
|
||||
status: DEFAULT_STATUS
|
||||
},
|
||||
sync: {
|
||||
status: DEFAULT_STATUS
|
||||
},
|
||||
time: {
|
||||
status: DEFAULT_STATUS
|
||||
},
|
||||
overall: {
|
||||
isReady: false,
|
||||
status: DEFAULT_STATUS,
|
||||
message: []
|
||||
}
|
||||
},
|
||||
netChain: DEFAULT_NETCHAIN,
|
||||
netPeers: {
|
||||
active: new BigNumber(0),
|
||||
|
||||
@@ -31,7 +31,8 @@ export default class Actionbar extends Component {
|
||||
title: nodeOrStringProptype(),
|
||||
buttons: PropTypes.array,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
className: PropTypes.string,
|
||||
health: PropTypes.node
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
||||
17
js/src/ui/StatusIndicator/index.js
Normal file
17
js/src/ui/StatusIndicator/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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/>.
|
||||
|
||||
export default from './statusIndicator';
|
||||
88
js/src/ui/StatusIndicator/statusIndicator.css
Normal file
88
js/src/ui/StatusIndicator/statusIndicator.css
Normal file
@@ -0,0 +1,88 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
.status {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.radial,.signal {
|
||||
display: inline-block;
|
||||
margin: .2em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.radial {
|
||||
border-radius: 100%;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
background-image: radial-gradient(ellipse at top, rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
|
||||
&.ok {
|
||||
background-color: #070;
|
||||
}
|
||||
&.bad {
|
||||
background-color: #c00;
|
||||
}
|
||||
&.needsAttention {
|
||||
background-color: #dc0;
|
||||
}
|
||||
}
|
||||
|
||||
.signal {
|
||||
width: 2em;
|
||||
width: calc(.9em + 5px);
|
||||
text-transform: initial;
|
||||
vertical-align: bottom;
|
||||
margin-top: -1em;
|
||||
|
||||
> .bar {
|
||||
display: inline-block;
|
||||
border: 1px solid #444;
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0.8);
|
||||
width: .3em;
|
||||
height: 1em;
|
||||
opacity: 0.7;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
vertical-align: bottom;
|
||||
|
||||
&.active {
|
||||
opacity: 1.0;
|
||||
background-image: linear-gradient(0, rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
}
|
||||
|
||||
&.bad {
|
||||
height: .4em;
|
||||
border-right: 0;
|
||||
}
|
||||
&.needsAttention {
|
||||
height: .6em;
|
||||
border-right: 0;
|
||||
}
|
||||
&.ok {
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.bad > .bar.active {
|
||||
background-color: #c00;
|
||||
}
|
||||
&.ok > .bar.active {
|
||||
background-color: #080;
|
||||
}
|
||||
&.needsAttention > .bar.active {
|
||||
background-color: #dc0;
|
||||
}
|
||||
}
|
||||
70
js/src/ui/StatusIndicator/statusIndicator.js
Normal file
70
js/src/ui/StatusIndicator/statusIndicator.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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 ReactTooltip from 'react-tooltip';
|
||||
|
||||
import styles from './statusIndicator.css';
|
||||
|
||||
const statuses = ['bad', 'needsAttention', 'ok'];
|
||||
|
||||
export default class StatusIndicator extends Component {
|
||||
static propTypes = {
|
||||
type: PropTypes.oneOf(['radial', 'signal']),
|
||||
id: PropTypes.string.isRequired,
|
||||
status: PropTypes.oneOf(statuses).isRequired,
|
||||
title: PropTypes.arrayOf(PropTypes.node),
|
||||
tooltipPlacement: PropTypes.oneOf(['left', 'top', 'bottom', 'right'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
type: 'signal',
|
||||
title: []
|
||||
};
|
||||
|
||||
render () {
|
||||
const { id, status, title, type, tooltipPlacement } = this.props;
|
||||
const tooltip = title.find(x => !x.isEmpty) ? (
|
||||
<ReactTooltip id={ `status-${id}` }>
|
||||
{ title.map(x => (<div key={ x }>{ x }</div>)) }
|
||||
</ReactTooltip>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<span className={ styles.status }>
|
||||
<span className={ `${styles[type]} ${styles[status]}` }
|
||||
data-tip={ title.length }
|
||||
data-for={ `status-${id}` }
|
||||
data-place={ tooltipPlacement }
|
||||
data-effect='solid'
|
||||
>
|
||||
{ type === 'signal' && statuses.map(this.renderBar) }
|
||||
</span>
|
||||
{tooltip}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderBar = (signal) => {
|
||||
const idx = statuses.indexOf(this.props.status);
|
||||
const isActive = statuses.indexOf(signal) <= idx;
|
||||
const activeClass = isActive ? styles.active : '';
|
||||
|
||||
return (
|
||||
<span key={ signal } className={ `${styles.bar} ${styles[signal]} ${activeClass}` } />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ export SectionList from './SectionList';
|
||||
export SelectionList from './SelectionList';
|
||||
export ShortenedHash from './ShortenedHash';
|
||||
export SignerIcon from './SignerIcon';
|
||||
export StatusIndicator from './StatusIndicator';
|
||||
export Tags from './Tags';
|
||||
export Title from './Title';
|
||||
export Tooltips, { Tooltip } from './Tooltips';
|
||||
|
||||
@@ -43,6 +43,7 @@ class Accounts extends Component {
|
||||
accountsInfo: PropTypes.object.isRequired,
|
||||
availability: PropTypes.string.isRequired,
|
||||
hasAccounts: PropTypes.bool.isRequired,
|
||||
health: PropTypes.object.isRequired,
|
||||
setVisibleAccounts: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
@@ -496,12 +497,14 @@ class Accounts extends Component {
|
||||
function mapStateToProps (state) {
|
||||
const { accounts, accountsInfo, hasAccounts } = state.personal;
|
||||
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||
const { health } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
accounts,
|
||||
accountsInfo,
|
||||
availability,
|
||||
hasAccounts
|
||||
hasAccounts,
|
||||
health
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { BlockStatus } from '~/ui';
|
||||
import { BlockStatus, StatusIndicator } from '~/ui';
|
||||
|
||||
import styles from './status.css';
|
||||
|
||||
@@ -28,11 +28,12 @@ class Status extends Component {
|
||||
isTest: PropTypes.bool,
|
||||
netChain: PropTypes.string,
|
||||
netPeers: PropTypes.object,
|
||||
health: PropTypes.object,
|
||||
upgradeStore: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { clientVersion, isTest, netChain, netPeers } = this.props;
|
||||
const { clientVersion, isTest, netChain, netPeers, health } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.status }>
|
||||
@@ -44,13 +45,20 @@ class Status extends Component {
|
||||
{ this.renderUpgradeButton() }
|
||||
</div>
|
||||
<div className={ styles.netinfo }>
|
||||
<BlockStatus />
|
||||
<div>
|
||||
<StatusIndicator
|
||||
type='signal'
|
||||
id='application.status.health'
|
||||
status={ health.overall.status }
|
||||
title={ health.overall.message }
|
||||
/>
|
||||
</div>
|
||||
<span title={ `${netPeers.connected.toFormat()}/${netPeers.max.toFormat()} peers` }>
|
||||
<BlockStatus />
|
||||
</span>
|
||||
<div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }>
|
||||
{ netChain }
|
||||
</div>
|
||||
<div className={ styles.peers }>
|
||||
{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -102,14 +110,7 @@ class Status extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='application.status.consensus.unknown'
|
||||
defaultMessage='Upgrade status is unknown.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
renderUpgradeButton () {
|
||||
@@ -136,10 +137,11 @@ class Status extends Component {
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus;
|
||||
const { clientVersion, health, netPeers, netChain, isTest } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
clientVersion,
|
||||
health,
|
||||
netPeers,
|
||||
netChain,
|
||||
isTest
|
||||
|
||||
@@ -81,6 +81,16 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.indicatorTab {
|
||||
font-size: 1.5rem;
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
padding: 20px 12px 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-left: -24px;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Link } from 'react-router';
|
||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { Tooltip } from '~/ui';
|
||||
import { Tooltip, StatusIndicator } from '~/ui';
|
||||
|
||||
import Tab from './Tab';
|
||||
import styles from './tabBar.css';
|
||||
@@ -33,6 +33,7 @@ class TabBar extends Component {
|
||||
|
||||
static propTypes = {
|
||||
pending: PropTypes.array,
|
||||
health: PropTypes.object.isRequired,
|
||||
views: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
@@ -41,12 +42,29 @@ class TabBar extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { health } = this.props;
|
||||
|
||||
return (
|
||||
<Toolbar className={ styles.toolbar }>
|
||||
<ToolbarGroup className={ styles.first }>
|
||||
<div />
|
||||
</ToolbarGroup>
|
||||
<div className={ styles.tabs }>
|
||||
<Link
|
||||
activeClassName={ styles.tabactive }
|
||||
className={ `${styles.tabLink} ${styles.indicatorTab}` }
|
||||
key='status'
|
||||
to='/status'
|
||||
>
|
||||
<div className={ styles.indicator }>
|
||||
<StatusIndicator
|
||||
type='signal'
|
||||
id='topbar.health'
|
||||
status={ health.overall.status }
|
||||
title={ health.overall.message }
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
{ this.renderTabItems() }
|
||||
<Tooltip
|
||||
className={ styles.tabbarTooltip }
|
||||
@@ -101,6 +119,7 @@ function mapStateToProps (initState) {
|
||||
return (state) => {
|
||||
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||
const { views } = state.settings;
|
||||
const { health } = state.nodeStatus;
|
||||
|
||||
const viewIds = Object
|
||||
.keys(views)
|
||||
@@ -114,7 +133,7 @@ function mapStateToProps (initState) {
|
||||
});
|
||||
|
||||
if (isEqual(viewIds, filteredViewIds)) {
|
||||
return { views: filteredViews };
|
||||
return { views: filteredViews, health };
|
||||
}
|
||||
|
||||
filteredViewIds = viewIds;
|
||||
@@ -123,7 +142,7 @@ function mapStateToProps (initState) {
|
||||
id
|
||||
}));
|
||||
|
||||
return { views: filteredViews };
|
||||
return { views: filteredViews, health };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ function createStore () {
|
||||
nodeStatus: {
|
||||
nodeKind: {
|
||||
'availability': 'personal'
|
||||
},
|
||||
health: {
|
||||
overall: {
|
||||
status: 'ok',
|
||||
message: []
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ import { connect } from 'react-redux';
|
||||
import store from 'store';
|
||||
|
||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||
import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, ParityBackground, SelectionList } from '~/ui';
|
||||
import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, ParityBackground, SelectionList, StatusIndicator } from '~/ui';
|
||||
import { CancelIcon, FingerprintIcon } from '~/ui/Icons';
|
||||
import DappsStore from '~/views/Dapps/dappsStore';
|
||||
import { Embedded as Signer } from '~/views/Signer';
|
||||
@@ -50,7 +50,8 @@ class ParityBar extends Component {
|
||||
static propTypes = {
|
||||
dapp: PropTypes.bool,
|
||||
externalLink: PropTypes.string,
|
||||
pending: PropTypes.array
|
||||
pending: PropTypes.array,
|
||||
health: PropTypes.object
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -210,7 +211,7 @@ class ParityBar extends Component {
|
||||
}
|
||||
|
||||
renderBar () {
|
||||
const { dapp } = this.props;
|
||||
const { dapp, health } = this.props;
|
||||
|
||||
if (!dapp) {
|
||||
return null;
|
||||
@@ -218,6 +219,13 @@ class ParityBar extends Component {
|
||||
|
||||
return (
|
||||
<div className={ styles.cornercolor }>
|
||||
<StatusIndicator
|
||||
type='signal'
|
||||
id='paritybar.health'
|
||||
status={ health.overall.status }
|
||||
title={ health.overall.message }
|
||||
tooltipPlacement='right'
|
||||
/>
|
||||
<Button
|
||||
className={ styles.iconButton }
|
||||
icon={
|
||||
@@ -699,9 +707,11 @@ class ParityBar extends Component {
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { pending } = state.signer;
|
||||
const { health } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
pending
|
||||
pending,
|
||||
health
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,14 @@ function createRedux (state = {}) {
|
||||
},
|
||||
signer: {
|
||||
pending: []
|
||||
},
|
||||
nodeStatus: {
|
||||
health: {
|
||||
overall: {
|
||||
status: 'ok',
|
||||
message: []
|
||||
}
|
||||
}
|
||||
}
|
||||
}, state)
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||
import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon, StatusIcon } from '~/ui/Icons';
|
||||
import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon } from '~/ui/Icons';
|
||||
|
||||
import styles from './views.css';
|
||||
|
||||
@@ -65,14 +65,6 @@ const defaultViews = {
|
||||
value: 'contract'
|
||||
},
|
||||
|
||||
status: {
|
||||
active: false,
|
||||
onlyPersonal: true,
|
||||
icon: <StatusIcon />,
|
||||
route: '/status',
|
||||
value: 'status'
|
||||
},
|
||||
|
||||
signer: {
|
||||
active: true,
|
||||
fixed: true,
|
||||
|
||||
@@ -113,17 +113,6 @@ class Views extends Component {
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderView('status',
|
||||
<FormattedMessage
|
||||
id='settings.views.status.label'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='settings.views.status.description'
|
||||
defaultMessage='See how the Parity node is performing in terms of connections to the network, logs from the actual running instance and details of mining (if enabled and configured).'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderView('signer',
|
||||
<FormattedMessage
|
||||
|
||||
152
js/src/views/Status/Health/health.js
Normal file
152
js/src/views/Status/Health/health.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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, StatusIndicator } from '~/ui';
|
||||
|
||||
import grid from '../NodeStatus/nodeStatus.css';
|
||||
|
||||
const HealthItem = (props) => {
|
||||
const status = props.item.status || 'needsAttention';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>
|
||||
<StatusIndicator
|
||||
id={ props.id }
|
||||
title={ [
|
||||
(<div>{ props.item.message }</div>)
|
||||
] }
|
||||
status={ status }
|
||||
/>
|
||||
{ props.title }
|
||||
<small> ({ props.details })</small>
|
||||
</h3>
|
||||
<p>
|
||||
{ status !== 'ok' ? props.item.message : '' }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
HealthItem.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.node.isRequired,
|
||||
details: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
]).isRequired,
|
||||
item: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
class Health extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
peers: PropTypes.object.isRequired,
|
||||
sync: PropTypes.object.isRequired,
|
||||
time: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {};
|
||||
|
||||
render () {
|
||||
const { peers, sync, time } = this.props;
|
||||
const [yes, no] = [(
|
||||
<FormattedMessage
|
||||
id='status.health.yes'
|
||||
defaultMessage='yes'
|
||||
/>
|
||||
), (
|
||||
<FormattedMessage
|
||||
id='status.health.no'
|
||||
defaultMessage='no'
|
||||
/>
|
||||
)];
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ContainerTitle
|
||||
title={
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='status.health.title'
|
||||
defaultMessage='Node Health'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className={ grid.container }>
|
||||
<div className={ grid.row }>
|
||||
<div className={ grid.col4 }>
|
||||
<HealthItem
|
||||
id='status.health.sync'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='status.health.sync'
|
||||
defaultMessage='Chain Synchronized'
|
||||
/>
|
||||
}
|
||||
details={ !sync.details ? yes : no }
|
||||
item={ sync }
|
||||
/>
|
||||
</div>
|
||||
<div className={ grid.col4 }>
|
||||
<HealthItem
|
||||
id='status.health.peers'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='status.health.peers'
|
||||
defaultMessage='Connected Peers'
|
||||
/>
|
||||
}
|
||||
details={ (peers.details || []).join('/') }
|
||||
item={ peers }
|
||||
/>
|
||||
</div>
|
||||
<div className={ grid.col4 }>
|
||||
<HealthItem
|
||||
id='status.health.time'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='status.health.time'
|
||||
defaultMessage='Time Synchronized'
|
||||
/>
|
||||
}
|
||||
details={ `${time.details || 0} ms` }
|
||||
item={ time }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return state.nodeStatus.health;
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Health);
|
||||
17
js/src/views/Status/Health/index.js
Normal file
17
js/src/views/Status/Health/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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/>.
|
||||
|
||||
export default from './health';
|
||||
@@ -44,7 +44,7 @@
|
||||
}
|
||||
|
||||
.col,
|
||||
.col3, .col4_5, .col6, .col12 {
|
||||
.col3, .col4, .col4_5, .col6, .col12 {
|
||||
float: left;
|
||||
padding: 0 1em;
|
||||
box-sizing: border-box;
|
||||
@@ -57,6 +57,13 @@
|
||||
width: calc(100% / 12 * 3);
|
||||
}
|
||||
|
||||
.col4 {
|
||||
width: 33.3%;
|
||||
width: -webkit-calc(100% / 12 * 4);
|
||||
width: -moz-calc(100% / 12 * 4);
|
||||
width: calc(100% / 12 * 4);
|
||||
}
|
||||
|
||||
.col4_5 {
|
||||
width: 37.5%;
|
||||
width: -webkit-calc(100% / 12 * 4.5);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { Page } from '~/ui';
|
||||
|
||||
import Debug from './Debug';
|
||||
import Health from './Health';
|
||||
import Peers from './Peers';
|
||||
import NodeStatus from './NodeStatus';
|
||||
|
||||
@@ -35,6 +36,7 @@ export default () => (
|
||||
}
|
||||
>
|
||||
<div className={ styles.body }>
|
||||
<Health />
|
||||
<NodeStatus />
|
||||
<Peers />
|
||||
<Debug />
|
||||
|
||||
@@ -58,3 +58,7 @@
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import store from 'store';
|
||||
|
||||
import { Button } from '~/ui';
|
||||
import { Button, StatusIndicator } from '~/ui';
|
||||
|
||||
import styles from './syncWarning.css';
|
||||
|
||||
@@ -38,7 +38,8 @@ export const showSyncWarning = () => {
|
||||
|
||||
class SyncWarning extends Component {
|
||||
static propTypes = {
|
||||
isSyncing: PropTypes.bool
|
||||
isOk: PropTypes.bool.isRequired,
|
||||
health: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -47,10 +48,10 @@ class SyncWarning extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { isSyncing } = this.props;
|
||||
const { isOk, health } = this.props;
|
||||
const { dontShowAgain, show } = this.state;
|
||||
|
||||
if (!isSyncing || isSyncing === null || !show) {
|
||||
if (isOk || !show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,18 +60,19 @@ class SyncWarning extends Component {
|
||||
<div className={ styles.overlay } />
|
||||
<div className={ styles.modal }>
|
||||
<div className={ styles.body }>
|
||||
<FormattedMessage
|
||||
id='syncWarning.message.line1'
|
||||
defaultMessage={ `
|
||||
Your Parity node is still syncing to the chain.
|
||||
` }
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='syncWarning.message.line2'
|
||||
defaultMessage={ `
|
||||
Some of the shown information might be out-of-date.
|
||||
` }
|
||||
/>
|
||||
<div className={ styles.status }>
|
||||
<StatusIndicator
|
||||
type='signal'
|
||||
id='healthWarning.indicator'
|
||||
status={ health.overall.status }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
health.overall.message.map(message => (
|
||||
<p key={ message }>{ message }</p>
|
||||
))
|
||||
}
|
||||
|
||||
<div className={ styles.button }>
|
||||
<Checkbox
|
||||
@@ -113,14 +115,13 @@ class SyncWarning extends Component {
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { syncing } = state.nodeStatus;
|
||||
// syncing could be an Object, false, or null
|
||||
const isSyncing = syncing
|
||||
? true
|
||||
: syncing;
|
||||
const { health } = state.nodeStatus;
|
||||
const isNotAvailableYet = health.overall.isReady;
|
||||
const isOk = isNotAvailableYet || health.overall.status === 'ok';
|
||||
|
||||
return {
|
||||
isSyncing
|
||||
isOk,
|
||||
health
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,12 @@ function createRedux (syncing = null) {
|
||||
getState: () => {
|
||||
return {
|
||||
nodeStatus: {
|
||||
syncing
|
||||
health: {
|
||||
overall: {
|
||||
status: syncing ? 'needsAttention' : 'ok',
|
||||
message: []
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user