OSDN Git Service

update fereation function and page.
authorZhiting Lin <zlin035@uottawa.ca>
Fri, 28 Jun 2019 06:09:52 +0000 (14:09 +0800)
committerZhiting Lin <zlin035@uottawa.ca>
Fri, 28 Jun 2019 06:09:52 +0000 (14:09 +0800)
20 files changed:
src/actions.js
src/features/app/components/Navigation/Navigation.jsx
src/features/federation/actions.js [new file with mode: 0644]
src/features/federation/components/DetailSummary/DetailSummary.jsx [new file with mode: 0644]
src/features/federation/components/DetailSummary/DetailSummary.scss [new file with mode: 0644]
src/features/federation/components/List.jsx [new file with mode: 0644]
src/features/federation/components/ListItem/ListItem.jsx [new file with mode: 0644]
src/features/federation/components/ListItem/ListItem.scss [new file with mode: 0644]
src/features/federation/components/index.js [new file with mode: 0644]
src/features/federation/index.js [new file with mode: 0644]
src/features/federation/reducers.js [new file with mode: 0644]
src/features/federation/routes.js [new file with mode: 0644]
src/features/shared/actions/list.js
src/locales/en/translation.json
src/locales/zh/translation.json
src/reducers.js
src/routes.js
src/sdk/api/federations.js [new file with mode: 0644]
src/sdk/client.js
src/utility/environment.js

index 2abeca7..e137661 100644 (file)
@@ -6,6 +6,7 @@ import { actions as backUp } from 'features/backup'
 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'
@@ -24,6 +25,7 @@ const actions = {
   balance,
   configuration,
   core,
+  federation,
   initialization,
   key: mockhsm,
   testnet,
index 95038a4..9715bd4 100644 (file)
@@ -7,18 +7,30 @@ import Sync from '../Sync/Sync'
 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
@@ -56,6 +68,12 @@ class Navigation extends React.Component {
               {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}>
@@ -114,5 +132,8 @@ export default connect(
       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))
diff --git a/src/features/federation/actions.js b/src/features/federation/actions.js
new file mode 100644 (file)
index 0000000..15a4a21
--- /dev/null
@@ -0,0 +1,6 @@
+import { baseListActions } from 'features/shared/actions'
+
+let actions = {
+  ...baseListActions('federation')
+}
+export default actions
diff --git a/src/features/federation/components/DetailSummary/DetailSummary.jsx b/src/features/federation/components/DetailSummary/DetailSummary.jsx
new file mode 100644 (file)
index 0000000..0826d6b
--- /dev/null
@@ -0,0 +1,63 @@
+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>
+              &nbsp;<code>{item.txHash||'-'}</code>&nbsp;
+            </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 )
diff --git a/src/features/federation/components/DetailSummary/DetailSummary.scss b/src/features/federation/components/DetailSummary/DetailSummary.scss
new file mode 100644 (file)
index 0000000..07ce97a
--- /dev/null
@@ -0,0 +1,160 @@
+.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
diff --git a/src/features/federation/components/List.jsx b/src/features/federation/components/List.jsx
new file mode 100644 (file)
index 0000000..4b27b5f
--- /dev/null
@@ -0,0 +1,20 @@
+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)
+))
diff --git a/src/features/federation/components/ListItem/ListItem.jsx b/src/features/federation/components/ListItem/ListItem.jsx
new file mode 100644 (file)
index 0000000..2ded565
--- /dev/null
@@ -0,0 +1,67 @@
+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)
diff --git a/src/features/federation/components/ListItem/ListItem.scss b/src/features/federation/components/ListItem/ListItem.scss
new file mode 100644 (file)
index 0000000..c831262
--- /dev/null
@@ -0,0 +1,69 @@
+.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;
+}
diff --git a/src/features/federation/components/index.js b/src/features/federation/components/index.js
new file mode 100644 (file)
index 0000000..d994524
--- /dev/null
@@ -0,0 +1,8 @@
+import List from './List'
+import DetailSummary from './DetailSummary/DetailSummary'
+
+
+export {
+  List,
+  DetailSummary,
+}
diff --git a/src/features/federation/index.js b/src/features/federation/index.js
new file mode 100644 (file)
index 0000000..f4fbe4d
--- /dev/null
@@ -0,0 +1,9 @@
+import actions from './actions'
+import reducers from './reducers'
+import routes from './routes'
+
+export {
+  actions,
+  reducers,
+  routes
+}
diff --git a/src/features/federation/reducers.js b/src/features/federation/reducers.js
new file mode 100644 (file)
index 0000000..f8ec394
--- /dev/null
@@ -0,0 +1,33 @@
+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
+})
diff --git a/src/features/federation/routes.js b/src/features/federation/routes.js
new file mode 100644 (file)
index 0000000..f529e0a
--- /dev/null
@@ -0,0 +1,4 @@
+import { List  } from './components'
+import { makeRoutes } from 'features/shared'
+
+export default (store) => makeRoutes(store, 'federation', List )
index 95b8bc0..db66c59 100644 (file)
@@ -29,7 +29,10 @@ export default function(type, options = {}) {
           }
         }
       ).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
index 4da07db..629f742 100644 (file)
@@ -73,7 +73,8 @@
     "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",
index 0256c85..c674b1e 100644 (file)
@@ -73,7 +73,8 @@
     "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": "支出",
index a542289..9d52c3a 100644 (file)
@@ -7,6 +7,7 @@ import { reducers as app } from 'features/app'
 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'
@@ -53,6 +54,7 @@ const makeRootReducer = () => (state, action) => {
     balance,
     core,
     form,
+    federation,
     initialization,
     key: mockhsm,
     routing,
index e6ce7c1..f29d1b9 100644 (file)
@@ -6,6 +6,7 @@ import { routes as assets } from 'features/assets'
 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'
@@ -24,6 +25,7 @@ const makeRoutes = (store) => ({
     balances(store),
     configuration,
     core,
+    federation(store),
     peers(store),
     initialization,
     transactions(store),
diff --git a/src/sdk/api/federations.js b/src/sdk/api/federations.js
new file mode 100644 (file)
index 0000000..70418d6
--- /dev/null
@@ -0,0 +1,16 @@
+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
index 3933806..8f8a92f 100644 (file)
@@ -7,6 +7,7 @@ const backUpAPI = require('./api/backUp')
 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')
@@ -44,6 +45,8 @@ class Client {
 
     this.config = configAPI(this)
 
+    this.federations = federationAPI(this)
+
     this.mockHsm = {
       keys: mockHsmKeysAPI(this),
       signerConnection: new Connection(`${opts.url}/mockhsm`, opts.accessToken, opts.agent)
index efe16fe..55feffa 100644 (file)
@@ -32,6 +32,8 @@ export const btmID = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 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'