mirror of https://github.com/embarklabs/embark.git
Adding transactions explorer
This commit is contained in:
parent
ede5afa6e0
commit
20bf924687
|
@ -10,6 +10,10 @@ export const RECEIVE_PROCESSES_ERROR = 'RECEIVE_PROCESSES_ERROR';
|
||||||
export const FETCH_BLOCKS = 'FETCH_BLOCKS';
|
export const FETCH_BLOCKS = 'FETCH_BLOCKS';
|
||||||
export const RECEIVE_BLOCKS = 'RECEIVE_BLOCKS';
|
export const RECEIVE_BLOCKS = 'RECEIVE_BLOCKS';
|
||||||
export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR';
|
export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR';
|
||||||
|
// Transactions
|
||||||
|
export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS';
|
||||||
|
export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS';
|
||||||
|
export const RECEIVE_TRANSACTIONS_ERROR = 'RECEIVE_TRANSACTIONS_ERROR';
|
||||||
|
|
||||||
export function fetchAccounts() {
|
export function fetchAccounts() {
|
||||||
return {
|
return {
|
||||||
|
@ -68,3 +72,23 @@ export function receiveBlocksError() {
|
||||||
type: RECEIVE_BLOCKS_ERROR
|
type: RECEIVE_BLOCKS_ERROR
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchTransactions(blockFrom) {
|
||||||
|
return {
|
||||||
|
type: FETCH_TRANSACTIONS,
|
||||||
|
blockFrom
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function receiveTransactions(transactions) {
|
||||||
|
return {
|
||||||
|
type: RECEIVE_TRANSACTIONS,
|
||||||
|
transactions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function receiveTransactionsError() {
|
||||||
|
return {
|
||||||
|
type: RECEIVE_TRANSACTIONS_ERROR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ export function fetchBlocks(from) {
|
||||||
return axios.get(`${BASE_URL}/blockchain/blocks`, {params: {from}});
|
return axios.get(`${BASE_URL}/blockchain/blocks`, {params: {from}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchTransactions(blockFrom) {
|
||||||
|
return axios.get(`${BASE_URL}/blockchain/transactions`, {params: {blockFrom}});
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchProcesses() {
|
export function fetchProcesses() {
|
||||||
return axios.get(`${BASE_URL}/processes`);
|
return axios.get(`${BASE_URL}/processes`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
|
|
||||||
import AccountsContainer from '../containers/AccountsContainer';
|
import AccountsContainer from '../containers/AccountsContainer';
|
||||||
import BlocksContainer from '../containers/BlocksContainer';
|
import BlocksContainer from '../containers/BlocksContainer';
|
||||||
|
import TransactionsContainer from '../containers/TransactionsContainer';
|
||||||
|
|
||||||
const ExplorerLayout = () => (
|
const ExplorerLayout = () => (
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
|
@ -31,6 +32,14 @@ const ExplorerLayout = () => (
|
||||||
>
|
>
|
||||||
Blocks
|
Blocks
|
||||||
</List.GroupItem>
|
</List.GroupItem>
|
||||||
|
<List.GroupItem
|
||||||
|
className="d-flex align-items-center"
|
||||||
|
to="/embark/explorer/transactions"
|
||||||
|
icon="activity"
|
||||||
|
RootComponent={withRouter(NavLink)}
|
||||||
|
>
|
||||||
|
Transactions
|
||||||
|
</List.GroupItem>
|
||||||
</List.Group>
|
</List.Group>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
@ -38,6 +47,7 @@ const ExplorerLayout = () => (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/embark/explorer/accounts" component={AccountsContainer} />
|
<Route exact path="/embark/explorer/accounts" component={AccountsContainer} />
|
||||||
<Route exact path="/embark/explorer/blocks" component={BlocksContainer} />
|
<Route exact path="/embark/explorer/blocks" component={BlocksContainer} />
|
||||||
|
<Route exact path="/embark/explorer/transactions" component={TransactionsContainer} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Page,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
Table
|
||||||
|
} from "tabler-react";
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const Transactions = ({transactions}) => (
|
||||||
|
<Page.Content title="Transactions">
|
||||||
|
<Grid.Row>
|
||||||
|
<Grid.Col>
|
||||||
|
<Card>
|
||||||
|
<Table
|
||||||
|
responsive
|
||||||
|
className="card-table table-vcenter text-nowrap"
|
||||||
|
headerItems={[
|
||||||
|
{content: "Block Number"},
|
||||||
|
{content: "From"},
|
||||||
|
{content: "To"},
|
||||||
|
{content: "Type"},
|
||||||
|
{content: "Hash"}]}
|
||||||
|
bodyItems={
|
||||||
|
transactions.map((transaction) => {
|
||||||
|
return ([
|
||||||
|
{content: transaction.blockNumber},
|
||||||
|
{content: transaction.from},
|
||||||
|
{content: transaction.to},
|
||||||
|
{content: transaction.to ? "Contract Call" : "Contract Creation"},
|
||||||
|
{content: transaction.hash}
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid.Row>
|
||||||
|
</Page.Content>
|
||||||
|
);
|
||||||
|
|
||||||
|
Transactions.propTypes = {
|
||||||
|
transactions: PropTypes.arrayOf(PropTypes.object)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Transactions;
|
|
@ -8,7 +8,6 @@ import Loading from '../components/Loading';
|
||||||
import LoadMore from '../components/LoadMore';
|
import LoadMore from '../components/LoadMore';
|
||||||
|
|
||||||
class BlocksContainer extends Component {
|
class BlocksContainer extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.props.blocks.data) {
|
if (!this.props.blocks.data) {
|
||||||
this.props.fetchBlocks();
|
this.props.fetchBlocks();
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import {fetchTransactions} from '../actions';
|
||||||
|
import Transactions from '../components/Transactions';
|
||||||
|
import Loading from '../components/Loading';
|
||||||
|
import LoadMore from '../components/LoadMore';
|
||||||
|
|
||||||
|
class TransactionsContainer extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
if (!this.props.transactions.data) {
|
||||||
|
this.props.fetchTransactions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
this.props.fetchTransactions(this.loadMoreFrom());
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMoreFrom() {
|
||||||
|
let transactions = this.props.transactions.data;
|
||||||
|
return transactions[transactions.length - 1].blockNumber - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {transactions} = this.props;
|
||||||
|
if (!transactions.data) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactions.error) {
|
||||||
|
return (
|
||||||
|
<h1>
|
||||||
|
<i>Error API...</i>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Transactions transactions={transactions.data}/>
|
||||||
|
{(this.loadMoreFrom() > 0) ? <LoadMore loadMore={() => this.loadMore()} /> : <React.Fragment />}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {transactions: state.transactions};
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionsContainer.propTypes = {
|
||||||
|
transactions: PropTypes.object,
|
||||||
|
fetchTransactions: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
{
|
||||||
|
fetchTransactions
|
||||||
|
},
|
||||||
|
)(TransactionsContainer);
|
|
@ -3,7 +3,7 @@ import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions";
|
||||||
export default function accounts(state = {}, action) {
|
export default function accounts(state = {}, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case RECEIVE_ACCOUNTS:
|
case RECEIVE_ACCOUNTS:
|
||||||
return Object.assign({}, state, {data: action.accounts.data});
|
return {...state, data: [...state.data || [], ...action.accounts.data]};
|
||||||
case RECEIVE_ACCOUNTS_ERROR:
|
case RECEIVE_ACCOUNTS_ERROR:
|
||||||
return Object.assign({}, state, {error: true});
|
return Object.assign({}, state, {error: true});
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -2,11 +2,13 @@ import {combineReducers} from 'redux';
|
||||||
import processesReducer from './processesReducer';
|
import processesReducer from './processesReducer';
|
||||||
import accountsReducer from './accountsReducer';
|
import accountsReducer from './accountsReducer';
|
||||||
import blocksReducer from './blocksReducer';
|
import blocksReducer from './blocksReducer';
|
||||||
|
import transactionsReducer from './transactionsReducer';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
accounts: accountsReducer,
|
accounts: accountsReducer,
|
||||||
processes: processesReducer,
|
processes: processesReducer,
|
||||||
blocks: blocksReducer
|
blocks: blocksReducer,
|
||||||
|
transactions: transactionsReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import {RECEIVE_TRANSACTIONS, RECEIVE_TRANSACTIONS_ERROR} from "../actions";
|
||||||
|
|
||||||
|
export default function transactions(state = {}, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case RECEIVE_TRANSACTIONS:
|
||||||
|
return {...state, data: [...state.data || [], ...action.transactions.data]};
|
||||||
|
case RECEIVE_TRANSACTIONS_ERROR:
|
||||||
|
return Object.assign({}, state, {error: true});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,19 @@ import * as actions from '../actions';
|
||||||
import * as api from '../api';
|
import * as api from '../api';
|
||||||
import {all, call, fork, put, takeEvery} from 'redux-saga/effects';
|
import {all, call, fork, put, takeEvery} from 'redux-saga/effects';
|
||||||
|
|
||||||
|
export function *fetchTransactions(payload) {
|
||||||
|
try {
|
||||||
|
const transactions = yield call(api.fetchTransactions, payload.blockFrom);
|
||||||
|
yield put(actions.receiveTransactions(transactions));
|
||||||
|
} catch (e) {
|
||||||
|
yield put(actions.receiveTransactionsError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function *watchFetchTransactions() {
|
||||||
|
yield takeEvery(actions.FETCH_TRANSACTIONS, fetchTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
export function *fetchBlocks(payload) {
|
export function *fetchBlocks(payload) {
|
||||||
try {
|
try {
|
||||||
const blocks = yield call(api.fetchBlocks, payload.from);
|
const blocks = yield call(api.fetchBlocks, payload.from);
|
||||||
|
@ -42,5 +55,10 @@ export function *watchFetchProcesses() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function *root() {
|
export default function *root() {
|
||||||
yield all([fork(watchFetchAccounts), fork(watchFetchProcesses), fork(watchFetchBlocks)]);
|
yield all([
|
||||||
|
fork(watchFetchAccounts),
|
||||||
|
fork(watchFetchProcesses),
|
||||||
|
fork(watchFetchBlocks),
|
||||||
|
fork(watchFetchTransactions)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,39 +336,61 @@ class BlockchainConnector {
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
let from = parseInt(req.query.from, 10);
|
let from = parseInt(req.query.from, 10);
|
||||||
let limit = req.query.limit || 10;
|
let limit = req.query.limit || 10;
|
||||||
let results = [];
|
self.getBlocks(from, limit, false, res.send.bind(res));
|
||||||
async.waterfall([
|
|
||||||
function(callback) {
|
|
||||||
if (!isNaN(from)) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
self.getBlockNumber((err, blockNumber) => {
|
|
||||||
if (err) {
|
|
||||||
self.logger.error(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){
|
|
||||||
self.logger.error(err);
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
results.push(block);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
], function () {
|
|
||||||
res.send(results);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
plugin.registerAPICall(
|
||||||
|
'get',
|
||||||
|
'/embark-api/blockchain/transactions',
|
||||||
|
(req, res) => {
|
||||||
|
let blockFrom = parseInt(req.query.blockFrom, 10);
|
||||||
|
let blockLimit = req.query.blockLimit || 10;
|
||||||
|
self.getTransactions(blockFrom, blockLimit, res.send.bind(res));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransactions(blockFrom, blockLimit, callback) {
|
||||||
|
this.getBlocks(blockFrom, blockLimit, true, (blocks) => {
|
||||||
|
let transactions = blocks.reduce((acc, block) => acc.concat(block.transactions), []);
|
||||||
|
callback(transactions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlocks(from, limit, returnTransactionObjects, callback) {
|
||||||
|
let self = this;
|
||||||
|
let blocks = [];
|
||||||
|
async.waterfall([
|
||||||
|
function(next) {
|
||||||
|
if (!isNaN(from)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
self.getBlockNumber((err, blockNumber) => {
|
||||||
|
if (err) {
|
||||||
|
self.logger.error(err);
|
||||||
|
from = 0;
|
||||||
|
} else {
|
||||||
|
from = blockNumber;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(next) {
|
||||||
|
async.times(limit, function(n, eachCb) {
|
||||||
|
self.web3.eth.getBlock(from - n, returnTransactionObjects, function(err, block) {
|
||||||
|
if (err){
|
||||||
|
self.logger.error(err);
|
||||||
|
return eachCb();
|
||||||
|
}
|
||||||
|
blocks.push(block);
|
||||||
|
eachCb();
|
||||||
|
});
|
||||||
|
}, next);
|
||||||
|
}
|
||||||
|
], function () {
|
||||||
|
callback(blocks);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue