diff --git a/.env.example b/.env.example
index abc9a6a..08c2c1a 100644
--- a/.env.example
+++ b/.env.example
@@ -2,3 +2,4 @@ PUBLIC=localhost:8082
CHAIN_SERVERS=http://47.96.231.19:4000
APP_NAME=Microscope
LNGS=zh,en,ja-JP,ko,de,it,fr
+DEBUG_ACCOUNTS=0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee,0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeea
diff --git a/.gitignore b/.gitignore
index 8de4ba1..7b238fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@ cita-toys-wallet
# default server list
defaultServerList.json
+
+# vscode setting
+.vscode
diff --git a/.vscode/database.json b/.vscode/database.json
deleted file mode 100644
index 9e26dfe..0000000
--- a/.vscode/database.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
\ No newline at end of file
diff --git a/.vscode/last.sql b/.vscode/last.sql
deleted file mode 100644
index e69de29..0000000
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index e69de29..0000000
diff --git a/README.md b/README.md
index 0649c9d..21277ce 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,7 @@ set env variables in `./.env`
PUBLIC= # public content server address
CHAIN_SERVERS= # default appchain addresses
APP_NAME= # explorer name
+DEBUG_ACCOUNTS= # built-in debug account's private key, e.g. 0xaeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee,0xaeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeea
```
> NOTICE: Our CDN for static assets is available at `https://cdn.cryptape.com/`, namely icons and images can be added by setting `PUBLIC=https://cdn.cryptape.com/` on `.env`.
diff --git a/config/webpack.config.base.js b/config/webpack.config.base.js
index 4cfabd9..5c0bcbe 100644
--- a/config/webpack.config.base.js
+++ b/config/webpack.config.base.js
@@ -44,7 +44,8 @@ module.exports = {
'process.env': {
CHAIN_SERVERS: JSON.stringify(process.env.CHAIN_SERVERS),
APP_NAME: JSON.stringify(process.env.APP_NAME),
- LNGS: JSON.stringify(process.env.LNGS)
+ LNGS: JSON.stringify(process.env.LNGS),
+ DEBUG_ACCOUNTS: JSON.stringify(process.env.DEBUG_ACCOUNTS),
},
}),
],
diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js
index caf513d..dc51365 100644
--- a/config/webpack.config.dev.js
+++ b/config/webpack.config.dev.js
@@ -55,7 +55,7 @@ const devConfig = {
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development'),
- OBSERVABLE_INTERVAL: 10000,
+ OBSERVABLE_INTERVAL: 1000,
PUBLIC: JSON.stringify(process.env.PUBLIC),
},
}),
@@ -73,6 +73,7 @@ const devConfig = {
],
devServer: {
hot: true,
+ host: '0.0.0.0',
historyApiFallback: true,
},
}
diff --git a/package-lock.json b/package-lock.json
index 3cc2352..543d743 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14232,14 +14232,6 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
- "typedarray-to-buffer": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
- "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
- "requires": {
- "is-typedarray": "^1.0.0"
- }
- },
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
@@ -15594,8 +15586,27 @@
"integrity": "sha512-Cx64NgDStynKaUGDIIOfaCd0fZusL8h5avKTkdTjUu2aHhFJhZoVBGVLhoDtUaqZGWIZGcBJOoVf2JkGUOjDRQ==",
"requires": {
"underscore": "1.8.3",
- "web3-core-helpers": "1.0.0-beta.35",
- "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2"
+ "web3-core-helpers": "1.0.0-beta.35"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "websocket": {
+ "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
+ "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
+ "requires": {
+ "debug": "^2.2.0",
+ "nan": "^2.3.3",
+ "typedarray-to-buffer": "^3.1.2",
+ "yaeti": "^0.0.6"
+ }
+ }
}
},
"web3-shh": {
@@ -16836,26 +16847,6 @@
"source-map": "~0.6.1"
}
},
- "websocket": {
- "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
- "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible",
- "requires": {
- "debug": "^2.2.0",
- "nan": "^2.3.3",
- "typedarray-to-buffer": "^3.1.2",
- "yaeti": "^0.0.6"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- }
- }
- },
"websocket-driver": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
@@ -17110,11 +17101,6 @@
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
- "yaeti": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
- "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
- },
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
diff --git a/package.json b/package.json
index e29cb96..45ce368 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"dependencies": {
"@material-ui/core": "^1.2.0",
"@material-ui/icons": "^1.1.0",
- "@nervos/observables": "^0.18.0",
+ "@nervos/observables": "0.18.3",
"@nervos/signer": "^1.0.28",
"@reactivex/rxjs": "^5.5.6",
"axios": "^0.18.0",
diff --git a/src/Routes/containers.ts b/src/Routes/containers.ts
index 97ca8e6..dfe90c8 100644
--- a/src/Routes/containers.ts
+++ b/src/Routes/containers.ts
@@ -5,21 +5,21 @@ export default [
name: 'Homepage',
component: 'Homepage',
exact: true,
- nav: false,
+ nav: false
},
{
path: '/block/:blockHash',
name: 'BlockByHash',
component: 'Block',
exact: true,
- nav: false,
+ nav: false
},
{
path: '/height/:height',
name: 'BlockByHeight',
component: 'Block',
exact: true,
- nav: false,
+ nav: false
},
{
path: '/blocks',
@@ -28,9 +28,7 @@ export default [
exact: true,
nav: true,
icon: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/block.svg`,
- iconActive: `${
- process.env.PUBLIC
- }/microscopeIcons/mobile_navs/block_active.svg`,
+ iconActive: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/block_active.svg`
},
{
path: '/transactions',
@@ -39,23 +37,21 @@ export default [
exact: true,
nav: true,
icon: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/transaction.svg`,
- iconActive: `${
- process.env.PUBLIC
- }/microscopeIcons/mobile_navs/transaction_active.svg`,
+ iconActive: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/transaction_active.svg`
},
{
path: '/transaction/:transaction',
name: 'Transaction',
component: 'Transaction',
exact: true,
- nav: false,
+ nav: false
},
{
path: '/account/:account',
name: 'Account',
component: 'Account',
exact: true,
- nav: false,
+ nav: false
},
{
path: '/graphs',
@@ -64,9 +60,16 @@ export default [
exact: true,
nav: true,
icon: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/statistics.svg`,
- iconActive: `${
- process.env.PUBLIC
- }/microscopeIcons/mobile_navs/statistics_active.svg`,
+ iconActive: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/statistics_active.svg`
+ },
+ {
+ path: '/debugger',
+ name: 'Debugger',
+ component: 'Debugger',
+ exact: true,
+ nav: true,
+ icon: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/statistics.svg`,
+ iconActive: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/statistics_active.svg`
},
{
path: '/config',
@@ -75,9 +78,7 @@ export default [
exact: true,
nav: true,
icon: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/config.svg`,
- iconActive: `${
- process.env.PUBLIC
- }/microscopeIcons/mobile_navs/config_active.svg`,
+ iconActive: `${process.env.PUBLIC}/microscopeIcons/mobile_navs/config_active.svg`
},
- { path: '/', name: 'Footer', component: 'Footer', exact: false, nav: false },
+ { path: '/', name: 'Footer', component: 'Footer', exact: false, nav: false }
]
diff --git a/src/components/ContractInfoPanel/index.tsx b/src/components/ContractInfoPanel/index.tsx
new file mode 100644
index 0000000..b86e649
--- /dev/null
+++ b/src/components/ContractInfoPanel/index.tsx
@@ -0,0 +1,74 @@
+import * as React from 'react'
+import { List, Divider } from '@material-ui/core'
+import { ABI, ABIElement } from '../../typings'
+import { withObservables } from '../../contexts/observables'
+import { copyToClipboard } from '../../utils/copyToClipboard'
+import { handleError, dismissError } from '../../utils/handleError'
+
+const styles = require('./styles.scss')
+
+const Infoblock = ({ title, code }) => (
+
+
+ {title}
+
+
+
{code}
+
+)
+
+interface PanelProps {
+ CITAObservables: any
+ // account: string
+ abi: any
+ code: string
+}
+
+interface PanelState {
+ // abi: string
+ // code: string
+}
+
+// class Panel extends React.Component {
+// state = {
+// abi: JSON.stringify(this.props.abi),
+// code: ''
+// }
+// public componentWillMount () {
+// // this.getabi()
+// this.getcode()
+// }
+// // private getabi = () => this.props.CITAObservables.getAbi(this.citaParams).subscribe((abi) => this.setState({ abi }))
+// private getcode = () => this.props.CITAObservables.getCode(this.citaParams).subscribe(code => this.setState({ code }))
+// private citaParams = {
+// contractAddr: this.props.account,
+// blockNumber: 'latest'
+// }
+// public render () {
+// // const { abi, code } = this.state
+// const abi = JSON.stringify(this.props.abi)
+// const { code } = this.props
+// return (
+//
+//
+//
+//
+// )
+// }
+// }
+
+const Panel = (props: PanelProps) => {
+ // const { abi, code } = this.state
+ const abi = JSON.stringify(props.abi)
+ const { code } = props
+ return (
+
+
+
+
+ )
+}
+
+export default withObservables(Panel)
diff --git a/src/components/ContractInfoPanel/styles.scss b/src/components/ContractInfoPanel/styles.scss
new file mode 100644
index 0000000..7bc99cd
--- /dev/null
+++ b/src/components/ContractInfoPanel/styles.scss
@@ -0,0 +1,92 @@
+@import '../../styles/color.scss';
+.infoblock {
+ max-width: 650px;
+ margin: 0 auto 57px;
+ &:first-child {
+ margin-top: 102px;
+ }
+ // input {
+ // border: 1px solid #d3ddec;
+ // width: 120px;
+ // height: 34px;
+ // line-height: 34px;
+ // border-radius: 4px;
+ // padding: 7px;
+ // margin: 8px;
+ // box-sizing: border-box;
+ // }
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ margin-bottom: 10px;
+ }
+ .title {
+ font-size: 0.875rem;
+ font-weight: 500;
+ text-align: center;
+ color: #2e313e;
+ }
+ .button {
+ padding: 8px 20px;
+ border: none;
+ border-radius: 4px;
+ background: $active;
+ color: #fff;
+ font-size: 0.875rem;
+ }
+ .code {
+ font-size: 0.875rem;
+ line-height: 1.71;
+ color: #2e313e;
+ border-radius: 4px;
+ border: solid 1px #e7edf5;
+ background-color: #fafbff;
+ padding: 14px 77px 16px 18px;
+ word-break: break-word;
+ max-height: 280px;
+ overflow: auto;
+ }
+}
+
+// .title {
+// line-height: 34px;
+// margin-top: 8px;
+// text-transform: capitalize;
+// &:after {
+// content: ' Method';
+// }
+// }
+
+// .inputs,
+// .outputs {
+// position: relative;
+// line-height: 34px;
+// min-height: 34px;
+// margin-left: 65px;
+// &:before {
+// position: absolute;
+// top: 8px;
+// right: 100%;
+// display: inline-block;
+// width: 65px;
+// }
+// }
+
+// .inputs {
+// &:before {
+// content: 'Inputs: ';
+// }
+// }
+
+// .outputs {
+// &:before {
+// content: 'Outputs: ';
+// }
+// }
+
+// .noEls {
+// display: inline-block;
+// margin: 8px;
+// color: #ccc;
+// }
diff --git a/src/components/DebugAccounts/debugAccounts.scss b/src/components/DebugAccounts/debugAccounts.scss
new file mode 100644
index 0000000..ac0a517
--- /dev/null
+++ b/src/components/DebugAccounts/debugAccounts.scss
@@ -0,0 +1,3 @@
+.accountSecondaryText {
+ text-align: right
+}
diff --git a/src/components/DebugAccounts/index.tsx b/src/components/DebugAccounts/index.tsx
new file mode 100644
index 0000000..00877ee
--- /dev/null
+++ b/src/components/DebugAccounts/index.tsx
@@ -0,0 +1,93 @@
+import {
+ Button,
+ ExpansionPanel,
+ ExpansionPanelActions,
+ ExpansionPanelDetails,
+ ExpansionPanelSummary,
+ List,
+ ListItem,
+ ListItemText,
+ TextField
+} from '@material-ui/core'
+import { ExpandMore as ExpandMoreIcon } from '@material-ui/icons'
+import { Link } from 'react-router-dom'
+import * as React from 'react'
+
+const styles = require('./debugAccounts.scss')
+const texts = require('../../styles/text.scss')
+
+export interface DebugAccount {
+ privateKey: string
+ address: string
+ balance?: string
+}
+const DebugAccounts = ({
+ accounts,
+ privateKeysField,
+ handleAccountsInput,
+ updateDebugAccounts
+}: {
+accounts: DebugAccount[]
+privateKeysField: string
+handleAccountsInput: React.EventHandler>
+updateDebugAccounts: React.EventHandler>
+}) => (
+
+ }>
+ Debug Accounts(
+ {accounts.length})
+
+
+
+
+
+
+
+ {accounts.map(account => (
+
+
+ {account.address || 'null'}
+
+ }
+ secondary={account.privateKey}
+ />
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+)
+
+export default DebugAccounts
diff --git a/src/components/ErrorNotification/index.tsx b/src/components/ErrorNotification/index.tsx
index d5904d8..4355008 100644
--- a/src/components/ErrorNotification/index.tsx
+++ b/src/components/ErrorNotification/index.tsx
@@ -18,7 +18,7 @@ export default ({ error, dismissError }) => (
href="https://docs.nervos.org/cita/#/rpc_guide/rpc_error_code"
target="_blank"
rel="noopener noreferrer"
- className={texts.highlight}
+ className={texts.errMsgLink}
>
More
diff --git a/src/components/HomepageLists/BlockList.tsx b/src/components/HomepageLists/BlockList.tsx
index e32a87d..10e8236 100644
--- a/src/components/HomepageLists/BlockList.tsx
+++ b/src/components/HomepageLists/BlockList.tsx
@@ -39,12 +39,16 @@ export default translate('microscope')(
}}
primary={
-
+
{t('hash')}:{' '}
-
-
- {block.hash}
-
+
+ {block.hash.slice(0, -4)}
+ {block.hash.slice(-4)}
{`${fromNow(block.header.timestamp)} ago`}
diff --git a/src/components/HomepageLists/TransactionList.tsx b/src/components/HomepageLists/TransactionList.tsx
index 18799e1..29762c5 100644
--- a/src/components/HomepageLists/TransactionList.tsx
+++ b/src/components/HomepageLists/TransactionList.tsx
@@ -28,10 +28,16 @@ export default translate('microscope')(
classes={{ primary: styles.primary, root: styles.listItemTextRoot }}
primary={
-
+
{t('transaction')}:{' '}
-
- {tx.hash}
+
+ {tx.hash.slice(0, -4)}
+ {tx.hash.slice(-4)}
{`${fromNow(tx.timestamp)} ago`}
diff --git a/src/components/HomepageLists/homepageList.scss b/src/components/HomepageLists/homepageList.scss
index 934ea06..498a479 100644
--- a/src/components/HomepageLists/homepageList.scss
+++ b/src/components/HomepageLists/homepageList.scss
@@ -2,7 +2,7 @@
.primary {
color: $text;
- display: flex !important;
+ // display: flex !important;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
@@ -10,13 +10,21 @@
a {
word-break: break-all;
}
+ a.hashlink {
+ word-break: keep-all;
+ white-space: nowrap;
+ display: inline-flex;
+ flex-wrap: nowrap;
+ max-width: 100%;
+ text-transform: none;
+ }
}
.secondary {
color: $text;
word-break: break-all;
- &>span {
+ & > span {
display: block;
}
}
@@ -31,9 +39,9 @@
margin-left: 33px;
display: flex;
align-items: stretch !important;
- height: 115px;
border-bottom: 1px solid $divider;
text-transform: capitalize;
+ height: 130px;
svg {
font-size: 1.5rem;
@@ -56,7 +64,7 @@
background-size: cover;
min-width: 100px;
padding: 0 20px;
- color: #FFF !important;
+ color: #fff !important;
border-radius: 4px;
box-sizing: border-box;
line-height: 1.4;
diff --git a/src/components/Icons/icons.scss b/src/components/Icons/icons.scss
new file mode 100644
index 0000000..943b976
--- /dev/null
+++ b/src/components/Icons/icons.scss
@@ -0,0 +1,12 @@
+@keyframes rotation {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.loading {
+ animation: rotation 2s linear infinite;
+}
diff --git a/src/components/Icons/index.tsx b/src/components/Icons/index.tsx
new file mode 100644
index 0000000..9c02df1
--- /dev/null
+++ b/src/components/Icons/index.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react'
+
+const styles = require('./icons.scss')
+
+const IconBase = ({ name, className = '' }) => (
+
+)
+
+const Loading = () =>
+
+export { Loading, IconBase }
+
+export default IconBase
diff --git a/src/components/MetadataPanel/index.tsx b/src/components/MetadataPanel/index.tsx
index 0b776df..25539f8 100644
--- a/src/components/MetadataPanel/index.tsx
+++ b/src/components/MetadataPanel/index.tsx
@@ -2,6 +2,7 @@ import * as React from 'react'
import { List, ListItem, ListItemText } from '@material-ui/core'
import { translate } from 'react-i18next'
import { Metadata } from '../../typings'
+import { Loading } from '../../components/Icons'
const styles = require('./metadata.scss')
const text = require('../../styles/text.scss')
@@ -12,11 +13,11 @@ const list = [
{ name: 'Operator', value: 'operator' },
{ name: 'Website', value: 'website' },
{ name: 'Genesis Time', value: 'genesisTimestamp' },
- // { name: 'Version', value: 'version' },
- { name: 'Block Interval', value: 'blockInterval' },
+ { name: 'Version', value: 'version' },
+ { name: 'Block Interval', value: 'blockInterval', unitName: 'ms' },
{ name: 'Token Name', value: 'tokenName' },
- { name: 'Token Symbol', value: 'tokenSymbol' }
- // { name: 'Economical Model', value: 'economicalModel' }
+ { name: 'Token Symbol', value: 'tokenSymbol' },
+ { name: 'Economical Model', value: 'economicalModel' }
]
const MetadataRender = translate('microscope')(
@@ -24,7 +25,10 @@ const MetadataRender = translate('microscope')(
{list.map(item => (
- {t(item.name)}: {metadata[item.value]}
+ {t(item.name)}:{' '}
+
+ {metadata[item.value]} {item.unitName || ''}
+
))}
@@ -49,8 +53,10 @@ interface MetadataPanelProps {
metadata: Metadata
searchIp: string
searchResult: Metadata
+ waitingMetadata: boolean
+ inputChainError: boolean
handleInput: (key: string) => (e: any) => void
- switchChain: (ip?: string) => (e) => void
+ switchChain: (ip?: string, immediate?: boolean) => (e) => void
handleKeyUp: (e: React.KeyboardEvent
) => void
t: (key: string) => string
serverList: ServerList
@@ -60,6 +66,8 @@ const MetadataPanel: React.SFC = ({
metadata,
searchIp,
searchResult,
+ inputChainError,
+ waitingMetadata,
handleInput,
handleKeyUp,
switchChain,
@@ -74,18 +82,33 @@ const MetadataPanel: React.SFC = ({
{t('other')} {t('chain')}
+
+ If you connect to an AppChain node instead of a
ReBirth server,
+ Microscope will NOT be fully functional.
+
+ {inputChainError ? (
+
Please enter a URL to AppChain node or ReBirth server
+ ) : null}
+
{searchResult.chainId !== -1 ? (
) : (
@@ -93,7 +116,7 @@ const MetadataPanel: React.SFC = ({
{serverList.map(({ serverName, serverIp }) => (
div {
+ & > div {
height: 0.875rem;
line-height: 1.7rem;
height: 1.7rem;
@@ -54,11 +54,20 @@ $sidebar-padding: 29px;
padding: $sidebar-padding;
height: 34px;
box-sizing: content-box;
+ position: relative;
input {
border: 1px solid #ccc;
border-radius: 4px;
flex: 1;
}
+ input:focus {
+ border-color: $form-focus;
+ box-shadow: 0 0 4px 0 $form-focus;
+ }
+ input.error:focus {
+ border-color: $form-error;
+ box-shadow: 0 0 4px 0 $form-error;
+ }
button {
border: none;
border-radius: 4px;
@@ -74,6 +83,16 @@ $sidebar-padding: 29px;
cursor: default;
}
}
+ .chainerror {
+ color: $form-error;
+ position: absolute;
+ bottom: 0;
+ }
+ .chainerror {
+ color: $form-error;
+ position: absolute;
+ bottom: 0;
+ }
}
.listItem:hover {
diff --git a/src/components/SearchPanel/index.tsx b/src/components/SearchPanel/index.tsx
index aa9f86f..99d7126 100644
--- a/src/components/SearchPanel/index.tsx
+++ b/src/components/SearchPanel/index.tsx
@@ -6,38 +6,43 @@ import { unsigner } from '@nervos/signer'
import { withObservables } from '../../contexts/observables'
import { IContainerProps, IBlock, UnsignedTransaction } from '../../typings'
import { initBlock, initUnsignedTransaction } from '../../initValues'
+import { fetchTransactions } from '../../utils/fetcher'
import toText from '../../utils/toText'
import bytesToHex from '../../utils/bytesToHex'
import valueFormatter from '../../utils/valueFormatter'
+import check from '../../utils/check'
const styles = require('./styles.scss')
+const NOT_FOUND_IMG = `${process.env.PUBLIC}/images/search_not_found.png`
enum SearchType {
BLOCK,
TRANSACTION,
ACCOUNT,
- HEIGHT
+ HEIGHT,
+ ERROR
}
const searchGen = keyword => {
- switch (keyword.length) {
- case 64:
- case 66: {
- return [
- { type: SearchType.BLOCK, url: `/block/${keyword}` },
- { type: SearchType.TRANSACTION, url: `/transactions/${keyword}` }
- ]
- }
- case 40:
- case 42: {
- return [{ type: SearchType.ACCOUNT, url: `/account/${keyword}` }]
- }
- default: {
- return [{ type: SearchType.HEIGHT, url: `/height/${keyword}` }]
- }
+ let word = check.format0x(keyword)
+ word = word.toLocaleLowerCase()
+ if (check.address(word)) {
+ return { type: SearchType.ACCOUNT, value: word }
+ } else if (check.transaction(word)) {
+ return { type: SearchType.TRANSACTION, value: word }
+ } else if (check.height(word)) {
+ return { type: SearchType.HEIGHT, value: word }
}
+ return { type: SearchType.ERROR, value: word }
}
+const NotFound = () => (
+
+
+
Not Found
+
+)
+
const BlockDisplay = translate('microscope')(({ block, t }: { block: IBlock; t: (key: string) => string }) => (
Block
@@ -130,7 +135,9 @@ const initState = {
block: initBlock,
transaction: { ...initUnsignedTransaction, hash: '' },
txCount: '',
- balance: ''
+ balance: '',
+ searchValueError: false,
+ searched: false
}
type SearchPanelState = typeof initState
@@ -141,7 +148,8 @@ class SearchPanel extends React.Component
{
private handleInput = (e: React.SyntheticEvent) => {
const { value } = e.currentTarget
this.setState({
- keyword: value
+ keyword: value,
+ searched: false
})
}
private handleKeyUp = (e: React.KeyboardEvent) => {
@@ -149,69 +157,85 @@ class SearchPanel extends React.Component {
this.handleSearch()
}
}
+ private fetchHeight = value =>
+ this.props.CITAObservables.blockByNumber(value).subscribe(block =>
+ this.setState(state => Object.assign({}, state, { block }))
+ )
+ private fetchTxOrBlock = value => {
+ this.props.CITAObservables.blockByHash(value).subscribe(block =>
+ this.setState(state => Object.assign({}, state, { block }))
+ )
+ this.props.CITAObservables.getTransaction(value).subscribe(transaction => {
+ const unsignedTransaction = unsigner(transaction.content)
+ unsignedTransaction.hash = transaction.hash
+ this.setState(state => Object.assign({}, state, { transaction: unsignedTransaction }))
+ })
+ }
+ private fetchAccount = value => {
+ fetchTransactions({ account: value })
+ .then(({ result }) => this.setState(state => Object.assign({}, state, { txCount: result.count })))
+ .catch(() => {
+ this.props.CITAObservables.getTransactionCount({
+ addr: value,
+ blockNumber: 'latest'
+ }).subscribe(txCount => {
+ this.setState(state => Object.assign({}, state, { txCount }))
+ })
+ })
+ return this.props.CITAObservables.getBalance({
+ addr: value,
+ blockNumber: 'latest'
+ }).subscribe(balance => {
+ this.setState(state => Object.assign({}, state, { balance }))
+ })
+ }
+ private inputSearchError = () =>
+ this.setState({
+ searchValueError: true
+ })
private handleSearch = () => {
const { keyword } = this.state
+ const { fetchHeight, fetchTxOrBlock, fetchAccount, inputSearchError } = this
if (keyword === '') return
// clear history
- this.setState({ ...initState, keyword })
-
- const { CITAObservables } = this.props
- const searches = searchGen(keyword)
- searches.forEach(search => {
- switch (search.type) {
- case SearchType.BLOCK: {
- return CITAObservables.blockByHash(keyword).subscribe(block =>
- this.setState(state => Object.assign({}, state, { block }))
- )
- }
- case SearchType.HEIGHT: {
- return CITAObservables.blockByNumber((+keyword).toString(16)).subscribe(block =>
- this.setState(state => Object.assign({}, state, { block }))
- )
- }
- case SearchType.TRANSACTION: {
- return CITAObservables.getTransaction(keyword).subscribe(transaction => {
- const unsignedTransaction = unsigner(transaction.content)
- unsignedTransaction.hash = transaction.hash
- this.setState(state => Object.assign({}, state, { transaction: unsignedTransaction }))
- })
- }
- case SearchType.ACCOUNT: {
- // CITAObservables.getBalance({ addr: keyword, blockNumber: "latest" }).subscribe(balance => {
- // this.setState(state => Object.assign({}, state, {balance}))
- // })
- CITAObservables.getTransactionCount({
- addr: keyword,
- blockNumber: 'latest'
- }).subscribe(txCount => {
- this.setState(state => Object.assign({}, state, { txCount }))
- })
- return CITAObservables.getBalance({
- addr: keyword,
- blockNumber: 'latest'
- }).subscribe(balance => {
- this.setState(state => Object.assign({}, state, { balance }))
- })
- }
- default: {
- return false
- }
- }
- })
+ this.setState({ ...initState, keyword, searched: true })
+ const typeTable = {
+ [SearchType.HEIGHT]: fetchHeight,
+ [SearchType.TRANSACTION]: fetchTxOrBlock,
+ [SearchType.ACCOUNT]: fetchAccount,
+ [SearchType.ERROR]: inputSearchError
+ }
+ const search = searchGen(keyword)
+ typeTable[search.type](search.value)
}
render () {
- const { keyword, block, transaction, balance, txCount } = this.state
+ const { keyword, block, transaction, balance, txCount, searchValueError, searched } = this.state
return (
-
-
-
+
+
+
+
+
+ {searchValueError ? (
+
+ Please enter Address or Transaction Hash or Block Hash or Block Height
+
+ ) : null}
+
{block.hash ?
: null}
{transaction.hash ?
: null}
{balance !== '' ?
: null}
+ {searched && !searchValueError && !block.hash && !transaction.hash && !balance ?
: null}
)
}
diff --git a/src/components/SearchPanel/styles.scss b/src/components/SearchPanel/styles.scss
index 5231f10..dec354f 100644
--- a/src/components/SearchPanel/styles.scss
+++ b/src/components/SearchPanel/styles.scss
@@ -1,34 +1,65 @@
@import '../../styles/color.scss';
$sidebar-padding: 29px;
+
.fields {
- display: flex;
- padding: $sidebar-padding;
- height: 34px;
- box-sizing: content-box;
+ & {
+ display: flex;
+ padding: 29px;
+ box-sizing: content-box;
+ position: relative;
+ flex-flow: column;
+ }
+
+ .search {
+ display: flex;
+ height: 34px;
+ margin-bottom: 10px;
+ }
+
input {
border: 1px solid #ccc;
border-right-color: transparent;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
flex: 1;
+
&:hover,
&:focus {
- border-color: $active;
+ border-color: $form-focus;
+ box-shadow: 0 0 4px 0 $form-focus;
border-right-color: transparent;
}
}
+
button {
border: none;
border-radius: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
background-color: $active;
- color: #FFF;
+ color: #fff;
padding: 4px 25px;
}
}
-@media(min-width: 800px) {
+.fields.error {
+ input {
+
+ &:hover,
+ &:focus {
+ border-color: $form-error;
+ box-shadow: 0 0 4px 0 $form-error;
+ }
+ }
+
+ .errormessage {
+ color: $form-error;
+ width: 416px;
+ max-width: 100%;
+ }
+}
+
+@media (min-width: 800px) {
.fields {
input {
min-width: 320px;
@@ -49,13 +80,16 @@ $sidebar-padding: 29px;
padding: $sidebar-padding;
padding-top: 34px;
padding-bottom: 43px;
+
tr {
font-size: 0.875rem;
line-height: 3;
height: 3em;
+
td:first-child:after {
content: ':';
}
+
td:last-child {
max-width: 30vw;
overflow: hidden;
@@ -82,3 +116,16 @@ $sidebar-padding: 29px;
.display {
text-transform: capitalize;
}
+
+.notFound {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ font-size: 16px;
+ color: #6c7184;
+
+ img {
+ width: 150px;
+ margin-bottom: 20px;
+ }
+}
diff --git a/src/components/TableWithSelector/index.tsx b/src/components/TableWithSelector/index.tsx
index 416da43..2b835cb 100644
--- a/src/components/TableWithSelector/index.tsx
+++ b/src/components/TableWithSelector/index.tsx
@@ -39,7 +39,10 @@ export interface TableWithSelectorProps {
type: SelectorType
key: string
text: string
- items?: { key: string; text: string }[]
+ items?: { key: string; text: string; check?: any; format?: any; errorMessage?: any }[]
+ check?: any
+ format?: any
+ errorMessage?: any
}[]
selectorsValue?: any
onSubmit?: any
@@ -53,7 +56,8 @@ export interface TableWithSelectorProps {
class TableWithSelector extends React.Component
string }, any> {
state = {
on: false,
- selectorsValue: this.props.selectorsValue
+ selectorsValue: this.props.selectorsValue,
+ selectorsError: {} as any
}
showDialog = (on: boolean = false) => (e?: any) => {
@@ -64,24 +68,52 @@ class TableWithSelector extends React.Component e => {
e.persist()
- this.setState(state => {
- const { selectorsValue } = state
- const newSelectorsValue = {
- ...selectorsValue,
- [selector]: e.target.value
+ const { value } = e.target
+ this.setState(state => ({
+ selectorsValue: {
+ ...state.selectorsValue,
+ [selector]: value
+ },
+ selectorsError: {
+ ...state.selectorsError,
+ [selector]: false
}
-
- return {
- selectorsValue: newSelectorsValue
+ }))
+ }
+ handleSubmit = e => {
+ const { selectorsError } = this.state
+ let allright = true
+ Object.keys(selectorsError).forEach(key => {
+ const error = selectorsError[key]
+ if (error === undefined || error) {
+ allright = false
}
})
+ if (allright) {
+ this.props.onSubmit(this.state.selectorsValue)
+ this.showDialog(false)()
+ }
}
- handleSubmit = e => {
- this.props.onSubmit(this.state.selectorsValue)
- this.showDialog(false)()
+ handleSelectorBlur = (selector: string, check: any = () => false, format: any = v => v) => e => {
+ e.persist()
+ const { value } = e.target
+ const valueError = value ? !check(value) : false
+ this.setState(state => {
+ const { selectorsValue, selectorsError } = state
+ return {
+ selectorsValue: {
+ ...selectorsValue,
+ [selector]: value
+ },
+ selectorsError: {
+ ...selectorsError,
+ [selector]: valueError
+ }
+ }
+ })
}
render () {
- const { on, selectorsValue } = this.state
+ const { on, selectorsValue, selectorsError } = this.state
const { headers, items, selectors, pageSize, pageNo, count, t, inset, searchText } = this.props
const total = Math.ceil(count / pageSize)
// const activeParams = paramsFilter(this.props.selectorsValue)
@@ -92,7 +124,9 @@ class TableWithSelector extends React.Component
{selectors.map(selector => (
- {t(selector.text)}
+
+ {t(selector.text)}
+
))}
) : (
-
+
+
+ {selectorsError[selector.key] ? (
+
{selector.errorMessage}
+ ) : null}
+
)
)}
diff --git a/src/components/TableWithSelector/tableWithSelector.scss b/src/components/TableWithSelector/tableWithSelector.scss
index 3d9f1b4..f6ee477 100644
--- a/src/components/TableWithSelector/tableWithSelector.scss
+++ b/src/components/TableWithSelector/tableWithSelector.scss
@@ -196,3 +196,28 @@
}
}
}
+
+.inputouter {
+ & {
+ position: relative;
+ }
+ input:focus {
+ border-color: $form-focus;
+ box-shadow: 0 0 4px 0 $form-focus;
+ }
+}
+
+.inputouter.error {
+ // input {
+ // border: $form-error;
+ // }
+ input:focus {
+ border-color: $form-error;
+ box-shadow: 0 0 4px 0 $form-error;
+ }
+ .errormessage {
+ color: $form-error;
+ position: absolute;
+ bottom: 0;
+ }
+}
diff --git a/src/config/localstorage.ts b/src/config/localstorage.ts
index fb405c7..c343940 100644
--- a/src/config/localstorage.ts
+++ b/src/config/localstorage.ts
@@ -2,6 +2,7 @@ export enum LOCAL_STORAGE {
SERVER_LIST = 'server_list',
PRIV_KEY_LIST = 'privkey_list',
PANEL_CONFIGS = 'panel_configs',
+ LOCAL_DEBUG_ACCOUNTS = 'localDebugAccounts'
}
export interface PanelConfigs {
logo: string
diff --git a/src/containers/Account/index.tsx b/src/containers/Account/index.tsx
index 4a83b45..ad38226 100644
--- a/src/containers/Account/index.tsx
+++ b/src/containers/Account/index.tsx
@@ -18,6 +18,7 @@ import Banner from '../../components/Banner'
import Dialog from '../Dialog'
import ErrorNotification from '../../components/ErrorNotification'
import LocalAccounts from '../../components/LocalAccounts'
+import ContractInfoPanel from '../../components/ContractInfoPanel'
import { AccountType } from '../../typings/account'
import { IContainerProps } from '../../typings'
@@ -31,7 +32,7 @@ import valueFormatter from '../../utils/valueFormatter'
const layouts = require('../../styles/layout.scss')
const text = require('../../styles/text.scss')
-const accountFormatter = (addr: string) => (addr.startsWith('0x') ? addr : `0x${addr}`)
+const accountFormatter = (addr: string) => (/^0x/i.test(addr) ? addr : `0x${addr}`)
interface AccountProps extends IContainerProps {}
type AccountState = typeof initAccountState
class Account extends React.Component {
@@ -52,14 +53,22 @@ class Account extends React.Component {
public componentDidCatch (err) {
this.handleError(err)
}
- private onMount = account => {
+ private onMount = accountInput => {
+ const account = accountFormatter(accountInput)
this.setState(initAccountState)
this.updateBasicInfo(account)
+ this.fetchContractCode(account)
}
private onTabClick = (e, value) => {
- this.setState({ panelOn: !!value })
+ this.setState({ panelOn: value })
}
+ private setTransactionsCount = count => this.setState({ txCount: count })
+ private fetchContractCode = account =>
+ this.props.CITAObservables.getCode({
+ contractAddr: account,
+ blockNumber: 'latest'
+ }).subscribe(code => this.setState({ code }))
protected readonly addrGroups = [
{
key: 'normals',
@@ -76,20 +85,20 @@ class Account extends React.Component {
]
private fetchInfo = addr => {
// NOTE: async
- this.setState(state => ({ loading: state.loading + 3 })) // for get balance, get transaction count, and get abi
+ this.setState(state => ({ loading: state.loading + 2 })) // for get balance, get transaction count, and get abi
this.props.CITAObservables.getBalance({ addr, blockNumber: 'latest' })
// .finally(() => this.setState(state => ({ loading: state.loading - 1 })))
.subscribe(
(balance: string) => this.setState(state => ({ loading: state.loading - 1, balance: `${+balance}` })),
this.handleError
)
- this.props.CITAObservables.getTransactionCount({
- addr,
- blockNumber: 'latest'
- }).subscribe(
- (count: string) => this.setState(state => ({ txCount: +count, loading: state.loading - 1 })),
- this.handleError
- )
+ // this.props.CITAObservables.getTransactionCount({
+ // addr,
+ // blockNumber: 'latest'
+ // }).subscribe(
+ // (count: string) => this.setState(state => ({ txCount: +count, loading: state.loading - 1 })),
+ // this.handleError
+ // )
this.props.CITAObservables.getAbi({
contractAddr: addr,
@@ -222,6 +231,25 @@ class Account extends React.Component {
}
private handleError = handleError(this)
private dismissError = dismissError(this)
+ private renderPanelByTab = () => {
+ const { abi, addr, panelOn, code } = this.state
+ // const { account } = this.props.match.params
+ const erc = (
+ abiEl.type === 'function')}
+ handleAbiValueChange={this.handleAbiValueChange}
+ handleEthCall={this.handleEthCall}
+ />
+ )
+ const tx =
+ const info =
+ const table = {
+ tx,
+ abi: erc,
+ info
+ }
+ return table[panelOn]
+ }
render () {
const {
loading,
@@ -237,8 +265,10 @@ class Account extends React.Component {
erc20sAdd,
erc721sAdd,
abi,
+ code,
error
} = this.state
+
return (
{loading ? (
@@ -261,20 +291,13 @@ class Account extends React.Component {
管理本地账户} />
-
-
- {abi && abi.length ? : null}
+
+
+ {abi && abi.length ? : null}
+ {code === '0x' ? null : }
- {panelOn ? (
- abiEl.type === 'function')}
- handleAbiValueChange={this.handleAbiValueChange}
- handleEthCall={this.handleEthCall}
- />
- ) : (
-
- )}
+ {this.renderPanelByTab()}
diff --git a/src/containers/Block/index.tsx b/src/containers/Block/index.tsx
index 61395dd..f421c12 100644
--- a/src/containers/Block/index.tsx
+++ b/src/containers/Block/index.tsx
@@ -166,9 +166,16 @@ class Block extends React.Component {
-
+
+ TimeStamp
+ {new Date(header.timestamp).toLocaleString()}
+
+
Transactions
- {transactions.length}
+ {transactions.length}
Proposer
diff --git a/src/containers/ConfigPage/index.tsx b/src/containers/ConfigPage/index.tsx
index 3e9b7ad..468d125 100644
--- a/src/containers/ConfigPage/index.tsx
+++ b/src/containers/ConfigPage/index.tsx
@@ -17,6 +17,7 @@ import {
import { ExpandMore as ExpandMoreIcon } from '@material-ui/icons'
import Banner from '../../components/Banner'
+import Icon, {Loading} from '../../components/Icons'
import { PanelConfigs } from '../../config/localstorage'
@@ -47,7 +48,8 @@ enum ConfigPanel {
HEADER = 'header',
BLOCK = 'block',
TRANSACTION = 'transaction',
- GRAPH = 'graph'
+ GRAPH = 'graph',
+ DEBUGGER = 'debugger'
}
export interface ConfigPageProps {
@@ -57,6 +59,8 @@ export interface ConfigPageProps {
export interface ConfigPageState {
configs: PanelConfigs
+ inputTimeout?: any
+ saving?: boolean
}
const ConfigDetail = translate('microscope')(
@@ -65,12 +69,16 @@ const ConfigDetail = translate('microscope')(
value,
handleSwitch,
handleInput,
+ controlInputScope,
+ saving,
t
}: {
config: ConfigDetail
value: number | string | boolean | undefined
handleSwitch: (key: string) => (e: any) => void
handleInput: (key: string) => (e: React.ChangeEvent) => void
+ controlInputScope: any
+ saving: any
t: (key: string) => string
}) => (
@@ -96,7 +104,8 @@ const ConfigDetail = translate('microscope')(
/>
) : (
-
+
+ {saving ? : }
)}
@@ -111,6 +120,8 @@ const ConfigItem = translate('microscope')(
values,
handleSwitch,
handleInput,
+ controlInputScope,
+ saving,
t
}: {
title: any
@@ -118,6 +129,8 @@ const ConfigItem = translate('microscope')(
values: any
handleSwitch: any
handleInput: any
+ controlInputScope: any
+ saving: any
t: any
}) => (
@@ -136,6 +149,8 @@ const ConfigItem = translate('microscope')(
value={values[config.key]}
handleSwitch={handleSwitch}
handleInput={handleInput}
+ controlInputScope={controlInputScope}
+ saving={saving}
/>
))}
@@ -151,7 +166,8 @@ class ConfigPage extends React.Component {
// ConfigPanel.HEADER,
ConfigPanel.BLOCK,
ConfigPanel.TRANSACTION,
- ConfigPanel.GRAPH
+ ConfigPanel.GRAPH,
+ ConfigPanel.DEBUGGER
]
static configs = [
{
@@ -285,12 +301,20 @@ class ConfigPage extends React.Component {
type: ConfigType.VALUE,
key: 'graphMaxCount',
title: 'MaxCount'
+ },
+ {
+ panel: ConfigPanel.DEBUGGER,
+ type: ConfigType.DISPLAY,
+ key: 'debugger',
+ title: 'Debugger'
}
] as ConfigDetail[]
public constructor (props) {
super(props)
this.state = {
- configs: props.config.panelConfigs
+ configs: props.config.panelConfigs,
+ inputTimeout: null,
+ saving: false
}
}
@@ -318,6 +342,29 @@ class ConfigPage extends React.Component {
return state
})
}
+ private controlInputScope = key => e => {
+ const { configs, inputTimeout } = this.state
+ const { value } = e.currentTarget
+ let v = Number(value)
+ this.setState({ configs: { ...configs, [key]: v }, saving: true })
+
+ clearTimeout(inputTimeout)
+ if (Math.round(v) === v && v >= 10 && v <= 100) {
+ this.props.config.changePanelConfig({ ...configs, [key]: v })
+ this.setState({ saving: false })
+ } else {
+ const t = setTimeout(() => {
+ if (v < 10) {
+ v = 10
+ } else {
+ v = 100
+ }
+ this.props.config.changePanelConfig({ ...configs, [key]: v })
+ this.setState({ configs: { ...configs, [key]: v }, saving: false })
+ }, 1000)
+ this.setState({ inputTimeout: t, saving: true })
+ }
+ }
public render () {
return (
@@ -331,6 +378,8 @@ class ConfigPage extends React.Component {
values={this.state.configs}
handleSwitch={this.handleSwitch}
handleInput={this.handleInput}
+ controlInputScope={this.controlInputScope}
+ saving={this.state.saving}
/>
))}
diff --git a/src/containers/Debugger/debugger.scss b/src/containers/Debugger/debugger.scss
new file mode 100644
index 0000000..706dd23
--- /dev/null
+++ b/src/containers/Debugger/debugger.scss
@@ -0,0 +1,16 @@
+.card {
+ border-radius: 2px;
+ box-shadow: 0 0 10px 0 rgba(170, 170, 170, 0.2) !important;
+}
+
+.listCards {
+ display: flex;
+ flex-direction: column;
+}
+
+.blockListHeader {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+}
diff --git a/src/containers/Debugger/index.tsx b/src/containers/Debugger/index.tsx
new file mode 100644
index 0000000..c41cfd8
--- /dev/null
+++ b/src/containers/Debugger/index.tsx
@@ -0,0 +1,161 @@
+import * as React from 'react'
+import { Grid } from '@material-ui/core'
+import { Chain } from '@nervos/plugin'
+import { unsigner } from '@nervos/signer'
+import * as EthAccount from 'web3-eth-accounts'
+
+import StaticCard from '../../components/StaticCard'
+import BlockList from '../../components/HomepageLists/BlockList'
+import TransactionList from '../../components/HomepageLists/TransactionList'
+import DebugAccounts, { DebugAccount } from '../../components/DebugAccounts'
+import ErrorNotification from '../../components/ErrorNotification'
+
+import { withObservables } from '../../contexts/observables'
+
+import hideLoader from '../../utils/hideLoader'
+import { IContainerProps, TransactionFromServer } from '../../typings'
+import { handleError, dismissError } from '../../utils/handleError'
+import { getLocalDebugAccounts, setLocalDebugAccounts } from '../../utils/accessLocalstorage'
+
+const layout = require('../../styles/layout.scss')
+const styles = require('./debugger.scss')
+
+const ethAccounts = new EthAccount()
+
+const privateKeysToAccounts = (privateKeys: string[]) =>
+ privateKeys.map(privateKey => {
+ const { address } = ethAccounts.privateKeyToAccount(privateKey)
+ return {
+ privateKey,
+ address,
+ balance: 'unloaded'
+ }
+ })
+
+const initState = {
+ accounts: [] as DebugAccount[],
+ privateKeysField: '',
+ blocks: [] as Chain.Block[],
+ transactions: [] as TransactionFromServer[],
+ error: {
+ code: '',
+ message: ''
+ }
+}
+
+interface DebuggerProps extends IContainerProps {}
+
+type DebuggerState = typeof initState
+
+class Debugger extends React.Component {
+ public readonly state = initState
+ public componentDidMount () {
+ hideLoader()
+ this.loadDebugAccounts()
+ this.props.CITAObservables.newBlockByNumberSubject.subscribe((block: Chain.Block) => {
+ if (block.body.transactions.length) {
+ this.setState((state: DebuggerState) => {
+ const blocks = [...state.blocks, block]
+ const newTransactions = block.body.transactions.map(tx => {
+ const unsignedTx = unsigner(tx.content)
+ return {
+ blockNumber: block.number,
+ content: tx.content,
+ from: unsignedTx.sender.address,
+ gasUsed: '',
+ hash: tx.hash,
+ timestamp: block.header.timestamp,
+ to: unsignedTx.transaction.to,
+ value: +unsignedTx.transaction.value.join('')
+ }
+ })
+ const transactions = [...state.transactions, ...newTransactions]
+
+ return {
+ blocks,
+ transactions
+ }
+ })
+ }
+ })
+ this.props.CITAObservables.newBlockByNumberSubject.subscribe(block => {
+ // new block comes
+ this.fetchAndUpdateAccounts(this.state.accounts)
+ })
+ }
+ public loadDebugAccounts = () => {
+ let privateKeys = getLocalDebugAccounts()
+ if (!privateKeys.length) {
+ privateKeys = process.env.DEBUG_ACCOUNTS ? process.env.DEBUG_ACCOUNTS.split(',') : []
+ }
+ const accounts = privateKeysToAccounts(privateKeys)
+ this.setState({ accounts, privateKeysField: privateKeys.join(',') })
+ this.fetchAndUpdateAccounts(accounts)
+ }
+ public updateDebugAccounts = () => {
+ const { privateKeysField } = this.state
+ try {
+ const privateKeys = Array.from(new Set(privateKeysField.replace(/(\s|\n|\r)+/gi, '').split(',')))
+ const accounts = privateKeysToAccounts(privateKeys)
+ setLocalDebugAccounts(privateKeys)
+ this.fetchAndUpdateAccounts(accounts)
+ } catch (err) {
+ window.alert(err)
+ }
+ }
+ public fetchAndUpdateAccounts = (accounts: DebugAccount[]) => {
+ accounts.forEach((account, idx) => {
+ this.props.CITAObservables.getBalance({ addr: account.address, blockNumber: 'latest' }).subscribe(balance => {
+ this.setState(state => {
+ const _accounts = [...accounts]
+ _accounts[idx].balance = `${+balance}`
+ return { ...state, accounts: _accounts }
+ })
+ })
+ })
+ }
+ private handleInput = key => (e: React.ChangeEvent) => {
+ const { value } = e.currentTarget
+ this.setState(state => ({
+ ...state,
+ [key]: value
+ }))
+ }
+ private handleError = handleError(this)
+ private dismissError = dismissError(this)
+ public render () {
+ return (
+
+
+ {window.location.hostname === 'localhost' ? (
+
+ ) : null}
+ 800 ? 24 : 0}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+export default withObservables(Debugger)
diff --git a/src/containers/Footer/index.tsx b/src/containers/Footer/index.tsx
index 7d725f7..56f7e4e 100644
--- a/src/containers/Footer/index.tsx
+++ b/src/containers/Footer/index.tsx
@@ -43,6 +43,11 @@ class Footer extends React.Component<{ t: (key: string) => string }, any> {
contacts: {
title: 'contact us',
items: [
+ {
+ icon: 'github',
+ title: 'Github',
+ url: 'https://github.com/cryptape/microscope'
+ },
{
icon: 'email',
title: 'appchain.contact@cryptape.com',
@@ -57,6 +62,11 @@ class Footer extends React.Component<{ t: (key: string) => string }, any> {
icon: 'wechat',
title: '微信公众号',
url: 'https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzUzNzg4NTAzOA==&scene=124wechat_redirect'
+ },
+ {
+ icon: 'forum',
+ title: 'Nervos Forums',
+ url: 'https://forums.nervos.org'
}
] as Contact[]
}
@@ -73,7 +83,7 @@ class Footer extends React.Component<{ t: (key: string) => string }, any> {