Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f8f890ff7 | |||
| f9ea9948f0 | |||
| 2b120e44ca | |||
| 0df15d6109 | |||
| 56df3a2561 | |||
| 7f2ff39ec9 | |||
| 739fc45500 | |||
| d6d03bf7c6 | |||
| 2624cf04c9 | |||
| dcec6e0188 | |||
| 480f6dc7f9 | |||
| a62243fe5c | |||
| ec42eafc2b | |||
| f90778d338 | |||
| 650d100dd2 | |||
| 960ffb8fdd | |||
| be8b88516b |
+1
-1
@@ -3,5 +3,5 @@ REACT_APP_BEE_HOST=http://localhost:1633
|
|||||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||||
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
||||||
REACT_APP_ETHERSCAN_HOST=etherscan.io
|
REACT_APP_BLOCKCHAIN_EXPLORER_URL=https://blockscout.com/xdai/mainnet
|
||||||
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
||||||
+1
-1
@@ -2,5 +2,5 @@ REACT_APP_BEE_HOST=http://localhost:1633
|
|||||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||||
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
||||||
REACT_APP_ETHERSCAN_HOST=etherscan.io
|
REACT_APP_BLOCKCHAIN_EXPLORER_URL=https://blockscout.com/xdai/mainnet
|
||||||
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
||||||
|
|||||||
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.6.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.5.0...v0.6.0) (2021-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add retry to accounting ([#166](https://www.github.com/ethersphere/bee-dashboard/issues/166)) ([a62243f](https://www.github.com/ethersphere/bee-dashboard/commit/a62243fe5c45b7dd9be6e92f82ebdf0b64bd8f0d))
|
||||||
|
* add tooltips and health indicator to peers ([#169](https://www.github.com/ethersphere/bee-dashboard/issues/169)) ([480f6dc](https://www.github.com/ethersphere/bee-dashboard/commit/480f6dc7f9c58a4aae87e0dea7082a4bd3dc900b))
|
||||||
|
* bee provider caching the state of the app and refreshing periodically ([#172](https://www.github.com/ethersphere/bee-dashboard/issues/172)) ([2624cf0](https://www.github.com/ethersphere/bee-dashboard/commit/2624cf04c939e87f025c1f4ff417808073742dab))
|
||||||
|
* changing API urls does not need the app refresh ([#173](https://www.github.com/ethersphere/bee-dashboard/issues/173)) ([d6d03bf](https://www.github.com/ethersphere/bee-dashboard/commit/d6d03bf7c6d2705de22f43825b85b32c2f0181fb))
|
||||||
|
* remove the last update component ([#179](https://www.github.com/ethersphere/bee-dashboard/issues/179)) ([56df3a2](https://www.github.com/ethersphere/bee-dashboard/commit/56df3a2561c3c00237b5d107eb054403af3012f8))
|
||||||
|
* synchronized platform tabs ([#165](https://www.github.com/ethersphere/bee-dashboard/issues/165)) ([ec42eaf](https://www.github.com/ethersphere/bee-dashboard/commit/ec42eafc2b768ba06649f628c733e8d3440fdcaf))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* enum index for supported platforms ([#170](https://www.github.com/ethersphere/bee-dashboard/issues/170)) ([dcec6e0](https://www.github.com/ethersphere/bee-dashboard/commit/dcec6e01887465c74a68feede52b476791bbefa7))
|
||||||
|
* remove nested ternary operator and simplify ping peer functionality ([#181](https://www.github.com/ethersphere/bee-dashboard/issues/181)) ([2b120e4](https://www.github.com/ethersphere/bee-dashboard/commit/2b120e44ca5e01451cc43e362195c04587836a03))
|
||||||
|
|
||||||
|
## [0.5.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.4.0...v0.5.0) (2021-08-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* updated troubleshooting instructions and links for mainnet ([#161](https://www.github.com/ethersphere/bee-dashboard/issues/161)) ([960ffb8](https://www.github.com/ethersphere/bee-dashboard/commit/960ffb8fdd6cbfe4928b758da6cac9ba94050c00))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* amend readme ([#155](https://www.github.com/ethersphere/bee-dashboard/issues/155)) ([be8b885](https://www.github.com/ethersphere/bee-dashboard/commit/be8b88516b00d79a623798588d3d4dac3340e8b2))
|
||||||
|
|
||||||
## [0.4.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.3.1...v0.4.0) (2021-06-29)
|
## [0.4.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.3.1...v0.4.0) (2021-06-29)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
* nugaon vojtechsimetka
|
* @Cafe137 @vojtechsimetka
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ git clone git@github.com:ethersphere/bee-dashboard.git
|
|||||||
|
|
||||||
cd bee-dashboard
|
cd bee-dashboard
|
||||||
|
|
||||||
|
npm i
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -82,8 +84,10 @@ There are some ways you can make this module better:
|
|||||||
|
|
||||||
## Maintainers
|
## Maintainers
|
||||||
|
|
||||||
- [nugaon](https://github.com/nugaon)
|
|
||||||
- [vojtechsimetka](https://github.com/vojtechsimetka)
|
- [vojtechsimetka](https://github.com/vojtechsimetka)
|
||||||
|
- [Cafe137](https://github.com/Cafe137)
|
||||||
|
|
||||||
|
See what "Maintainer" means [here](https://github.com/ethersphere/repo-maintainer).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
Generated
+29
-25
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.4.0",
|
"version": "0.6.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.4.0",
|
"version": "0.6.0",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersphere/bee-js": "^1.0.0",
|
"@ethersphere/bee-js": "^1.2.0",
|
||||||
"@material-ui/core": "^4.11.4",
|
"@material-ui/core": "^4.11.4",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
@@ -1460,19 +1460,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ethersphere/bee-js": {
|
"node_modules/@ethersphere/bee-js": {
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-1.2.0.tgz",
|
||||||
"integrity": "sha512-u+TnKf0loAQba7AfZ54kXIEWjNdqVMUR6NXwINhHa2IdoD2b52rcuP96eMImlUm4tEJkxi2yAS+lcIZ2U9/1Vw==",
|
"integrity": "sha512-RtRsTOq1ZtftT//0ibPWjohIwMOgJ/4KhMynQ1yaroih6G6krAnXve2K/hZtyWA9d7dpLxcT4aKd9S4QmISOgA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"elliptic": "^6.5.4",
|
"elliptic": "^6.5.4",
|
||||||
"isomorphic-ws": "^4.0.1",
|
"isomorphic-ws": "^4.0.1",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
"tar-js": "^0.3.0",
|
"tar-js": "^0.3.0",
|
||||||
"ws": "^7.4.4"
|
"ws": "^7.5.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"bee": "1.0.0-2572fa48",
|
"bee": "1.1.0-80cdea19",
|
||||||
"node": ">=12.0.0",
|
"node": ">=12.0.0",
|
||||||
"npm": ">=6.0.0"
|
"npm": ">=6.0.0"
|
||||||
}
|
}
|
||||||
@@ -4776,10 +4776,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001191",
|
"version": "1.0.30001251",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz",
|
||||||
"integrity": "sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==",
|
"integrity": "sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/capture-exit": {
|
"node_modules/capture-exit": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -21434,9 +21438,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "7.4.5",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
|
||||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.3.0"
|
"node": ">=8.3.0"
|
||||||
},
|
},
|
||||||
@@ -22895,16 +22899,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ethersphere/bee-js": {
|
"@ethersphere/bee-js": {
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-1.2.0.tgz",
|
||||||
"integrity": "sha512-u+TnKf0loAQba7AfZ54kXIEWjNdqVMUR6NXwINhHa2IdoD2b52rcuP96eMImlUm4tEJkxi2yAS+lcIZ2U9/1Vw==",
|
"integrity": "sha512-RtRsTOq1ZtftT//0ibPWjohIwMOgJ/4KhMynQ1yaroih6G6krAnXve2K/hZtyWA9d7dpLxcT4aKd9S4QmISOgA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"elliptic": "^6.5.4",
|
"elliptic": "^6.5.4",
|
||||||
"isomorphic-ws": "^4.0.1",
|
"isomorphic-ws": "^4.0.1",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
"tar-js": "^0.3.0",
|
"tar-js": "^0.3.0",
|
||||||
"ws": "^7.4.4"
|
"ws": "^7.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
@@ -25776,9 +25780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001191",
|
"version": "1.0.30001251",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz",
|
||||||
"integrity": "sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==",
|
"integrity": "sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"capture-exit": {
|
"capture-exit": {
|
||||||
@@ -39876,9 +39880,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "7.4.5",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
|
||||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"xml-name-validator": {
|
"xml-name-validator": {
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.4.0",
|
"version": "0.6.0",
|
||||||
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bee",
|
"bee",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersphere/bee-js": "^1.0.0",
|
"@ethersphere/bee-js": "^1.2.0",
|
||||||
"@material-ui/core": "^4.11.4",
|
"@material-ui/core": "^4.11.4",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { SnackbarProvider } from 'notistack'
|
|||||||
import BaseRouter from './routes/routes'
|
import BaseRouter from './routes/routes'
|
||||||
import { lightTheme, darkTheme } from './theme'
|
import { lightTheme, darkTheme } from './theme'
|
||||||
import { Provider as StampsProvider } from './providers/Stamps'
|
import { Provider as StampsProvider } from './providers/Stamps'
|
||||||
|
import { Provider as PlatformProvider } from './providers/Platform'
|
||||||
|
import { Provider as BeeProvider } from './providers/Bee'
|
||||||
|
import { Provider as SettingsProvider } from './providers/Settings'
|
||||||
|
|
||||||
const App = (): ReactElement => {
|
const App = (): ReactElement => {
|
||||||
const [themeMode, toggleThemeMode] = useState('light')
|
const [themeMode, toggleThemeMode] = useState('light')
|
||||||
@@ -35,7 +38,10 @@ const App = (): ReactElement => {
|
|||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
||||||
|
<SettingsProvider>
|
||||||
|
<BeeProvider>
|
||||||
<StampsProvider>
|
<StampsProvider>
|
||||||
|
<PlatformProvider>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
<>
|
<>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
@@ -44,7 +50,10 @@ const App = (): ReactElement => {
|
|||||||
</Router>
|
</Router>
|
||||||
</>
|
</>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
|
</PlatformProvider>
|
||||||
</StampsProvider>
|
</StampsProvider>
|
||||||
|
</BeeProvider>
|
||||||
|
</SettingsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import { ReactElement, useState, useContext } from 'react'
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import { Alert, AlertTitle } from '@material-ui/lab'
|
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||||
import Collapse from '@material-ui/core/Collapse'
|
import Collapse from '@material-ui/core/Collapse'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import CloseIcon from '@material-ui/icons/Close'
|
import CloseIcon from '@material-ui/icons/Close'
|
||||||
import { useStatusNodeVersion } from '../hooks/status'
|
import { Context } from '../providers/Bee'
|
||||||
import { SUPPORTED_BEE_VERSION_EXACT } from '@ethersphere/bee-js'
|
import { SUPPORTED_BEE_VERSION_EXACT } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
@@ -18,12 +18,12 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
|
|
||||||
export default function VersionAlert(): ReactElement | null {
|
export default function VersionAlert(): ReactElement | null {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { isLoading, userVersion } = useStatusNodeVersion()
|
const { isLoading, latestUserVersionExact } = useContext(Context)
|
||||||
const [open, setOpen] = useState<boolean>(true)
|
const [open, setOpen] = useState<boolean>(true)
|
||||||
|
|
||||||
const isExactlySupportedBeeVersion = SUPPORTED_BEE_VERSION_EXACT === userVersion
|
const isExactlySupportedBeeVersion = SUPPORTED_BEE_VERSION_EXACT === latestUserVersionExact
|
||||||
|
|
||||||
if (isLoading || !userVersion) return null
|
if (isLoading || !latestUserVersionExact) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse in={!isExactlySupportedBeeVersion && open}>
|
<Collapse in={!isExactlySupportedBeeVersion && open}>
|
||||||
@@ -44,9 +44,9 @@ export default function VersionAlert(): ReactElement | null {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AlertTitle>Warning</AlertTitle>
|
<AlertTitle>Warning</AlertTitle>
|
||||||
Your Bee node version (<code>{userVersion}</code>) does not exactly match the Bee version we tested the Bee
|
Your Bee node version (<code>{latestUserVersionExact}</code>) does not exactly match the Bee version we tested
|
||||||
Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality may not
|
the Bee Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality
|
||||||
work properly.
|
may not work properly.
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import { CircularProgress, Container } from '@material-ui/core'
|
||||||
import Button from '@material-ui/core/Button'
|
import Button from '@material-ui/core/Button'
|
||||||
import Dialog from '@material-ui/core/Dialog'
|
import Dialog from '@material-ui/core/Dialog'
|
||||||
import DialogActions from '@material-ui/core/DialogActions'
|
import DialogActions from '@material-ui/core/DialogActions'
|
||||||
import DialogContent from '@material-ui/core/DialogContent'
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||||
import { Container, CircularProgress } from '@material-ui/core'
|
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
|
import { ReactElement, useState, useContext } from 'react'
|
||||||
import { beeDebugApi } from '../services/bee'
|
import { Context as SettingsContext } from '../providers/Settings'
|
||||||
|
|
||||||
import EthereumAddress from './EthereumAddress'
|
import EthereumAddress from './EthereumAddress'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -17,10 +15,11 @@ interface Props {
|
|||||||
uncashedAmount: string
|
uncashedAmount: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement {
|
export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactElement {
|
||||||
const [open, setOpen] = useState<boolean>(false)
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
const { beeDebugApi } = useContext(SettingsContext)
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -31,16 +30,18 @@ export default function DepositModal({ peerId, uncashedAmount }: Props): ReactEl
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCashout = () => {
|
const handleCashout = () => {
|
||||||
|
if (!beeDebugApi) return
|
||||||
|
|
||||||
if (peerId) {
|
if (peerId) {
|
||||||
setLoadingCashout(true)
|
setLoadingCashout(true)
|
||||||
beeDebugApi.chequebook
|
beeDebugApi
|
||||||
.peerCashout(peerId)
|
.cashoutLastCheque(peerId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
enqueueSnackbar(
|
enqueueSnackbar(
|
||||||
<span>
|
<span>
|
||||||
Successfully cashed out cheque. Transaction
|
Successfully cashed out cheque. Transaction
|
||||||
<EthereumAddress hideBlockie transaction address={res} network={'goerli'} />
|
<EthereumAddress hideBlockie transaction address={res} />
|
||||||
</span>,
|
</span>,
|
||||||
{ variant: 'success' },
|
{ variant: 'success' },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import React, { ReactElement, useEffect } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { withStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import TabsContainer from './TabsContainer'
|
||||||
import { Tabs, Tab, Box, Typography } from '@material-ui/core'
|
|
||||||
import CodeBlock from './CodeBlock'
|
import CodeBlock from './CodeBlock'
|
||||||
|
import { Context } from '../providers/Platform'
|
||||||
interface TabPanelProps {
|
|
||||||
children?: React.ReactNode
|
|
||||||
index: number
|
|
||||||
value: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
linux: string
|
linux: string
|
||||||
@@ -15,133 +9,23 @@ interface Props {
|
|||||||
showLineNumbers?: boolean
|
showLineNumbers?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
|
||||||
return {
|
|
||||||
id: `simple-tab-${index}`,
|
|
||||||
'aria-controls': `simple-tabpanel-${index}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOS() {
|
|
||||||
const userAgent = window.navigator.userAgent
|
|
||||||
const platform = window.navigator.platform
|
|
||||||
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
|
|
||||||
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']
|
|
||||||
const iosPlatforms = ['iPhone', 'iPad', 'iPod']
|
|
||||||
|
|
||||||
if (macosPlatforms.includes(platform)) return 'macOS'
|
|
||||||
|
|
||||||
if (iosPlatforms.includes(platform)) return 'iOS'
|
|
||||||
|
|
||||||
if (windowsPlatforms.includes(platform)) return 'windows'
|
|
||||||
|
|
||||||
if (/Android/.test(userAgent)) return 'android'
|
|
||||||
|
|
||||||
if (/Linux/.test(platform)) return 'linux'
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CodeBlockTabs(props: Props): ReactElement {
|
export default function CodeBlockTabs(props: Props): ReactElement {
|
||||||
const [value, setValue] = React.useState(0)
|
const { platform, setPlatform } = useContext(Context)
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
|
|
||||||
setValue(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const os = getOS()
|
|
||||||
|
|
||||||
if (os === 'windows') {
|
|
||||||
setValue(0)
|
|
||||||
} else if (os === 'linux') {
|
|
||||||
setValue(0)
|
|
||||||
} else if (os === 'macOS') {
|
|
||||||
setValue(1)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
function TabPanel(props: TabPanelProps) {
|
|
||||||
const { children, value, index, ...other } = props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<TabsContainer
|
||||||
role="tabpanel"
|
index={platform}
|
||||||
hidden={value !== index}
|
indexChanged={setPlatform}
|
||||||
id={`simple-tabpanel-${index}`}
|
values={[
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
{
|
||||||
{...other}
|
label: 'Linux',
|
||||||
>
|
component: <CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.linux} />,
|
||||||
{value === index && (
|
|
||||||
<Box style={{ marginTop: '-12px' }}>
|
|
||||||
<Typography component="div">{children}</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const AntTabs = withStyles({
|
|
||||||
root: {
|
|
||||||
borderBottom: '1px solid #e8e8e8',
|
|
||||||
},
|
},
|
||||||
indicator: {
|
{
|
||||||
backgroundColor: '#3f51b5',
|
label: 'macOS',
|
||||||
|
component: <CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.mac} />,
|
||||||
},
|
},
|
||||||
})(Tabs)
|
]}
|
||||||
|
/>
|
||||||
interface StyledTabProps {
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const AntTab = withStyles((theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
root: {
|
|
||||||
textTransform: 'none',
|
|
||||||
minWidth: 72,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
fontWeight: theme.typography.fontWeightRegular,
|
|
||||||
marginRight: theme.spacing(4),
|
|
||||||
fontFamily: [
|
|
||||||
'-apple-system',
|
|
||||||
'BlinkMacSystemFont',
|
|
||||||
'"Segoe UI"',
|
|
||||||
'Roboto',
|
|
||||||
'"Helvetica Neue"',
|
|
||||||
'Arial',
|
|
||||||
'sans-serif',
|
|
||||||
'"Apple Color Emoji"',
|
|
||||||
'"Segoe UI Emoji"',
|
|
||||||
'"Segoe UI Symbol"',
|
|
||||||
].join(','),
|
|
||||||
'&:hover': {
|
|
||||||
color: '#3f51b5',
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
'&$selected': {
|
|
||||||
color: '#3f51b5',
|
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
|
||||||
},
|
|
||||||
'&:focus': {
|
|
||||||
color: '#3f51b5',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
selected: {},
|
|
||||||
}),
|
|
||||||
)((props: StyledTabProps) => <Tab disableRipple {...props} />)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
|
|
||||||
<AntTab label="Linux" {...a11yProps(0)} />
|
|
||||||
<AntTab label="MacOS" {...a11yProps(1)} />
|
|
||||||
</AntTabs>
|
|
||||||
<TabPanel value={value} index={0}>
|
|
||||||
<CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.linux} />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={value} index={1}>
|
|
||||||
<CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.mac} />
|
|
||||||
</TabPanel>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,25 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useState } from 'react'
|
||||||
import { TextField, Button, CircularProgress, Container } from '@material-ui/core'
|
import { TextField, Button } from '@material-ui/core'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
defaultHost?: string
|
defaultHost?: string
|
||||||
hostName: string
|
setHost: (host: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ConnectToHost(props: Props): ReactElement {
|
export default function ConnectToHost(props: Props): ReactElement {
|
||||||
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
|
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
|
||||||
const [connectingToHost, setConnectingToHost] = useState(false)
|
|
||||||
const [host, setHost] = useState('')
|
const [host, setHost] = useState('')
|
||||||
|
|
||||||
const handleNewHostConnection = () => {
|
const handleNewHostConnection = () => {
|
||||||
if (host) {
|
if (host) {
|
||||||
setConnectingToHost(true)
|
props.setHost(host)
|
||||||
sessionStorage.setItem(props.hostName, host)
|
|
||||||
toggleHostInputVisibility(!hostInputVisible)
|
toggleHostInputVisibility(!hostInputVisible)
|
||||||
window.location.reload()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{hostInputVisible ? (
|
||||||
// FIXME: this should be broken up
|
|
||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
hostInputVisible ? (
|
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<TextField
|
<TextField
|
||||||
defaultValue={props.defaultHost}
|
defaultValue={props.defaultHost}
|
||||||
@@ -46,17 +40,11 @@ export default function ConnectToHost(props: Props): ReactElement {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : connectingToHost ? (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '0px' }}>
|
|
||||||
<CircularProgress size={20} />
|
|
||||||
</Container>
|
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size="small" variant="outlined">
|
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size="small" variant="outlined">
|
||||||
Change host
|
Change host
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)}
|
||||||
/* eslint-enable no-nested-ternary */
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ReactElement } from 'react'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
address: string | undefined
|
address: string | undefined
|
||||||
network?: string
|
|
||||||
hideBlockie?: boolean
|
hideBlockie?: boolean
|
||||||
transaction?: boolean
|
transaction?: boolean
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
@@ -37,9 +36,9 @@ export default function EthereumAddress(props: Props): ReactElement {
|
|||||||
}
|
}
|
||||||
: { marginRight: '7px' }
|
: { marginRight: '7px' }
|
||||||
}
|
}
|
||||||
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${
|
href={`${process.env.REACT_APP_BLOCKCHAIN_EXPLORER_URL}/${props.transaction ? 'tx' : 'address'}/${
|
||||||
props.transaction ? 'tx' : 'address'
|
props.address
|
||||||
}/${props.address}`}
|
}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Card, CardContent, Typography } from '@material-ui/core/'
|
import { Card, CardContent, Typography } from '@material-ui/core/'
|
||||||
|
|
||||||
import EthereumAddress from '../components/EthereumAddress'
|
import EthereumAddress from '../components/EthereumAddress'
|
||||||
import { Skeleton } from '@material-ui/lab'
|
|
||||||
|
|
||||||
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
|
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
@@ -28,9 +27,7 @@ const useStyles = makeStyles(() =>
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nodeAddresses: NodeAddresses | null
|
nodeAddresses: NodeAddresses | null
|
||||||
isLoadingNodeAddresses: boolean
|
|
||||||
chequebookAddress: ChequebookAddressResponse | null
|
chequebookAddress: ChequebookAddressResponse | null
|
||||||
isLoadingChequebookAddress: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EthereumAddressCard(props: Props): ReactElement {
|
function EthereumAddressCard(props: Props): ReactElement {
|
||||||
@@ -38,36 +35,23 @@ function EthereumAddressCard(props: Props): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.root}>
|
<Card className={classes.root}>
|
||||||
{props.isLoadingNodeAddresses ? (
|
|
||||||
<div style={{ padding: '16px' }}>
|
|
||||||
<Skeleton width={300} height={30} animation="wave" />
|
|
||||||
<Skeleton width={300} height={50} animation="wave" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={classes.details}>
|
<div className={classes.details}>
|
||||||
<CardContent className={classes.content}>
|
<CardContent className={classes.content}>
|
||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Ethereum Address
|
Ethereum Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={props.nodeAddresses?.ethereum} network={'goerli'} />
|
<EthereumAddress address={props.nodeAddresses?.ethereum} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{props.isLoadingChequebookAddress ? (
|
|
||||||
<div style={{ padding: '16px' }}>
|
|
||||||
<Skeleton width={300} height={30} animation="wave" />
|
|
||||||
<Skeleton width={300} height={50} animation="wave" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={classes.details}>
|
<div className={classes.details}>
|
||||||
<CardContent className={classes.content}>
|
<CardContent className={classes.content}>
|
||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Chequebook Contract Address
|
Chequebook Contract Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} network={'goerli'} />
|
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
date: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LastUpdate({ date }: Props): ReactElement {
|
|
||||||
const [duration, setDuration] = useState<string>('never')
|
|
||||||
|
|
||||||
const refresh = () => {
|
|
||||||
if (!date) setDuration('never')
|
|
||||||
else setDuration(`${((Date.now() - date) / 1000).toFixed()} seconds ago`)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refresh()
|
|
||||||
const i = setInterval(refresh, 1000)
|
|
||||||
|
|
||||||
return () => clearInterval(i)
|
|
||||||
}, [date])
|
|
||||||
|
|
||||||
return <span>Last Update: {duration}</span>
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, ReactElement } from 'react'
|
import { useState, ReactElement } from 'react'
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Toolbar, Chip, IconButton } from '@material-ui/core/'
|
import { Toolbar, IconButton } from '@material-ui/core/'
|
||||||
|
|
||||||
import { Sun, Moon } from 'react-feather'
|
import { Sun, Moon } from 'react-feather'
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ const useStyles = makeStyles(() =>
|
|||||||
width: `calc(100% - ${drawerWidth}px)`,
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
marginLeft: drawerWidth,
|
marginLeft: drawerWidth,
|
||||||
},
|
},
|
||||||
network: {},
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -42,16 +41,11 @@ export default function SideBar(props: Props): ReactElement {
|
|||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'fixed' }} className={classes.appBar}>
|
<div style={{ display: 'fixed' }} className={classes.appBar}>
|
||||||
<Toolbar style={{ display: 'flex' }}>
|
<Toolbar style={{ display: 'flex' }}>
|
||||||
<Chip style={{ marginLeft: '7px' }} size="small" label="Goerli" className={classes.network} />
|
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<div style={{ float: 'right' }}>
|
<div style={{ float: 'right' }}>
|
||||||
<IconButton style={{ marginRight: '10px' }} aria-label="dark-mode" onClick={() => switchTheme()}>
|
<IconButton style={{ marginRight: '10px' }} aria-label="dark-mode" onClick={() => switchTheme()}>
|
||||||
{props.themeMode === 'dark' ? <Moon /> : <Sun />}
|
{props.themeMode === 'dark' ? <Moon /> : <Sun />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{/* <Chip
|
|
||||||
label="Connect Wallet"
|
|
||||||
color="primary"
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
import type { ReactElement } from 'react'
|
|
||||||
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import { Card, CardContent, Typography } from '@material-ui/core/'
|
import { Card, CardContent, Typography } from '@material-ui/core/'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { Skeleton } from '@material-ui/lab'
|
import { Skeleton } from '@material-ui/lab'
|
||||||
|
import type { ReactElement } from 'react'
|
||||||
|
import { Title } from './Title'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: {
|
root: {
|
||||||
minWidth: 275,
|
minWidth: 275,
|
||||||
},
|
},
|
||||||
title: {
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
pos: {
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
statistic?: string
|
statistic?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
tooltip?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StatCard({ loading, label, statistic }: Props): ReactElement {
|
export default function StatCard({ loading, label, statistic, tooltip }: Props): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,9 +31,7 @@ export default function StatCard({ loading, label, statistic }: Props): ReactEle
|
|||||||
)}
|
)}
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<>
|
<>
|
||||||
<Typography className={classes.title} color="textSecondary" gutterBottom>
|
<Title label={label} tooltip={tooltip} />
|
||||||
{label}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h5" component="h2">
|
<Typography variant="h5" component="h2">
|
||||||
{statistic}
|
{statistic}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -15,13 +15,7 @@ function TabPanel(props: TabPanelProps) {
|
|||||||
const { children, value, index, ...other } = props
|
const { children, value, index, ...other } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div role="tabpanel" hidden={value !== index} {...other}>
|
||||||
role="tabpanel"
|
|
||||||
hidden={value !== index}
|
|
||||||
id={`simple-tabpanel-${index}`}
|
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{value === index && (
|
{value === index && (
|
||||||
<Box p={3}>
|
<Box p={3}>
|
||||||
<Typography>{children}</Typography>
|
<Typography>{children}</Typography>
|
||||||
@@ -44,25 +38,30 @@ interface TabsValues {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
values: TabsValues[]
|
values: TabsValues[]
|
||||||
|
index?: number
|
||||||
|
indexChanged?: (index: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SimpleTabs({ values }: Props): ReactElement {
|
export default function SimpleTabs({ values, index, indexChanged }: Props): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [value, setValue] = React.useState<number>(0)
|
const [value, setValue] = React.useState<number>(index || 0)
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<Record<string, never>>, newValue: number) => {
|
const handleChange = (event: React.ChangeEvent<Record<string, never>>, newValue: number) => {
|
||||||
setValue(newValue)
|
if (indexChanged) indexChanged(newValue)
|
||||||
|
else setValue(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const v = index !== undefined ? index : value
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
|
<Tabs value={v} onChange={handleChange}>
|
||||||
{values.map(({ label }, index) => (
|
{values.map(({ label }, idx) => (
|
||||||
<Tab key={index} label={label} />
|
<Tab key={idx} label={label} />
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{values.map(({ component }, index) => (
|
{values.map(({ component }, idx) => (
|
||||||
<TabPanel key={index} value={value} index={index}>
|
<TabPanel key={idx} value={v} index={idx}>
|
||||||
{component}
|
{component}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Grid, Tooltip, Typography } from '@material-ui/core/'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { Info } from '@material-ui/icons'
|
||||||
|
import type { ReactElement } from 'react'
|
||||||
|
|
||||||
|
interface TitleProps {
|
||||||
|
label: string
|
||||||
|
tooltip?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
title: {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Title({ label, tooltip }: TitleProps): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
if (!tooltip) {
|
||||||
|
return (
|
||||||
|
<Typography className={classes.title} color="textSecondary" gutterBottom>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// span is needed as Tooltip expects a non-functional element!
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip}>
|
||||||
|
<span>
|
||||||
|
<Grid container direction="row" justify="space-between">
|
||||||
|
<Typography className={classes.title} color="textSecondary" gutterBottom>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
<Info />
|
||||||
|
</Grid>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,25 +1,63 @@
|
|||||||
import type { Topology } from '@ethersphere/bee-js'
|
import type { Topology } from '@ethersphere/bee-js'
|
||||||
import { Grid } from '@material-ui/core/'
|
import { Grid } from '@material-ui/core/'
|
||||||
import type { ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
|
import { pickThreshold, ThresholdValues } from '../utils/threshold'
|
||||||
import StatCard from './StatCard'
|
import StatCard from './StatCard'
|
||||||
|
|
||||||
interface Props {
|
interface RootProps {
|
||||||
isLoading: boolean
|
|
||||||
topology: Topology | null
|
topology: Topology | null
|
||||||
error: Error | null // FIXME: should display error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopologyStats = ({ isLoading, topology }: Props): ReactElement => (
|
interface Props extends RootProps {
|
||||||
|
thresholds: ThresholdValues
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopologyStats = (props: RootProps): ReactElement => {
|
||||||
|
const thresholds: ThresholdValues = {
|
||||||
|
connectedPeers: pickThreshold('connectedPeers', props.topology?.connected || 0),
|
||||||
|
population: pickThreshold('population', props.topology?.population || 0),
|
||||||
|
depth: pickThreshold('depth', props.topology?.depth || 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Indicator {...props} thresholds={thresholds} />
|
||||||
|
<Metrics {...props} thresholds={thresholds} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Indicator = ({ thresholds }: Props): ReactElement => {
|
||||||
|
const maximumTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.maximumScore, 0)
|
||||||
|
const actualTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.score, 0)
|
||||||
|
const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<StatCard label="Overall Health Indicator" statistic={percentageText} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Metrics = ({ topology, thresholds }: Props): ReactElement => (
|
||||||
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
|
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
||||||
<StatCard label="Connected Peers" statistic={topology?.connected.toString()} loading={isLoading} />
|
<StatCard
|
||||||
|
label="Connected Peers"
|
||||||
|
statistic={topology?.connected.toString()}
|
||||||
|
tooltip={thresholds.connectedPeers.explanation}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid key={2} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
<Grid key={2} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
||||||
<StatCard label="Population" statistic={topology?.population.toString()} loading={isLoading} />
|
<StatCard
|
||||||
|
label="Population"
|
||||||
|
statistic={topology?.population.toString()}
|
||||||
|
tooltip={thresholds.population.explanation}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
||||||
<StatCard label="Depth" statistic={topology?.depth.toString()} loading={isLoading} />
|
<StatCard label="Depth" statistic={topology?.depth.toString()} tooltip={thresholds.depth.explanation} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ interface Props {
|
|||||||
action: (amount: bigint) => Promise<string>
|
action: (amount: bigint) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WithdrawModal({
|
export default function WithdrawDepositModal({
|
||||||
successMessage,
|
successMessage,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
dialogMessage,
|
dialogMessage,
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// These values can for now be constants because their change in the app reloads the page
|
|
||||||
export const apiHost = sessionStorage.getItem('api_host') || process.env.REACT_APP_BEE_HOST || 'http://localhost:1633'
|
|
||||||
export const debugApiHost =
|
|
||||||
sessionStorage.getItem('debug_api_host') || process.env.REACT_APP_BEE_DEBUG_HOST || 'http://localhost:1635'
|
|
||||||
@@ -1,18 +1,24 @@
|
|||||||
import type { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { beeDebugApi } from '../services/bee'
|
import { Context as SettingsContext } from '../providers/Settings'
|
||||||
|
|
||||||
import WDModal from '../components/WDModal'
|
import WithdrawDepositModal from '../components/WithdrawDepositModal'
|
||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
|
|
||||||
export default function DepositModal(): ReactElement {
|
export default function DepositModal(): ReactElement {
|
||||||
|
const { beeDebugApi } = useContext(SettingsContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WDModal
|
<WithdrawDepositModal
|
||||||
successMessage="Successful deposit."
|
successMessage="Successful deposit."
|
||||||
errorMessage="Error with depositing"
|
errorMessage="Error with depositing"
|
||||||
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
||||||
label="Deposit"
|
label="Deposit"
|
||||||
min={new BigNumber(0)}
|
min={new BigNumber(0)}
|
||||||
action={beeDebugApi.chequebook.deposit}
|
action={(amount: bigint) => {
|
||||||
|
if (!beeDebugApi) throw new Error('Bee Debug URL is not valid')
|
||||||
|
|
||||||
|
return beeDebugApi.depositTokens(amount.toString())
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import type { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { beeDebugApi } from '../services/bee'
|
import { Context as SettingsContext } from '../providers/Settings'
|
||||||
|
|
||||||
import WDModal from '../components/WDModal'
|
import WithdrawDepositModal from '../components/WithdrawDepositModal'
|
||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
|
|
||||||
export default function WithdrawModal(): ReactElement {
|
export default function WithdrawModal(): ReactElement {
|
||||||
|
const { beeDebugApi } = useContext(SettingsContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WDModal
|
<WithdrawDepositModal
|
||||||
successMessage="Successful withdrawl."
|
successMessage="Successful withdrawl."
|
||||||
errorMessage="Error with withdrawing."
|
errorMessage="Error with withdrawing."
|
||||||
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
||||||
label="Withdraw"
|
label="Withdraw"
|
||||||
min={new BigNumber(0)}
|
min={new BigNumber(0)}
|
||||||
action={beeDebugApi.chequebook.withdraw}
|
action={(amount: bigint) => {
|
||||||
|
if (!beeDebugApi) throw new Error('Bee Debug URL is not valid')
|
||||||
|
|
||||||
|
return beeDebugApi.withdrawTokens(amount.toString())
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-27
@@ -1,15 +1,11 @@
|
|||||||
import { LastCashoutActionResponse } from '@ethersphere/bee-js'
|
import { LastCashoutActionResponse, BeeDebug } from '@ethersphere/bee-js'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Token } from '../models/Token'
|
import { Token } from '../models/Token'
|
||||||
import { beeDebugApi } from '../services/bee'
|
import { makeRetriablePromise, unwrapPromiseSettlements } from '../utils'
|
||||||
import { Balance, Settlement, useApiPeerBalances, useApiSettlements } from './apiHooks'
|
import { Balance, Settlements, Settlement } from '../types'
|
||||||
|
|
||||||
interface UseAccountingHook {
|
interface UseAccountingHook {
|
||||||
isLoading: boolean
|
|
||||||
isLoadingUncashed: boolean
|
isLoadingUncashed: boolean
|
||||||
error: Error | null
|
|
||||||
totalsent: Token
|
|
||||||
totalreceived: Token
|
|
||||||
accounting: Accounting[] | null
|
accounting: Accounting[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,39 +72,34 @@ function mergeAccounting(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAccounting = (): UseAccountingHook => {
|
export const useAccounting = (
|
||||||
const settlements = useApiSettlements()
|
beeDebugApi: BeeDebug | null,
|
||||||
const balances = useApiPeerBalances()
|
settlements: Settlements | null,
|
||||||
|
balances: Balance[] | null,
|
||||||
const [err, setErr] = useState<Error | null>(null)
|
): UseAccountingHook => {
|
||||||
const [isLoadingUncashed, setIsloadingUncashed] = useState<boolean>(false)
|
const [isLoadingUncashed, setIsloadingUncashed] = useState<boolean>(false)
|
||||||
const [uncashedAmounts, setUncashedAmounts] = useState<LastCashoutActionResponse[] | undefined>(undefined)
|
const [uncashedAmounts, setUncashedAmounts] = useState<LastCashoutActionResponse[] | undefined>(undefined)
|
||||||
|
|
||||||
const error = balances.error || settlements.error || err
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// We don't have any settlements loaded yet or we are already loading/have loaded the uncashed amounts
|
// We don't have any settlements loaded yet or we are already loading/have loaded the uncashed amounts
|
||||||
if (isLoadingUncashed || !settlements.settlements || uncashedAmounts || error) return
|
if (isLoadingUncashed || !beeDebugApi || !settlements || uncashedAmounts) return
|
||||||
|
|
||||||
setIsloadingUncashed(true)
|
setIsloadingUncashed(true)
|
||||||
const promises = settlements.settlements.settlements
|
const promises = settlements.settlements
|
||||||
.filter(({ received }) => received.toBigNumber.gt('0'))
|
.filter(({ received }) => received.toBigNumber.gt('0'))
|
||||||
.map(({ peer }) => beeDebugApi.chequebook.getPeerLastCashout(peer))
|
.map(({ peer }) => makeRetriablePromise(() => beeDebugApi.getLastCashoutAction(peer)))
|
||||||
|
|
||||||
Promise.all(promises)
|
Promise.allSettled(promises).then(settlements => {
|
||||||
.then(setUncashedAmounts)
|
const results = unwrapPromiseSettlements(settlements)
|
||||||
.catch(setErr)
|
setUncashedAmounts(results.fulfilled)
|
||||||
.finally(() => setIsloadingUncashed(false))
|
setIsloadingUncashed(false)
|
||||||
}, [settlements, isLoadingUncashed, uncashedAmounts, error])
|
})
|
||||||
|
}, [settlements, isLoadingUncashed, uncashedAmounts])
|
||||||
|
|
||||||
const accounting = mergeAccounting(balances.peerBalances, settlements.settlements?.settlements, uncashedAmounts)
|
const accounting = mergeAccounting(balances, settlements?.settlements, uncashedAmounts)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading: settlements.isLoadingSettlements || balances.isLoadingPeerBalances,
|
|
||||||
isLoadingUncashed,
|
isLoadingUncashed,
|
||||||
error,
|
|
||||||
accounting,
|
accounting,
|
||||||
totalsent: settlements.settlements?.totalSent || new Token('0'),
|
|
||||||
totalreceived: settlements.settlements?.totalReceived || new Token('0'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,406 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import {
|
|
||||||
NodeAddresses,
|
|
||||||
ChequebookAddressResponse,
|
|
||||||
LastChequesResponse,
|
|
||||||
Health,
|
|
||||||
Peer,
|
|
||||||
Topology,
|
|
||||||
LastChequesForPeerResponse,
|
|
||||||
} from '@ethersphere/bee-js'
|
|
||||||
|
|
||||||
import { beeDebugApi, beeApi } from '../services/bee'
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Token } from '../models/Token'
|
|
||||||
|
|
||||||
export interface HealthHook {
|
|
||||||
health: boolean
|
|
||||||
isLoadingHealth: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
export const useApiHealth = (): HealthHook => {
|
|
||||||
const [health, setHealth] = useState<boolean>(false)
|
|
||||||
const [isLoadingHealth, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeApi.status
|
|
||||||
.health()
|
|
||||||
.then(res => {
|
|
||||||
setHealth(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { health, isLoadingHealth, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DebugHealthHook {
|
|
||||||
nodeHealth: Health | null
|
|
||||||
isLoadingNodeHealth: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDebugApiHealth = (): DebugHealthHook => {
|
|
||||||
const [nodeHealth, setNodeHealth] = useState<Health | null>(null)
|
|
||||||
const [isLoadingNodeHealth, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.status
|
|
||||||
.nodeHealth()
|
|
||||||
.then(res => {
|
|
||||||
setNodeHealth(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { nodeHealth, isLoadingNodeHealth, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeAddressesHook {
|
|
||||||
nodeAddresses: NodeAddresses | null
|
|
||||||
isLoadingNodeAddresses: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiNodeAddresses = (): NodeAddressesHook => {
|
|
||||||
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
|
||||||
const [isLoadingNodeAddresses, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.connectivity
|
|
||||||
.addresses()
|
|
||||||
.then(res => {
|
|
||||||
setNodeAddresses(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { nodeAddresses, isLoadingNodeAddresses, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeTopologyHook {
|
|
||||||
topology: Topology | null
|
|
||||||
isLoading: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiNodeTopology = (): NodeTopologyHook => {
|
|
||||||
const [topology, setNodeTopology] = useState<Topology | null>(null)
|
|
||||||
const [isLoading, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.connectivity
|
|
||||||
.topology()
|
|
||||||
.then(res => {
|
|
||||||
setNodeTopology(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { topology, isLoading, error }
|
|
||||||
}
|
|
||||||
export interface ChequebookAddressHook {
|
|
||||||
chequebookAddress: ChequebookAddressResponse | null
|
|
||||||
isLoadingChequebookAddress: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiChequebookAddress = (): ChequebookAddressHook => {
|
|
||||||
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
|
|
||||||
const [isLoadingChequebookAddress, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.address()
|
|
||||||
.then(res => {
|
|
||||||
setChequebookAddress(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { chequebookAddress, isLoadingChequebookAddress, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodePeersHook {
|
|
||||||
peers: Peer[] | null
|
|
||||||
isLoading: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiNodePeers = (): NodePeersHook => {
|
|
||||||
const [peers, setPeers] = useState<Peer[] | null>(null)
|
|
||||||
const [isLoading, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.connectivity
|
|
||||||
.listPeers()
|
|
||||||
.then(res => {
|
|
||||||
setPeers(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { peers, isLoading, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChequebookBalance {
|
|
||||||
totalBalance: Token
|
|
||||||
availableBalance: Token
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChequebookBalanceHook {
|
|
||||||
chequebookBalance: ChequebookBalance | null
|
|
||||||
isLoadingChequebookBalance: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiChequebookBalance = (): ChequebookBalanceHook => {
|
|
||||||
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalance | null>(null)
|
|
||||||
const [isLoadingChequebookBalance, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.balance()
|
|
||||||
.then(({ totalBalance, availableBalance }) => {
|
|
||||||
const balance = {
|
|
||||||
totalBalance: new Token(totalBalance),
|
|
||||||
availableBalance: new Token(availableBalance),
|
|
||||||
}
|
|
||||||
setChequebookBalance(balance)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { chequebookBalance, isLoadingChequebookBalance, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Balance {
|
|
||||||
peer: string
|
|
||||||
balance: Token
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerBalanceHook {
|
|
||||||
peerBalances: Balance[] | null
|
|
||||||
isLoadingPeerBalances: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiPeerBalances = (): PeerBalanceHook => {
|
|
||||||
const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null)
|
|
||||||
const [isLoadingPeerBalances, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.balance
|
|
||||||
.balances()
|
|
||||||
.then(res => {
|
|
||||||
// for some reason sometimes these are numbers and not BigInts
|
|
||||||
const balances = res.balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) }))
|
|
||||||
setPeerBalances(balances)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { peerBalances, isLoadingPeerBalances, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerChequesHook {
|
|
||||||
peerCheques: LastChequesResponse | null
|
|
||||||
isLoadingPeerCheques: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiPeerCheques = (): PeerChequesHook => {
|
|
||||||
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
|
||||||
const [isLoadingPeerCheques, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.getLastCheques()
|
|
||||||
.then(res => {
|
|
||||||
setPeerCheques(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { peerCheques, isLoadingPeerCheques, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerLastChequesHook {
|
|
||||||
peerCheque: LastChequesForPeerResponse | null
|
|
||||||
isLoadingPeerCheque: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiPeerLastCheque = (peerId: string): PeerLastChequesHook => {
|
|
||||||
const [peerCheque, setPeerCheque] = useState<LastChequesForPeerResponse | null>(null)
|
|
||||||
const [isLoadingPeerCheque, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.getPeerLastCheques(peerId)
|
|
||||||
.then(res => {
|
|
||||||
setPeerCheque(res)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [peerId])
|
|
||||||
|
|
||||||
return { peerCheque, isLoadingPeerCheque, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Settlement {
|
|
||||||
peer: string
|
|
||||||
received: Token
|
|
||||||
sent: Token
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Settlements {
|
|
||||||
totalReceived: Token
|
|
||||||
totalSent: Token
|
|
||||||
settlements: Settlement[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SettlementsHook {
|
|
||||||
settlements: Settlements | null
|
|
||||||
isLoadingSettlements: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiSettlements = (): SettlementsHook => {
|
|
||||||
const [settlements, setSettlements] = useState<Settlements | null>(null)
|
|
||||||
const [isLoadingSettlements, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.settlements
|
|
||||||
.getSettlements()
|
|
||||||
.then(({ totalReceived, settlements, totalSent }) => {
|
|
||||||
const set = {
|
|
||||||
totalReceived: new Token(totalReceived),
|
|
||||||
totalSent: new Token(totalSent),
|
|
||||||
settlements: settlements.map(({ peer, received, sent }) => ({
|
|
||||||
peer,
|
|
||||||
received: new Token(received),
|
|
||||||
sent: new Token(sent),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
setSettlements(set)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { settlements, isLoadingSettlements, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LastCashout {
|
|
||||||
peer: string
|
|
||||||
uncashedAmount: Token
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerLastCashoutHook {
|
|
||||||
peerCashout: LastCashout | null
|
|
||||||
isLoadingPeerCashout: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiPeerLastCashout = (peerId: string): PeerLastCashoutHook => {
|
|
||||||
const [peerCashout, setPeerCashout] = useState<LastCashout | null>(null)
|
|
||||||
const [isLoadingPeerCashout, setLoading] = useState<boolean>(true)
|
|
||||||
const [error, setError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true)
|
|
||||||
beeDebugApi.chequebook
|
|
||||||
.getPeerLastCashout(peerId)
|
|
||||||
.then(({ peer, uncashedAmount }) => {
|
|
||||||
setPeerCashout({ peer, uncashedAmount: new Token(uncashedAmount.toString()) })
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
setError(error)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, [peerId])
|
|
||||||
|
|
||||||
return { peerCashout, isLoadingPeerCashout, error }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LatestBeeReleaseHook {
|
export interface LatestBeeReleaseHook {
|
||||||
latestBeeRelease: LatestBeeRelease | null
|
latestBeeRelease: LatestBeeRelease | null
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import { ChequebookAddressResponse } from '@ethersphere/bee-js'
|
|
||||||
import {
|
|
||||||
ChequebookBalance,
|
|
||||||
useApiChequebookAddress,
|
|
||||||
useApiChequebookBalance,
|
|
||||||
useApiHealth,
|
|
||||||
useApiNodeAddresses,
|
|
||||||
useApiNodeTopology,
|
|
||||||
useDebugApiHealth,
|
|
||||||
useLatestBeeRelease,
|
|
||||||
} from './apiHooks'
|
|
||||||
import semver from 'semver'
|
|
||||||
import { engines } from '../../package.json'
|
|
||||||
|
|
||||||
export interface StatusChequebookHook extends StatusHookCommon {
|
|
||||||
chequebookBalance: ChequebookBalance | null
|
|
||||||
chequebookAddress: ChequebookAddressResponse | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStatusNodeVersion = (): StatusNodeVersionHook => {
|
|
||||||
const { latestBeeRelease, isLoadingLatestBeeRelease } = useLatestBeeRelease()
|
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
|
||||||
|
|
||||||
const latestVersion = semver.coerce(latestBeeRelease?.name)?.version
|
|
||||||
const latestUserVersion = semver.coerce(nodeHealth?.version)?.version
|
|
||||||
|
|
||||||
const isLatestBeeVersion = Boolean(
|
|
||||||
latestVersion &&
|
|
||||||
latestUserVersion &&
|
|
||||||
semver.satisfies(latestVersion, latestUserVersion, {
|
|
||||||
includePrerelease: true,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading: isLoadingNodeHealth || isLoadingLatestBeeRelease,
|
|
||||||
isOk: Boolean(
|
|
||||||
nodeHealth &&
|
|
||||||
semver.satisfies(nodeHealth.version, engines.bee, {
|
|
||||||
includePrerelease: true,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
userVersion: nodeHealth?.version,
|
|
||||||
latestVersion,
|
|
||||||
latestUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
|
|
||||||
isLatestBeeVersion,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStatusEthereumConnection = (): StatusEthereumConnectionHook => {
|
|
||||||
const { isLoadingNodeAddresses, nodeAddresses } = useApiNodeAddresses()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading: isLoadingNodeAddresses,
|
|
||||||
isOk: Boolean(nodeAddresses?.ethereum),
|
|
||||||
nodeAddresses,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStatusDebugConnection = (): StatusHookCommon => {
|
|
||||||
const { isLoadingNodeHealth, nodeHealth } = useDebugApiHealth()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading: isLoadingNodeHealth,
|
|
||||||
isOk: Boolean(nodeHealth?.status === 'ok'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStatusConnection = (): StatusHookCommon => {
|
|
||||||
const { isLoadingHealth, health } = useApiHealth()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading: isLoadingHealth,
|
|
||||||
isOk: health,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStatusTopology = (): StatusTopologyHook => {
|
|
||||||
const { topology, isLoading } = useApiNodeTopology()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading,
|
|
||||||
isOk: Boolean(topology?.connected && topology?.connected > 0),
|
|
||||||
topology,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStatusChequebook = (): StatusChequebookHook => {
|
|
||||||
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
|
||||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading: isLoadingChequebookAddress || isLoadingChequebookBalance,
|
|
||||||
isOk:
|
|
||||||
Boolean(chequebookAddress?.chequebookAddress) &&
|
|
||||||
chequebookBalance !== null &&
|
|
||||||
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0),
|
|
||||||
chequebookBalance,
|
|
||||||
chequebookAddress,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import { useState, useEffect, ReactElement } from 'react'
|
import { useState, useEffect, useContext, ReactElement } from 'react'
|
||||||
import ErrorBoundary from '../components/ErrorBoundary'
|
import ErrorBoundary from '../components/ErrorBoundary'
|
||||||
import AlertVersion from '../components/AlertVersion'
|
import AlertVersion from '../components/AlertVersion'
|
||||||
|
import { Container, CircularProgress } from '@material-ui/core'
|
||||||
|
|
||||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
import SideBar from '../components/SideBar'
|
import SideBar from '../components/SideBar'
|
||||||
import NavBar from '../components/NavBar'
|
import NavBar from '../components/NavBar'
|
||||||
|
|
||||||
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks'
|
import { Context } from '../providers/Bee'
|
||||||
|
|
||||||
import { RouteComponentProps } from 'react-router'
|
import { RouteComponentProps } from 'react-router'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
@@ -31,9 +33,7 @@ const Dashboard = (props: Props): ReactElement => {
|
|||||||
|
|
||||||
const [themeMode, toggleThemeMode] = useState('light')
|
const [themeMode, toggleThemeMode] = useState('light')
|
||||||
|
|
||||||
// FIXME: handle errrors and loading
|
const { isLoading, apiHealth, debugApiHealth } = useContext(Context)
|
||||||
const { health } = useApiHealth()
|
|
||||||
const { nodeHealth } = useDebugApiHealth()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const theme = localStorage.getItem('theme')
|
const theme = localStorage.getItem('theme')
|
||||||
@@ -56,12 +56,18 @@ const Dashboard = (props: Props): ReactElement => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
|
<SideBar {...props} themeMode={themeMode} health={apiHealth} nodeHealth={debugApiHealth} />
|
||||||
<NavBar themeMode={themeMode} />
|
<NavBar themeMode={themeMode} />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<main className={classes.content}>
|
<main className={classes.content}>
|
||||||
<AlertVersion />
|
<AlertVersion />
|
||||||
{props.children}
|
{isLoading ? (
|
||||||
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Container>
|
||||||
|
) : (
|
||||||
|
props.children
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ReactElement } from 'react'
|
|||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Card, CardContent, Typography, Theme } from '@material-ui/core/'
|
import { Card, CardContent, Typography, Theme } from '@material-ui/core/'
|
||||||
import { Skeleton } from '@material-ui/lab'
|
|
||||||
import WithdrawModal from '../../containers/WithdrawModal'
|
import WithdrawModal from '../../containers/WithdrawModal'
|
||||||
import DepositModal from '../../containers/DepositModal'
|
import DepositModal from '../../containers/DepositModal'
|
||||||
|
|
||||||
@@ -45,12 +44,11 @@ interface ChequebookBalance {
|
|||||||
interface Props {
|
interface Props {
|
||||||
chequebookAddress: ChequebookAddressResponse | null
|
chequebookAddress: ChequebookAddressResponse | null
|
||||||
chequebookBalance: ChequebookBalance | null
|
chequebookBalance: ChequebookBalance | null
|
||||||
totalsent: Token
|
totalsent?: Token
|
||||||
totalreceived: Token
|
totalreceived?: Token
|
||||||
isLoading: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }: Props): ReactElement {
|
function AccountCard({ totalreceived, totalsent, chequebookBalance }: Props): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -66,7 +64,6 @@ function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }:
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className={classes.root}>
|
<Card className={classes.root}>
|
||||||
{!isLoading && (
|
|
||||||
<CardContent className={classes.gridContainer}>
|
<CardContent className={classes.gridContainer}>
|
||||||
<div>
|
<div>
|
||||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||||
@@ -85,18 +82,10 @@ function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }:
|
|||||||
Total Sent / Received
|
Total Sent / Received
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
{totalsent.toFixedDecimal()} / {totalreceived.toFixedDecimal()} BZZ
|
{totalsent?.toFixedDecimal()} / {totalreceived?.toFixedDecimal()} BZZ
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
)}
|
|
||||||
{isLoading && (
|
|
||||||
<div className={classes.gridContainer}>
|
|
||||||
<Skeleton width={180} height={110} animation="wave" />
|
|
||||||
<Skeleton width={180} height={110} animation="wave" />
|
|
||||||
<Skeleton width={180} height={110} animation="wave" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import type { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Container, CircularProgress } from '@material-ui/core'
|
|
||||||
|
|
||||||
import AccountCard from '../accounting/AccountCard'
|
import AccountCard from '../accounting/AccountCard'
|
||||||
import BalancesTable from './BalancesTable'
|
import BalancesTable from './BalancesTable'
|
||||||
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
||||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
import {
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
useApiNodeAddresses,
|
|
||||||
useApiChequebookAddress,
|
|
||||||
useApiChequebookBalance,
|
|
||||||
useApiHealth,
|
|
||||||
useDebugApiHealth,
|
|
||||||
} from '../../hooks/apiHooks'
|
|
||||||
import { useAccounting } from '../../hooks/accounting'
|
import { useAccounting } from '../../hooks/accounting'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
@@ -29,44 +22,25 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
export default function Accounting(): ReactElement {
|
export default function Accounting(): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements, peerBalances } = useContext(
|
||||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
BeeContext,
|
||||||
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
|
||||||
const { health, isLoadingHealth } = useApiHealth()
|
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
|
||||||
const { isLoading, totalsent, totalreceived, accounting, isLoadingUncashed, error } = useAccounting()
|
|
||||||
|
|
||||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)
|
)
|
||||||
}
|
const { beeDebugApi } = useContext(SettingsContext)
|
||||||
|
|
||||||
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
const { accounting, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances)
|
||||||
|
|
||||||
|
if (!status.all) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<AccountCard
|
<AccountCard
|
||||||
chequebookAddress={chequebookAddress}
|
chequebookAddress={chequebookAddress}
|
||||||
isLoading={isLoadingChequebookAddress || isLoading || isLoadingChequebookBalance}
|
|
||||||
chequebookBalance={chequebookBalance}
|
chequebookBalance={chequebookBalance}
|
||||||
totalsent={totalsent}
|
totalsent={settlements?.totalSent}
|
||||||
totalreceived={totalreceived}
|
totalreceived={settlements?.totalReceived}
|
||||||
/>
|
/>
|
||||||
<EthereumAddressCard
|
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
|
||||||
nodeAddresses={nodeAddresses}
|
<BalancesTable accounting={accounting} isLoadingUncashed={isLoadingUncashed} />
|
||||||
isLoadingNodeAddresses={isLoadingNodeAddresses}
|
|
||||||
chequebookAddress={chequebookAddress}
|
|
||||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
|
||||||
/>
|
|
||||||
{error && (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
Error loading accounting details: {error.message}
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
{!error && <BalancesTable accounting={accounting} isLoadingUncashed={isLoadingUncashed} />}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import { ReactElement, useState, useContext } from 'react'
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import { Paper, InputBase, IconButton, FormHelperText } from '@material-ui/core'
|
import { Paper, InputBase, IconButton, FormHelperText } from '@material-ui/core'
|
||||||
import { Search } from '@material-ui/icons'
|
import { Search } from '@material-ui/icons'
|
||||||
import { apiHost } from '../../constants'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { Utils } from '@ethersphere/bee-js'
|
import { Utils } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
@@ -28,6 +28,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
|
|
||||||
export default function Files(): ReactElement {
|
export default function Files(): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const { apiUrl } = useContext(SettingsContext)
|
||||||
|
|
||||||
const [referenceInput, setReferenceInput] = useState('')
|
const [referenceInput, setReferenceInput] = useState('')
|
||||||
const [referenceError, setReferenceError] = useState<Error | null>(null)
|
const [referenceError, setReferenceError] = useState<Error | null>(null)
|
||||||
@@ -50,7 +51,7 @@ export default function Files(): ReactElement {
|
|||||||
onChange={handleReferenceChange}
|
onChange={handleReferenceChange}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
href={`${apiHost}/bzz/${referenceInput}`}
|
href={`${apiUrl}/bzz/${referenceInput}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
disabled={referenceError !== null || !referenceInput}
|
disabled={referenceError !== null || !referenceInput}
|
||||||
className={classes.iconButton}
|
className={classes.iconButton}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import UploadSizeAlert from '../../components/AlertUploadSize'
|
|||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||||
import PeerDetailDrawer from '../../components/PeerDetail'
|
import PeerDetailDrawer from '../../components/PeerDetail'
|
||||||
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
|
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
import { beeApi } from '../../services/bee'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
||||||
import SelectStamp from './SelectStamp'
|
import SelectStamp from './SelectStamp'
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ export default function Files(): ReactElement {
|
|||||||
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
|
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
|
||||||
|
|
||||||
const { isLoading, error, stamps } = useContext(Context)
|
const { isLoading, error, stamps } = useContext(Context)
|
||||||
|
const { beeApi } = useContext(SettingsContext)
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
// Choose a postage stamp that has the lowest usage
|
// Choose a postage stamp that has the lowest usage
|
||||||
@@ -40,8 +41,11 @@ export default function Files(): ReactElement {
|
|||||||
|
|
||||||
const uploadFile = () => {
|
const uploadFile = () => {
|
||||||
if (file === null || selectedStamp === null) return
|
if (file === null || selectedStamp === null) return
|
||||||
|
|
||||||
|
if (!beeApi) return
|
||||||
|
|
||||||
setIsUploadingFile(true)
|
setIsUploadingFile(true)
|
||||||
beeApi.files
|
beeApi
|
||||||
.uploadFile(selectedStamp.batchID, file)
|
.uploadFile(selectedStamp.batchID, file)
|
||||||
.then(hash => {
|
.then(hash => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
import { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
|
|
||||||
import { Container, CircularProgress } from '@material-ui/core'
|
import { Container } from '@material-ui/core'
|
||||||
|
|
||||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
|
import { Context } from '../../providers/Bee'
|
||||||
import Download from './Download'
|
import Download from './Download'
|
||||||
import Upload from './Upload'
|
import Upload from './Upload'
|
||||||
import TabsContainer from '../../components/TabsContainer'
|
import TabsContainer from '../../components/TabsContainer'
|
||||||
|
|
||||||
export default function Files(): ReactElement {
|
export default function Files(): ReactElement {
|
||||||
const { health, isLoadingHealth } = useApiHealth()
|
const { status } = useContext(Context)
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
|
||||||
|
|
||||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
if (!status.all) return <TroubleshootConnectionCard />
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!health || nodeHealth?.status !== 'ok') return <TroubleshootConnectionCard />
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import { ReactElement, useState, useContext } from 'react'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -10,12 +10,11 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Paper,
|
Paper,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Container,
|
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import { Autorenew } from '@material-ui/icons'
|
import { Autorenew } from '@material-ui/icons'
|
||||||
|
|
||||||
import { beeDebugApi } from '../../services/bee'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import type { Peer } from '@ethersphere/bee-js'
|
import type { Peer } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
@@ -26,47 +25,43 @@ const useStyles = makeStyles({
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
peers: Peer[] | null
|
peers: Peer[] | null
|
||||||
isLoading: boolean
|
}
|
||||||
error: Error | null
|
|
||||||
|
interface PeerLatency {
|
||||||
|
rtt: string
|
||||||
|
loading: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPingState(peerLatency: Record<string, PeerLatency>, peer: Peer): ReactElement {
|
||||||
|
if (peerLatency[peer.address]?.loading) return <CircularProgress size={20} />
|
||||||
|
|
||||||
|
if (peerLatency[peer.address]?.rtt) return <span>{peerLatency[peer.address]?.rtt}</span>
|
||||||
|
|
||||||
|
return <Autorenew />
|
||||||
}
|
}
|
||||||
|
|
||||||
function PeerTable(props: Props): ReactElement {
|
function PeerTable(props: Props): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const { beeDebugApi } = useContext(SettingsContext)
|
||||||
|
|
||||||
const [peerLatency, setPeerLatency] = useState([{ peerId: '', rtt: '', loading: false }])
|
const [peerLatency, setPeerLatency] = useState<Record<string, PeerLatency>>({})
|
||||||
|
|
||||||
const PingPeer = (peerId: string) => {
|
const pingPeer = (peerId: string) => {
|
||||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: '', loading: true }])
|
setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: '', loading: true } }))
|
||||||
beeDebugApi.connectivity
|
beeDebugApi
|
||||||
.ping(peerId)
|
?.pingPeer(peerId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: res.rtt, loading: false }])
|
setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: res.rtt, loading: false } }))
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: 'error', loading: false }])
|
setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: 'error', loading: false } }))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.isLoading) {
|
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.error || props.peers === null) {
|
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<p>Failed to load peers</p>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table className={classes.table} aria-label="simple table">
|
<Table className={classes.table}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Index</TableCell>
|
<TableCell>Index</TableCell>
|
||||||
@@ -75,7 +70,7 @@ function PeerTable(props: Props): ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{props.peers.map((peer: Peer, idx: number) => (
|
{props.peers?.map((peer: Peer, idx: number) => (
|
||||||
<TableRow key={peer.address}>
|
<TableRow key={peer.address}>
|
||||||
<TableCell component="th" scope="row">
|
<TableCell component="th" scope="row">
|
||||||
{idx + 1}
|
{idx + 1}
|
||||||
@@ -83,21 +78,8 @@ function PeerTable(props: Props): ReactElement {
|
|||||||
<TableCell>{peer.address}</TableCell>
|
<TableCell>{peer.address}</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<Tooltip title="Ping node">
|
<Tooltip title="Ping node">
|
||||||
<Button color="primary" onClick={() => PingPeer(peer.address)}>
|
<Button color="primary" onClick={() => pingPeer(peer.address)}>
|
||||||
{
|
{getPingState(peerLatency, peer)}
|
||||||
// FIXME: this should be broken up
|
|
||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
peerLatency.find(item => item.peerId === peer.address) ? (
|
|
||||||
peerLatency.filter(item => item.peerId === peer.address)[0].loading ? (
|
|
||||||
<CircularProgress size={20} />
|
|
||||||
) : (
|
|
||||||
peerLatency.filter(item => item.peerId === peer.address)[0].rtt
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Autorenew />
|
|
||||||
)
|
|
||||||
/* eslint-enable no-nested-ternary */
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -1,32 +1,21 @@
|
|||||||
import { Container, CircularProgress } from '@material-ui/core/'
|
|
||||||
import PeerTable from './PeerTable'
|
import PeerTable from './PeerTable'
|
||||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
|
|
||||||
import { useApiNodeTopology, useApiNodePeers, useDebugApiHealth } from '../../hooks/apiHooks'
|
import { Context } from '../../providers/Bee'
|
||||||
import TopologyStats from '../../components/TopologyStats'
|
import TopologyStats from '../../components/TopologyStats'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
|
|
||||||
export default function Peers(): ReactElement {
|
export default function Peers(): ReactElement {
|
||||||
const topology = useApiNodeTopology()
|
const { topology, peers, status } = useContext(Context)
|
||||||
const debugHealth = useDebugApiHealth()
|
|
||||||
const peers = useApiNodePeers()
|
|
||||||
|
|
||||||
if (debugHealth.isLoadingNodeHealth) {
|
if (!status.all) {
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debugHealth.error) {
|
|
||||||
return <TroubleshootConnectionCard />
|
return <TroubleshootConnectionCard />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopologyStats {...topology} />
|
<TopologyStats topology={topology} />
|
||||||
<PeerTable {...peers} />
|
<PeerTable peers={peers} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useState, useContext } from 'react'
|
||||||
import { Paper, Container, TextField, Typography, Button } from '@material-ui/core'
|
import { Paper, Container, TextField, Typography, Button } from '@material-ui/core'
|
||||||
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
|
|
||||||
export default function Settings(): ReactElement {
|
export default function Settings(): ReactElement {
|
||||||
const [refreshVisibility, toggleRefreshVisibility] = useState(false)
|
const { apiUrl, apiDebugUrl, setApiUrl, setDebugApiUrl } = useContext(SettingsContext)
|
||||||
const [host, setHost] = useState('')
|
const [host, setHost] = useState(apiUrl)
|
||||||
const [debugHost, setDebugHost] = useState('')
|
const [debugHost, setDebugHost] = useState(apiDebugUrl)
|
||||||
|
|
||||||
const handleNewHostConnection = () => {
|
const submit = () => {
|
||||||
if (host) {
|
if (host !== apiUrl) setApiUrl(host)
|
||||||
sessionStorage.setItem('api_host', host)
|
|
||||||
|
if (debugHost !== apiDebugUrl) setDebugApiUrl(debugHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debugHost) {
|
const touched = host !== apiUrl || debugHost !== apiDebugUrl
|
||||||
sessionStorage.setItem('debug_api_host', debugHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (host || debugHost) {
|
|
||||||
toggleRefreshVisibility(!refreshVisibility)
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -34,16 +28,13 @@ export default function Settings(): ReactElement {
|
|||||||
placeholder="ex: 127.0.0.0.1:1633"
|
placeholder="ex: 127.0.0.0.1:1633"
|
||||||
helperText="Enter node host override / port"
|
helperText="Enter node host override / port"
|
||||||
fullWidth
|
fullWidth
|
||||||
defaultValue={
|
defaultValue={apiUrl}
|
||||||
sessionStorage.getItem('api_host') ? sessionStorage.getItem('api_host') : process.env.REACT_APP_BEE_HOST
|
|
||||||
}
|
|
||||||
margin="normal"
|
margin="normal"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
}}
|
}}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setHost(e.target.value)
|
setHost(e.target.value)
|
||||||
toggleRefreshVisibility(true)
|
|
||||||
}}
|
}}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
/>
|
/>
|
||||||
@@ -55,14 +46,9 @@ export default function Settings(): ReactElement {
|
|||||||
placeholder="ex: 127.0.0.0.1:1635"
|
placeholder="ex: 127.0.0.0.1:1635"
|
||||||
helperText="Enter node debug host override / port"
|
helperText="Enter node debug host override / port"
|
||||||
fullWidth
|
fullWidth
|
||||||
defaultValue={
|
defaultValue={apiDebugUrl}
|
||||||
sessionStorage.getItem('debug_api_host')
|
|
||||||
? sessionStorage.getItem('debug_api_host')
|
|
||||||
: process.env.REACT_APP_BEE_DEBUG_HOST
|
|
||||||
}
|
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setDebugHost(e.target.value)
|
setDebugHost(e.target.value)
|
||||||
toggleRefreshVisibility(true)
|
|
||||||
}}
|
}}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
@@ -71,9 +57,9 @@ export default function Settings(): ReactElement {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
{refreshVisibility ? (
|
{touched ? (
|
||||||
<div style={{ marginTop: '20px' }}>
|
<div style={{ marginTop: '20px' }}>
|
||||||
<Button variant="outlined" color="primary" onClick={() => handleNewHostConnection()}>
|
<Button variant="outlined" color="primary" onClick={submit}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import DialogTitle from '@material-ui/core/DialogTitle'
|
|||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import { FormikHelpers, Form, Field, Formik } from 'formik'
|
import { FormikHelpers, Form, Field, Formik } from 'formik'
|
||||||
import { TextField } from 'formik-material-ui'
|
import { TextField } from 'formik-material-ui'
|
||||||
import { beeApi } from '../../services/bee'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { Context } from '../../providers/Stamps'
|
import { Context } from '../../providers/Stamps'
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
@@ -54,6 +54,7 @@ export default function FormDialog({ label }: Props): ReactElement {
|
|||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
const { refresh } = useContext(Context)
|
const { refresh } = useContext(Context)
|
||||||
|
const { beeApi } = useContext(SettingsContext)
|
||||||
const handleClickOpen = () => setOpen(true)
|
const handleClickOpen = () => setOpen(true)
|
||||||
const handleClose = () => setOpen(false)
|
const handleClose = () => setOpen(false)
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
@@ -66,10 +67,12 @@ export default function FormDialog({ label }: Props): ReactElement {
|
|||||||
// This is really just a typeguard, the validation pretty much guarantees these will have the right values
|
// This is really just a typeguard, the validation pretty much guarantees these will have the right values
|
||||||
if (!values.depth || !values.amount) return
|
if (!values.depth || !values.amount) return
|
||||||
|
|
||||||
|
if (!beeApi) return
|
||||||
|
|
||||||
const amount = BigInt(values.amount)
|
const amount = BigInt(values.amount)
|
||||||
const depth = Number.parseInt(values.depth)
|
const depth = Number.parseInt(values.depth)
|
||||||
const options = values.label ? { label: values.label } : undefined
|
const options = values.label ? { label: values.label } : undefined
|
||||||
await beeApi.stamps.buyPostageStamp(amount, depth, options)
|
await beeApi.createPostageBatch(amount.toString(), depth, options)
|
||||||
actions.resetForm()
|
actions.resetForm()
|
||||||
await refresh()
|
await refresh()
|
||||||
handleClose()
|
handleClose()
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import { Container, CircularProgress } from '@material-ui/core'
|
|||||||
import StampsTable from './StampsTable'
|
import StampsTable from './StampsTable'
|
||||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
import CreatePostageStampModal from './CreatePostageStampModal'
|
import CreatePostageStampModal from './CreatePostageStampModal'
|
||||||
import LastUpdate from '../../components/LastUpdate'
|
|
||||||
|
|
||||||
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
|
|
||||||
import { Context } from '../../providers/Stamps'
|
import { Context } from '../../providers/Stamps'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -32,16 +31,15 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
export default function Accounting(): ReactElement {
|
export default function Accounting(): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const { health, isLoadingHealth } = useApiHealth()
|
const beeContext = useContext(BeeContext)
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
const { stamps, isLoading, error, start, stop } = useContext(Context)
|
||||||
const { stamps, isLoading, error, lastUpdate, start, stop } = useContext(Context)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
start()
|
start()
|
||||||
|
|
||||||
return () => stop()
|
return () => stop()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
if (beeContext.isLoading) {
|
||||||
return (
|
return (
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
@@ -49,7 +47,7 @@ export default function Accounting(): ReactElement {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
if (!beeContext.status.all) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
@@ -62,7 +60,6 @@ export default function Accounting(): ReactElement {
|
|||||||
<>
|
<>
|
||||||
<div className={classes.actions}>
|
<div className={classes.actions}>
|
||||||
<CreatePostageStampModal />
|
<CreatePostageStampModal />
|
||||||
<LastUpdate date={lastUpdate} />
|
|
||||||
<div style={{ height: '5px' }}>{isLoading && <CircularProgress />}</div>
|
<div style={{ height: '5px' }}>{isLoading && <CircularProgress />}</div>
|
||||||
</div>
|
</div>
|
||||||
<StampsTable postageStamps={stamps} />
|
<StampsTable postageStamps={stamps} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react'
|
import { ReactElement, useEffect, useState, useContext } from 'react'
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
|
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
|
||||||
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
|
import { CheckCircle, Error, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
|
||||||
|
|
||||||
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
|
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
|
||||||
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
|
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
|
||||||
@@ -9,7 +9,7 @@ import VersionCheck from './SetupSteps/VersionCheck'
|
|||||||
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
|
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
|
||||||
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
|
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
|
||||||
import PeerConnection from './SetupSteps/PeerConnection'
|
import PeerConnection from './SetupSteps/PeerConnection'
|
||||||
import { StatusChequebookHook } from '../../hooks/status'
|
import { Context } from '../../providers/Bee'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -30,66 +30,65 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
interface Step {
|
interface Step {
|
||||||
label: string
|
label: string
|
||||||
isOk: boolean
|
isOk: boolean
|
||||||
isLoading: boolean
|
|
||||||
component: ReactElement
|
component: ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
export default function NodeSetupWorkflow(): ReactElement {
|
||||||
nodeVersion: StatusNodeVersionHook
|
|
||||||
ethereumConnection: StatusEthereumConnectionHook
|
|
||||||
debugApiConnection: StatusHookCommon
|
|
||||||
apiConnection: StatusHookCommon
|
|
||||||
topology: StatusTopologyHook
|
|
||||||
chequebook: StatusChequebookHook
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NodeSetupWorkflow({
|
|
||||||
nodeVersion,
|
|
||||||
ethereumConnection,
|
|
||||||
debugApiConnection,
|
|
||||||
apiConnection,
|
|
||||||
topology,
|
|
||||||
chequebook,
|
|
||||||
}: Props): ReactElement {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [activeStep, setActiveStep] = useState(-1)
|
const [activeStep, setActiveStep] = useState(-1)
|
||||||
|
|
||||||
|
const {
|
||||||
|
status,
|
||||||
|
isLoading,
|
||||||
|
latestUserVersion,
|
||||||
|
latestPublishedVersion,
|
||||||
|
isLatestBeeVersion,
|
||||||
|
latestBeeVersionUrl,
|
||||||
|
topology,
|
||||||
|
nodeAddresses,
|
||||||
|
chequebookAddress,
|
||||||
|
} = useContext(Context)
|
||||||
|
|
||||||
const steps: Step[] = [
|
const steps: Step[] = [
|
||||||
{
|
{
|
||||||
label: 'Connected to Node DebugAPI',
|
label: 'Connected to Node DebugAPI',
|
||||||
isOk: debugApiConnection.isOk,
|
isOk: status.debugApiConnection,
|
||||||
isLoading: debugApiConnection.isLoading,
|
component: <DebugConnectionCheck isOk={status.debugApiConnection} />,
|
||||||
component: <DebugConnectionCheck {...debugApiConnection} />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Running latest Bee version',
|
label: 'Running latest Bee version',
|
||||||
isOk: nodeVersion.isOk,
|
isOk: status.version,
|
||||||
isLoading: nodeVersion.isLoading,
|
component: (
|
||||||
component: <VersionCheck {...nodeVersion} />,
|
<VersionCheck
|
||||||
|
isOk={status.version}
|
||||||
|
isLatestBeeVersion={isLatestBeeVersion}
|
||||||
|
userVersion={latestUserVersion}
|
||||||
|
latestVersion={latestPublishedVersion}
|
||||||
|
latestUrl={latestBeeVersionUrl}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Connected to Ethereum Blockchain',
|
label: 'Connected to xDai Blockchain',
|
||||||
isOk: ethereumConnection.isOk,
|
isOk: status.blockchainConnection,
|
||||||
isLoading: ethereumConnection.isLoading,
|
component: <EthereumConnectionCheck isOk={status.blockchainConnection} nodeAddresses={nodeAddresses} />,
|
||||||
component: <EthereumConnectionCheck {...ethereumConnection} />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Deployed and Funded Chequebook',
|
label: 'Deployed and Funded Chequebook',
|
||||||
isOk: chequebook.isOk,
|
isOk: status.chequebook,
|
||||||
isLoading: chequebook.isLoading,
|
component: (
|
||||||
component: <ChequebookDeployFund ethereumAddress={ethereumConnection.nodeAddresses?.ethereum} {...chequebook} />,
|
<ChequebookDeployFund chequebookAddress={chequebookAddress?.chequebookAddress} isOk={status.chequebook} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Connected to Node API',
|
label: 'Connected to Node API',
|
||||||
isOk: apiConnection.isOk,
|
isOk: status.apiConnection,
|
||||||
isLoading: apiConnection.isLoading,
|
component: <NodeConnectionCheck isOk={status.apiConnection} />,
|
||||||
component: <NodeConnectionCheck {...apiConnection} />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Connected to Peers',
|
label: 'Connected to Peers',
|
||||||
isOk: topology.isOk,
|
isOk: status.topology,
|
||||||
isLoading: topology.isLoading,
|
component: <PeerConnection isOk={status.topology} topology={topology} />,
|
||||||
component: <PeerConnection {...topology} />,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ export default function NodeSetupWorkflow({
|
|||||||
if (activeStep >= 0 && activeStep < steps.length) return
|
if (activeStep >= 0 && activeStep < steps.length) return
|
||||||
|
|
||||||
// If any step is not fully loaded yet return
|
// If any step is not fully loaded yet return
|
||||||
if (!steps.every(step => !step.isLoading)) return
|
if (!isLoading) return
|
||||||
|
|
||||||
// Select first step that is not OK
|
// Select first step that is not OK
|
||||||
// This is deliberately a for loop (and not forEach) so that we can terminate the useEffect from within the cycle
|
// This is deliberately a for loop (and not forEach) so that we can terminate the useEffect from within the cycle
|
||||||
@@ -123,18 +122,11 @@ export default function NodeSetupWorkflow({
|
|||||||
<Paper className={classes.root}>
|
<Paper className={classes.root}>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Node Setup
|
Node Setup
|
||||||
<span style={{ marginLeft: '25px' }}>
|
|
||||||
<Button variant="outlined" size="small" onClick={() => window.location.reload()}>
|
|
||||||
<Sync />
|
|
||||||
<span style={{ marginLeft: '7px' }}>Refresh Checks</span>
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
|
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
|
||||||
{steps.map(({ label, isOk, component, isLoading }, index) => (
|
{steps.map(({ label, isOk, component }, index) => (
|
||||||
<Step key={label}>
|
<Step key={label}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
disabled={isLoading}
|
|
||||||
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
|
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
|
||||||
StepIconComponent={() => {
|
StepIconComponent={() => {
|
||||||
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
|
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
|
||||||
|
|||||||
@@ -2,33 +2,25 @@ import { Typography } from '@material-ui/core/'
|
|||||||
import EthereumAddress from '../../../components/EthereumAddress'
|
import EthereumAddress from '../../../components/EthereumAddress'
|
||||||
import DepositModal from '../../../containers/DepositModal'
|
import DepositModal from '../../../containers/DepositModal'
|
||||||
import type { ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import type { StatusChequebookHook } from '../../../hooks/status'
|
|
||||||
|
|
||||||
interface Props extends StatusChequebookHook {
|
interface Props extends StatusHookCommon {
|
||||||
ethereumAddress?: string
|
chequebookAddress?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChequebookDeployFund = ({
|
const ChequebookDeployFund = ({ chequebookAddress, isOk }: Props): ReactElement | null => {
|
||||||
isLoading,
|
|
||||||
chequebookAddress,
|
|
||||||
chequebookBalance,
|
|
||||||
ethereumAddress,
|
|
||||||
}: Props): ReactElement | null => {
|
|
||||||
if (isLoading) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p style={{ marginBottom: '20px', display: 'flex' }}>
|
<p style={{ marginBottom: '20px', display: 'flex' }}>{chequebookAddress && <DepositModal />}</p>
|
||||||
{chequebookAddress?.chequebookAddress && <DepositModal />}
|
|
||||||
</p>
|
|
||||||
<div style={{ marginBottom: '10px' }}>
|
<div style={{ marginBottom: '10px' }}>
|
||||||
{!(chequebookAddress?.chequebookAddress && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) && (
|
{!isOk && (
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
Your chequebook is either not deployed or funded. Join{' '}
|
Your chequebook is either not deployed or funded. To run the node you will need xDAI and xBZZ on the xDai
|
||||||
<a href="https://discord.gg/ykCupZMuww">our discord channel</a>, get verified and send a message{' '}
|
network. You may need to aquire BZZ through (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and
|
||||||
<pre>sprinkle {ethereumAddress || '<YOUR BEE NODE ETH ADDRESS>'}</pre> in the <pre>#faucet-request</pre>{' '}
|
bridge it to the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To
|
||||||
channel to get Goerli ETH and Goerli BZZ token.
|
pay the transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it
|
||||||
|
to xDai network through the <a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '}
|
||||||
|
<a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -36,7 +28,7 @@ const ChequebookDeployFund = ({
|
|||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Chequebook Address
|
Chequebook Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={chequebookAddress?.chequebookAddress} network={'goerli'} />
|
<EthereumAddress address={chequebookAddress} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
import type { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
|
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
|
||||||
import MuiAlert from '@material-ui/lab/Alert'
|
import MuiAlert from '@material-ui/lab/Alert'
|
||||||
import { ExpandMoreSharp } from '@material-ui/icons/'
|
import { ExpandMoreSharp } from '@material-ui/icons/'
|
||||||
|
|
||||||
import ConnectToHost from '../../../components/ConnectToHost'
|
import ConnectToHost from '../../../components/ConnectToHost'
|
||||||
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||||
import { debugApiHost } from '../../../constants'
|
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||||
|
|
||||||
type Props = StatusHookCommon
|
type Props = StatusHookCommon
|
||||||
|
|
||||||
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
|
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
|
||||||
if (isLoading) return null
|
const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext)
|
||||||
|
|
||||||
const changeDebugApiUrl = (
|
const changeDebugApiUrl = (
|
||||||
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
|
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
|
||||||
<span style={{ marginRight: '15px' }}>
|
<span style={{ marginRight: '15px' }}>
|
||||||
Debug API (<Typography variant="button">{debugApiHost}</Typography>)
|
Debug API (<Typography variant="button">{apiDebugUrl}</Typography>)
|
||||||
</span>
|
</span>
|
||||||
<ConnectToHost hostName={'debug_api_host'} defaultHost={debugApiHost} />
|
<ConnectToHost
|
||||||
|
setHost={(host: string) => {
|
||||||
|
console.log(host) // eslint-disable-line
|
||||||
|
setDebugApiUrl(host)
|
||||||
|
}}
|
||||||
|
defaultHost={apiDebugUrl}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +37,7 @@ export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactEl
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
|
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
|
||||||
We cannot connect to your nodes debug API at <Typography variant="button">{debugApiHost}</Typography>. Please
|
We cannot connect to your nodes debug API at <Typography variant="button">{apiDebugUrl}</Typography>. Please
|
||||||
check the following to troubleshoot your issue.
|
check the following to troubleshoot your issue.
|
||||||
<Accordion style={{ marginTop: '20px' }}>
|
<Accordion style={{ marginTop: '20px' }}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
|
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
|
||||||
|
|||||||
@@ -4,37 +4,31 @@ import EthereumAddress from '../../../components/EthereumAddress'
|
|||||||
|
|
||||||
type Props = StatusEthereumConnectionHook
|
type Props = StatusEthereumConnectionHook
|
||||||
|
|
||||||
export default function EthereumConnectionCheck({ isLoading, isOk, nodeAddresses }: Props): ReactElement | null {
|
export default function EthereumConnectionCheck({ isOk, nodeAddresses }: Props): ReactElement | null {
|
||||||
if (isLoading) return null
|
|
||||||
|
|
||||||
if (isOk) {
|
if (isOk) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Node Address
|
Node Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={nodeAddresses?.ethereum} network={'goerli'} />
|
<EthereumAddress address={nodeAddresses?.ethereum} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your chequebook
|
Your Bee node must have access to the xDai blockchain, so that it can interact and deploy your chequebook
|
||||||
contract. You can run{' '}
|
contract. You can run{' '}
|
||||||
<a href="https://github.com/goerli/testnet" rel="noreferrer" target="_blank">
|
<a href="https://www.xdaichain.com/" rel="noreferrer" target="_blank">
|
||||||
your own Goerli node
|
your own xDai node
|
||||||
</a>
|
</a>
|
||||||
, or use a provider such as{' '}
|
, or use a provider instead - we recommend{' '}
|
||||||
<a href="https://rpc.slock.it/goerli" rel="noreferrer" target="_blank">
|
<a href="https://getblock.io/" rel="noreferrer" target="_blank">
|
||||||
rpc.slock.it/goerli
|
Getblock
|
||||||
</a>{' '}
|
|
||||||
or{' '}
|
|
||||||
<a href="https://infura.io/" rel="noreferrer" target="_blank">
|
|
||||||
Infura
|
|
||||||
</a>
|
</a>
|
||||||
. By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, simply change
|
. By default, Bee expects a local node at http://localhost:8545. To use a provider instead, simply change the{' '}
|
||||||
your <strong>--swap-endpoint</strong> in your configuration file.
|
<strong>swap-endpoint</strong> in your configuration file.
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
|
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
|
||||||
import { ExpandMoreSharp } from '@material-ui/icons/'
|
import { ExpandMoreSharp } from '@material-ui/icons/'
|
||||||
|
|
||||||
import ConnectToHost from '../../../components/ConnectToHost'
|
import ConnectToHost from '../../../components/ConnectToHost'
|
||||||
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||||
import { apiHost } from '../../../constants'
|
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||||
|
|
||||||
type Props = StatusHookCommon
|
type Props = StatusHookCommon
|
||||||
|
|
||||||
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
|
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
|
||||||
if (isLoading) return null
|
const { setApiUrl, apiUrl } = useContext(SettingsContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', marginBottom: '25px' }}>
|
<div style={{ display: 'flex', marginBottom: '25px' }}>
|
||||||
<span style={{ marginRight: '15px' }}>
|
<span style={{ marginRight: '15px' }}>
|
||||||
Node API (<Typography variant="button">{apiHost}</Typography>)
|
Node API (<Typography variant="button">{apiUrl}</Typography>)
|
||||||
</span>
|
</span>
|
||||||
<ConnectToHost hostName="api_host" defaultHost={apiHost} />
|
<ConnectToHost setHost={setApiUrl} defaultHost={apiUrl} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{!isOk && (
|
{!isOk && (
|
||||||
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
|
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
|
||||||
We cannot connect to your nodes API at <Typography variant="button">{apiHost}</Typography>. Please check the
|
We cannot connect to your nodes API at <Typography variant="button">{apiUrl}</Typography>. Please check the
|
||||||
following to troubleshoot your issue.
|
following to troubleshoot your issue.
|
||||||
<Accordion style={{ marginTop: '20px' }}>
|
<Accordion style={{ marginTop: '20px' }}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
|
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import { Typography } from '@material-ui/core/'
|
|||||||
|
|
||||||
type Props = StatusTopologyHook
|
type Props = StatusTopologyHook
|
||||||
|
|
||||||
export default function PeerConnection({ isLoading, isOk, topology }: Props): ReactElement | null {
|
export default function PeerConnection({ isOk, topology }: Props): ReactElement | null {
|
||||||
if (isLoading) return null
|
|
||||||
|
|
||||||
const peers = (
|
const peers = (
|
||||||
<div style={{ display: 'flex', marginTop: '15px' }}>
|
<div style={{ display: 'flex', marginTop: '15px' }}>
|
||||||
<div style={{ marginRight: '30px' }}>
|
<div style={{ marginRight: '30px' }}>
|
||||||
|
|||||||
@@ -4,15 +4,7 @@ import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
|||||||
|
|
||||||
type Props = StatusNodeVersionHook
|
type Props = StatusNodeVersionHook
|
||||||
|
|
||||||
export default function VersionCheck({
|
export default function VersionCheck({ isOk, userVersion, latestVersion, latestUrl }: Props): ReactElement | null {
|
||||||
isLoading,
|
|
||||||
isOk,
|
|
||||||
userVersion,
|
|
||||||
latestVersion,
|
|
||||||
latestUrl,
|
|
||||||
}: Props): ReactElement | null {
|
|
||||||
if (isLoading) return null
|
|
||||||
|
|
||||||
const version = (
|
const version = (
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<div style={{ marginRight: '30px' }}>
|
<div style={{ marginRight: '30px' }}>
|
||||||
|
|||||||
+20
-48
@@ -1,18 +1,10 @@
|
|||||||
import { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { Container, CircularProgress } from '@material-ui/core'
|
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
import NodeSetupWorkflow from './NodeSetupWorkflow'
|
import NodeSetupWorkflow from './NodeSetupWorkflow'
|
||||||
import StatusCard from './StatusCard'
|
import StatusCard from './StatusCard'
|
||||||
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
||||||
import {
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
useStatusEthereumConnection,
|
|
||||||
useStatusNodeVersion,
|
|
||||||
useStatusDebugConnection,
|
|
||||||
useStatusConnection,
|
|
||||||
useStatusTopology,
|
|
||||||
useStatusChequebook,
|
|
||||||
} from '../../hooks/status'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -27,50 +19,30 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
export default function Status(): ReactElement {
|
export default function Status(): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const nodeVersion = useStatusNodeVersion()
|
const {
|
||||||
const ethereumConnection = useStatusEthereumConnection()
|
status,
|
||||||
const debugApiConnection = useStatusDebugConnection()
|
latestUserVersion,
|
||||||
const apiConnection = useStatusConnection()
|
isLatestBeeVersion,
|
||||||
const topology = useStatusTopology()
|
latestBeeVersionUrl,
|
||||||
const chequebook = useStatusChequebook()
|
topology,
|
||||||
|
nodeAddresses,
|
||||||
const checks = [nodeVersion, ethereumConnection, debugApiConnection, apiConnection, topology, chequebook]
|
chequebookAddress,
|
||||||
|
} = useContext(BeeContext)
|
||||||
// If any check data are still loading
|
|
||||||
if (!checks.every(c => !c.isLoading)) {
|
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<StatusCard
|
<StatusCard
|
||||||
userBeeVersion={nodeVersion.userVersion}
|
userBeeVersion={latestUserVersion}
|
||||||
isLatestBeeVersion={nodeVersion.isLatestBeeVersion}
|
isLatestBeeVersion={isLatestBeeVersion}
|
||||||
isOk={checks.every(c => c.isOk)}
|
isOk={status.all}
|
||||||
nodeTopology={topology.topology}
|
nodeTopology={topology}
|
||||||
latestUrl={nodeVersion.latestUrl}
|
latestUrl={latestBeeVersionUrl}
|
||||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
nodeAddresses={nodeAddresses}
|
||||||
/>
|
|
||||||
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
|
|
||||||
<EthereumAddressCard
|
|
||||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
|
||||||
isLoadingNodeAddresses={ethereumConnection.isLoading}
|
|
||||||
chequebookAddress={chequebook.chequebookAddress}
|
|
||||||
isLoadingChequebookAddress={chequebook.isLoading}
|
|
||||||
/>
|
/>
|
||||||
|
{nodeAddresses && chequebookAddress && (
|
||||||
|
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
|
||||||
)}
|
)}
|
||||||
<NodeSetupWorkflow
|
<NodeSetupWorkflow />
|
||||||
nodeVersion={nodeVersion}
|
|
||||||
ethereumConnection={ethereumConnection}
|
|
||||||
debugApiConnection={debugApiConnection}
|
|
||||||
apiConnection={apiConnection}
|
|
||||||
topology={topology}
|
|
||||||
chequebook={chequebook}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,351 @@
|
|||||||
|
import type { ChequebookBalance, Balance, Settlements } from '../types'
|
||||||
|
import { createContext, ReactChild, ReactElement, useEffect, useState, useContext } from 'react'
|
||||||
|
import { Token } from '../models/Token'
|
||||||
|
import semver from 'semver'
|
||||||
|
import { engines } from '../../package.json'
|
||||||
|
import { Context as SettingsContext } from './Settings'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
NodeAddresses,
|
||||||
|
ChequebookAddressResponse,
|
||||||
|
LastChequesResponse,
|
||||||
|
Health,
|
||||||
|
Peer,
|
||||||
|
Topology,
|
||||||
|
} from '@ethersphere/bee-js'
|
||||||
|
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
||||||
|
|
||||||
|
interface Status {
|
||||||
|
all: boolean
|
||||||
|
version: boolean
|
||||||
|
blockchainConnection: boolean
|
||||||
|
debugApiConnection: boolean
|
||||||
|
apiConnection: boolean
|
||||||
|
topology: boolean
|
||||||
|
chequebook: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextInterface {
|
||||||
|
status: Status
|
||||||
|
latestPublishedVersion?: string
|
||||||
|
latestUserVersion?: string
|
||||||
|
latestUserVersionExact?: string
|
||||||
|
isLatestBeeVersion: boolean
|
||||||
|
latestBeeVersionUrl: string
|
||||||
|
error: Error | null
|
||||||
|
apiHealth: boolean
|
||||||
|
debugApiHealth: Health | null
|
||||||
|
nodeAddresses: NodeAddresses | null
|
||||||
|
topology: Topology | null
|
||||||
|
chequebookAddress: ChequebookAddressResponse | null
|
||||||
|
peers: Peer[] | null
|
||||||
|
chequebookBalance: ChequebookBalance | null
|
||||||
|
peerBalances: Balance[] | null
|
||||||
|
peerCheques: LastChequesResponse | null
|
||||||
|
settlements: Settlements | null
|
||||||
|
latestBeeRelease: LatestBeeRelease | null
|
||||||
|
isLoading: boolean
|
||||||
|
isRefreshing: boolean
|
||||||
|
lastUpdate: number | null
|
||||||
|
start: (frequency?: number) => void
|
||||||
|
stop: () => void
|
||||||
|
refresh: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: ContextInterface = {
|
||||||
|
status: {
|
||||||
|
all: false,
|
||||||
|
version: false,
|
||||||
|
blockchainConnection: false,
|
||||||
|
debugApiConnection: false,
|
||||||
|
apiConnection: false,
|
||||||
|
topology: false,
|
||||||
|
chequebook: false,
|
||||||
|
},
|
||||||
|
latestPublishedVersion: undefined,
|
||||||
|
latestUserVersion: undefined,
|
||||||
|
latestUserVersionExact: undefined,
|
||||||
|
isLatestBeeVersion: false,
|
||||||
|
latestBeeVersionUrl: 'https://github.com/ethersphere/bee/releases/latest',
|
||||||
|
error: null,
|
||||||
|
apiHealth: false,
|
||||||
|
debugApiHealth: null,
|
||||||
|
nodeAddresses: null,
|
||||||
|
topology: null,
|
||||||
|
chequebookAddress: null,
|
||||||
|
peers: null,
|
||||||
|
chequebookBalance: null,
|
||||||
|
peerBalances: null,
|
||||||
|
peerCheques: null,
|
||||||
|
settlements: null,
|
||||||
|
latestBeeRelease: null,
|
||||||
|
isLoading: true,
|
||||||
|
isRefreshing: false,
|
||||||
|
lastUpdate: null,
|
||||||
|
start: () => {}, // eslint-disable-line
|
||||||
|
stop: () => {}, // eslint-disable-line
|
||||||
|
refresh: () => Promise.reject(),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Context = createContext<ContextInterface>(initialValues)
|
||||||
|
export const Consumer = Context.Consumer
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactChild
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatus(
|
||||||
|
debugApiHealth: Health | null,
|
||||||
|
nodeAddresses: NodeAddresses | null,
|
||||||
|
apiHealth: boolean,
|
||||||
|
topology: Topology | null,
|
||||||
|
chequebookAddress: ChequebookAddressResponse | null,
|
||||||
|
chequebookBalance: ChequebookBalance | null,
|
||||||
|
error: Error | null,
|
||||||
|
): Status {
|
||||||
|
const status = {
|
||||||
|
version: Boolean(
|
||||||
|
debugApiHealth &&
|
||||||
|
semver.satisfies(debugApiHealth.version, engines.bee, {
|
||||||
|
includePrerelease: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
blockchainConnection: Boolean(nodeAddresses?.ethereum),
|
||||||
|
debugApiConnection: Boolean(debugApiHealth?.status === 'ok'),
|
||||||
|
apiConnection: apiHealth,
|
||||||
|
topology: Boolean(topology?.connected && topology?.connected > 0),
|
||||||
|
chequebook:
|
||||||
|
Boolean(chequebookAddress?.chequebookAddress) &&
|
||||||
|
chequebookBalance !== null &&
|
||||||
|
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...status, all: !error && Object.values(status).every(v => v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Provider({ children }: Props): ReactElement {
|
||||||
|
const { beeApi, beeDebugApi } = useContext(SettingsContext)
|
||||||
|
const [apiHealth, setApiHealth] = useState<boolean>(false)
|
||||||
|
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
|
||||||
|
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
||||||
|
const [topology, setNodeTopology] = useState<Topology | null>(null)
|
||||||
|
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
|
||||||
|
const [peers, setPeers] = useState<Peer[] | null>(null)
|
||||||
|
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalance | null>(null)
|
||||||
|
const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null)
|
||||||
|
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
||||||
|
const [settlements, setSettlements] = useState<Settlements | null>(null)
|
||||||
|
const { latestBeeRelease } = useLatestBeeRelease()
|
||||||
|
|
||||||
|
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||||
|
const [isRefreshing, setIsRefreshing] = useState<boolean>(initialValues.isRefreshing)
|
||||||
|
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||||
|
const [frequency, setFrequency] = useState<number | null>(30000)
|
||||||
|
|
||||||
|
const latestPublishedVersion = semver.coerce(latestBeeRelease?.name)?.version
|
||||||
|
const latestUserVersion = semver.coerce(debugApiHealth?.version)?.version
|
||||||
|
const latestUserVersionExact = debugApiHealth?.version
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
setApiHealth(false)
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}, [beeApi])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
setDebugApiHealth(null)
|
||||||
|
setNodeAddresses(null)
|
||||||
|
setNodeTopology(null)
|
||||||
|
setPeers(null)
|
||||||
|
setChequebookAddress(null)
|
||||||
|
setChequebookBalance(null)
|
||||||
|
setPeerBalances(null)
|
||||||
|
setPeerCheques(null)
|
||||||
|
setSettlements(null)
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}, [beeDebugApi])
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
// Don't want to refresh when already refreshing
|
||||||
|
if (isRefreshing) return
|
||||||
|
|
||||||
|
// Not a valid bee api
|
||||||
|
if (!beeApi || !beeDebugApi) {
|
||||||
|
setIsLoading(false)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsRefreshing(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
// Wrap the chequebook balance call to return BZZ values as Token object
|
||||||
|
const chequeBalanceWrapper = async () => {
|
||||||
|
const { totalBalance, availableBalance } = await beeDebugApi.getChequebookBalance()
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalBalance: new Token(totalBalance),
|
||||||
|
availableBalance: new Token(availableBalance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the balances call to return BZZ values as Token object
|
||||||
|
const peerBalanceWrapper = async () => {
|
||||||
|
const { balances } = await beeDebugApi.getAllBalances()
|
||||||
|
|
||||||
|
return balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the settlements call to return BZZ values as Token object
|
||||||
|
const settlementsWrapper = async () => {
|
||||||
|
const { totalReceived, settlements, totalSent } = await beeDebugApi.getAllSettlements()
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalReceived: new Token(totalReceived),
|
||||||
|
totalSent: new Token(totalSent),
|
||||||
|
settlements: settlements.map(({ peer, received, sent }) => ({
|
||||||
|
peer,
|
||||||
|
received: new Token(received),
|
||||||
|
sent: new Token(sent),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
// API health
|
||||||
|
beeApi
|
||||||
|
.isConnected()
|
||||||
|
.then(setApiHealth)
|
||||||
|
.catch(() => setApiHealth(false)),
|
||||||
|
|
||||||
|
// Debug API health
|
||||||
|
beeDebugApi
|
||||||
|
.getHealth()
|
||||||
|
.then(setDebugApiHealth)
|
||||||
|
.catch(() => setDebugApiHealth(null)),
|
||||||
|
|
||||||
|
// Node Addresses
|
||||||
|
beeDebugApi
|
||||||
|
.getNodeAddresses()
|
||||||
|
.then(setNodeAddresses)
|
||||||
|
.catch(() => setNodeAddresses(null)),
|
||||||
|
|
||||||
|
// Network Topology
|
||||||
|
beeDebugApi
|
||||||
|
.getTopology()
|
||||||
|
.then(setNodeTopology)
|
||||||
|
.catch(() => setNodeTopology(null)),
|
||||||
|
|
||||||
|
// Peers
|
||||||
|
beeDebugApi
|
||||||
|
.getPeers()
|
||||||
|
.then(setPeers)
|
||||||
|
.catch(() => setPeers(null)),
|
||||||
|
|
||||||
|
// Chequebook address
|
||||||
|
beeDebugApi
|
||||||
|
.getChequebookAddress()
|
||||||
|
.then(setChequebookAddress)
|
||||||
|
.catch(() => setChequebookAddress(null)),
|
||||||
|
|
||||||
|
// Cheques
|
||||||
|
beeDebugApi
|
||||||
|
.getLastCheques()
|
||||||
|
.then(setPeerCheques)
|
||||||
|
.catch(() => setPeerCheques(null)),
|
||||||
|
|
||||||
|
// Chequebook balance
|
||||||
|
chequeBalanceWrapper()
|
||||||
|
.then(setChequebookBalance)
|
||||||
|
.catch(() => setChequebookBalance(null)),
|
||||||
|
|
||||||
|
// Peer balances
|
||||||
|
peerBalanceWrapper()
|
||||||
|
.then(setPeerBalances)
|
||||||
|
.catch(() => setPeerBalances(null)),
|
||||||
|
|
||||||
|
// Settlements
|
||||||
|
settlementsWrapper()
|
||||||
|
.then(setSettlements)
|
||||||
|
.catch(() => setSettlements(null)),
|
||||||
|
]
|
||||||
|
|
||||||
|
await Promise.allSettled(promises)
|
||||||
|
} catch (e) {
|
||||||
|
setError(e)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
setIsRefreshing(false)
|
||||||
|
setLastUpdate(Date.now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = (freq = 30000) => setFrequency(freq)
|
||||||
|
const stop = () => setFrequency(null)
|
||||||
|
|
||||||
|
// Start the update loop
|
||||||
|
useEffect(() => {
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
// Start autorefresh only if the frequency is set
|
||||||
|
if (frequency) {
|
||||||
|
const interval = setInterval(refresh, frequency)
|
||||||
|
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [frequency, beeDebugApi, beeApi])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider
|
||||||
|
value={{
|
||||||
|
status: getStatus(
|
||||||
|
debugApiHealth,
|
||||||
|
nodeAddresses,
|
||||||
|
apiHealth,
|
||||||
|
topology,
|
||||||
|
chequebookAddress,
|
||||||
|
chequebookBalance,
|
||||||
|
error,
|
||||||
|
),
|
||||||
|
latestUserVersion,
|
||||||
|
latestUserVersionExact,
|
||||||
|
latestPublishedVersion,
|
||||||
|
isLatestBeeVersion: Boolean(
|
||||||
|
latestPublishedVersion &&
|
||||||
|
latestUserVersion &&
|
||||||
|
semver.satisfies(latestPublishedVersion, latestUserVersion, {
|
||||||
|
includePrerelease: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
latestBeeVersionUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
|
||||||
|
error,
|
||||||
|
apiHealth,
|
||||||
|
debugApiHealth,
|
||||||
|
nodeAddresses,
|
||||||
|
topology,
|
||||||
|
chequebookAddress,
|
||||||
|
peers,
|
||||||
|
chequebookBalance,
|
||||||
|
peerBalances,
|
||||||
|
peerCheques,
|
||||||
|
settlements,
|
||||||
|
latestBeeRelease,
|
||||||
|
isLoading,
|
||||||
|
isRefreshing,
|
||||||
|
lastUpdate,
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
refresh,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { createContext, ReactChild, ReactElement, useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
// These need to be numeric values as they are used as indexes in the TabsContainer
|
||||||
|
export enum Platforms {
|
||||||
|
macOS = 0,
|
||||||
|
Linux,
|
||||||
|
Windows,
|
||||||
|
iOS,
|
||||||
|
Android,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SupportedPlatforms {
|
||||||
|
macOS = Platforms.macOS,
|
||||||
|
Linux = Platforms.Linux,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextInterface {
|
||||||
|
platform: SupportedPlatforms
|
||||||
|
setPlatform: (platform: SupportedPlatforms) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: ContextInterface = {
|
||||||
|
platform: SupportedPlatforms.macOS,
|
||||||
|
setPlatform: () => {}, // eslint-disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Context = createContext<ContextInterface>(initialValues)
|
||||||
|
export const Consumer = Context.Consumer
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactChild
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSupportedPlatform(platform: unknown): platform is SupportedPlatforms {
|
||||||
|
return Object.keys(SupportedPlatforms).includes(platform as string)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOS(): Platforms | null {
|
||||||
|
const userAgent = window.navigator.userAgent
|
||||||
|
const platform = window.navigator.platform
|
||||||
|
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
|
||||||
|
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']
|
||||||
|
const iosPlatforms = ['iPhone', 'iPad', 'iPod']
|
||||||
|
|
||||||
|
if (macosPlatforms.includes(platform)) return Platforms.macOS
|
||||||
|
|
||||||
|
if (iosPlatforms.includes(platform)) return Platforms.iOS
|
||||||
|
|
||||||
|
if (windowsPlatforms.includes(platform)) return Platforms.Windows
|
||||||
|
|
||||||
|
if (/Android/.test(userAgent)) return Platforms.Android
|
||||||
|
|
||||||
|
if (/Linux/.test(platform)) return Platforms.Linux
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Provider({ children }: Props): ReactElement {
|
||||||
|
const [platform, setPlatform] = useState<SupportedPlatforms>(SupportedPlatforms.Linux)
|
||||||
|
|
||||||
|
// This is in useEffect as it really just needs to run once and not on each re-render
|
||||||
|
useEffect(() => {
|
||||||
|
const os = getOS()
|
||||||
|
|
||||||
|
setPlatform(isSupportedPlatform(os) ? os : SupportedPlatforms.Linux)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <Context.Provider value={{ platform, setPlatform }}>{children}</Context.Provider>
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { createContext, ReactChild, ReactElement, useState, useEffect } from 'react'
|
||||||
|
import { Bee, BeeDebug } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
|
interface ContextInterface {
|
||||||
|
apiUrl: string
|
||||||
|
apiDebugUrl: string
|
||||||
|
beeApi: Bee | null
|
||||||
|
beeDebugApi: BeeDebug | null
|
||||||
|
setApiUrl: (url: string) => void
|
||||||
|
setDebugApiUrl: (url: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: ContextInterface = {
|
||||||
|
apiUrl: sessionStorage.getItem('api_host') || process.env.REACT_APP_BEE_HOST || 'http://localhost:1633',
|
||||||
|
apiDebugUrl:
|
||||||
|
sessionStorage.getItem('debug_api_host') || process.env.REACT_APP_BEE_DEBUG_HOST || 'http://localhost:1635',
|
||||||
|
beeApi: null,
|
||||||
|
beeDebugApi: null,
|
||||||
|
setApiUrl: () => {}, // eslint-disable-line
|
||||||
|
setDebugApiUrl: () => {}, // eslint-disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Context = createContext<ContextInterface>(initialValues)
|
||||||
|
export const Consumer = Context.Consumer
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactChild
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Provider({ children }: Props): ReactElement {
|
||||||
|
const [apiUrl, setApiUrl] = useState<string>(initialValues.apiUrl)
|
||||||
|
const [apiDebugUrl, setDebugApiUrl] = useState<string>(initialValues.apiDebugUrl)
|
||||||
|
const [beeApi, setBeeApi] = useState<Bee | null>(null)
|
||||||
|
const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
setBeeApi(new Bee(apiUrl))
|
||||||
|
sessionStorage.setItem('api_host', apiUrl)
|
||||||
|
} catch (e) {
|
||||||
|
setBeeApi(null)
|
||||||
|
}
|
||||||
|
}, [apiUrl])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
setBeeDebugApi(new BeeDebug(apiDebugUrl))
|
||||||
|
sessionStorage.setItem('debug_api_host', apiDebugUrl)
|
||||||
|
} catch (e) {
|
||||||
|
setBeeDebugApi(null)
|
||||||
|
}
|
||||||
|
}, [apiDebugUrl])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={{ apiUrl, apiDebugUrl, beeApi, beeDebugApi, setApiUrl, setDebugApiUrl }}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PostageBatch } from '@ethersphere/bee-js'
|
import { PostageBatch } from '@ethersphere/bee-js'
|
||||||
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
|
import { createContext, ReactChild, ReactElement, useEffect, useState, useContext } from 'react'
|
||||||
import { beeApi } from '../services/bee'
|
import { Context as SettingsContext } from './Settings'
|
||||||
|
|
||||||
export interface EnrichedPostageBatch extends PostageBatch {
|
export interface EnrichedPostageBatch extends PostageBatch {
|
||||||
usage: number
|
usage: number
|
||||||
@@ -48,6 +48,7 @@ function enrichStamp(postageBatch: PostageBatch): EnrichedPostageBatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Provider({ children }: Props): ReactElement {
|
export function Provider({ children }: Props): ReactElement {
|
||||||
|
const { beeApi } = useContext(SettingsContext)
|
||||||
const [stamps, setStamps] = useState<EnrichedPostageBatch[] | null>(initialValues.stamps)
|
const [stamps, setStamps] = useState<EnrichedPostageBatch[] | null>(initialValues.stamps)
|
||||||
const [error, setError] = useState<Error | null>(initialValues.error)
|
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||||
@@ -58,9 +59,11 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
// Don't want to refresh when already refreshing
|
// Don't want to refresh when already refreshing
|
||||||
if (isLoading) return
|
if (isLoading) return
|
||||||
|
|
||||||
|
if (!beeApi) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const stamps = await beeApi.stamps.getPostageStamps()
|
const stamps = await beeApi.getAllPostageBatch()
|
||||||
|
|
||||||
setStamps(stamps.map(enrichStamp))
|
setStamps(stamps.map(enrichStamp))
|
||||||
setLastUpdate(Date.now())
|
setLastUpdate(Date.now())
|
||||||
|
|||||||
Vendored
-1
@@ -6,7 +6,6 @@ interface LatestBeeRelease {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StatusHookCommon {
|
interface StatusHookCommon {
|
||||||
isLoading: boolean
|
|
||||||
isOk: boolean
|
isOk: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import {
|
|
||||||
Address,
|
|
||||||
AllSettlements,
|
|
||||||
BalanceResponse,
|
|
||||||
Bee,
|
|
||||||
BeeDebug,
|
|
||||||
ChequebookAddressResponse,
|
|
||||||
ChequebookBalanceResponse,
|
|
||||||
Data,
|
|
||||||
FileData,
|
|
||||||
Health,
|
|
||||||
LastCashoutActionResponse,
|
|
||||||
LastChequesForPeerResponse,
|
|
||||||
LastChequesResponse,
|
|
||||||
NodeAddresses,
|
|
||||||
Peer,
|
|
||||||
PingResponse,
|
|
||||||
PostageBatch,
|
|
||||||
PostageBatchOptions,
|
|
||||||
Reference,
|
|
||||||
Topology,
|
|
||||||
} from '@ethersphere/bee-js'
|
|
||||||
import { apiHost, debugApiHost } from '../constants'
|
|
||||||
|
|
||||||
const beeJSClient = () => new Bee(apiHost)
|
|
||||||
|
|
||||||
const beeJSDebugClient = () => new BeeDebug(debugApiHost)
|
|
||||||
|
|
||||||
export const beeApi = {
|
|
||||||
status: {
|
|
||||||
health(): Promise<boolean> {
|
|
||||||
return beeJSClient().isConnected()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
uploadFile(postageBatchId: Address, file: File): Promise<Reference> {
|
|
||||||
return beeJSClient().uploadFile(postageBatchId, file)
|
|
||||||
},
|
|
||||||
downloadFile(hash: string | Reference): Promise<FileData<Data>> {
|
|
||||||
return beeJSClient().downloadFile(hash)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
stamps: {
|
|
||||||
getPostageStamps(): Promise<PostageBatch[]> {
|
|
||||||
return beeJSClient().getAllPostageBatch()
|
|
||||||
},
|
|
||||||
buyPostageStamp(amount: bigint, depth: number, options: PostageBatchOptions = {}): Promise<Address> {
|
|
||||||
return beeJSClient().createPostageBatch(amount.toString(), depth, options)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const beeDebugApi = {
|
|
||||||
status: {
|
|
||||||
nodeHealth(): Promise<Health> {
|
|
||||||
return beeJSDebugClient().getHealth()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
connectivity: {
|
|
||||||
addresses(): Promise<NodeAddresses> {
|
|
||||||
return beeJSDebugClient().getNodeAddresses()
|
|
||||||
},
|
|
||||||
listPeers(): Promise<Peer[]> {
|
|
||||||
return beeJSDebugClient().getPeers()
|
|
||||||
},
|
|
||||||
topology(): Promise<Topology> {
|
|
||||||
return beeJSDebugClient().getTopology()
|
|
||||||
},
|
|
||||||
ping(peerId: string): Promise<PingResponse> {
|
|
||||||
return beeJSDebugClient().pingPeer(peerId)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
balances(): Promise<BalanceResponse> {
|
|
||||||
return beeJSDebugClient().getAllBalances()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
chequebook: {
|
|
||||||
address(): Promise<ChequebookAddressResponse> {
|
|
||||||
return beeJSDebugClient().getChequebookAddress()
|
|
||||||
},
|
|
||||||
balance(): Promise<ChequebookBalanceResponse> {
|
|
||||||
return beeJSDebugClient().getChequebookBalance()
|
|
||||||
},
|
|
||||||
getLastCheques(): Promise<LastChequesResponse> {
|
|
||||||
return beeJSDebugClient().getLastCheques()
|
|
||||||
},
|
|
||||||
peerCashout(peerId: string): Promise<string> {
|
|
||||||
return beeJSDebugClient().cashoutLastCheque(peerId)
|
|
||||||
},
|
|
||||||
getPeerLastCashout(peerId: string): Promise<LastCashoutActionResponse> {
|
|
||||||
return beeJSDebugClient().getLastCashoutAction(peerId)
|
|
||||||
},
|
|
||||||
getPeerLastCheques(peerId: string): Promise<LastChequesForPeerResponse> {
|
|
||||||
return beeJSDebugClient().getLastChequesForPeer(peerId)
|
|
||||||
},
|
|
||||||
withdraw(amount: bigint): Promise<string> {
|
|
||||||
return beeJSDebugClient().withdrawTokens(amount.toString())
|
|
||||||
},
|
|
||||||
deposit(amount: bigint): Promise<string> {
|
|
||||||
return beeJSDebugClient().depositTokens(amount.toString())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settlements: {
|
|
||||||
getSettlements(): Promise<AllSettlements> {
|
|
||||||
return beeJSDebugClient().getAllSettlements()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Token } from './models/Token'
|
||||||
|
|
||||||
|
export interface ChequebookBalance {
|
||||||
|
totalBalance: Token
|
||||||
|
availableBalance: Token
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Balance {
|
||||||
|
peer: string
|
||||||
|
balance: Token
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Settlement {
|
||||||
|
peer: string
|
||||||
|
received: Token
|
||||||
|
sent: Token
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Settlements {
|
||||||
|
totalReceived: Token
|
||||||
|
totalSent: Token
|
||||||
|
settlements: Settlement[]
|
||||||
|
}
|
||||||
@@ -32,3 +32,77 @@ export function makeBigNumber(value: BigNumber | BigInt | number | string): BigN
|
|||||||
|
|
||||||
throw new TypeError(`Not a BigNumber or BigNumber convertible value. Type: ${typeof value} value: ${value}`)
|
throw new TypeError(`Not a BigNumber or BigNumber convertible value. Type: ${typeof value} value: ${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PromiseSettlements<T> = {
|
||||||
|
fulfilled: PromiseFulfilledResult<T>[]
|
||||||
|
rejected: PromiseRejectedResult[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UnwrappedPromiseSettlements<T> = {
|
||||||
|
fulfilled: T[]
|
||||||
|
rejected: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sleepMs(ms: number): Promise<void> {
|
||||||
|
await new Promise<void>(resolve =>
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve()
|
||||||
|
}, ms),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the returned results of `Promise.allSettled` to an object
|
||||||
|
* with `fulfilled` and `rejected` arrays for easy access.
|
||||||
|
*
|
||||||
|
* The results still need to be unwrapped to get the fulfilled values or rejection reasons.
|
||||||
|
*/
|
||||||
|
export function mapPromiseSettlements<T>(promises: PromiseSettledResult<T>[]): PromiseSettlements<T> {
|
||||||
|
const fulfilled = promises.filter(promise => promise.status === 'fulfilled') as PromiseFulfilledResult<T>[]
|
||||||
|
const rejected = promises.filter(promise => promise.status === 'rejected') as PromiseRejectedResult[]
|
||||||
|
|
||||||
|
return { fulfilled, rejected }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the returned values of `Promise.allSettled` to an object
|
||||||
|
* with `fulfilled` and `rejected` arrays for easy access.
|
||||||
|
*
|
||||||
|
* For rejected promises, the value is the stringified `reason`,
|
||||||
|
* or `'Unknown error'` string when it is unavailable.
|
||||||
|
*/
|
||||||
|
export function unwrapPromiseSettlements<T>(
|
||||||
|
promiseSettledResults: PromiseSettledResult<T>[],
|
||||||
|
): UnwrappedPromiseSettlements<T> {
|
||||||
|
const values = mapPromiseSettlements(promiseSettledResults)
|
||||||
|
const fulfilled = values.fulfilled.map(x => x.value)
|
||||||
|
const rejected = values.rejected.map(x => (x.reason ? String(x.reason) : 'Unknown error'))
|
||||||
|
|
||||||
|
return { fulfilled, rejected }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a `Promise<T>` or async function inside a new `Promise<T>`,
|
||||||
|
* which retries the original function up to `maxRetries` times,
|
||||||
|
* waiting `delayMs` milliseconds between failed attempts.
|
||||||
|
*
|
||||||
|
* If all attempts fail, then this `Promise<T>` also rejects.
|
||||||
|
*/
|
||||||
|
export function makeRetriablePromise<T>(fn: () => Promise<T>, maxRetries = 3, delayMs = 1000): Promise<T> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
for (let tries = 0; tries < maxRetries; tries++) {
|
||||||
|
try {
|
||||||
|
const results = await fn()
|
||||||
|
resolve(results)
|
||||||
|
|
||||||
|
return
|
||||||
|
} catch (error) {
|
||||||
|
if (tries < maxRetries - 1) {
|
||||||
|
await sleepMs(delayMs)
|
||||||
|
} else {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
const OPTIMAL_CONNECTED_PEERS = 200
|
||||||
|
const OPTIMAL_POPULATION = 100_000
|
||||||
|
const OPTIMAL_DEPTH = 12
|
||||||
|
|
||||||
|
interface Threshold {
|
||||||
|
minimumValue: number
|
||||||
|
explanation: string
|
||||||
|
score: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type Thresholds = {
|
||||||
|
connectedPeers: Threshold[]
|
||||||
|
population: Threshold[]
|
||||||
|
depth: Threshold[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThresholdValue = {
|
||||||
|
score: number
|
||||||
|
maximumScore: number
|
||||||
|
explanation: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ThresholdValues = {
|
||||||
|
connectedPeers: ThresholdValue
|
||||||
|
population: ThresholdValue
|
||||||
|
depth: ThresholdValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const GENERIC_ERROR = 'There may be issues with your Bee node or connection.'
|
||||||
|
|
||||||
|
const THRESHOLDS: Thresholds = {
|
||||||
|
connectedPeers: [
|
||||||
|
{
|
||||||
|
minimumValue: OPTIMAL_CONNECTED_PEERS,
|
||||||
|
explanation: `Perfect! ${OPTIMAL_CONNECTED_PEERS} or more connected peers indicate a healthy topology.`,
|
||||||
|
score: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumValue: 1,
|
||||||
|
explanation: `Your Bee node is connected to peers, but this number should ideally be above ${OPTIMAL_CONNECTED_PEERS}. If you have only started your Bee node, this number may increase quickly.`,
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumValue: 0,
|
||||||
|
explanation: 'Your Bee node has not connected to any peers. ' + GENERIC_ERROR,
|
||||||
|
score: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
population: [
|
||||||
|
{
|
||||||
|
minimumValue: OPTIMAL_POPULATION,
|
||||||
|
explanation:
|
||||||
|
'Perfect! Your Bee node seems to have a realistic value for the network size, which means everything is working well on your end.',
|
||||||
|
score: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumValue: 1,
|
||||||
|
explanation: `Population is usually above ${OPTIMAL_POPULATION.toLocaleString()}. If the number does not increase within a few hours, there may be issues with your Bee node.`,
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumValue: 0,
|
||||||
|
explanation: 'Your Bee node has no information on the network population. ' + GENERIC_ERROR,
|
||||||
|
score: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
depth: [
|
||||||
|
{
|
||||||
|
minimumValue: OPTIMAL_DEPTH,
|
||||||
|
explanation: 'Perfect! Your Bee node has the highest available depth.',
|
||||||
|
score: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumValue: 1,
|
||||||
|
explanation: `Your Bee node is supposed to reach a depth of ${OPTIMAL_DEPTH} eventually. Stagnation or decrease in this number may indicate problems with your Bee node.`,
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumValue: 0,
|
||||||
|
explanation: 'Your Bee node has not started building its topology yet. ' + GENERIC_ERROR,
|
||||||
|
score: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pickThreshold(key: keyof Thresholds, value: number): ThresholdValue {
|
||||||
|
const thresholds = THRESHOLDS[key]
|
||||||
|
const maximumScore = thresholds[0].score
|
||||||
|
for (const item of thresholds) {
|
||||||
|
if (value >= item.minimumValue) {
|
||||||
|
return {
|
||||||
|
score: item.score,
|
||||||
|
maximumScore,
|
||||||
|
explanation: item.explanation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const last = thresholds[thresholds.length - 1]
|
||||||
|
|
||||||
|
return {
|
||||||
|
score: last.score,
|
||||||
|
maximumScore,
|
||||||
|
explanation: last.explanation,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user