import { actions as transactionFeed } from 'features/transactionFeeds'
import { actions as tutorial } from 'features/tutorial'
import { actions as unspent } from 'features/unspents'
+import { actions as peer } from 'features/peers'
const actions = {
accessControl,
transactionFeed,
tutorial,
unspent,
+ peer
}
export default actions
import React from 'react'
import { connect } from 'react-redux'
import { ProgressBar, OverlayTrigger, Tooltip } from 'react-bootstrap'
+import { Link } from 'react-router'
import navStyles from '../Navigation/Navigation.scss'
import styles from './Sync.scss'
import {withNamespaces} from 'react-i18next'
if (syncing) {
return <ul className={`${navStyles.navigation} ${styles.main}`}>
<li key='sync-title' className={navStyles.navigationTitle}>{ networkID } { t('sync.status')}</li>
- <li key='sync-peer-count' className={(peerCount>0)?styles.blockHightlight: null}>{t('sync.peer')}: {peerCount}</li>
+ <li key='sync-peer-count' className={(peerCount>0)?styles.blockHightlight: null}>
+ <Link to={'/peers'}>
+ {t('sync.peer')}: {peerCount}
+ </Link>
+ </li>
<li key='sync-status'> <OverlayTrigger placement='top' overlay={tooltip}>
<div> {t('sync.synchronizing')} {progressInstance} </div>
</OverlayTrigger></li>
const elems = []
elems.push(<li key='sync-title' className={navStyles.navigationTitle}>{ networkID } {t('sync.status') }</li>)
- elems.push(<li key='sync-peer-count' className={(peerCount>0)?styles.blockHightlight: null}>{t('sync.peer')}: {peerCount}</li>)
+ elems.push(<li key='sync-peer-count' className={(peerCount>0)?styles.blockHightlight: null}>
+ <Link to={'/peers'}>
+ {t('sync.peer')}: {peerCount}
+ </Link>
+ </li>)
if(!syncing && currentBlock == highestBlock){
elems.push(<li className={styles.blockHightlight} key='sync-done'>
--- /dev/null
+import { baseListActions } from 'features/shared/actions'
+import { chainClient } from 'utility/environment'
+import {push} from 'react-router-redux'
+
+const disconnect = (id) => {
+ return (dispatch) => {
+ return chainClient().peers.disconnect({peer_id: id})
+ .then((resp) => {
+ if(resp.status == 'fail'){
+ dispatch({type: 'ERROR', payload: { 'message': resp.msg}})
+ }else{
+ dispatch(push({
+ pathname: '/peers',
+ state: {
+ preserveFlash: true
+ }
+ }))
+ }
+ })
+ .catch((err) => {
+ if (!err.status) {
+ dispatch({type: 'ERROR', payload: { 'message': err}})
+ }
+ })
+ }
+}
+
+let actions = {
+ disconnect,
+ ...baseListActions('peer')
+}
+
+export default actions
--- /dev/null
+import { BaseList, TableList } from 'features/shared/components'
+import ListItem from './ListItem'
+import { withNamespaces } from 'react-i18next'
+import styles from './List.scss'
+import { actions } from 'features/peers'
+
+const type = 'peer'
+
+const mapStateToProps = (state, props) => {
+ return {
+ skipCreate: true,
+ ...BaseList.mapStateToProps(type, ListItem, {
+ wrapperComponent: TableList,
+ wrapperProps: {
+ titles: props.t('peers.formTitle', { returnObjects: true }),
+ styles: styles.main
+ }
+ })(state)
+ }
+}
+
+const mapDispatchToProps = ( dispatch ) => ({
+ itemActions: {
+ disconnect: (id) => dispatch(actions.disconnect(id))
+ },
+ ...BaseList.mapDispatchToProps(type),
+})
+
+export default withNamespaces('translations') (BaseList.connect(
+ mapStateToProps,
+ mapDispatchToProps
+))
--- /dev/null
+.main {
+ th, td {
+ &:first-child {
+ padding-left: $gutter-size;
+ width: 33%;
+ }
+
+ &:last-child {
+ width: 100px;
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import {withNamespaces} from 'react-i18next'
+
+class ListItem extends React.Component {
+ render() {
+ const {item, t} = this.props
+
+ return(
+ <tr>
+ <td>{item.remoteAddr || '-'}</td>
+ <td><code>{item.height}</code></td>
+ <td><code>{item.ping}</code></td>
+ <td><code>{item.duration}</code></td>
+ <td>{ item.totalSent+ item.totalReceived }</td>
+ <td>
+ <button className='btn btn-link' onClick={() => this.props.disconnect(item.peerId)}>
+ {t('peers.disconnect')}
+ </button>
+ </td>
+ </tr>
+ )
+ }
+}
+
+export default withNamespaces('translations') (ListItem)
--- /dev/null
+import List from './List'
+
+export {
+ List,
+}
--- /dev/null
+import actions from './actions'
+import reducers from './reducers'
+import routes from './routes'
+
+export {
+ actions,
+ reducers,
+ routes,
+}
--- /dev/null
+import { reducers } from 'features/shared'
+import { combineReducers } from 'redux'
+
+const type = 'peer'
+
+const itemsReducer = (state = {}, action) => {
+ if (action.type == 'RECEIVED_PEER_ITEMS') {
+ return action.param.data
+ }
+ return state
+}
+
+export default combineReducers({
+ items: itemsReducer,
+ queries: reducers.queriesReducer(type)
+})
--- /dev/null
+// import { RoutingContainer } from 'features/shared/components'
+// import { PeerIndex } from './components'
+// import { List } from './components'
+//
+//
+// export default {
+// path: 'peers',
+// component: RoutingContainer,
+// indexRoute: { component: List }
+// }
+//
+
+import { List } from './components'
+import { makeRoutes } from 'features/shared'
+
+export default (store) => makeRoutes(store, 'peer', List)
class TableList extends React.Component {
render() {
return (
- <table className={styles.main}>
+ <table className={`${styles.main} ${this.props.styles || ''}`}>
<thead>
<tr>
{this.props.titles.map(title => <th key={title}>{title}</th>)}
"newKey":"Create a key",
"newToken":"Create an access token",
"newAsset":"Create an asset",
- "resetPassword":"Reset password"
+ "resetPassword":"Reset password",
+ "peer":"Peers information"
},
"form":{
"detail": "Details",
"selectFile":"Select Restore File",
"restore":"Restore"
},
+ "peers":{
+ "formTitle": ["Remote Address", "Height", "Ping", "Duration", "Total Byte"],
+ "disconnect" : "Disconnect"
+ },
"mnemonic":{
"backup":"Backup Seed",
"backupMessage":"Mnemonic used to restore the key related information. Please write down the following seed and save it in a secure location.",
"newKey":"新建密钥",
"newToken":"新建访问令牌",
"newAsset":"新建资产",
- "resetPassword":"重置密钥密码"
+ "resetPassword":"重置密钥密码",
+ "peer":"节点信息"
},
"form":{
"detail": "详情",
"selectFile":"选择备份文件",
"restore":"恢复"
},
+ "peers":{
+ "formTitle": ["远程地址", "高度", "Ping", "时长", "总字节"],
+ "disconnect" : "断开"
+ },
"mnemonic":{
"backup":"备份助记词",
"backupMessage":"助记词用于恢复密钥相关的信息。 请将它准确的抄写到纸上,并存放在的安全的地方。",
import { reducers as transactionFeed } from 'features/transactionFeeds'
import { reducers as tutorial } from 'features/tutorial'
import { reducers as unspent } from 'features/unspents'
+import { reducers as peer } from 'features/peers'
import { clear as clearStorage } from 'utility/localStorage'
const makeRootReducer = () => (state, action) => {
initialization,
key: mockhsm,
routing,
+ peer,
testnet,
transaction,
transactionFeed,
import { routes as unspents } from 'features/unspents'
import { routes as mockhsm } from 'features/mockhsm'
import { routes as backup } from 'features/backup'
+import { routes as peers } from 'features/peers'
const makeRoutes = (store) => ({
path: '/',
balances(store),
configuration,
core,
+ peers(store),
initialization,
transactions(store),
transactionFeeds(store),
--- /dev/null
+const shared = require('../shared')
+
+const peersAPI = (client) => {
+ return {
+ query: (params, cb) => shared.query(client, 'peers', '/list-peers', params, {cb}),
+
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'peers', params, processor, cb),
+
+ connect: (params, cb) => shared.query(client, 'peers', '/connect-peer', params, {cb}),
+
+ disconnect: (params, cb) => shared.query(client, 'peers', '/disconnect-peer', params, {cb}),
+ }
+}
+
+module.exports = peersAPI
const transactionsAPI = require('./api/transactions')
const transactionFeedsAPI = require('./api/transactionFeeds')
const unspentOutputsAPI = require('./api/unspentOutputs')
+const peersAPI = require('./api/peer')
class Client {
constructor(opts = {}) {
signerConnection: new Connection(`${opts.url}/mockhsm`, opts.accessToken, opts.agent)
}
+ this.peers = peersAPI(this)
+
this.transactions = transactionsAPI(this)
this.transactionFeeds = transactionFeedsAPI(this)