From b00ce3c9fa746e9ecc20f2e1702024b58907ac86 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 2 Aug 2018 11:27:33 +0100 Subject: [PATCH] Adding blocks explorer --- embark-ui/package.json | 1 + embark-ui/src/actions/index.js | 23 +++++++ embark-ui/src/api/index.js | 10 ++- embark-ui/src/components/Accounts.js | 65 ++++++++++++------- embark-ui/src/components/Blocks.js | 41 ++++++++++++ embark-ui/src/components/ExplorerLayout.js | 46 +++++++++++++ embark-ui/src/components/Loading.js | 12 ++++ embark-ui/src/containers/AccountsContainer.js | 32 +++++---- embark-ui/src/containers/BlocksContainer.js | 48 ++++++++++++++ .../src/containers/ProcessesContainer.js | 10 ++- embark-ui/src/reducers/accountsReducer.js | 12 ++++ embark-ui/src/reducers/blocksReducer.js | 12 ++++ embark-ui/src/reducers/index.js | 19 ++---- embark-ui/src/routes.js | 14 ++-- embark-ui/src/sagas/index.js | 15 ++++- embark-ui/src/store/configureStore.js | 4 +- lib/modules/blockchain_connector/index.js | 44 +++++++++++++ 17 files changed, 341 insertions(+), 67 deletions(-) create mode 100644 embark-ui/src/components/Blocks.js create mode 100644 embark-ui/src/components/ExplorerLayout.js create mode 100644 embark-ui/src/components/Loading.js create mode 100644 embark-ui/src/containers/BlocksContainer.js create mode 100644 embark-ui/src/reducers/accountsReducer.js create mode 100644 embark-ui/src/reducers/blocksReducer.js diff --git a/embark-ui/package.json b/embark-ui/package.json index ffa0a95c..830597e9 100644 --- a/embark-ui/package.json +++ b/embark-ui/package.json @@ -6,6 +6,7 @@ "axios": "^0.18.0", "connected-react-router": "^4.3.0", "history": "^4.7.2", + "prop-types": "^15.6.2", "react": "^16.4.1", "react-dom": "^16.4.1", "react-redux": "^5.0.7", diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 5d1ea19f..00fe79ed 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -6,6 +6,10 @@ export const RECEIVE_ACCOUNTS_ERROR = 'RECEIVE_ACCOUNTS_ERROR'; export const FETCH_PROCESSES = 'FETCH_PROCESSES'; export const RECEIVE_PROCESSES = 'RECEIVE_PROCESSES'; export const RECEIVE_PROCESSES_ERROR = 'RECEIVE_PROCESSES_ERROR'; +// Blocks +export const FETCH_BLOCKS = 'FETCH_BLOCKS'; +export const RECEIVE_BLOCKS = 'RECEIVE_BLOCKS'; +export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR'; export function fetchAccounts() { return { @@ -44,3 +48,22 @@ export function receiveProcessesError() { type: RECEIVE_PROCESSES_ERROR }; } + +export function fetchBlocks() { + return { + type: FETCH_BLOCKS + }; +} + +export function receiveBlocks(blocks) { + return { + type: RECEIVE_BLOCKS, + blocks + }; +} + +export function receiveBlocksError() { + return { + type: RECEIVE_BLOCKS_ERROR + }; +} diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 732f38d4..2d63b683 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -1,9 +1,15 @@ import axios from "axios"; +const BASE_URL = 'http://localhost:8000/embark-api'; + export function fetchAccounts() { - return axios.get('http://localhost:8000/embark-api/blockchain/accounts'); + return axios.get(`${BASE_URL}/blockchain/accounts`); +} + +export function fetchBlocks() { + return axios.get(`${BASE_URL}/blockchain/blocks`); } export function fetchProcesses() { - return axios.get('http://localhost:8000/embark-api/processes'); + return axios.get(`${BASE_URL}/processes`); } diff --git a/embark-ui/src/components/Accounts.js b/embark-ui/src/components/Accounts.js index 3a07de77..06176c81 100644 --- a/embark-ui/src/components/Accounts.js +++ b/embark-ui/src/components/Accounts.js @@ -1,31 +1,46 @@ import React from 'react'; +import { + Page, + Grid, + Card, + Table +} from "tabler-react"; +import PropTypes from 'prop-types'; + const Accounts = ({accounts}) => ( - -

Accounts

- - - - - - - - - - - {accounts.map((account) => { - return ( - - - - - - - ) - })} - -
AddressBalanceTX countIndex
{account.address}{account.balance} ETH{account.transactionCount}{account.index}
-
+ + + + + { + return ([ + {content: account.address}, + {content: account.balance}, + {content: account.transactionCount}, + {content: account.index} + ]); + }) + } + /> + + + + ); +Accounts.propTypes = { + accounts: PropTypes.object +}; + export default Accounts; diff --git a/embark-ui/src/components/Blocks.js b/embark-ui/src/components/Blocks.js new file mode 100644 index 00000000..ba78b70c --- /dev/null +++ b/embark-ui/src/components/Blocks.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { + Page, + Grid, + Card, + Table +} from "tabler-react"; +import PropTypes from 'prop-types'; + + +const Blocks = ({blocks}) => ( + + + + +
{ + return ([ + {content: block.number}, + {content: new Date(block.timestamp * 1000).toLocaleString()}, + {content: block.gasUsed}, + {content: block.transactions.length} + ]); + }) + } + /> + + + + +); + +Blocks.propTypes = { + blocks: [PropTypes.object] +}; + +export default Blocks; diff --git a/embark-ui/src/components/ExplorerLayout.js b/embark-ui/src/components/ExplorerLayout.js new file mode 100644 index 00000000..b4b70f98 --- /dev/null +++ b/embark-ui/src/components/ExplorerLayout.js @@ -0,0 +1,46 @@ +import React from 'react'; +import {NavLink, Route, Switch, withRouter} from 'react-router-dom'; +import { + Page, + Grid, + List, +} from "tabler-react"; + +import AccountsContainer from '../containers/AccountsContainer'; +import BlocksContainer from '../containers/BlocksContainer'; + +const ExplorerLayout = () => ( + + + Explorer +
+ + + Accounts + + + Blocks + + +
+
+ + + + + + +
+); + +export default ExplorerLayout; diff --git a/embark-ui/src/components/Loading.js b/embark-ui/src/components/Loading.js new file mode 100644 index 00000000..a142e5cc --- /dev/null +++ b/embark-ui/src/components/Loading.js @@ -0,0 +1,12 @@ +import React from 'react'; +import {Grid, Loader} from 'tabler-react'; + +const Loading = () => ( + + + + + +); + +export default Loading; diff --git a/embark-ui/src/containers/AccountsContainer.js b/embark-ui/src/containers/AccountsContainer.js index 45c92f2c..95a817d7 100644 --- a/embark-ui/src/containers/AccountsContainer.js +++ b/embark-ui/src/containers/AccountsContainer.js @@ -1,21 +1,20 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { fetchAccounts } from '../actions'; +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; + +import {fetchAccounts} from '../actions'; import Accounts from '../components/Accounts'; +import Loading from '../components/Loading'; class AccountsContainer extends Component { - componentWillMount() { + componentDidMount() { this.props.fetchAccounts(); } render() { - const { accounts } = this.props; + const {accounts} = this.props; if (!accounts.data) { - return ( -

- Loading accounts... -

- ) + return ; } if (accounts.error) { @@ -23,22 +22,27 @@ class AccountsContainer extends Component {

Error API...

- ) + ); } return ( ); } -}; +} function mapStateToProps(state) { - return { accounts: state.accounts } + return {accounts: state.accounts}; } +AccountsContainer.propTypes = { + accounts: PropTypes.object, + fetchAccounts: PropTypes.func +}; + export default connect( mapStateToProps, { fetchAccounts }, -)(AccountsContainer) +)(AccountsContainer); diff --git a/embark-ui/src/containers/BlocksContainer.js b/embark-ui/src/containers/BlocksContainer.js new file mode 100644 index 00000000..54b8580a --- /dev/null +++ b/embark-ui/src/containers/BlocksContainer.js @@ -0,0 +1,48 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; + +import {fetchBlocks} from '../actions'; +import Blocks from '../components/Blocks'; +import Loading from '../components/Loading'; + +class BlocksContainer extends Component { + componentDidMount() { + this.props.fetchBlocks(); + } + + render() { + const {blocks} = this.props; + if (!blocks.data) { + return ; + } + + if (blocks.error) { + return ( +

+ Error API... +

+ ); + } + + return ( + + ); + } +} + +function mapStateToProps(state) { + return {blocks: state.blocks}; +} + +BlocksContainer.propTypes = { + blocks: PropTypes.object, + fetchBlocks: PropTypes.func +}; + +export default connect( + mapStateToProps, + { + fetchBlocks + }, +)(BlocksContainer); diff --git a/embark-ui/src/containers/ProcessesContainer.js b/embark-ui/src/containers/ProcessesContainer.js index 83516e85..baa35293 100644 --- a/embark-ui/src/containers/ProcessesContainer.js +++ b/embark-ui/src/containers/ProcessesContainer.js @@ -1,9 +1,11 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; -import {fetchProcesses} from '../actions'; import {Tabs, Tab} from 'tabler-react'; import PropTypes from 'prop-types'; +import {fetchProcesses} from '../actions'; +import Loading from '../components/Loading'; + import "./css/processContainer.css"; class ProcessesContainer extends Component { @@ -14,11 +16,7 @@ class ProcessesContainer extends Component { render() { const {processes} = this.props; if (!processes.data) { - return ( -

- Loading processes... -

- ); + return ; } if (processes.error) { diff --git a/embark-ui/src/reducers/accountsReducer.js b/embark-ui/src/reducers/accountsReducer.js new file mode 100644 index 00000000..a89cde48 --- /dev/null +++ b/embark-ui/src/reducers/accountsReducer.js @@ -0,0 +1,12 @@ +import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions"; + +export default function accounts(state = {}, action) { + switch (action.type) { + case RECEIVE_ACCOUNTS: + return Object.assign({}, state, {data: action.accounts.data}); + case RECEIVE_ACCOUNTS_ERROR: + return Object.assign({}, state, {error: true}); + default: + return state; + } +} diff --git a/embark-ui/src/reducers/blocksReducer.js b/embark-ui/src/reducers/blocksReducer.js new file mode 100644 index 00000000..55b447c6 --- /dev/null +++ b/embark-ui/src/reducers/blocksReducer.js @@ -0,0 +1,12 @@ +import {RECEIVE_BLOCKS, RECEIVE_BLOCKS_ERROR} from "../actions"; + +export default function accounts(state = {}, action) { + switch (action.type) { + case RECEIVE_BLOCKS: + return Object.assign({}, state, {data: action.blocks.data}); + case RECEIVE_BLOCKS_ERROR: + return Object.assign({}, state, {error: true}); + default: + return state; + } +} diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index a1d79e0f..2ecb959e 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -1,17 +1,12 @@ import {combineReducers} from 'redux'; -import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions"; import processesReducer from './processesReducer'; +import accountsReducer from './accountsReducer'; +import blocksReducer from './blocksReducer'; -function accounts(state = {}, action) { - switch (action.type) { - case RECEIVE_ACCOUNTS: - return Object.assign({}, state, {data: action.accounts.data}); - case RECEIVE_ACCOUNTS_ERROR: - return Object.assign({}, state, {error: true}); - default: - return state; - } -} +const rootReducer = combineReducers({ + accounts: accountsReducer, + processes: processesReducer, + blocks: blocksReducer +}); -const rootReducer = combineReducers({accounts, processes: processesReducer}); export default rootReducer; diff --git a/embark-ui/src/routes.js b/embark-ui/src/routes.js index 47936a84..0797357d 100644 --- a/embark-ui/src/routes.js +++ b/embark-ui/src/routes.js @@ -1,16 +1,18 @@ import React from 'react'; -import {Route, Switch} from 'react-router'; +import {Route, Switch} from 'react-router-dom'; + import Home from './components/Home'; -import AccountsContainer from './containers/AccountsContainer'; -import ProcessesContainer from './containers/ProcessesContainer'; import NoMatch from './components/NoMatch'; +import ExplorerLayout from './components/ExplorerLayout'; + +import ProcessesContainer from './containers/ProcessesContainer'; const routes = ( - - - + + + diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 17b609ad..cc1c0b82 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -2,6 +2,19 @@ import * as actions from '../actions'; import * as api from '../api'; import {all, call, fork, put, takeEvery} from 'redux-saga/effects'; +export function *fetchBlocks() { + try { + const blocks = yield call(api.fetchBlocks); + yield put(actions.receiveBlocks(blocks)); + } catch (e) { + yield put(actions.receiveBlocksError()); + } +} + +export function *watchFetchBlocks() { + yield takeEvery(actions.FETCH_BLOCKS, fetchBlocks); +} + export function *fetchAccounts() { try { const accounts = yield call(api.fetchAccounts); @@ -29,5 +42,5 @@ export function *watchFetchProcesses() { } export default function *root() { - yield all([fork(watchFetchAccounts), fork(watchFetchProcesses)]); + yield all([fork(watchFetchAccounts), fork(watchFetchProcesses), fork(watchFetchBlocks)]); } diff --git a/embark-ui/src/store/configureStore.js b/embark-ui/src/store/configureStore.js index 375be841..70885ef3 100644 --- a/embark-ui/src/store/configureStore.js +++ b/embark-ui/src/store/configureStore.js @@ -6,12 +6,14 @@ import history from '../history'; import rootReducer from '../reducers'; import saga from '../sagas'; +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + export default function configureStore() { const sagaMiddleware = createSagaMiddleware(); const store = createStore( connectRouter(history)(rootReducer), - compose( + composeEnhancers( applyMiddleware( routerMiddleware(history), sagaMiddleware diff --git a/lib/modules/blockchain_connector/index.js b/lib/modules/blockchain_connector/index.js index 0827f079..3f2abac9 100644 --- a/lib/modules/blockchain_connector/index.js +++ b/lib/modules/blockchain_connector/index.js @@ -327,6 +327,46 @@ class BlockchainConnector { }); } ); + + plugin.registerAPICall( + 'get', + '/embark-api/blockchain/blocks', + (req, res) => { + let from = req.query.from; + let limit = req.query.limit || 10; + let results = []; + async.waterfall([ + function(callback) { + if (from) { + return callback(); + } + self.getBlockNumber((err, blockNumber) => { + if (err) { + from = 0; + } else { + from = blockNumber; + } + callback(); + }); + }, + function(callback) { + async.times(limit, function(n, next) { + self.web3.eth.getBlock(from - n, function(err, block) { + if (err){ + return next(); + } + results.push(block); + next(); + }); + }, function() { + callback(); + }); + } + ], function () { + res.send(results); + }); + } + ); } @@ -334,6 +374,10 @@ class BlockchainConnector { return this.web3.eth.defaultAccount; } + getBlockNumber(cb) { + return this.web3.eth.getBlockNumber(cb); + } + setDefaultAccount(account) { this.web3.eth.defaultAccount = account; }