Merge pull request #3505 from ethcore/check-updates

Auto-updating
This commit is contained in:
Gav Wood
2016-12-16 13:01:49 +01:00
committed by GitHub
108 changed files with 3766 additions and 470 deletions

View File

@@ -36,7 +36,7 @@
"ci:build:npm": "NODE_ENV=production webpack --config webpack/npm",
"start": "npm install && npm run build:lib && npm run build:dll && npm run start:app",
"start:app": "node webpack/dev.server",
"clean": "rm -rf ./build ./coverage",
"clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build",
"coveralls": "npm run testCoverage && coveralls < coverage/lcov.info",
"lint": "npm run lint:css && npm run lint:js",
"lint:cached": "npm run lint:css && npm run lint:js:cached",

View File

@@ -110,7 +110,8 @@ export function outPeers (peers) {
return {
active: outNumber(peers.active),
connected: outNumber(peers.connected),
max: outNumber(peers.max)
max: outNumber(peers.max),
peers: peers.peers.map(p => { Object.keys(p.protocols).forEach(k => { p.protocols[k].difficulty = outNumber(p.protocols[k].difficulty); }); return p; })
};
}

View File

@@ -147,10 +147,50 @@ describe('api/format/output', () => {
describe('outPeers', () => {
it('converts all internal numbers to BigNumbers', () => {
expect(outPeers({ active: 789, connected: '456', max: 0x7b })).to.deep.equal({
expect(outPeers({
active: 789,
connected: '456',
max: 0x7b,
peers: [
{
caps: ['par/1'],
id: '0x01',
name: 'Parity',
network: {
localAddress: '10.0.0.1',
remoteAddress: '10.0.0.1'
},
protocols: {
par: {
difficulty: '0x0f',
head: '0x02',
version: 63
}
}
}
]
})).to.deep.equal({
active: new BigNumber(789),
connected: new BigNumber(456),
max: new BigNumber(123)
max: new BigNumber(123),
peers: [
{
caps: ['par/1'],
id: '0x01',
name: 'Parity',
network: {
localAddress: '10.0.0.1',
remoteAddress: '10.0.0.1'
},
protocols: {
par: {
difficulty: new BigNumber(15),
head: '0x02',
version: 63
}
}
}
]
});
});
});

View File

@@ -54,6 +54,11 @@ export default class Parity {
.execute('parity_checkRequest', inNumber16(requestId));
}
consensusCapability () {
return this._transport
.execute('parity_consensusCapability');
}
dappsPort () {
return this._transport
.execute('parity_dappsPort')
@@ -90,6 +95,11 @@ export default class Parity {
.execute('parity_enode');
}
executeUpgrade () {
return this._transport
.execute('parity_executeUpgrade');
}
extraData () {
return this._transport
.execute('parity_extraData');
@@ -243,6 +253,11 @@ export default class Parity {
.then(outAddress);
}
releasesInfo () {
return this._transport
.execute('parity_releasesInfo');
}
removeReservedPeer (encode) {
return this._transport
.execute('parity_removeReservedPeer', encode);
@@ -315,4 +330,14 @@ export default class Parity {
.execute('parity_unsignedTransactionsCount')
.then(outNumber);
}
upgradeReady () {
return this._transport
.execute('parity_upgradeReady');
}
versionInfo () {
return this._transport
.execute('parity_versionInfo');
}
}

View File

@@ -80,7 +80,7 @@ describe('api/rpc/parity', () => {
describe('newPeers', () => {
it('returns the peer structure, formatted', () => {
mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]);
mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789, peers: [] } } }]);
return instance.netPeers().then((peers) => {
expect(peers.active.eq(123)).to.be.true;

View File

@@ -100,6 +100,15 @@ export default {
}
},
consensusCapability: {
desc: 'Returns an object or string detailing the state of parity capability of maintaining consensus',
params: [],
returns: {
type: Object,
desc: 'Either "capable", {"capableUntil":N}, {"incapableSince":N} or "unknown" (N is a block number)'
}
},
dappsPort: {
desc: 'Returns the port the dapps are running on, error if not enabled',
params: [],
@@ -163,6 +172,15 @@ export default {
}
},
executeUpgrade: {
desc: 'Performs an upgrade',
params: [],
returns: {
type: Boolean,
desc: 'returns true if the upgrade to the release specified in parity_upgradeReady was successfully executed, false if not'
}
},
extraData: {
desc: 'Returns currently set extra data',
params: [],
@@ -468,6 +486,15 @@ export default {
}
},
releasesInfo: {
desc: 'returns a ReleasesInfo object describing the current status of releases',
params: [],
returns: {
type: Object,
desc: '"fork":N,"minor":null,"this_fork":MN,"track":R} (N is a block number representing the latest known fork of this chain which may be in the future, MN is a block number representing the latest known fork that the currently running binary can sync past or null if not known, R is a ReleaseInfo object describing the latest release in this release track)'
}
},
removeReservedPeer: {
desc: '?',
params: [
@@ -651,5 +678,23 @@ export default {
type: Quantity,
desc: 'Number of unsigned transactions'
}
},
upgradeReady: {
desc: 'returns a ReleaseInfo object describing the release which is available for upgrade or null if none is available',
params: [],
returns: {
type: Object,
desc: '{"binary":H,"fork":15100,"is_critical":true,"version":V} where H is the Keccak-256 checksum of the release parity binary and V is a VersionInfo object describing the release'
}
},
versionInfo: {
desc: 'returns a VersionInfo object describing our current version',
params: [],
returns: {
type: Object,
desc: '{"hash":H,"track":T,"version":{"major":N,"minor":N,"patch":N}} (H is a 160-bit Git commit hash, T is a ReleaseTrack, either "stable", "beta", "nightly" or "unknown" and N is a version number)'
}
}
};

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './upgradeParity';

View File

@@ -0,0 +1,146 @@
// 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, computed, observable, transaction } from 'mobx';
import store from 'store';
const LS_UPDATE = '_parity::update';
const A_MINUTE = 60 * 1000;
const A_DAY = 24 * 60 * A_MINUTE;
const STEP_INFO = 0;
const STEP_UPDATING = 1;
const STEP_COMPLETED = 2;
const STEP_ERROR = 2;
const CHECK_INTERVAL = 1 * A_MINUTE;
export default class Store {
@observable available = null;
@observable consensusCapability = null;
@observable closed = true;
@observable error = null;
@observable remindAt = 0;
@observable step = 0;
@observable upgrading = null;
@observable version = null;
constructor (api) {
this._api = api;
this.loadStorage();
this.checkUpgrade();
setInterval(this.checkUpgrade, CHECK_INTERVAL);
}
@computed get isVisible () {
return !this.closed && Date.now() >= this.remindAt;
}
@action closeModal = () => {
transaction(() => {
this.closed = true;
this.setStep(0, null);
});
}
@action loadStorage = () => {
const values = store.get(LS_UPDATE) || {};
this.remindAt = values.remindAt ? values.remindAt : 0;
return values;
}
@action openModal = () => {
this.closed = false;
}
@action setStep = (step, error = null) => {
transaction(() => {
this.error = error;
this.step = step;
});
}
@action setUpgrading () {
transaction(() => {
this.upgrading = this.available;
this.setStep(STEP_UPDATING, null);
});
}
@action setVersions (available, version, consensusCapability) {
transaction(() => {
this.available = available;
this.consensusCapability = consensusCapability;
this.version = version;
});
}
@action snoozeTillTomorrow = () => {
this.remindAt = Date.now() + A_DAY;
store.set(LS_UPDATE, Object.assign(this.loadStorage(), { remindAt: this.remindAt }));
}
@action upgradeNow = () => {
this.setUpgrading();
return this._api.parity
.executeUpgrade()
.then((result) => {
if (!result) {
throw new Error('Unable to complete update');
}
this.setStep(STEP_COMPLETED, null);
})
.catch((error) => {
console.error('upgradeNow', error);
this.setStep(STEP_ERROR, error);
});
}
checkUpgrade = () => {
if (!this._api) {
return;
}
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);
});
}
}
export {
STEP_COMPLETED,
STEP_ERROR,
STEP_INFO,
STEP_UPDATING
};

View File

@@ -0,0 +1,58 @@
// 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 Store from './store';
let store;
describe('modals/UpgradeParity/store', () => {
describe('@actions', () => {
beforeEach(() => {
store = new Store();
});
describe('openModal & closeModal', () => {
it('toggles between the closed states', () => {
expect(store.closed).to.be.true;
store.openModal();
expect(store.closed).to.be.false;
store.closeModal();
expect(store.closed).to.be.true;
});
it('resets the step state upon closing', () => {
store.setStep(5, 'soem error');
store.closeModal();
expect(store.step).to.equal(0);
expect(store.error).to.be.null;
});
});
describe('setStep', () => {
it('sets the step as provided', () => {
expect(store.step).to.equal(0);
store.setStep(3);
expect(store.step).to.equal(3);
});
it('sets the error when provided', () => {
expect(store.error).to.be.null;
store.setStep(3, new Error('some error'));
expect(store.error).to.match(/some error/);
});
});
});
});

View File

@@ -0,0 +1,32 @@
/* 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/>.
*/
.error {
padding-top: 1.25em;
}
.infoStep {
div+div {
padding-top: 1.25em;
}
}
.version {
display: inline;
opacity: 0.5;
white-space: normal;
}

View File

@@ -0,0 +1,257 @@
// 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 { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { Button } from '~/ui';
import { CancelIcon, DoneIcon, NextIcon } from '~/ui/Icons';
import Modal, { Busy, Completed } from '~/ui/Modal';
import { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from './store';
import styles from './upgradeParity.css';
@observer
export default class UpgradeParity extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
};
static propTypes = {
store: PropTypes.object.isRequired
}
render () {
const { store } = this.props;
if (!store.isVisible) {
return null;
}
return (
<Modal
actions={ this.renderActions() }
current={ store.step }
steps={ [
<FormattedMessage
id='upgradeParity.step.info'
key='info'
defaultMessage='upgrade available' />,
<FormattedMessage
key='updating'
id='upgradeParity.step.updating'
defaultMessage='upgrading parity' />,
store.step === STEP_ERROR
? <FormattedMessage
id='upgradeParity.step.error'
key='error'
defaultMessage='error' />
: <FormattedMessage
id='upgradeParity.step.completed'
key='completed'
defaultMessage='upgrade completed' />
] }
visible>
{ this.renderStep() }
</Modal>
);
}
renderActions () {
const { store } = this.props;
const closeButton =
<Button
icon={ <CancelIcon /> }
key='close'
label={
<FormattedMessage
id='upgradeParity.button.close'
defaultMessage='close' />
}
onClick={ store.closeModal } />;
const doneButton =
<Button
icon={ <DoneIcon /> }
key='done'
label={
<FormattedMessage
id='upgradeParity.button.done'
defaultMessage='done' />
}
onClick={ store.closeModal } />;
switch (store.step) {
case STEP_INFO:
return [
<Button
icon={ <NextIcon /> }
key='upgrade'
label={
<FormattedMessage
id='upgradeParity.button.upgrade'
defaultMessage='upgrade now' />
}
onClick={ store.upgradeNow } />,
closeButton
];
case STEP_UPDATING:
return [
closeButton
];
case STEP_COMPLETED:
case STEP_ERROR:
return [
doneButton
];
}
}
renderStep () {
const { store } = this.props;
const currentversion = this.formatVersion(store);
const newversion = store.upgrading
? this.formatVersion(store.upgrading)
: this.formatVersion(store.available);
switch (store.step) {
case STEP_INFO:
return (
<div className={ styles.infoStep }>
<div>
<FormattedMessage
id='upgradeParity.info.upgrade'
defaultMessage='A new version of Parity, version {newversion} is available as an upgrade from your current version {currentversion}'
values={ {
currentversion: <div className={ styles.version }>{ currentversion }</div>,
newversion: <div className={ styles.version }>{ newversion }</div>
} } />
</div>
{ this.renderConsensusInfo() }
</div>
);
case STEP_UPDATING:
return (
<Busy
title={
<FormattedMessage
id='upgradeParity.busy'
defaultMessage='Your upgrade to Parity {newversion} is currently in progress'
values={ {
newversion: <div className={ styles.version }>{ newversion }</div>
} } />
} />
);
case STEP_COMPLETED:
case STEP_ERROR:
if (store.error) {
return (
<Completed>
<div>
<FormattedMessage
id='upgradeParity.failed'
defaultMessage='Your upgrade to Parity {newversion} has failed with an error.'
values={ {
newversion: <div className={ styles.version }>{ newversion }</div>
} } />
</div>
<div className={ styles.error }>
{ store.error.message }
</div>
</Completed>
);
}
return (
<Completed>
<FormattedMessage
id='upgradeParity.completed'
defaultMessage='Your upgrade to Parity {newversion} has been successfully completed.'
values={ {
newversion: <div className={ styles.version }>{ newversion }</div>
} } />
</Completed>
);
}
}
renderConsensusInfo () {
const { store } = this.props;
const { consensusCapability } = store;
if (consensusCapability) {
if (consensusCapability === 'capable') {
return (
<div>
<FormattedMessage
id='upgradeParity.consensus.capable'
defaultMessage='Your current Parity version is capable of handling the network requirements.' />
</div>
);
} else if (consensusCapability.capableUntil) {
return (
<div>
<FormattedMessage
id='upgradeParity.consensus.capableUntil'
defaultMessage='Your current Parity version is capable of handling the network requirements until block {blockNumber}'
values={ {
blockNumber: consensusCapability.capableUntil
} } />
</div>
);
} else if (consensusCapability.incapableSince) {
return (
<div>
<FormattedMessage
id='upgradeParity.consensus.incapableSince'
defaultMessage='Your current Parity version is incapable of handling the network requirements since block {blockNumber}'
values={ {
blockNumber: consensusCapability.incapableSince
} } />
</div>
);
}
}
return (
<div>
<FormattedMessage
id='upgradeParity.consensus.unknown'
defaultMessage='Your current Parity version is capable of handling the network requirements.' />
</div>
);
}
formatVersion (struct) {
if (!struct || !struct.version) {
return (
<FormattedMessage
id='upgradeParity.version.unknown'
defaultMessage='unknown' />
);
}
const { track, version } = struct.version;
return `${version.major}.${version.minor}.${version.patch}-${track}`;
}
}

View File

@@ -23,12 +23,13 @@ import DeployContract from './DeployContract';
import EditMeta from './EditMeta';
import ExecuteContract from './ExecuteContract';
import FirstRun from './FirstRun';
import LoadContract from './LoadContract';
import SaveContract from './SaveContract';
import Shapeshift from './Shapeshift';
import Verification from './Verification';
import Transfer from './Transfer';
import PasswordManager from './PasswordManager';
import SaveContract from './SaveContract';
import LoadContract from './LoadContract';
import UpgradeParity from './UpgradeParity';
import WalletSettings from './WalletSettings';
export {
@@ -41,11 +42,12 @@ export {
EditMeta,
ExecuteContract,
FirstRun,
LoadContract,
SaveContract,
Shapeshift,
Verification,
Transfer,
PasswordManager,
LoadContract,
SaveContract,
UpgradeParity,
WalletSettings
};

31
js/src/ui/Icons/index.js Normal file
View File

@@ -0,0 +1,31 @@
// 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 AddIcon from 'material-ui/svg-icons/content/add';
import CancelIcon from 'material-ui/svg-icons/content/clear';
import DoneIcon from 'material-ui/svg-icons/action/done-all';
import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back';
import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
import SnoozeIcon from 'material-ui/svg-icons/av/snooze';
export {
AddIcon,
CancelIcon,
DoneIcon,
PrevIcon,
NextIcon,
SnoozeIcon
};

View File

@@ -16,17 +16,19 @@
import React, { Component, PropTypes } from 'react';
import { nodeOrStringProptype } from '~/util/proptypes';
import styles from './busy.css';
export default class Busy extends Component {
static propTypes = {
title: PropTypes.string,
state: PropTypes.string,
children: PropTypes.node
children: PropTypes.node,
state: nodeOrStringProptype(),
title: nodeOrStringProptype()
}
render () {
const { children, title, state } = this.props;
const { children, state, title } = this.props;
return (
<div className={ styles.center }>

View File

@@ -36,7 +36,13 @@ export default class Title extends Component {
return (
<div className={ styles.title }>
<h3>{ steps ? steps[current] : title }</h3>
<h3>
{
steps
? steps[current]
: title
}
</h3>
{ this.renderSteps() }
{ this.renderWaiting() }
</div>
@@ -63,10 +69,10 @@ export default class Title extends Component {
renderTimeline () {
const { steps } = this.props;
return steps.map((label) => {
return steps.map((label, index) => {
return (
<Step
key={ label }>
key={ label.key || index }>
<StepLabel>
{ label }
</StepLabel>

View File

@@ -14,16 +14,17 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.actions {
background: rgba(0, 0, 0, 0.25) !important;
}
.actions button:not([disabled]) {
color: white !important;
}
button:not([disabled]) {
color: white !important;
.actions button:not([disabled]) svg {
fill: white !important;
svg {
fill: white !important;
}
}
}
.body {
@@ -36,26 +37,26 @@
.content {
transform: translate(0px, 0px) !important;
transition: none !important;
}
.content>div {
background: rgba(0, 0, 0, 0.5) !important;
transition: none !important;
&>div {
background: rgba(0, 0, 0, 0.5) !important;
transition: none !important;
}
}
.title {
background: rgba(0, 0, 0, 0.25) !important;
padding: 1em;
margin-bottom: 0;
background: rgba(0, 0, 0, 0.25) !important;
}
.title h3 {
margin: 0;
text-transform: uppercase;
}
h3 {
margin: 0;
text-transform: uppercase;
}
.title .steps {
margin-bottom: -1em;
.steps {
margin-bottom: -1em;
}
}
.waiting {

View File

@@ -14,10 +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 { Dialog } from 'material-ui';
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Dialog } from 'material-ui';
import { nodeOrStringProptype } from '~/util/proptypes';
@@ -51,7 +51,7 @@ class Modal extends Component {
render () {
const { muiTheme } = this.context;
const { actions, busy, className, current, children, compact, steps, waiting, title, visible, settings } = this.props;
const { actions, busy, children, className, current, compact, settings, steps, title, visible, waiting } = this.props;
const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed);
const header = (
<Title

View File

@@ -33,6 +33,7 @@ import Errors from './Errors';
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
import GasPriceEditor from './GasPriceEditor';
import GasPriceSelector from './GasPriceSelector';
import Icons from './Icons';
import IdentityIcon from './IdentityIcon';
import IdentityName from './IdentityName';
import LanguageSelector from './LanguageSelector';
@@ -72,6 +73,7 @@ export {
FormWrap,
GasPriceEditor,
GasPriceSelector,
Icons,
Input,
InputAddress,
InputAddressSelect,

View File

@@ -16,7 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { FirstRun } from '~/modals';
import { FirstRun, UpgradeParity } from '~/modals';
import { Errors, ParityBackground, Tooltips } from '~/ui';
import styles from '../application.css';
@@ -28,20 +28,24 @@ export default class Container extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
onCloseFirstRun: PropTypes.func,
showFirstRun: PropTypes.bool,
onCloseFirstRun: PropTypes.func
upgradeStore: PropTypes.object.isRequired
};
render () {
const { children, showFirstRun, onCloseFirstRun } = this.props;
const { muiTheme } = this.context;
const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props;
return (
<ParityBackground className={ styles.container } muiTheme={ muiTheme }>
<ParityBackground
className={ styles.container }
muiTheme={ muiTheme }>
<FirstRun
visible={ showFirstRun }
onClose={ onCloseFirstRun } />
onClose={ onCloseFirstRun }
visible={ showFirstRun } />
<Tooltips />
<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
};
}

View File

@@ -20,12 +20,12 @@
.row {
margin: 0 -1em;
}
.row::after {
display: table;
clear: both;
content: '';
&::after {
display: table;
clear: both;
content: '';
}
}
.blockInfo {
@@ -44,7 +44,7 @@
}
.col,
.col1, .col2, .col3, .col4, .col5, .col6, .col7, .col8, .col9, .col10, .col11, .col12 {
.col3, .col4_5, .col6, .col12 {
float: left;
padding: 0 1em;
box-sizing: border-box;
@@ -57,18 +57,11 @@
width: calc(100% / 12 * 3);
}
.col4 {
width: 33.33333%;
width: -webkit-calc(100% / 12 * 4);
width: -moz-calc(100% / 12 * 4);
width: calc(100% / 12 * 4);
}
.col5 {
width: 41.66665%;
width: -webkit-calc(100% / 12 * 5);
width: -moz-calc(100% / 12 * 5);
width: calc(100% / 12 * 5);
.col4_5 {
width: 37.5%;
width: -webkit-calc(100% / 12 * 4.5);
width: -moz-calc(100% / 12 * 4.5);
width: calc(100% / 12 * 4.5);
}
.col6 {

View File

@@ -68,13 +68,13 @@ export default class Status extends Component {
</div>
</div>
</div>
<div className={ styles.col5 }>
<div className={ styles.col4_5 }>
<MiningSettings
{ ...this._test('mining') }
nodeStatus={ nodeStatus }
actions={ this.props.actions } />
</div>
<div className={ styles.col4 }>
<div className={ styles.col4_5 }>
{ this.renderSettings() }
</div>
</div>
@@ -102,6 +102,7 @@ export default class Status extends Component {
<div { ...this._test('settings') }>
<ContainerTitle title='network settings' />
<Input
allowCopy
readOnly
label='chain'
value={ nodeStatus.netChain }
@@ -109,6 +110,7 @@ export default class Status extends Component {
<div className={ styles.row }>
<div className={ styles.col6 }>
<Input
allowCopy
readOnly
label='peers'
value={ peers }
@@ -116,6 +118,7 @@ export default class Status extends Component {
</div>
<div className={ styles.col6 }>
<Input
allowCopy
readOnly
label='network port'
value={ nodeStatus.netPort.toString() }
@@ -124,6 +127,7 @@ export default class Status extends Component {
</div>
<Input
allowCopy
readOnly
label='rpc enabled'
value={ rpcSettings.enabled ? 'yes' : 'no' }
@@ -131,6 +135,7 @@ export default class Status extends Component {
<div className={ styles.row }>
<div className={ styles.col6 }>
<Input
allowCopy
readOnly
label='rpc interface'
value={ rpcSettings.interface }
@@ -138,12 +143,24 @@ export default class Status extends Component {
</div>
<div className={ styles.col6 }>
<Input
allowCopy
readOnly
label='rpc port'
value={ rpcSettings.port.toString() }
{ ...this._test('rpc-port') } />
</div>
</div>
<div className={ styles.row }>
<div className={ styles.col12 }>
<Input
allowCopy
readOnly
label='enode'
value={ nodeStatus.enode }
{ ...this._test('node-enode') } />
</div>
</div>
</div>
);
}