import { actions as balance } from 'features/balances'
import { actions as configuration } from 'features/configuration'
import { actions as core } from 'features/core'
+import { actions as federation } from 'features/federation'
import { actions as initialization } from 'features/initialization'
import { actions as mockhsm } from 'features/mockhsm'
import { actions as testnet } from 'features/testnet'
balance,
configuration,
core,
+ federation,
initialization,
key: mockhsm,
testnet,
import {docsRoot, releaseUrl} from '../../../../utility/environment'
import { capitalize } from 'utility/string'
import {withNamespaces} from 'react-i18next'
+import actions from 'actions'
class Navigation extends React.Component {
constructor(props) {
super(props)
-
this.openTutorial = this.openTutorial.bind(this)
+ this.state = {
+ showFed: false
+ }
}
openTutorial(event) {
event.preventDefault()
this.props.openTutorial()
}
+ componentDidMount() {
+ this.props.fetchFederationItem()
+ .then(resp =>{
+ this.setState({showFed: true})
+ })
+ .catch(e =>{
+ this.setState({showFed: false})
+ })
+ }
render() {
const t = this.props.t
{capitalize((t('crumbName.balance')))}
</Link>
</li>
+ { this.state.showFed && <li>
+ <Link to='/federations' activeClassName={styles.active}>
+ {navIcon('balance', styles)}
+ {capitalize((t('crumbName.federation')))}
+ </Link>
+ </li>}
</ul>
<ul className={styles.navigation}>
routing: state.routing, // required for <Link>s to update active state on navigation
showNavAdvance: state.app.navAdvancedState === 'advance'
}
- }
+ },
+ (dispatch) => ({
+ fetchFederationItem: () => dispatch(actions.federation.fetchItems()),
+ })
)( withNamespaces('translations')(Navigation))
--- /dev/null
+import { baseListActions } from 'features/shared/actions'
+
+let actions = {
+ ...baseListActions('federation')
+}
+export default actions
--- /dev/null
+import React from 'react'
+import styles from './DetailSummary.scss'
+import {withNamespaces} from 'react-i18next'
+import { RelativeTime } from 'features/shared/components'
+
+class DetailSummary extends React.Component {
+ normalizeInouts(items) {
+ const source = {
+ chain: items.sourceChainName,
+ txHash: items.sourceTxHash,
+ type:'From',
+ address: items.crosschainRequests[0].fromAddress,
+ timestamp: items.sourceBlockTimestamp
+ }
+
+ const dest = {
+ chain: items.sourceChainName === 'vapor'? 'bytom':'vapor' ,
+ txHash: items.destTxHash,
+ type:'To',
+ address: items.crosschainRequests[0].toAddress,
+ timestamp: items.destBlockTimestamp
+ }
+
+ const normalized = [source, dest]
+ return normalized
+ }
+
+ render() {
+ const item = this.props.transaction
+
+ const summary = this.normalizeInouts(item)
+
+ const t = this.props.t
+
+ return(<div className={styles.main}>
+ {summary.map((item, index) =>
+ <div className={`${styles.row} ${styles.titleBar}`} key={index}>
+ <div className={`${styles.title}`}>
+ <label>{t('transaction.crossChainId', {id: item.chain})}
+ </label>
+ <code>{item.txHash||'-'}</code>
+ </div>
+
+ <div className={styles.middle}>
+ <div className={styles.account}>
+ <div className={`${styles.colLabel} ${styles.col}`}> {item.type}</div>
+ <div className={`${styles.colAccount} ${styles.col}`}>
+ {item.address}
+ </div>
+ </div>
+
+ </div>
+
+ <div className={styles.end}>
+ <RelativeTime timestamp={item.source_block_timestamp} />
+ </div>
+ </div>
+ )}
+ </div>)
+ }
+}
+
+export default withNamespaces('translations') ( DetailSummary )
--- /dev/null
+.main {
+ background: $background-color;
+ width: 100%;
+
+ .row {
+ display: flex;
+ border-bottom: 1px solid $border-light-color;
+ }
+ .col {
+ padding-top: 13px;
+ padding-bottom: 13px;
+ padding-right: 10px;
+ }
+
+ .col:last-child {
+ padding-right: $gutter-size;
+ }
+
+ .row:last-child {
+ border-bottom: none;
+ }
+
+ a {
+ .rawId {
+ color: $highlight-default;
+ }
+
+ &:hover .rawId {
+ text-decoration: underline;
+ }
+ }
+}
+
+
+
+.colAction {
+ padding-left: 25px ;
+ text-transform: uppercase;
+ font-weight: 500;
+ img {
+ width: 20px;
+ margin-bottom: 4px;
+ margin-right: 6px;
+ }
+}
+
+.colAction, .colAmount {
+ color: $text-strong-color;
+}
+
+.colAction{
+ width: 25%;
+ flex-grow: 1;
+}
+
+.account{
+ display: flex;
+ flex: 1;
+}
+
+.middle{
+ width: 20%;
+ display: flex;
+ flex-grow: 2;
+}
+
+.end{
+ width: 15%;
+ display: flex;
+ flex-grow: 1;
+ justify-content: flex-end;
+}
+.amount {
+ background: none;
+}
+
+.emphasisLabel{
+ color: $text-strong-color;
+}
+
+.colAccount {
+ width: 150px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.colLabel {
+ color: $text-light-color;
+ text-align: right;
+}
+
+.colUnit {
+ text-align: right;
+ white-space: nowrap;
+
+ > a{
+ max-width: 100px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: block;
+ }
+}
+
+.immature {
+ text-transform: lowercase;
+ margin-left: 5px;
+ color: $text-danger;
+}
+
+.rawId {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 150px;
+ vertical-align: middle;
+}
+
+.recievedAmount {
+ text-align: right;
+}
+
+.titleBar {
+ background: $background-color;
+ border-bottom: 1px solid $border-color;
+ display: flex;
+ align-items: center;
+ padding: 5px 25px;
+
+ code {
+ display: inline-block;
+ font-size: $font-size-code;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: bottom;
+ width: 200px;
+ padding: 0 6px;
+ background: $background-emphasis-color;
+ border: 1px solid transparentize($border-color, 0.5);
+ line-height: 1.4;
+ }
+}
+
+.title {
+ flex-grow: 1;
+ align-items: center;
+ display: flex;
+
+ label {
+ color: $text-strong-color;
+ font-size: $font-size-chrome;
+ text-transform: uppercase;
+ font-weight: 500;
+ margin: 0 8px 0 0;
+ }
+ img{
+ width: 20px;
+ margin-left: $gutter-size/2;
+ margin-bottom: 2px;
+ }
+}
\ No newline at end of file
--- /dev/null
+import { BaseList } from 'features/shared/components'
+import ListItem from './ListItem/ListItem'
+import {withNamespaces} from 'react-i18next'
+
+const type = 'federation'
+
+const newStateToProps = (state, ownProps) => {
+ const props = {
+ skipCreate: true,
+ ...BaseList.mapStateToProps(type, ListItem)(state, ownProps),
+ }
+
+ return props
+}
+
+
+export default withNamespaces('translations')(BaseList.connect(
+ newStateToProps,
+ BaseList.mapDispatchToProps(type)
+))
--- /dev/null
+import React from 'react'
+import { Link } from 'react-router'
+import { DetailSummary } from 'features/federation/components'
+import styles from './ListItem.scss'
+import {withNamespaces} from 'react-i18next'
+import { converIntToDec } from 'utility/buildInOutDisplay'
+import { btmID } from 'utility/environment'
+
+class ListItem extends React.Component {
+ render() {
+ const item = this.props.item
+ const t = this.props.t
+
+ const confirmView = item.status
+
+ const txType = item.sourceChainName === 'vapor'?
+ 'crossOut':'crossIn'
+
+ const normalizeBtmAmountUnit = (assetID, amount, btmAmountUnit) => {
+ //normalize BTM Amount
+ if (assetID === btmID) {
+ switch (btmAmountUnit){
+ case 'BTM':
+ return converIntToDec(amount, 8)
+ case 'mBTM':
+ return converIntToDec(amount, 5)
+ }
+ }
+ return amount
+ }
+
+ const result = item.crosschainRequests[0]
+
+ return(
+ <div className={styles.main}>
+ <div className={styles.titleBar}>
+ <div className={styles.title}>
+ <div className={`${styles.colAction}`}>
+ <img src={require(`images/transactions/${txType}.svg`)}/> { t(`transaction.type.${txType}`)}
+ </div>
+
+ <span className={`${styles.confirmation} ${confirmView ==='pending' ? 'text-danger' : null}`}>
+ { t(`transaction.${confirmView}`) }
+ </span>
+ </div>
+ <div className={styles.end}>
+ {result.asset &&
+ [<div className={`${styles.recievedAmount}`}>
+ <code className={`${styles.emphasisLabel} ${styles.col}`}>{normalizeBtmAmountUnit(result.asset.assetId,result.amount, this.props.btmAmountUnit)}</code>
+ </div>,
+ <div className={`${styles.colUnit}`}>
+ <Link to={`/assets/${result.asset.assetId}`}>
+ {result.asset.assetId === btmID? 'BTM': result.asset.assetId}
+ </Link>
+ </div>]
+ }
+ </div>
+ </div>
+
+ <DetailSummary transaction={item} btmAmountUnit={this.props.btmAmountUnit}/>
+
+ </div>
+ )
+ }
+}
+
+export default withNamespaces('translations') (ListItem)
--- /dev/null
+.main {
+ border: 1px solid $border-color;
+ margin-bottom: 30px;
+}
+
+.titleBar {
+ background: $background-color;
+ border-bottom: 1px solid $border-color;
+ display: flex;
+ align-items: center;
+ padding: 16px 25px;
+}
+
+.title {
+ flex-grow: 1;
+ align-items: center;
+ display: flex;
+
+ label {
+ color: $text-strong-color;
+ font-size: $font-size-chrome;
+ text-transform: uppercase;
+ font-weight: 500;
+ margin: 0 8px 0 0;
+ }
+}
+
+.colAction {
+ text-transform: uppercase;
+ font-weight: 500;
+ color: $text-strong-color;
+ img {
+ width: 20px;
+ margin-bottom: 4px;
+ margin-right: 6px;
+ }
+}
+
+.confirmation {
+ position: absolute;
+ left: 43%;
+}
+
+.end{
+ display: flex;
+}
+
+.emphasisLabel{
+ color: $text-strong-color;
+}
+
+.colUnit {
+ text-align: right;
+ white-space: nowrap;
+
+ > a{
+ max-width: 100px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: block;
+ }
+}
+.recievedAmount {
+ text-align: right;
+}
+
+.col {
+ padding-right: 10px;
+}
--- /dev/null
+import List from './List'
+import DetailSummary from './DetailSummary/DetailSummary'
+
+
+export {
+ List,
+ DetailSummary,
+}
--- /dev/null
+import actions from './actions'
+import reducers from './reducers'
+import routes from './routes'
+
+export {
+ actions,
+ reducers,
+ routes
+}
--- /dev/null
+import { combineReducers } from 'redux'
+
+const itemsReducer = (state = {}, action) => {
+ if (action.type == 'APPEND_FEDERATION_PAGE') {
+ const newState = {}
+ action.param.result.data.forEach((item, index) => {
+ const id = `federation-${index}`
+ newState[id] = {
+ id: `federation-${index}`,
+ ...item
+ }
+ })
+
+ return newState
+ }
+ return state
+}
+
+const queriesReducer = (state = {}, action) => {
+ if (action.type == 'APPEND_FEDERATION_PAGE') {
+ return {
+ loadedOnce: true
+ }
+ }
+
+ return state
+}
+
+
+export default combineReducers({
+ items: itemsReducer,
+ queries: queriesReducer
+})
--- /dev/null
+import { List } from './components'
+import { makeRoutes } from 'features/shared'
+
+export default (store) => makeRoutes(store, 'federation', List )
}
}
).catch(error=>{
- dispatch({type: 'ERROR', payload: { 'message': error.msg}})
+ if(error.body){
+ dispatch({type: 'ERROR', payload: { 'message': error.body.msg}})
+ }
+ else throw error
})
return promise
"newToken":"Create an access token",
"newAsset":"Create an asset",
"resetPassword":"Reset password",
- "peer":"Peers information"
+ "peer":"Peers information",
+ "federation":"Federation"
},
"form":{
"detail": "Details",
"transaction":"Transaction",
"unconfirmedTx":"unconfirmed transaction",
"id":"Tx ID:",
+ "crossChainId":"Tx ID(__id__):",
"confirmed":"confirmed",
"contractStatus":"Contract Execution Status",
"confirmation":"__count__ confirmation",
"confirmation_plural":"__count__ confirmations",
"unconfirmedItem":"Unknown (0 confirmation)",
+ "pending":"Pending",
+ "completed":"Completed",
"type":{
"issue":"issue",
"sent":"sent",
"newToken":"新建访问令牌",
"newAsset":"新建资产",
"resetPassword":"重置密钥密码",
- "peer":"节点信息"
+ "peer":"节点信息",
+ "federation":"联邦"
},
"form":{
"detail": "详情",
"transaction":"交易",
"unconfirmedTx":"未确认交易",
"id":"交易ID:",
+ "crossChainId":"交易ID(__id__):",
"confirmed":"已确认",
"contractStatus":"合约运行状态",
"confirmation":"确认数 __count__",
"unconfirmedItem":"未知 (确认数 0)",
+ "pending":"等待确认中",
+ "completed":"已完成",
"type":{
"issue": "创建资产",
"sent": "支出",
import { reducers as asset } from 'features/assets'
import { reducers as balance } from 'features/balances'
import { reducers as core } from 'features/core'
+import { reducers as federation } from 'features/federation'
import { reducers as initialization } from 'features/initialization'
import { reducers as mockhsm } from 'features/mockhsm'
import { reducers as testnet } from 'features/testnet'
balance,
core,
form,
+ federation,
initialization,
key: mockhsm,
routing,
import { routes as balances } from 'features/balances'
import { routes as configuration } from 'features/configuration'
import { routes as core } from 'features/core'
+import { routes as federation } from 'features/federation'
import { routes as initialization } from 'features/initialization'
import { routes as transactions } from 'features/transactions'
import { routes as transactionFeeds } from 'features/transactionFeeds'
balances(store),
configuration,
core,
+ federation(store),
peers(store),
initialization,
transactions(store),
--- /dev/null
+const shared = require('../shared')
+const Connection = require('../connection')
+
+import { federationApiHost } from 'utility/environment'
+
+const federationEndpoint = '/api/v1/federation/'
+
+const federationAPI = (client) => {
+ return {
+ query: (params, cb) => shared.query( new Connection(federationApiHost, '', ''), 'federation', `${federationEndpoint}list-crosschain-txs`, params, {cb}),
+
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'federation', params, processor, cb),
+ }
+}
+
+module.exports = federationAPI
const balancesAPI = require('./api/balances')
const bytomCLI = require('./api/bytomCLI')
const configAPI = require('./api/config')
+const federationAPI = require('./api/federations')
const hsmSigner = require('./api/hsmSigner')
const mockHsmKeysAPI = require('./api/mockHsmKeys')
const transactionsAPI = require('./api/transactions')
this.config = configAPI(this)
+ this.federations = federationAPI(this)
+
this.mockHsm = {
keys: mockHsmKeysAPI(this),
signerConnection: new Connection(`${opts.url}/mockhsm`, opts.accessToken, opts.agent)
export const pageSize = 25
export const UTXOpageSize = 10
+export const federationApiHost = `${window.location.protocol}//${window.location.hostname}:9886`
+
export const testnetInfoUrl = process.env.TESTNET_INFO_URL || 'https://testnet-info.chain.com'
export const testnetUrl = process.env.TESTNET_GENERATOR_URL || 'https://testnet.chain.com'
export const docsRoot = 'https://github.com/Bytom/vapor/wiki'