Display capability status on statusbar

This commit is contained in:
Jaco Greeff 2016-12-14 14:37:25 +01:00
parent d28ec2ff97
commit a8f428ccca
8 changed files with 202 additions and 183 deletions

View File

@ -14,38 +14,67 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, computed, observable, transaction } from 'mobx'; import { action, observable, transaction } from 'mobx';
import store from 'store'; import store from 'store';
const LS_UPDATE = '_parity::update'; const LS_UPDATE = '_parity::update';
const A_DAY = 24 * 60 * 60 * 1000; const A_MINUTE = 60 * 1000;
const A_DAY = 24 * 60 * A_MINUTE;
const STEP_INFO = 1; const STEP_INFO = 1;
const STEP_UPDATING = 2; const STEP_UPDATING = 2;
const STEP_COMPLETED = 3; const STEP_COMPLETED = 3;
const STEP_ERROR = 4; const STEP_ERROR = 4;
export default class ModalStore { const CHECK_INTERVAL = 1 * A_MINUTE;
@observable closed = false;
export default class Store {
@observable available = null;
@observable consensusCapability = null;
@observable closed = true;
@observable error = null; @observable error = null;
@observable step = 0; @observable step = 0;
@observable upgrade = null; @observable upgrading = null;
@observable version = null;
constructor (upgradeStore) { constructor (api) {
this.upgrade = upgradeStore; this._api = api;
this.loadStorage(); this.loadStorage();
this.checkUpgrade();
setInterval(this.checkUpgrade, CHECK_INTERVAL);
} }
@computed get showUpgrade () { @action setUpgrading () {
return !this.closed && Date.now() >= this.remindAt;
}
@action closeModal = () => {
transaction(() => { transaction(() => {
this.closed = true; this.upgrading = this.available;
this.setStep(STEP_INFO); this.setStep(STEP_UPDATING);
});
}
@action setVersions (available, version, consensusCapability) {
transaction(() => {
this.available = available;
this.consensusCapability = consensusCapability;
this.version = version;
});
}
checkUpgrade = () => {
Promise
.all([
this._api.parity.upgradeReady(),
this._api.parity.consensusCapability(),
this._api.parity.versionInfo()
])
.then(([available, consensusCapability, version]) => {
console.log('[checkUpgrade]', 'available:', available, 'version:', version, 'consensusCapability:', consensusCapability);
this.setVersions(available, version, consensusCapability);
})
.catch((error) => {
console.warn('checkUpgrade', error);
}); });
} }
@ -57,6 +86,10 @@ export default class ModalStore {
return values; return values;
} }
@action openModal = () => {
this.closed = false;
}
@action setStep = (step, error = null) => { @action setStep = (step, error = null) => {
transaction(() => { transaction(() => {
this.error = error; this.error = error;
@ -70,9 +103,9 @@ export default class ModalStore {
} }
@action upgradeNow = () => { @action upgradeNow = () => {
this.setStep(STEP_UPDATING); this.setUpgrading();
this.upgrade return this._api.parity
.executeUpgrade() .executeUpgrade()
.then((result) => { .then((result) => {
if (!result) { if (!result) {

View File

@ -22,8 +22,7 @@ import { Button } from '~/ui';
import { CancelIcon, DoneIcon, NextIcon, SnoozeIcon } from '~/ui/Icons'; import { CancelIcon, DoneIcon, NextIcon, SnoozeIcon } from '~/ui/Icons';
import Modal, { Busy, Completed } from '~/ui/Modal'; import Modal, { Busy, Completed } from '~/ui/Modal';
import ModalStore, { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from './modalStore'; import { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from './store';
import UpgradeStore from './upgradeStore';
import styles from './upgradeParity.css'; import styles from './upgradeParity.css';
@observer @observer
@ -32,17 +31,21 @@ export default class UpgradeParity extends Component {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
}; };
store = new ModalStore(new UpgradeStore(this.context.api)); static propTypes = {
store: PropTypes.object.isRequired
}
render () { render () {
if (!this.store.upgrade.available || !this.store.showUpgrade) { const { store } = this.props;
if (!store.showUpgrade) {
return null; return null;
} }
return ( return (
<Modal <Modal
actions={ this.renderActions() } actions={ this.renderActions() }
current={ this.store.step } current={ store.step }
steps={ [ steps={ [
<FormattedMessage <FormattedMessage
id='upgradeParity.step.info' id='upgradeParity.step.info'
@ -50,7 +53,7 @@ export default class UpgradeParity extends Component {
<FormattedMessage <FormattedMessage
id='upgradeParity.step.updating' id='upgradeParity.step.updating'
defaultMessage='upgrading parity' />, defaultMessage='upgrading parity' />,
this.store.step === STEP_ERROR store.step === STEP_ERROR
? <FormattedMessage ? <FormattedMessage
id='upgradeParity.step.completed' id='upgradeParity.step.completed'
defaultMessage='upgrade completed' /> defaultMessage='upgrade completed' />
@ -65,6 +68,8 @@ export default class UpgradeParity extends Component {
} }
renderActions () { renderActions () {
const { store } = this.props;
const closeButton = const closeButton =
<Button <Button
icon={ <CancelIcon /> } icon={ <CancelIcon /> }
@ -73,7 +78,7 @@ export default class UpgradeParity extends Component {
id='upgradeParity.button.close' id='upgradeParity.button.close'
defaultMessage='close' /> defaultMessage='close' />
} }
onClick={ this.store.closeModal } />; onClick={ store.closeModal } />;
const doneButton = const doneButton =
<Button <Button
icon={ <DoneIcon /> } icon={ <DoneIcon /> }
@ -82,9 +87,9 @@ export default class UpgradeParity extends Component {
id='upgradeParity.button.done' id='upgradeParity.button.done'
defaultMessage='done' /> defaultMessage='done' />
} }
onClick={ this.store.closeModal } />; onClick={ store.closeModal } />;
switch (this.store.step) { switch (store.step) {
case STEP_INFO: case STEP_INFO:
return [ return [
<Button <Button
@ -94,7 +99,7 @@ export default class UpgradeParity extends Component {
id='upgradeParity.button.snooze' id='upgradeParity.button.snooze'
defaultMessage='ask me tomorrow' /> defaultMessage='ask me tomorrow' />
} }
onClick={ this.store.snoozeTillTomorrow } />, onClick={ store.snoozeTillTomorrow } />,
<Button <Button
icon={ <NextIcon /> } icon={ <NextIcon /> }
label={ label={
@ -102,7 +107,7 @@ export default class UpgradeParity extends Component {
id='upgradeParity.button.upgrade' id='upgradeParity.button.upgrade'
defaultMessage='upgrade now' /> defaultMessage='upgrade now' />
} }
onClick={ this.store.upgradeNow } />, onClick={ store.upgradeNow } />,
closeButton closeButton
]; ];
@ -120,17 +125,17 @@ export default class UpgradeParity extends Component {
} }
renderStep () { renderStep () {
const { available, consensusCapability, error, upgrading, version } = this.store.upgrade; const { store } = this.props;
const currentversion = this.renderVersion(version); const currentversion = this.renderVersion(store.version);
const newversion = upgrading const newversion = store.upgrading
? this.renderVersion(upgrading.version) ? this.renderVersion(store.upgrading.version)
: this.renderVersion(available.version); : this.renderVersion(store.available.version);
switch (this.store.step) { switch (store.step) {
case STEP_INFO: case STEP_INFO:
let consensusInfo = null; let consensusInfo = null;
if (consensusCapability === 'capable') { if (store.consensusCapability === 'capable') {
consensusInfo = ( consensusInfo = (
<div> <div>
<FormattedMessage <FormattedMessage
@ -138,25 +143,25 @@ export default class UpgradeParity extends Component {
defaultMessage='Your current Parity version is capable of handling the nework requirements.' /> defaultMessage='Your current Parity version is capable of handling the nework requirements.' />
</div> </div>
); );
} else if (consensusCapability.capableUntil) { } else if (store.consensusCapability.capableUntil) {
consensusInfo = ( consensusInfo = (
<div> <div>
<FormattedMessage <FormattedMessage
id='upgradeParity.consensus.capableUntil' id='upgradeParity.consensus.capableUntil'
defaultMessage='Your current Parity version is capable of handling the nework requirements until block {blockNumber}' defaultMessage='Your current Parity version is capable of handling the nework requirements until block {blockNumber}'
values={ { values={ {
blockNumber: consensusCapability.capableUntil blockNumber: store.consensusCapability.capableUntil
} } /> } } />
</div> </div>
); );
} else if (consensusCapability.incapableSince) { } else if (store.consensusCapability.incapableSince) {
consensusInfo = ( consensusInfo = (
<div> <div>
<FormattedMessage <FormattedMessage
id='upgradeParity.consensus.incapableSince' id='upgradeParity.consensus.incapableSince'
defaultMessage='Your current Parity version is incapable of handling the nework requirements since block {blockNumber}' defaultMessage='Your current Parity version is incapable of handling the nework requirements since block {blockNumber}'
values={ { values={ {
blockNumber: consensusCapability.incapableSince blockNumber: store.consensusCapability.incapableSince
} } /> } } />
</div> </div>
); );
@ -214,7 +219,7 @@ export default class UpgradeParity extends Component {
} } /> } } />
</div> </div>
<div className={ styles.error }> <div className={ styles.error }>
{ error.message } { store.error.message }
</div> </div>
</Completed> </Completed>
); );

View File

@ -1,67 +0,0 @@
// Copyright 2015, 2016 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 CHECK_INTERVAL = 1 * 60 * 1000;
export default class UpgradeStore {
@observable available = null;
@observable consensusCapability = null;
@observable upgrading = null;
@observable version = null;
constructor (api) {
this._api = api;
this.checkUpgrade();
setInterval(this.checkUpgrade, CHECK_INTERVAL);
}
@action setUpgrading () {
this.upgrading = this.available;
}
@action setVersions (available, version, consensusCapability) {
transaction(() => {
this.available = available;
this.consensusCapability = consensusCapability;
this.version = version;
});
}
checkUpgrade = () => {
Promise
.all([
this._api.parity.upgradeReady(),
this._api.parity.consensusCapability(),
this._api.parity.versionInfo()
])
.then(([available, consensusCapability, version]) => {
console.log('[checkUpgrade]', 'available:', available, 'version:', version, 'consensusCapability:', consensusCapability);
this.setVersions(available, version, consensusCapability);
})
.catch((error) => {
console.warn('checkUpgrade', error);
});
}
executeUpgrade = () => {
this.setUpgrading();
return this._api.parity.executeUpgrade();
}
}

View File

@ -29,20 +29,23 @@ export default class Container extends Component {
static propTypes = { static propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
onCloseFirstRun: PropTypes.func, onCloseFirstRun: PropTypes.func,
showFirstRun: PropTypes.bool showFirstRun: PropTypes.bool,
upgradeStore: PropTypes.object.isRequired
}; };
render () { render () {
const { muiTheme } = this.context; const { muiTheme } = this.context;
const { children, onCloseFirstRun, showFirstRun } = this.props; const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props;
return ( return (
<ParityBackground className={ styles.container } muiTheme={ muiTheme }> <ParityBackground
className={ styles.container }
muiTheme={ muiTheme }>
<FirstRun <FirstRun
onClose={ onCloseFirstRun } onClose={ onCloseFirstRun }
visible={ showFirstRun } /> visible={ showFirstRun } />
<Tooltips /> <Tooltips />
<UpgradeParity /> <UpgradeParity store={ upgradeStore } />
<Errors /> <Errors />
{ children } { children }
</ParityBackground> </ParityBackground>

View File

@ -14,55 +14,52 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.status { .status {
position: fixed; align-items: center;
background-color: rgba(0, 0, 0, 0.8);
bottom: 0; bottom: 0;
color: #ccc;
display: flex;
font-size: 0.75em;
left: 0; left: 0;
padding: .4em .5em;
position: fixed;
right: 0; right: 0;
z-index: 1000; z-index: 1000;
display: flex;
align-items: center;
padding: .4em .5em;
font-size: x-small;
color: #ccc;
background-color: rgba(0, 0, 0, 0.8);
}
.enode {
word-wrap: break-word;
}
.enode > * {
display: inline-block;
margin: 0 .25em;
vertical-align: middle;
}
.enode > :last-child {
margin-right: 0;
} }
.netinfo { .netinfo {
display: flex;
flex-grow: 1;
align-items: center;
color: #ddd; color: #ddd;
} flex-grow: 1;
text-align: right;
vertical-align: middle;
text-align: right;
.netinfo > * { div {
display: inline-block;
margin-left: 1em; margin-left: 1em;
} }
}
.network { .network {
padding: 0.25em 0.5em; border-radius: 0.4em;
border-radius: .4em;
line-height: 1.2; line-height: 1.2;
padding: 0.25em 0.5em;
text-transform: uppercase; text-transform: uppercase;
}
.networklive { &.live {
background: rgb(0, 136, 0); background: rgb(0, 136, 0);
} }
.networktest { &.test {
background: rgb(136, 0, 0); background: rgb(136, 0, 0);
} }
}
.upgrade {
div {
display: inline-block;
margin-left: 1em;
}
}

View File

@ -15,78 +15,118 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { BlockStatus } from '~/ui'; import { BlockStatus } from '~/ui';
import CopyToClipboard from '~/ui/CopyToClipboard';
import styles from './status.css'; import styles from './status.css';
class Status extends Component { class Status extends Component {
static propTypes = { static propTypes = {
blockNumber: PropTypes.object.isRequired,
clientVersion: PropTypes.string, clientVersion: PropTypes.string,
enode: PropTypes.string, isTest: PropTypes.bool,
netPeers: PropTypes.object,
netChain: PropTypes.string, netChain: PropTypes.string,
isTest: PropTypes.bool netPeers: PropTypes.object,
upgradeStore: PropTypes.object.isRequired
} }
render () { render () {
const { blockNumber, clientVersion, netChain, netPeers, isTest } = this.props; const { clientVersion, isTest, netChain, netPeers } = this.props;
const netStyle = `${styles.network} ${styles[isTest ? 'networktest' : 'networklive']}`;
if (!blockNumber) {
return null;
}
return ( return (
<div className={ styles.status }> <div className={ styles.status }>
<div className={ styles.version }> <div className={ styles.version }>
{ clientVersion } { clientVersion }
</div> </div>
<div className={ styles.upgrade }>
{ this.renderConsensus() }
{ this.renderUpgradeButton() }
</div>
<div className={ styles.netinfo }> <div className={ styles.netinfo }>
<BlockStatus /> <BlockStatus />
<div className={ netStyle }> <div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }>
{ isTest ? 'test' : netChain } { netChain }
</div> </div>
<div className={ styles.peers }> <div className={ styles.peers }>
{ netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers { netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
</div> </div>
</div> </div>
{ this.renderEnode() }
</div> </div>
); );
} }
renderEnode () { renderConsensus () {
const { enode } = this.props; const { upgradeStore } = this.props;
if (!enode) { if (upgradeStore.consensusCapability === 'capable') {
return (
<div>
<FormattedMessage
id='application.status.consensus.capable'
defaultMessage='Capable' />
</div>
);
} else if (upgradeStore.consensusCapability.capableUntil) {
return (
<div>
<FormattedMessage
id='application.status.consensus.capableUntil'
defaultMessage='Capable until #{blockNumber}'
values={ {
blockNumber: upgradeStore.consensusCapability.capableUntil
} } />
</div>
);
} else if (upgradeStore.consensusCapability.incapableSince) {
return (
<div>
<FormattedMessage
id='application.status.consensus.incapableSince'
defaultMessage='Incapable since #{blockNumber}'
values={ {
blockNumber: upgradeStore.consensusCapability.incapableSince
} } />
</div>
);
}
return (
<div>
<FormattedMessage
id='application.status.consensus.unknown'
defaultMessage='Unknown capability' />
</div>
);
}
renderUpgradeButton () {
const { upgradeStore } = this.props;
if (!upgradeStore.available) {
return null; return null;
} }
const [protocol, rest] = enode.split('://');
const [id, host] = rest.split('@');
const abbreviated = `${protocol}://${id.slice(0, 3)}${id.slice(-3)}@${host}`;
return ( return (
<div className={ styles.enode }> <div>
<CopyToClipboard data={ enode } size={ 12 } /> <a
<div>{ abbreviated }</div> href='javascript:void(0)'
onClick={ upgradeStore.openModal }>
<FormattedMessage
id='application.status.upgrade'
defaultMessage='Upgrade' />
</a>
</div> </div>
); );
} }
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { blockNumber, clientVersion, enode, netPeers, netChain, isTest } = state.nodeStatus; const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus;
return { return {
blockNumber,
clientVersion, clientVersion,
enode,
netPeers, netPeers,
netChain, netChain,
isTest isTest

View File

@ -22,5 +22,5 @@
} }
.content { .content {
padding-bottom: 1em; padding-bottom: 1.25em;
} }

View File

@ -19,6 +19,8 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import UpgradeStore from '~/modals/UpgradeParity/store';
import Connection from '../Connection'; import Connection from '../Connection';
import ParityBar from '../ParityBar'; import ParityBar from '../ParityBar';
@ -42,14 +44,15 @@ class Application extends Component {
} }
static propTypes = { static propTypes = {
blockNumber: PropTypes.object,
children: PropTypes.node, children: PropTypes.node,
netChain: PropTypes.string,
isTest: PropTypes.bool, isTest: PropTypes.bool,
pending: PropTypes.array, netChain: PropTypes.string,
blockNumber: PropTypes.object pending: PropTypes.array
} }
store = new Store(this.context.api); store = new Store(this.context.api);
upgradeStore = new UpgradeStore(this.context.api);
render () { render () {
const [root] = (window.location.hash || '').replace('#/', '').split('/'); const [root] = (window.location.hash || '').replace('#/', '').split('/');
@ -71,12 +74,13 @@ class Application extends Component {
} }
renderApp () { renderApp () {
const { children, pending, netChain, isTest, blockNumber } = this.props; const { blockNumber, children, pending, netChain, isTest } = this.props;
return ( return (
<Container <Container
showFirstRun={ this.store.firstrunVisible } upgradeStore={ this.upgradeStore }
onCloseFirstRun={ this.store.closeFirstrun }> onCloseFirstRun={ this.store.closeFirstrun }
showFirstRun={ this.store.firstrunVisible }>
<TabBar <TabBar
netChain={ netChain } netChain={ netChain }
isTest={ isTest } isTest={ isTest }
@ -84,7 +88,11 @@ class Application extends Component {
<div className={ styles.content }> <div className={ styles.content }>
{ children } { children }
</div> </div>
{ blockNumber ? (<Status />) : null } {
blockNumber
? <Status upgradeStore={ this.upgradeStore } />
: null
}
<Snackbar /> <Snackbar />
</Container> </Container>
); );
@ -102,16 +110,16 @@ class Application extends Component {
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { netChain, isTest, blockNumber } = state.nodeStatus; const { blockNumber, netChain, isTest } = state.nodeStatus;
const { hasAccounts } = state.personal; const { hasAccounts } = state.personal;
const { pending } = state.signer; const { pending } = state.signer;
return { return {
blockNumber,
hasAccounts, hasAccounts,
netChain,
isTest, isTest,
pending, netChain,
blockNumber pending
}; };
} }