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,41 +14,70 @@
// 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, computed, observable, transaction } from 'mobx';
import { action, observable, transaction } from 'mobx';
import store from 'store';
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_UPDATING = 2;
const STEP_COMPLETED = 3;
const STEP_ERROR = 4;
export default class ModalStore {
@observable closed = false;
const CHECK_INTERVAL = 1 * A_MINUTE;
export default class Store {
@observable available = null;
@observable consensusCapability = null;
@observable closed = true;
@observable error = null;
@observable step = 0;
@observable upgrade = null;
@observable upgrading = null;
@observable version = null;
constructor (upgradeStore) {
this.upgrade = upgradeStore;
constructor (api) {
this._api = api;
this.loadStorage();
this.checkUpgrade();
setInterval(this.checkUpgrade, CHECK_INTERVAL);
}
@computed get showUpgrade () {
return !this.closed && Date.now() >= this.remindAt;
}
@action closeModal = () => {
@action setUpgrading () {
transaction(() => {
this.closed = true;
this.setStep(STEP_INFO);
this.upgrading = this.available;
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);
});
}
@action loadStorage = () => {
const values = store.get(LS_UPDATE) || {};
@ -57,6 +86,10 @@ export default class ModalStore {
return values;
}
@action openModal = () => {
this.closed = false;
}
@action setStep = (step, error = null) => {
transaction(() => {
this.error = error;
@ -70,9 +103,9 @@ export default class ModalStore {
}
@action upgradeNow = () => {
this.setStep(STEP_UPDATING);
this.setUpgrading();
this.upgrade
return this._api.parity
.executeUpgrade()
.then((result) => {
if (!result) {

View File

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

View File

@ -14,55 +14,52 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.status {
position: fixed;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
bottom: 0;
color: #ccc;
display: flex;
font-size: 0.75em;
left: 0;
padding: .4em .5em;
position: fixed;
right: 0;
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 {
display: flex;
flex-grow: 1;
align-items: center;
color: #ddd;
}
flex-grow: 1;
text-align: right;
vertical-align: middle;
text-align: right;
.netinfo > * {
margin-left: 1em;
div {
display: inline-block;
margin-left: 1em;
}
}
.network {
padding: 0.25em 0.5em;
border-radius: .4em;
border-radius: 0.4em;
line-height: 1.2;
padding: 0.25em 0.5em;
text-transform: uppercase;
&.live {
background: rgb(0, 136, 0);
}
&.test {
background: rgb(136, 0, 0);
}
}
.networklive {
background: rgb(0, 136, 0);
}
.networktest {
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/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { BlockStatus } from '~/ui';
import CopyToClipboard from '~/ui/CopyToClipboard';
import styles from './status.css';
class Status extends Component {
static propTypes = {
blockNumber: PropTypes.object.isRequired,
clientVersion: PropTypes.string,
enode: PropTypes.string,
netPeers: PropTypes.object,
isTest: PropTypes.bool,
netChain: PropTypes.string,
isTest: PropTypes.bool
netPeers: PropTypes.object,
upgradeStore: PropTypes.object.isRequired
}
render () {
const { blockNumber, clientVersion, netChain, netPeers, isTest } = this.props;
const netStyle = `${styles.network} ${styles[isTest ? 'networktest' : 'networklive']}`;
if (!blockNumber) {
return null;
}
const { clientVersion, isTest, netChain, netPeers } = this.props;
return (
<div className={ styles.status }>
<div className={ styles.version }>
{ clientVersion }
</div>
<div className={ styles.upgrade }>
{ this.renderConsensus() }
{ this.renderUpgradeButton() }
</div>
<div className={ styles.netinfo }>
<BlockStatus />
<div className={ netStyle }>
{ isTest ? 'test' : netChain }
<div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }>
{ netChain }
</div>
<div className={ styles.peers }>
{ netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
</div>
</div>
{ this.renderEnode() }
</div>
);
}
renderEnode () {
const { enode } = this.props;
renderConsensus () {
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;
}
const [protocol, rest] = enode.split('://');
const [id, host] = rest.split('@');
const abbreviated = `${protocol}://${id.slice(0, 3)}${id.slice(-3)}@${host}`;
return (
<div className={ styles.enode }>
<CopyToClipboard data={ enode } size={ 12 } />
<div>{ abbreviated }</div>
<div>
<a
href='javascript:void(0)'
onClick={ upgradeStore.openModal }>
<FormattedMessage
id='application.status.upgrade'
defaultMessage='Upgrade' />
</a>
</div>
);
}
}
function mapStateToProps (state) {
const { blockNumber, clientVersion, enode, netPeers, netChain, isTest } = state.nodeStatus;
const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus;
return {
blockNumber,
clientVersion,
enode,
netPeers,
netChain,
isTest

View File

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

View File

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