diff --git a/package.json b/package.json index 92579d96d6a80dc8934401d7d2232688f68b43c2..a7c6f97f46b6d5c0725b7210eb462002ada0595f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "@parity/api": "^2.1.22", - "@parity/light.js": "https://github.com/parity-js/light.js#0c57c29de42122b0f07ff82dfaf38b48863d8d2e", + "@parity/light.js": "https://github.com/parity-js/light.js#aa03e7f2956718bf91f6f82771eee3b81d260a18", "async-retry": "^1.2.1", "axios": "^0.18.0", "checksum": "^0.1.1", diff --git a/src/Health/Health.js b/src/Health/Health.js index a4793bd2d984aa4da4cbe32202872f259c4e536a..8b4b3f9f60fbd783fbcae3bc3b6ae28d1dc53bd2 100644 --- a/src/Health/Health.js +++ b/src/Health/Health.js @@ -3,21 +3,69 @@ // // SPDX-License-Identifier: MIT -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; -class Health extends PureComponent { +import { STATUS } from '../stores/healthStore'; + +@inject('healthStore') +@observer +class Health extends Component { render () { return (
- + - Syncing... (4m) + + {this.statusToFriendlyMessage()} +
); } + + /** + * Get className from the status icon from the status enum + */ + statusToClassName = () => { + const { healthStore: { health: { status } } } = this.props; + switch (status) { + case STATUS.GOOD: + return '-good'; + case STATUS.DOWNLOADING: + case STATUS.RUNNING: + case STATUS.SYNCING: + return '-syncing'; + default: + return '-bad'; + } + }; + + statusToFriendlyMessage = () => { + const { healthStore: { health: { status, payload } } } = this.props; + switch (status) { + case STATUS.CANTCONNECT: + return "Can't connect to Parity"; + case STATUS.CLOCKNOTSYNC: + return 'Clock not sync'; + case STATUS.DOWNLOADING: + return `Downloading... (${payload.percentage}%)`; + case STATUS.GOOD: + return 'Synced'; + case STATUS.NOINTERNET: + return 'No internet'; + case STATUS.RUNNING: + return 'Running...'; + case STATUS.SYNCING: + return `Syncing...${payload.percentage + ? ` (${payload.percentage}%)` + : ''}`; + default: + return JSON.stringify(payload); // Just in case payload is an object + } + }; } export default Health; diff --git a/src/Loading/Loading.js b/src/Loading/Loading.js index fb18067ee81dd38672d92c32d0fcfc8305ed2bca..cd9f00622d13d4c7d71886bca207ece4b12f6322 100644 --- a/src/Loading/Loading.js +++ b/src/Loading/Loading.js @@ -5,48 +5,39 @@ import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; -import { Redirect } from 'react-router-dom'; +import { Link, Redirect } from 'react-router-dom'; + +import Health from '../Health'; @inject('parityStore') @observer class Loading extends Component { render () { - const { parityStore: { downloadProgress, isApiConnected } } = this.props; + const { parityStore: { isApiConnected } } = this.props; if (isApiConnected) { return ; } return ( -
+

This is the Loading page.

-
    -
  • -

    1. DL and install parity

    -
    -              progress: {Math.round(downloadProgress * 100)}%
    status:{' '} - {this.renderStatus()} -
    -
  • -
+ +
); } - - renderStatus = () => { - const { parityStore: { downloadProgress, isParityRunning } } = this.props; - - if (isParityRunning) { - return 'Connecting to API...'; - } else if (downloadProgress) { - return 'Downloading...'; - } else { - // We should be in browser now - return 'Please run:\nparity --light --ws-origins all'; - } - }; } export default Loading; diff --git a/src/stores/healthStore.js b/src/stores/healthStore.js new file mode 100644 index 0000000000000000000000000000000000000000..afae19ecef4a4588706ccb5979b54589fee64e4f --- /dev/null +++ b/src/stores/healthStore.js @@ -0,0 +1,152 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +import { action, computed, observable } from 'mobx'; +import { nodeHealth$, syncing$ } from '@parity/light.js'; + +import parityStore from './parityStore'; + +// List here all possible states of our health store. Each state can have a +// payload. +export const STATUS = { + CANTCONNECT: 'CANTCONNECT', // Can't connect to Parity's api + CLOCKNOTSYNC: 'CLOCKNOTSYNC', // Local clock is not sync + DOWNLOADING: 'DOWNLOADING', // Currently downloading Parity + GOOD: 'GOOD', // Everything's fine + NOINTERNET: 'NOINTERNET', // No network connection + OTHER: 'OTHER', // Unknown state, might have a payload + RUNNING: 'RUNNING', // Currently try to launch Parity + SYNCING: 'SYNCING' // Obvious +}; + +class HealthStore { + @observable nodeHealth; + @observable syncing; + + constructor () { + nodeHealth$().subscribe(this.setNodeHealth); + syncing$().subscribe(this.setSyncing); + } + + /** + * Calculate the current status. + * + * @return [Object{ status: StatusEnum, payload: Any}] - An object which + * represents the current status, with a custom payload. + */ + @computed + get health () { + // Check download progress + if (parityStore.downloadProgress > 0 && !parityStore.isParityRunning) { + return { + status: STATUS.DOWNLOADING, + payload: { percentage: Math.round(parityStore.downloadProgress * 100) } + }; + } + + // Check if we are currently launching + if (parityStore.isParityRunning && !parityStore.isApiConnected) { + return { + status: STATUS.RUNNING + }; + } + + // Check if we get responses from the WS server + if ( + !parityStore.isApiConnected || + !this.nodeHealth || + !Object.keys(this.nodeHealth).length + ) { + return { + status: STATUS.CANTCONNECT + }; + } + + // At this point we have a successful connection to parity + + // Check if we're syncing + if (this.syncing) { + const { currentBlock, highestBlock, startingBlock } = this.syncing; + const percentage = Math.round( + (currentBlock - startingBlock) * 100 / (highestBlock - startingBlock) + ); + return { + status: STATUS.SYNCING, + payload: { currentBlock, highestBlock, percentage, startingBlock } + }; + } + + // Find out if there are bad statuses + const bad = Object.values(this.nodeHealth) + .filter(x => x) + .map(({ status }) => status) + .find(s => s === 'bad'); + // Find out if there are needsAttention statuses + const needsAttention = Object.keys(this.nodeHealth) + .filter(key => key !== 'time') + .map(key => this.nodeHealth[key]) + .filter(x => x) + .map(({ status }) => status) + .find(s => s === 'needsattention'); + + if (!bad && !needsAttention) { + return { + status: STATUS.GOOD + }; + } + + // Now we have a bad or a needsattention message + + // Get all non-empty messages from all statuses + const details = Object.values(this.nodeHealth) + .map(({ message }) => message) + .filter(x => x); + + // If status is bad or needsattention, there should be an associated + // message. Just in case, we do an additional test. + if (!details || !details.length) { + return { status: STATUS.OTHER }; + } + + const message = details[0]; + console.log(message); // TODO WIP, to catch potential other messages + + if ( + message === + "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced." + ) { + return { status: STATUS.SYNCING }; + } + + if ( + message === + 'You are not connected to any peers. There is most likely some network issue. Fix connectivity.' + ) { + return { status: STATUS.NOINTERNET, payload: message }; + } + + if ( + message.includes( + 'Your clock is not in sync. Detected difference is too big for the protocol to work.' + ) + ) { + return { status: STATUS.CLOCKNOTSYNC, payload: message }; + } + + return { status: STATUS.OTHER, payload: message }; + } + + @action + setNodeHealth = nodeHealth => { + this.nodeHealth = nodeHealth; + }; + + @action + setSyncing = syncing => { + this.syncing = syncing; + }; +} + +export default new HealthStore(); diff --git a/src/stores/index.js b/src/stores/index.js index 6e071f07b14accd358aa7b5904897602433be7ea..d2346d8aa89dccc2693799b4812c4127dc15620c 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -4,12 +4,14 @@ // SPDX-License-Identifier: MIT import createAccountStore from './createAccountStore'; +import healthStore from './healthStore'; import parityStore from './parityStore'; import signerStore from './signerStore'; import tokensStore from './tokensStore'; export default { createAccountStore, + healthStore, parityStore, signerStore, tokensStore diff --git a/src/stores/parityStore.js b/src/stores/parityStore.js index a62c43780190a465e42e38fdf0c2048f28267030..daf392d429a6b6ec48af081c6e891f34df901c83 100644 --- a/src/stores/parityStore.js +++ b/src/stores/parityStore.js @@ -50,14 +50,15 @@ class ParityStore { } connectToApi = () => { - let provider = 'ws://127.0.0.1:8546'; // Default provider to be used in localhost:3000 + // Get the provider, optionally from --ws-interface and --ws-port flags + const [defaultInterface, defaultPort] = ['127.0.0.1', '8546']; + let provider = `ws://${defaultInterface}:${defaultPort}`; if (electron) { const { remote } = electron; const wsInterface = remote.getGlobal('wsInterface'); const wsPort = remote.getGlobal('wsPort'); - if (wsInterface && wsPort) { - provider = `ws://${wsInterface}:${wsPort}`; - } + provider = `ws://${wsInterface || defaultInterface}:${wsPort || + defaultPort}`; } const api = new Api( diff --git a/yarn.lock b/yarn.lock index d86a819ca3098ffa9a60e78e0313301bafe71a25..f86394648d3f0b88223a7e9d383df5882c576211 100644 --- a/yarn.lock +++ b/yarn.lock @@ -47,10 +47,10 @@ js-tokens "^3.0.0" "@babel/runtime@^7.0.0-beta.46": - version "7.0.0-beta.47" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.47.tgz#273f5e71629e80f6cbcd7507503848615e59f7e0" + version "7.0.0-beta.49" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.49.tgz#03b3bf07eb982072c8e851dd2ddd5110282e61bf" dependencies: - core-js "^2.5.3" + core-js "^2.5.6" regenerator-runtime "^0.11.1" "@babel/template@7.0.0-beta.44": @@ -131,9 +131,9 @@ u2f-api "0.0.9" u2f-api-polyfill "0.4.3" -"@parity/light.js@https://github.com/parity-js/light.js#0c57c29de42122b0f07ff82dfaf38b48863d8d2e": +"@parity/light.js@https://github.com/parity-js/light.js#aa03e7f2956718bf91f6f82771eee3b81d260a18": version "1.0.0" - resolved "https://github.com/parity-js/light.js#0c57c29de42122b0f07ff82dfaf38b48863d8d2e" + resolved "https://github.com/parity-js/light.js#aa03e7f2956718bf91f6f82771eee3b81d260a18" dependencies: "@babel/runtime" "^7.0.0-beta.46" "@parity/api" "^2.1.22" @@ -2212,9 +2212,9 @@ core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: version "2.5.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b" -core-js@^2.5.3: - version "2.5.6" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.6.tgz#0fe6d45bf3cac3ac364a9d72de7576f4eb221b9d" +core-js@^2.5.6: + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2"