Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cda1d4bbb1 | |||
| e8e707a9c4 | |||
| 28bbdfb2f6 | |||
| 630791cd75 | |||
| f316a5caf4 | |||
| 929f44f206 | |||
| d1720e243c | |||
| 3ce83d987d | |||
| 02a7bff733 | |||
| 766fe96d1c | |||
| 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,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"prettier",
|
"prettier",
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ jobs:
|
|||||||
- name: Commit linting
|
- name: Commit linting
|
||||||
uses: wagoid/commitlint-github-action@v2
|
uses: wagoid/commitlint-github-action@v2
|
||||||
|
|
||||||
# - name: Code linting
|
- name: Code linting
|
||||||
# run: npm run lint:check
|
run: npm run lint:check
|
||||||
# env:
|
env:
|
||||||
# CI: true
|
CI: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|||||||
@@ -1,5 +1,48 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.7.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.6.0...v0.7.0) (2021-08-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* removed dark theme and theme switching ([#190](https://www.github.com/ethersphere/bee-dashboard/issues/190)) ([d1720e2](https://www.github.com/ethersphere/bee-dashboard/commit/d1720e243c4415d75763a229250fa20e3664290e))
|
||||||
|
* separate info and status page ([#183](https://www.github.com/ethersphere/bee-dashboard/issues/183)) ([02a7bff](https://www.github.com/ethersphere/bee-dashboard/commit/02a7bff733b7fac70c6a36f94e6ba1425854a0af))
|
||||||
|
* styling of the sidebar ([#194](https://github.com/ethersphere/bee-dashboard/pull/194))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* bee 1.1.0 version reporting workaround ([#197](https://github.com/ethersphere/bee-dashboard/issues/197))
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
|
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
|
||||||
|
|
||||||
|
This project is intended to be used with **Bee version 1.1.0**. Using it with older or newer Bee versions is not recommended and may not work. Stay up to date by joining the [official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the [releases tab](https://github.com/ethersphere/bee-dashboard/releases).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
| Node Setup | Browse & Upload Files | Accounting | Peers | Settings |
|
| Node Setup | Browse & Upload Files | Accounting | Peers | Settings |
|
||||||
@@ -67,6 +69,8 @@ git clone git@github.com:ethersphere/bee-dashboard.git
|
|||||||
|
|
||||||
cd bee-dashboard
|
cd bee-dashboard
|
||||||
|
|
||||||
|
npm i
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -82,8 +86,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
+11017
-6873
File diff suppressed because it is too large
Load Diff
+44
-44
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.4.0",
|
"version": "0.7.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,52 +24,52 @@
|
|||||||
"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.1",
|
||||||
"@material-ui/core": "^4.11.4",
|
"@material-ui/core": "4.12.3",
|
||||||
"@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",
|
||||||
"@types/react-router": "^5.1.13",
|
"@types/react-router": "5.1.13",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "5.1.7",
|
||||||
"axios": "^0.21.1",
|
"axios": "0.21.1",
|
||||||
"bignumber.js": "^9.0.1",
|
"bignumber.js": "9.0.1",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "4.28.0",
|
||||||
"formik": "^2.2.8",
|
"formik": "2.2.8",
|
||||||
"formik-material-ui": "^3.0.1",
|
"formik-material-ui": "3.0.1",
|
||||||
"material-ui-dropzone": "^3.5.0",
|
"material-ui-dropzone": "3.5.0",
|
||||||
"notistack": "^1.0.9",
|
"notistack": "1.0.9",
|
||||||
"opener": "^1.5.2",
|
"opener": "1.5.2",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "1.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "17.0.2",
|
||||||
"react-copy-to-clipboard": "^5.0.3",
|
"react-copy-to-clipboard": "5.0.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-feather": "^2.0.9",
|
"react-feather": "2.0.9",
|
||||||
"react-identicons": "^1.2.5",
|
"react-identicons": "1.2.5",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"react-syntax-highlighter": "^15.4.3",
|
"react-syntax-highlighter": "15.4.3",
|
||||||
"semver": "^7.3.2",
|
"semver": "7.3.2",
|
||||||
"serve-handler": "^6.1.3"
|
"serve-handler": "6.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.12.0",
|
"@testing-library/jest-dom": "5.12.0",
|
||||||
"@testing-library/react": "^11.2.6",
|
"@testing-library/react": "11.2.6",
|
||||||
"@testing-library/user-event": "^13.1.5",
|
"@testing-library/user-event": "13.1.5",
|
||||||
"@types/jest": "^26.0.22",
|
"@types/jest": "26.0.22",
|
||||||
"@types/node": "^14.14.41",
|
"@types/node": "14.14.41",
|
||||||
"@types/qrcode.react": "^1.0.1",
|
"@types/qrcode.react": "1.0.1",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "17.0.3",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
"@types/react-copy-to-clipboard": "5.0.0",
|
||||||
"@types/react-dom": "^17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
"@types/react-syntax-highlighter": "^13.5.0",
|
"@types/react-syntax-highlighter": "13.5.0",
|
||||||
"@types/semver": "^7.3.6",
|
"@types/semver": "7.3.6",
|
||||||
"eslint": "^7.24.0",
|
"eslint": "7.32.0",
|
||||||
"eslint-config-prettier": "^8.2.0",
|
"eslint-config-prettier": "8.3.0",
|
||||||
"eslint-plugin-jest": "^24.3.5",
|
"eslint-plugin-jest": "24.4.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "3.4.1",
|
||||||
"eslint-plugin-react": "^7.23.2",
|
"eslint-plugin-react": "7.24.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "2.3.2",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "4.2.4",
|
||||||
"web-vitals": "^1.1.1"
|
"web-vitals": "1.1.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
|
|||||||
+1
-1
@@ -10,6 +10,6 @@ declare module 'react-identicons' {
|
|||||||
getColor?: () => string
|
getColor?: () => string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Identicon = (props: Props): JSXElementConstructor => ReactNode
|
const Identicon = (props: Props): JSXElementConstructor => ReactNode //eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
export default Identicon
|
export default Identicon
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-31
@@ -1,53 +1,42 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react'
|
import { ReactElement } from 'react'
|
||||||
import { BrowserRouter as Router } from 'react-router-dom'
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
|
||||||
import { ThemeProvider } from '@material-ui/styles'
|
import { ThemeProvider } from '@material-ui/core/styles'
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
import { SnackbarProvider } from 'notistack'
|
import { SnackbarProvider } from 'notistack'
|
||||||
|
|
||||||
import BaseRouter from './routes/routes'
|
import BaseRouter from './routes'
|
||||||
import { lightTheme, darkTheme } from './theme'
|
import Dashboard from './layout/Dashboard'
|
||||||
|
import { theme } 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')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const theme = localStorage.getItem('theme')
|
|
||||||
|
|
||||||
if (theme) {
|
|
||||||
toggleThemeMode(String(localStorage.getItem('theme')))
|
|
||||||
} else if (window?.matchMedia('(prefers-color-scheme: dark)')?.matches) {
|
|
||||||
toggleThemeMode('dark')
|
|
||||||
}
|
|
||||||
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.addEventListener('change', e => {
|
|
||||||
toggleThemeMode(e?.matches ? 'dark' : 'light')
|
|
||||||
})
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.removeEventListener('change', e => {
|
|
||||||
toggleThemeMode(e?.matches ? 'dark' : 'light')
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
<SettingsProvider>
|
||||||
|
<BeeProvider>
|
||||||
<StampsProvider>
|
<StampsProvider>
|
||||||
|
<PlatformProvider>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
|
<Router>
|
||||||
<>
|
<>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Router>
|
<Dashboard>
|
||||||
<BaseRouter />
|
<BaseRouter />
|
||||||
</Router>
|
</Dashboard>
|
||||||
</>
|
</>
|
||||||
|
</Router>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
|
</PlatformProvider>
|
||||||
</StampsProvider>
|
</StampsProvider>
|
||||||
|
</BeeProvider>
|
||||||
|
</SettingsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="172" height="30" viewBox="0 0 172 30">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M0 0H172V30H0z" transform="translate(-64.000000, -64.000000) translate(64.000000, 64.000000)"/>
|
||||||
|
<path fill="#F9F9F9" fill-rule="nonzero" d="M94.26 21.338c1.242 0 2.168-.427 2.779-1.28.61-.853.916-2.118.916-3.796 0-1.677-.306-2.942-.916-3.795-.611-.854-1.537-1.28-2.778-1.28h-2.836v10.15h2.836zm-.057-1.062h-1.556V12.25h1.556c.814 0 1.427.28 1.84.843.411.562.617 1.357.617 2.385v1.57c0 1.028-.206 1.823-.618 2.386-.412.562-1.025.843-1.84.843zm6.767 1.062l.858-2.894h3.563l.858 2.894h1.294l-3.112-10.151h-1.614l-3.112 10.15h1.265zm4.13-3.956h-2.981l1.425-4.9h.131l1.425 4.9zm7.698 4.13c1.135 0 2.01-.266 2.625-.8.616-.533.924-1.27.924-2.21 0-.33-.044-.645-.131-.945-.087-.3-.235-.572-.444-.815-.208-.242-.482-.45-.821-.625-.34-.175-.757-.305-1.251-.393l-1.09-.189c-.718-.126-1.215-.332-1.491-.618-.277-.286-.415-.657-.415-1.112 0-.582.184-1.018.553-1.31.368-.29.897-.436 1.585-.436.63 0 1.144.117 1.541.35.398.232.737.533 1.018.901l.858-.742c-.339-.494-.787-.877-1.345-1.148-.557-.272-1.243-.408-2.058-.408-1.037 0-1.861.238-2.472.713-.61.475-.916 1.178-.916 2.109 0 .32.044.627.13.923.088.296.234.567.437.815.204.247.473.458.807.632.335.175.75.306 1.244.393l1.134.189c.698.116 1.188.313 1.469.589.281.276.422.662.422 1.156 0 .61-.194 1.086-.582 1.425-.388.34-.95.51-1.687.51-.61 0-1.137-.122-1.578-.364-.441-.243-.86-.611-1.258-1.106l-.887.728c.378.523.858.952 1.44 1.287.581.334 1.328.501 2.24.501zm7.262-.174v-4.61h3.956v4.61h1.221V11.187h-1.221v4.48h-3.956v-4.48h-1.222v10.15h1.222zm11.988 0c.388 0 .75-.075 1.084-.226.334-.15.625-.356.872-.618.248-.261.439-.572.575-.93.135-.36.203-.742.203-1.15 0-.707-.184-1.248-.552-1.62-.369-.374-.839-.629-1.411-.764v-.044c.465-.145.846-.393 1.142-.742.295-.349.443-.829.443-1.44 0-.794-.247-1.43-.741-1.904-.495-.476-1.178-.713-2.051-.713h-3.447v10.15h3.883zm-.567-5.745h-2.094V12.25h2.094c.514 0 .916.116 1.207.349.291.232.436.581.436 1.047v.567c0 .465-.145.812-.436 1.04-.29.227-.693.341-1.207.341zm.204 4.683h-2.298v-3.68h2.298c.562 0 1.003.13 1.323.386.32.257.48.643.48 1.156v.597c0 .523-.16.911-.48 1.163-.32.252-.761.378-1.323.378zm8.774 1.236c.62 0 1.154-.118 1.6-.356.446-.237.814-.58 1.105-1.025.29-.446.504-.994.64-1.644.136-.65.203-1.39.203-2.225 0-.824-.067-1.563-.203-2.217-.136-.655-.35-1.205-.64-1.651-.29-.446-.66-.788-1.105-1.025-.446-.238-.98-.357-1.6-.357-.62 0-1.154.12-1.6.357-.446.237-.814.579-1.105 1.025-.29.446-.504.996-.64 1.65-.136.655-.204 1.394-.204 2.218 0 .834.068 1.576.204 2.225.136.65.35 1.198.64 1.644.29.446.66.788 1.105 1.025.446.238.98.356 1.6.356zm0-1.061c-.407 0-.751-.08-1.033-.24-.28-.16-.513-.386-.698-.676-.184-.291-.317-.643-.4-1.055-.082-.412-.123-.875-.123-1.389v-1.658c0-.504.041-.964.123-1.381.083-.417.216-.77.4-1.062.185-.29.417-.516.698-.676.282-.16.626-.24 1.033-.24.407 0 .751.08 1.032.24.282.16.514.385.699.676.184.291.317.645.4 1.062.082.417.123.877.123 1.381v1.658c0 .514-.041.977-.124 1.39-.082.411-.215.763-.4 1.054-.184.29-.416.516-.698.676-.28.16-.625.24-1.032.24zm6.564.887l.858-2.894h3.563l.858 2.894h1.294l-3.112-10.151h-1.614l-3.113 10.15h1.266zm4.13-3.956h-2.981l1.425-4.9h.13l1.426 4.9zm5.895 3.956v-4.334h1.614l2.472 4.334h1.367l-2.588-4.392c.814-.078 1.434-.364 1.861-.858.427-.495.64-1.154.64-1.978 0-.921-.247-1.638-.742-2.152-.494-.514-1.212-.771-2.152-.771h-3.694v10.15h1.222zm2.443-5.366h-2.443v-3.723h2.443c.514 0 .914.128 1.2.385.286.257.429.623.429 1.098v.756c0 .475-.143.841-.43 1.098-.285.257-.685.386-1.2.386zm8.454 5.366c1.241 0 2.167-.427 2.778-1.28.61-.853.916-2.118.916-3.796 0-1.677-.305-2.942-.916-3.795-.611-.854-1.537-1.28-2.778-1.28h-2.836v10.15h2.836zm-.058-1.062h-1.556V12.25h1.556c.814 0 1.428.28 1.84.843.412.562.618 1.357.618 2.385v1.57c0 1.028-.206 1.823-.618 2.386-.412.562-1.026.843-1.84.843z" transform="translate(-64.000000, -64.000000) translate(64.000000, 64.000000)"/>
|
||||||
|
<path fill="#F9F9F9" d="M5.064 14.685l4.822 2.709v5.4L5.064 25.5l-4.82-2.706v-5.4l4.82-2.71zm11.034 0l4.822 2.709v5.4L16.098 25.5l-4.82-2.706v-5.4l4.82-2.71zm42.17-3.332v7.644l1.655 1.432.017-.014-1.187 1.327h-.019l-2.097-1.815-2.505 1.465c-.41.237-.876.362-1.35.357-.709-.005-1.392-.277-1.91-.762-.539-.503-.838-1.213-.824-1.95-.002-.974.52-1.874 1.367-2.356l5.05-2.895v-.644H50.55v-1.79h7.719zm-29.357.085c.802-.392 1.705-.529 2.588-.392.845.17 1.627.57 2.258 1.159.166.145.443.417.632.606l.122.121.08.08-1.178 1.336c.014-.017-.801-.773-.883-.841-.389-.367-.863-.627-1.383-.754-.539-.12-1.104-.022-1.573.27-.323.195-.508.552-.484.928.006.372.213.71.54.885.547.291 1.136.49 1.747.586.387.075.766.172 1.102.261.335.088.662.21.974.359.314.11.602.285.844.515.233.194.416.438.537.715.152.298.232.626.236.96.034.957-.403 1.87-1.17 2.444-.81.602-1.8.911-2.809.876-.379.002-.76-.036-1.131-.114-1.007-.247-1.925-.768-2.652-1.506-.05-.043-.608-.566-.608-.566l1.145-1.378c.246.23.486.462.741.677.313.303.655.574 1.02.813.913.518 2.035.518 2.948 0 .375-.227.596-.638.58-1.074.008-.364-.223-.687-.569-.799-.634-.27-1.298-.47-1.979-.59-.864-.152-1.684-.485-2.408-.978-.602-.433-.895-1.095-.895-2.025-.018-.85.365-1.657 1.03-2.183.186-.152.386-.282.598-.39zm17.705-.135c0-.022.006.006.006.006h1.922l-1.606 10.212h-3.152l-1.15-8.808h-.024l-1.164 8.804H38.3l-1.573-10.214h1.926l1.267 8.822h.024l1.114-8.822h3.157l1.109 8.822h.025zm33.488-.29c.703-.039 1.386.244 1.858.765.435.557.655 1.252.618 1.958v7.76h-1.89V14.08c-.014-.314-.113-.621-.288-.886-.212-.26-.54-.396-.873-.359-.362-.011-.708.145-.936.427-.272.41-.4.9-.357 1.391v6.854h-1.88v-7.337c.018-.353-.097-.7-.317-.976-.206-.241-.508-.373-.824-.359-.376-.022-.74.145-.966.448-.253.366-.379.808-.358 1.252v6.978h-1.876v-10.21h1.876v.946c.153-.354.401-.657.715-.877.38-.245.821-.37 1.272-.358.465-.02.923.124 1.296.404.304.244.535.57.664.937.424-.855 1.312-1.381 2.266-1.341zm-13.503-.011c.899-.011 1.763.349 2.387.996.66.62 1.031 1.488 1.023 2.393v.588h-1.764v-.588c0-.05-.003-.099-.008-.146-.08-.877-.859-1.519-1.735-1.436-.832.111-1.449.829-1.431 1.668v5.205h2.574v1.764H61.56v-1.764h1.687v-5.62l-1.697-1.538 1.178-1.315 1.195 1.096c.626-.846 1.625-1.332 2.677-1.303zm-10.136 4.89L52.32 18.27c-.288.158-.465.46-.46.789.01.496.41.894.905.905.166-.003.328-.052.468-.143h-.022l3.254-1.89v-2.04zM10.544 5.254l1.847 1.102v3.181l.634.354 2.347 1.318v2.15l-4.82 2.707-4.82-2.706v-5.4l4.812-2.706zm5.638-.755l2.568 1.44v2.886l-2.548 1.428-.02-.011-2.553-1.43V5.951l.005-.021L16.182 4.5z" transform="translate(-64.000000, -64.000000) translate(64.000000, 64.000000)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
@@ -1,101 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
viewBox="0 0 230.07104 58.680001"
|
|
||||||
version="1.1"
|
|
||||||
id="svg28566"
|
|
||||||
sodipodi:docname="swarm-logo.svg"
|
|
||||||
inkscape:version="1.0.1 (3bc2e81, 2020-09-07)"
|
|
||||||
width="230.07104"
|
|
||||||
height="58.68">
|
|
||||||
<metadata
|
|
||||||
id="metadata28570">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title>Swarm Logo &amp; Lettering 4</dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1016"
|
|
||||||
id="namedview28568"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="3.1325"
|
|
||||||
inkscape:cx="123.33"
|
|
||||||
inkscape:cy="35.939998"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="27"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="Layer_2"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0" />
|
|
||||||
<defs
|
|
||||||
id="defs28540">
|
|
||||||
<style
|
|
||||||
id="style28538">.cls-1{fill:#fafafa;}.cls-2{fill:#242424;}</style>
|
|
||||||
</defs>
|
|
||||||
<title
|
|
||||||
id="title28542">Swarm Logo &amp; Lettering 4</title>
|
|
||||||
<g
|
|
||||||
id="Layer_2"
|
|
||||||
data-name="Layer 2"
|
|
||||||
transform="translate(-76.67,-71.05)">
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="m 206.24,90.06 -3.54,24.65 c 0,0 -0.06,0 -0.07,0 l -3.1,-24.65 c 0,0 0,0 0,0 h -8.82 l -3.11,24.65 c 0,0 -0.06,0 -0.07,0 l -3.54,-24.65 h -5.38 c 0,0 0,0 0,0 L 183,118.6 h 8.8 L 195.05,94 c 0,0 0.06,0 0.07,0 l 3.21,24.61 h 8.81 l 4.49,-28.53 h -5.37 c 0,0 -0.02,-0.08 -0.02,-0.02 z"
|
|
||||||
id="path28546" />
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="m 305,91.39 a 6.52,6.52 0 0 0 -5.19,-2.14 6.74,6.74 0 0 0 -6.33,3.75 v 0 a 5.85,5.85 0 0 0 -1.86,-2.62 5.61,5.61 0 0 0 -3.62,-1.13 6.26,6.26 0 0 0 -3.55,1 5.78,5.78 0 0 0 -2,2.45 v -2.64 c 0,0 0,0 0,0 h -5.24 c 0,0 0,0 0,0 v 28.53 h 5.24 v -19.5 a 5.72,5.72 0 0 1 1,-3.5 3.14,3.14 0 0 1 2.7,-1.25 2.85,2.85 0 0 1 2.3,1 4.08,4.08 0 0 1 0.89,2.73 v 20.5 c 0,0 0,0 0,0 h 5.25 V 99.42 a 6.08,6.08 0 0 1 1,-3.89 3.22,3.22 0 0 1 2.61,-1.19 2.75,2.75 0 0 1 2.44,1 4.9,4.9 0 0 1 0.81,2.92 v 20.28 c 0,0 0,0 0,0 h 5.28 V 96.86 A 8.18,8.18 0 0 0 305,91.39 Z"
|
|
||||||
id="path28548" />
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="m 243.47,115.52 -3.32,3.71 h -0.05 l -5.86,-5.07 c 0,0 0,0 0,0 l -7,4.09 a 7.38,7.38 0 0 1 -3.77,1 7.91,7.91 0 0 1 -5.34,-2.13 7.28,7.28 0 0 1 -2.3,-5.45 7.54,7.54 0 0 1 3.82,-6.58 L 233.76,97 c 0,0 0,0 0,0 v -1.8 h -16.53 c 0,0 0,0 0,0 v -5 c 0,0 0,0 0,0 h 21.57 v 21.36 c 0,0 0,0 0,0 l 4.62,4 z m -18.8,-1.66 9.09,-5.28 c 0,0 0,0 0,0 v -5.7 a 0.03,0 0 0 0 -0.06,0 l -11.58,6.65 a 2.46,2.46 0 0 0 -1.29,2.2 2.59,2.59 0 0 0 2.53,2.53 2.51,2.51 0 0 0 1.31,-0.4 z"
|
|
||||||
id="path28550" />
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="m 268.75,92 a 9.1,9.1 0 0 0 -6.67,-2.78 9,9 0 0 0 -7.48,3.64 l -3.34,-3.06 a 0.025,0 0 0 0 -0.05,0 l -3.29,3.67 4.74,4.3 v 15.7 h -4.71 c 0,0 0,0 0,0 v 4.93 h 17 c 0,0 0,0 0,0 v -4.93 h -7.19 V 98.93 a 4.61,4.61 0 0 1 4,-4.66 4.45,4.45 0 0 1 4.87,4.42 v 1.64 c 0,0 0,0 0,0 h 4.93 c 0,0 0,0 0,0 V 98.69 A 9.1,9.1 0 0 0 268.75,92 Z"
|
|
||||||
id="path28552" />
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="m 173.32,106.74 a 5.41,5.41 0 0 0 -1.5,-2 6.58,6.58 0 0 0 -2.36,-1.44 15.31,15.31 0 0 0 -2.72,-1 c -0.94,-0.25 -2,-0.52 -3.08,-0.73 a 15.43,15.43 0 0 1 -4.88,-1.64 2.85,2.85 0 0 1 -1.51,-2.47 2.81,2.81 0 0 1 1.35,-2.59 5.91,5.91 0 0 1 4.4,-0.76 8.68,8.68 0 0 1 3.86,2.11 c 0.23,0.19 2.51,2.3 2.47,2.35 l 3.29,-3.73 c 0,0 -1.58,-1.6 -2.33,-2.26 a 13,13 0 0 0 -6.31,-3.24 12.18,12.18 0 0 0 -7.23,1.1 9.58,9.58 0 0 0 -1.67,1.09 7.57,7.57 0 0 0 -2.88,6.1 c 0,2.6 0.82,4.45 2.5,5.66 a 17.33,17.33 0 0 0 6.73,2.73 25.41,25.41 0 0 1 5.53,1.65 2.29,2.29 0 0 1 1.59,2.23 3.36,3.36 0 0 1 -1.62,3 8.35,8.35 0 0 1 -8.24,0 19.32,19.32 0 0 1 -2.85,-2.27 c -0.71,-0.6 -1.38,-1.25 -2.07,-1.89 v 0 l -3.2,3.85 c 0,0 1.56,1.46 1.7,1.58 a 15.66,15.66 0 0 0 7.41,4.21 15.26,15.26 0 0 0 3.16,0.32 12.45,12.45 0 0 0 7.85,-2.45 8.17,8.17 0 0 0 3.27,-6.83 6.14,6.14 0 0 0 -0.66,-2.68 z"
|
|
||||||
id="path28554" />
|
|
||||||
<polygon
|
|
||||||
class="cls-2"
|
|
||||||
points="76.67,122.17 90.14,129.73 103.61,122.17 103.61,107.08 90.14,99.51 76.67,107.08 "
|
|
||||||
id="polygon28556" />
|
|
||||||
<polygon
|
|
||||||
class="cls-2"
|
|
||||||
points="121.2,71.05 114.08,75.05 114.07,75.11 114.07,83.1 121.2,87.1 121.26,87.13 128.38,83.14 128.38,75.08 "
|
|
||||||
id="polygon28558" />
|
|
||||||
<polygon
|
|
||||||
class="cls-2"
|
|
||||||
points="134.44,107.08 120.97,99.51 107.5,107.08 107.5,122.17 120.97,129.73 134.44,122.17 "
|
|
||||||
id="polygon28560" />
|
|
||||||
<polygon
|
|
||||||
class="cls-2"
|
|
||||||
points="105.45,73.16 92,80.72 92,95.81 105.47,103.37 118.94,95.81 118.94,89.8 112.38,86.12 110.61,85.13 110.61,83.1 110.61,76.24 "
|
|
||||||
id="polygon28562" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 468 142" xml:space="preserve"><style>.st1,.st2,.st3,.st4{opacity:.45;enable-background:new}.st2,.st3,.st4{opacity:.6}.st3,.st4{opacity:.3}.st4{opacity:.8}</style><g id="XMLID_41_"><g id="XMLID_1_"><g id="Swarm_typeface"><g id="XMLID_42_" opacity=".8"><path id="XMLID_31_" d="M193.7 90c0 9.1-9.5 13.4-17.4 13.4-8.5 0-17.8-3.3-17.8-13.2 0-1.7.1-3 2.2-3 1 0 1.4 1.3 1.4 2.1 0 8.5 6.8 10.6 14.2 10.6 5.8 0 13.8-2.8 13.8-9.8 0-13-31-5.3-31-20.3 0-8.6 9.6-10.8 16.5-10.8 8.1 0 17.1 3 17.1 12.6 0 1-1 1.7-1.9 1.7-1.1 0-1.7-1-1.7-1.9-.3-2.1-.6-3.5-2.1-5.2-2.6-3-7.7-3.6-11.5-3.6-4.5 0-12.9 1-12.9 7.1.1 10.9 31.1 3.5 31.1 20.3z"/><path id="XMLID_30_" d="M264.4 60.9c0 .8-13.2 37.6-14.5 40.9-.3 1-1 1.1-1.9 1.1-.9 0-1.6-.1-1.9-1.1l-12.9-36-12.9 36c-.3 1-1 1.1-1.9 1.1-.9 0-1.7-.1-1.9-1.1-1.3-3.3-14.5-40.1-14.5-40.9 0-1 .8-1.8 1.8-1.8.8 0 1.4.4 1.7 1.1l13 36.4 13-36.4c.3-.9 1-1.1 1.8-1.1.9 0 1.6.3 1.9 1.1l12.9 36.4 13-36.4c.3-.8.9-1.1 1.7-1.1.8 0 1.6.8 1.6 1.8z"/><path id="XMLID_45_" d="M315.3 60.9v39.2c0 1-.8 1.8-1.8 1.8s-1.8-.8-1.8-1.8v-8.2c-3.6 6.6-10.3 11.2-18 11.2-12.1 0-20.7-10.8-20.7-22.4s8.6-22.4 20.7-22.4c7.7 0 14.4 4.6 18 11.2v-8.6c0-.9.8-1.8 1.8-1.8s1.8.9 1.8 1.8zm-4.5 19.8c0-9.6-6.9-18.8-17.1-18.8-10 0-17.1 9.1-17.1 18.8s7.1 18.8 17.1 18.8c10.2 0 17.1-9.2 17.1-18.8z"/><path id="XMLID_27_" d="M352.4 61.3c0 1.1-.6 1.8-1.7 1.9-10.5 1.6-15.3 10.2-15.3 20.2v17.2c0 1-.8 1.8-1.8 1.8-1.1 0-1.8-.8-1.8-1.8V61.4c0-1 .8-1.8 1.8-1.8 1.1 0 1.8.8 1.8 1.8v8c3-5 8.9-9.8 15.1-9.8.8 0 1.9.6 1.9 1.7z"/><path id="XMLID_2_" d="M430.6 77.7v23c0 1-.9 1.8-1.8 1.8-1 0-1.8-.8-1.8-1.8v-23c0-7.7-4.4-15.3-13-15.3-10.9 0-15 11.6-15 20.6v17.8c0 1-.9 1.8-1.8 1.8-1 0-1.8-.8-1.8-1.8v-23c0-7.7-4.4-15.3-13-15.3-10.9 0-15.4 8.6-15.1 20.1 0 .3.1.8 0 .9v17.4c0 1-.8 1.8-1.8 1.8s-1.8-.8-1.8-1.8V61.4c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8V68c3.1-5.5 8.6-9.1 15.1-9.1 7.3 0 13 4.6 15.3 11.5 3-6.7 8.8-11.5 16.2-11.5 10.6-.1 16.7 9.1 16.7 18.8z"/></g></g></g><g id="Swarm_Icon"><path id="XMLID_26_" class="st1" d="M121.3 27v12.4l-11.1-6.2V20.7z"/><path id="XMLID_25_" class="st2" d="M110.2 33.2v12.4l11.1-6.2V27z"/><path id="XMLID_24_" class="st3" d="M37.4 102.7l22.1 12.4 22.1-12.4-22.1-12.5z"/><path id="XMLID_23_" class="st1" d="M59.5 90.2v24.9l-22.1-12.4V77.8z"/><path id="XMLID_22_" class="st1" d="M81.6 77.8v24.9L59.5 90.2V65.4z"/><path id="XMLID_21_" class="st2" d="M59.5 90.2v24.9l22.1-12.4V77.8z"/><path id="XMLID_20_" class="st2" d="M37.4 77.8v24.9l22.1-12.5V65.4z"/><path id="XMLID_19_" class="st4" d="M84 48.4l11 6.2 11.1-6.2L95 42.2z"/><path id="XMLID_18_" class="st4" d="M37.4 77.8l22.1 12.4 22.1-12.4-22.1-12.4z"/><path id="XMLID_17_" class="st3" d="M86.9 102.7l22.1 12.4 22.1-12.4L109 90.2z"/><path id="XMLID_16_" class="st1" d="M109 90.2v24.9l-22.1-12.4V77.8z"/><path id="XMLID_15_" class="st1" d="M131.1 77.8v24.9L109 90.2V65.4z"/><path id="XMLID_14_" class="st2" d="M109 90.2v24.9l22.1-12.4V77.8z"/><path id="XMLID_13_" class="st2" d="M86.9 77.8v24.9L109 90.2V65.4z"/><path id="XMLID_12_" class="st4" d="M86.9 77.8L109 90.2l22.1-12.4L109 65.4z"/><path id="XMLID_11_" class="st2" d="M84 35.9v12.5l11-6.2V29.7z"/><path id="XMLID_10_" class="st3" d="M61.9 60.8L84 73.2l22.1-12.4L84 48.4z"/><path id="XMLID_9_" class="st1" d="M84 48.4v24.8L61.9 60.8V35.9z"/><path id="XMLID_8_" class="st2" d="M61.9 35.9v24.9L84 48.4V23.5z"/><path id="XMLID_7_" class="st2" d="M95 54.6V42.2l-11 6.2v24.8L95 67l11.1-6.2V48.4z"/><g id="XMLID_5_"><path id="XMLID_6_" class="st1" d="M95 29.7l-11-6.2v24.9l22.1 12.4V48.4L95 42.2z"/></g><path id="XMLID_4_" class="st4" d="M84 23.5l11 6.2-11 6.2v12.5L61.9 35.9z"/><path id="XMLID_3_" class="st4" d="M99.2 27l11 6.2 11.1-6.2-11.1-6.3z"/></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" viewBox="0 0 230.07104 58.680001" version="1.1" id="svg28566" sodipodi:docname="swarm-logo.svg" inkscape:version="1.0.1 (3bc2e81, 2020-09-07)" width="230.07104" height="58.68">
|
|
||||||
<metadata id="metadata28570">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
|
||||||
<dc:title>Swarm Logo &amp; Lettering 4</dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1016" id="namedview28568" showgrid="false" inkscape:zoom="3.1325" inkscape:cx="123.33" inkscape:cy="35.939998" inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1" inkscape:current-layer="Layer_2" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"/>
|
|
||||||
<defs id="defs28540">
|
|
||||||
<style id="style28538">
|
|
||||||
.cls-1 {
|
|
||||||
fill: #fafafa;
|
|
||||||
}
|
|
||||||
.cls-2 {
|
|
||||||
fill: #dd7200;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<title id="title28542">Swarm Logo &amp; Lettering 4</title>
|
|
||||||
<g id="Layer_2" data-name="Layer 2" transform="translate(-76.67,-71.05)">
|
|
||||||
<path class="cls-2" d="m 206.24,90.06 -3.54,24.65 c 0,0 -0.06,0 -0.07,0 l -3.1,-24.65 c 0,0 0,0 0,0 h -8.82 l -3.11,24.65 c 0,0 -0.06,0 -0.07,0 l -3.54,-24.65 h -5.38 c 0,0 0,0 0,0 L 183,118.6 h 8.8 L 195.05,94 c 0,0 0.06,0 0.07,0 l 3.21,24.61 h 8.81 l 4.49,-28.53 h -5.37 c 0,0 -0.02,-0.08 -0.02,-0.02 z" id="path28546"/>
|
|
||||||
<path class="cls-2" d="m 305,91.39 a 6.52,6.52 0 0 0 -5.19,-2.14 6.74,6.74 0 0 0 -6.33,3.75 v 0 a 5.85,5.85 0 0 0 -1.86,-2.62 5.61,5.61 0 0 0 -3.62,-1.13 6.26,6.26 0 0 0 -3.55,1 5.78,5.78 0 0 0 -2,2.45 v -2.64 c 0,0 0,0 0,0 h -5.24 c 0,0 0,0 0,0 v 28.53 h 5.24 v -19.5 a 5.72,5.72 0 0 1 1,-3.5 3.14,3.14 0 0 1 2.7,-1.25 2.85,2.85 0 0 1 2.3,1 4.08,4.08 0 0 1 0.89,2.73 v 20.5 c 0,0 0,0 0,0 h 5.25 V 99.42 a 6.08,6.08 0 0 1 1,-3.89 3.22,3.22 0 0 1 2.61,-1.19 2.75,2.75 0 0 1 2.44,1 4.9,4.9 0 0 1 0.81,2.92 v 20.28 c 0,0 0,0 0,0 h 5.28 V 96.86 A 8.18,8.18 0 0 0 305,91.39 Z" id="path28548"/>
|
|
||||||
<path class="cls-2" d="m 243.47,115.52 -3.32,3.71 h -0.05 l -5.86,-5.07 c 0,0 0,0 0,0 l -7,4.09 a 7.38,7.38 0 0 1 -3.77,1 7.91,7.91 0 0 1 -5.34,-2.13 7.28,7.28 0 0 1 -2.3,-5.45 7.54,7.54 0 0 1 3.82,-6.58 L 233.76,97 c 0,0 0,0 0,0 v -1.8 h -16.53 c 0,0 0,0 0,0 v -5 c 0,0 0,0 0,0 h 21.57 v 21.36 c 0,0 0,0 0,0 l 4.62,4 z m -18.8,-1.66 9.09,-5.28 c 0,0 0,0 0,0 v -5.7 a 0.03,0 0 0 0 -0.06,0 l -11.58,6.65 a 2.46,2.46 0 0 0 -1.29,2.2 2.59,2.59 0 0 0 2.53,2.53 2.51,2.51 0 0 0 1.31,-0.4 z" id="path28550"/>
|
|
||||||
<path class="cls-2" d="m 268.75,92 a 9.1,9.1 0 0 0 -6.67,-2.78 9,9 0 0 0 -7.48,3.64 l -3.34,-3.06 a 0.025,0 0 0 0 -0.05,0 l -3.29,3.67 4.74,4.3 v 15.7 h -4.71 c 0,0 0,0 0,0 v 4.93 h 17 c 0,0 0,0 0,0 v -4.93 h -7.19 V 98.93 a 4.61,4.61 0 0 1 4,-4.66 4.45,4.45 0 0 1 4.87,4.42 v 1.64 c 0,0 0,0 0,0 h 4.93 c 0,0 0,0 0,0 V 98.69 A 9.1,9.1 0 0 0 268.75,92 Z" id="path28552"/>
|
|
||||||
<path class="cls-2" d="m 173.32,106.74 a 5.41,5.41 0 0 0 -1.5,-2 6.58,6.58 0 0 0 -2.36,-1.44 15.31,15.31 0 0 0 -2.72,-1 c -0.94,-0.25 -2,-0.52 -3.08,-0.73 a 15.43,15.43 0 0 1 -4.88,-1.64 2.85,2.85 0 0 1 -1.51,-2.47 2.81,2.81 0 0 1 1.35,-2.59 5.91,5.91 0 0 1 4.4,-0.76 8.68,8.68 0 0 1 3.86,2.11 c 0.23,0.19 2.51,2.3 2.47,2.35 l 3.29,-3.73 c 0,0 -1.58,-1.6 -2.33,-2.26 a 13,13 0 0 0 -6.31,-3.24 12.18,12.18 0 0 0 -7.23,1.1 9.58,9.58 0 0 0 -1.67,1.09 7.57,7.57 0 0 0 -2.88,6.1 c 0,2.6 0.82,4.45 2.5,5.66 a 17.33,17.33 0 0 0 6.73,2.73 25.41,25.41 0 0 1 5.53,1.65 2.29,2.29 0 0 1 1.59,2.23 3.36,3.36 0 0 1 -1.62,3 8.35,8.35 0 0 1 -8.24,0 19.32,19.32 0 0 1 -2.85,-2.27 c -0.71,-0.6 -1.38,-1.25 -2.07,-1.89 v 0 l -3.2,3.85 c 0,0 1.56,1.46 1.7,1.58 a 15.66,15.66 0 0 0 7.41,4.21 15.26,15.26 0 0 0 3.16,0.32 12.45,12.45 0 0 0 7.85,-2.45 8.17,8.17 0 0 0 3.27,-6.83 6.14,6.14 0 0 0 -0.66,-2.68 z" id="path28554"/>
|
|
||||||
<polygon class="cls-2" points="76.67,122.17 90.14,129.73 103.61,122.17 103.61,107.08 90.14,99.51 76.67,107.08 " id="polygon28556"/>
|
|
||||||
<polygon class="cls-2" points="121.2,71.05 114.08,75.05 114.07,75.11 114.07,83.1 121.2,87.1 121.26,87.13 128.38,83.14 128.38,75.08 " id="polygon28558"/>
|
|
||||||
<polygon class="cls-2" points="134.44,107.08 120.97,99.51 107.5,107.08 107.5,122.17 120.97,129.73 134.44,122.17 " id="polygon28560"/>
|
|
||||||
<polygon class="cls-2" points="105.45,73.16 92,80.72 92,95.81 105.47,103.37 118.94,95.81 118.94,89.8 112.38,86.12 110.61,85.13 110.61,83.1 110.61,76.24 " id="polygon28562"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -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>
|
label: 'macOS',
|
||||||
</Box>
|
component: <CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.mac} />,
|
||||||
)}
|
},
|
||||||
</div>
|
]}
|
||||||
)
|
/>
|
||||||
}
|
|
||||||
|
|
||||||
const AntTabs = withStyles({
|
|
||||||
root: {
|
|
||||||
borderBottom: '1px solid #e8e8e8',
|
|
||||||
},
|
|
||||||
indicator: {
|
|
||||||
backgroundColor: '#3f51b5',
|
|
||||||
},
|
|
||||||
})(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,61 +0,0 @@
|
|||||||
import { useState, ReactElement } from 'react'
|
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
|
||||||
import { Toolbar, Chip, IconButton } from '@material-ui/core/'
|
|
||||||
|
|
||||||
import { Sun, Moon } from 'react-feather'
|
|
||||||
|
|
||||||
const drawerWidth = 240
|
|
||||||
|
|
||||||
const useStyles = makeStyles(() =>
|
|
||||||
createStyles({
|
|
||||||
appBar: {
|
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
|
||||||
marginLeft: drawerWidth,
|
|
||||||
},
|
|
||||||
network: {},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
interface Props {
|
|
||||||
themeMode: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SideBar(props: Props): ReactElement {
|
|
||||||
const [darkMode, toggleDarkMode] = useState(false)
|
|
||||||
|
|
||||||
const switchTheme = () => {
|
|
||||||
const theme = localStorage.getItem('theme')
|
|
||||||
|
|
||||||
if (theme) {
|
|
||||||
localStorage.setItem('theme', theme === 'light' ? 'dark' : 'light')
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('theme', darkMode ? 'dark' : 'light')
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDarkMode(!darkMode)
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div style={{ display: 'fixed' }} className={classes.appBar}>
|
|
||||||
<Toolbar style={{ display: 'flex' }}>
|
|
||||||
<Chip style={{ marginLeft: '7px' }} size="small" label="Goerli" className={classes.network} />
|
|
||||||
<div style={{ width: '100%' }}>
|
|
||||||
<div style={{ float: 'right' }}>
|
|
||||||
<IconButton style={{ marginRight: '10px' }} aria-label="dark-mode" onClick={() => switchTheme()}>
|
|
||||||
{props.themeMode === 'dark' ? <Moon /> : <Sun />}
|
|
||||||
</IconButton>
|
|
||||||
{/* <Chip
|
|
||||||
label="Connect Wallet"
|
|
||||||
color="primary"
|
|
||||||
/> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Toolbar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
+70
-116
@@ -1,51 +1,45 @@
|
|||||||
import { ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import { Link, RouteComponentProps } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
||||||
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink } from '@material-ui/core'
|
|
||||||
import { OpenInNewSharp } from '@material-ui/icons'
|
import { OpenInNewSharp } from '@material-ui/icons'
|
||||||
import { Activity, FileText, DollarSign, Share2, Settings, Layers } from 'react-feather'
|
import { Divider, List, Drawer, Grid, Link as MUILink } from '@material-ui/core'
|
||||||
|
import { Home, FileText, DollarSign, Share2, Settings, Layers, BookOpen } from 'react-feather'
|
||||||
|
import { ROUTES } from '../routes'
|
||||||
|
import SideBarItem from './SideBarItem'
|
||||||
|
import SideBarStatus from './SideBarStatus'
|
||||||
|
|
||||||
import SwarmLogoOrange from '../assets/swarm-logo-orange.svg'
|
import Logo from '../assets/logo.svg'
|
||||||
import { Health } from '@ethersphere/bee-js'
|
|
||||||
|
|
||||||
const drawerWidth = 240
|
|
||||||
|
|
||||||
const navBarItems = [
|
const navBarItems = [
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Info',
|
||||||
id: 'status',
|
path: ROUTES.INFO,
|
||||||
path: '/',
|
icon: Home,
|
||||||
icon: Activity,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Files',
|
label: 'Files',
|
||||||
id: 'files',
|
path: ROUTES.FILES,
|
||||||
path: '/files/',
|
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Stamps',
|
label: 'Stamps',
|
||||||
id: 'stamps',
|
path: ROUTES.STAMPS,
|
||||||
path: '/stamps/',
|
|
||||||
icon: Layers,
|
icon: Layers,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Accounting',
|
label: 'Accounting',
|
||||||
id: 'accounting',
|
path: ROUTES.ACCOUNTING,
|
||||||
path: '/accounting/',
|
|
||||||
icon: DollarSign,
|
icon: DollarSign,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Peers',
|
label: 'Peers',
|
||||||
id: 'peers',
|
path: ROUTES.PEERS,
|
||||||
path: '/peers/',
|
|
||||||
icon: Share2,
|
icon: Share2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
id: 'settings',
|
path: ROUTES.SETTINGS,
|
||||||
path: '/settings/',
|
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -53,122 +47,82 @@ const navBarItems = [
|
|||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex',
|
flexWrap: 'nowrap',
|
||||||
},
|
minHeight: '100vh',
|
||||||
appBar: {
|
paddingTop: theme.spacing(8),
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
paddingBottom: theme.spacing(8),
|
||||||
marginLeft: drawerWidth,
|
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
padding: 1,
|
marginLeft: theme.spacing(8),
|
||||||
marginTop: 20,
|
marginRight: theme.spacing(8),
|
||||||
},
|
},
|
||||||
drawer: {
|
icon: {
|
||||||
width: drawerWidth,
|
height: theme.spacing(4),
|
||||||
flexShrink: 0,
|
|
||||||
},
|
},
|
||||||
drawerPaper: {
|
iconSmall: {
|
||||||
width: drawerWidth,
|
height: theme.spacing(2),
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
marginLeft: theme.spacing(4),
|
||||||
|
marginRight: theme.spacing(4),
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
color: '#9f9f9f',
|
||||||
|
textDecoration: 'none',
|
||||||
|
'&:hover': {
|
||||||
|
textDecoration: 'none',
|
||||||
|
|
||||||
|
// https://github.com/mui-org/material-ui/issues/22543
|
||||||
|
'@media (hover: none)': {
|
||||||
|
textDecoration: 'none',
|
||||||
},
|
},
|
||||||
activeSideBar: {
|
|
||||||
color: '#dd7700',
|
|
||||||
},
|
},
|
||||||
activeSideBarItem: {
|
|
||||||
borderLeft: '4px solid #dd7700',
|
|
||||||
backgroundColor: 'inherit !important',
|
|
||||||
},
|
},
|
||||||
toolbar: theme.mixins.toolbar,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Props extends RouteComponentProps {
|
export default function SideBar(): ReactElement {
|
||||||
themeMode: string
|
|
||||||
health: boolean
|
|
||||||
nodeHealth: Health | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SideBar(props: Props): ReactElement {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<Drawer variant="permanent">
|
||||||
<Drawer
|
<Grid container direction="column" justifyContent="space-between" className={classes.root}>
|
||||||
className={classes.drawer}
|
<Grid className={classes.logo}>
|
||||||
variant="permanent"
|
<Link to={ROUTES.INFO}>
|
||||||
classes={{
|
<img alt="swarm" src={Logo} />
|
||||||
paper: classes.drawerPaper,
|
|
||||||
}}
|
|
||||||
anchor="left"
|
|
||||||
>
|
|
||||||
<div className={classes.toolbar} style={{ textAlign: 'left', marginLeft: 20 }}>
|
|
||||||
<Link to="/">
|
|
||||||
<img
|
|
||||||
alt="swarm"
|
|
||||||
className={classes.logo}
|
|
||||||
src={props.themeMode === 'light' ? SwarmLogoOrange : SwarmLogoOrange}
|
|
||||||
style={{ maxHeight: '30px', alignItems: 'center' }}
|
|
||||||
/>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</Grid>
|
||||||
|
<Grid>
|
||||||
<List>
|
<List>
|
||||||
{navBarItems.map(item => (
|
{navBarItems.map(p => (
|
||||||
<Link to={item.path} key={item.id} style={{ color: 'inherit', textDecoration: 'none' }}>
|
<Link to={p.path} key={p.path} className={classes.link}>
|
||||||
<ListItem
|
<SideBarItem
|
||||||
button
|
key={p.path}
|
||||||
selected={props.location.pathname === item.path}
|
iconStart={<p.icon className={classes.icon} />}
|
||||||
className={props.location.pathname === item.path ? classes.activeSideBarItem : ''}
|
path={p.path}
|
||||||
>
|
label={p.label}
|
||||||
<ListItemIcon className={props.location.pathname === item.path ? classes.activeSideBar : ''}>
|
|
||||||
<item.icon style={{ height: '20px' }} />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={item.label}
|
|
||||||
className={props.location.pathname === item.path ? classes.activeSideBar : ''}
|
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
<Divider />
|
<Divider className={classes.divider} />
|
||||||
<List>
|
<List>
|
||||||
<MUILink href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" style={{ textDecoration: 'none' }}>
|
<MUILink href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" className={classes.link}>
|
||||||
<ListItem button>
|
<SideBarItem
|
||||||
<ListItemText primary={'Docs'} />
|
iconStart={<BookOpen className={classes.icon} />}
|
||||||
<OpenInNewSharp fontSize="small" />
|
iconEnd={<OpenInNewSharp className={classes.iconSmall} />}
|
||||||
</ListItem>
|
label={<span>Docs</span>}
|
||||||
|
/>
|
||||||
</MUILink>
|
</MUILink>
|
||||||
</List>
|
</List>
|
||||||
<div style={{ position: 'fixed', bottom: 0, width: 'inherit', padding: '10px' }}>
|
</Grid>
|
||||||
<ListItem>
|
<Grid>
|
||||||
<div style={{ marginRight: '30px' }}>
|
<Link to={ROUTES.STATUS} className={classes.link}>
|
||||||
<div
|
<SideBarStatus path={ROUTES.STATUS} />
|
||||||
style={{
|
</Link>
|
||||||
backgroundColor: props.health ? '#32c48d' : '#c9201f',
|
</Grid>
|
||||||
marginRight: '7px',
|
</Grid>
|
||||||
height: '10px',
|
|
||||||
width: '10px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
display: 'inline-block',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span>API</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
backgroundColor: props.nodeHealth?.status === 'ok' ? '#32c48d' : '#c9201f',
|
|
||||||
marginRight: '7px',
|
|
||||||
height: '10px',
|
|
||||||
width: '10px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
display: 'inline-block',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span>Debug API</span>
|
|
||||||
</div>
|
|
||||||
</ListItem>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { ReactElement, ReactNode } from 'react'
|
||||||
|
import { useLocation, matchPath } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { createStyles, Theme, makeStyles, withStyles } from '@material-ui/core/styles'
|
||||||
|
import { ListItemText, ListItemIcon, ListItem } from '@material-ui/core'
|
||||||
|
|
||||||
|
const StyledListItem = withStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
paddingLeft: theme.spacing(4),
|
||||||
|
paddingRight: theme.spacing(4),
|
||||||
|
borderLeft: '4px solid rgba(0,0,0,0)',
|
||||||
|
'&.Mui-selected, &.Mui-selected:hover': {
|
||||||
|
borderLeft: `4px solid ${theme.palette.primary.main}`,
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
color: '#f9f9f9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
color: '#f9f9f9',
|
||||||
|
|
||||||
|
// https://github.com/mui-org/material-ui/issues/22543
|
||||||
|
'@media (hover: none)': {
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
color: '#f9f9f9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))(ListItem)
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
icon: {
|
||||||
|
color: 'inherit',
|
||||||
|
},
|
||||||
|
activeIcon: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
iconStart?: ReactNode
|
||||||
|
iconEnd?: ReactNode
|
||||||
|
path?: string
|
||||||
|
label: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SideBarItem({ iconStart, iconEnd, path, label }: Props): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
|
const location = useLocation()
|
||||||
|
const isSelected = Boolean(matchPath(location.pathname, { path, exact: true }))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledListItem button selected={isSelected} disableRipple>
|
||||||
|
<ListItemIcon className={isSelected ? classes.activeIcon : classes.icon}>{iconStart}</ListItemIcon>
|
||||||
|
<ListItemText primary={label} />
|
||||||
|
<ListItemIcon className={isSelected ? classes.activeIcon : classes.icon}>{iconEnd}</ListItemIcon>
|
||||||
|
</StyledListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { ReactElement, useContext } from 'react'
|
||||||
|
import { useLocation, matchPath } from 'react-router-dom'
|
||||||
|
import { ArrowRight } from 'react-feather'
|
||||||
|
|
||||||
|
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { ListItemText, ListItemIcon, ListItem, Typography } from '@material-ui/core'
|
||||||
|
import { Context } from '../providers/Bee'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
icon: {
|
||||||
|
color: 'inherit',
|
||||||
|
},
|
||||||
|
iconSmall: {
|
||||||
|
height: theme.spacing(2),
|
||||||
|
},
|
||||||
|
|
||||||
|
root: {
|
||||||
|
height: theme.spacing(4),
|
||||||
|
paddingLeft: theme.spacing(1),
|
||||||
|
paddingRight: theme.spacing(4),
|
||||||
|
color: '#f9f9f9',
|
||||||
|
borderLeft: '0px solid rgba(0,0,0,0)',
|
||||||
|
'&.Mui-selected, &.Mui-selected:hover': {
|
||||||
|
borderLeft: `0px solid ${theme.palette.primary.main}`,
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rootError: {
|
||||||
|
backgroundColor: 'rgba(255, 58, 82, 0.25)',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
|
||||||
|
// https://github.com/mui-org/material-ui/issues/22543
|
||||||
|
'@media (hover: none)': {
|
||||||
|
backgroundColor: '#2c2c2c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
smallerText: {
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
path?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SideBarItem({ path }: Props): ReactElement {
|
||||||
|
const { status } = useContext(Context)
|
||||||
|
const classes = useStyles()
|
||||||
|
const location = useLocation()
|
||||||
|
const isSelected = Boolean(matchPath(location.pathname, { path, exact: true }))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
button
|
||||||
|
classes={{ root: `${classes.root} ${status.all ? '' : classes.rootError}`, button: classes.button }}
|
||||||
|
selected={isSelected}
|
||||||
|
disableRipple
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
backgroundColor: status.all ? '#1de600' : '#ff3a52',
|
||||||
|
height: '14px',
|
||||||
|
width: '14px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'inline-block',
|
||||||
|
marginLeft: 30,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={<Typography className={classes.smallerText}>{`Node ${status.all ? 'OK' : 'Error'}`}</Typography>}
|
||||||
|
/>
|
||||||
|
<ListItemIcon className={classes.icon}>
|
||||||
|
{status.all ? null : <ArrowRight className={classes.iconSmall} />}
|
||||||
|
</ListItemIcon>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'
|
|||||||
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { Card, CardContent, Typography } from '@material-ui/core/'
|
import { Card, CardContent, Typography } from '@material-ui/core/'
|
||||||
|
import { ROUTES } from '../routes'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: {
|
root: {
|
||||||
@@ -26,7 +27,7 @@ export default function TroubleshootConnectionCard(): ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
|
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
|
||||||
<strong>
|
<strong>
|
||||||
<Link to="/">Click to run status checks</Link> on your nodes connection or check out the{' '}
|
<Link to={ROUTES.STATUS}>Click to run status checks</Link> on your nodes connection or check out the{' '}
|
||||||
<a href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" rel="noreferrer">
|
<a href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" rel="noreferrer">
|
||||||
Swarm Bee Docs
|
Swarm Bee Docs
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-33
@@ -1,19 +1,18 @@
|
|||||||
import { useState, useEffect, ReactElement } from 'react'
|
import { 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 { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks'
|
import { Context } from '../providers/Bee'
|
||||||
import { RouteComponentProps } from 'react-router'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
content: {
|
content: {
|
||||||
marginLeft: '240px',
|
marginLeft: 300,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
@@ -22,46 +21,28 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Props extends RouteComponentProps {
|
interface Props {
|
||||||
children?: ReactElement
|
children?: ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard = (props: Props): ReactElement => {
|
const Dashboard = (props: Props): ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [themeMode, toggleThemeMode] = useState('light')
|
const { isLoading } = useContext(Context)
|
||||||
|
|
||||||
// FIXME: handle errrors and loading
|
|
||||||
const { health } = useApiHealth()
|
|
||||||
const { nodeHealth } = useDebugApiHealth()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const theme = localStorage.getItem('theme')
|
|
||||||
|
|
||||||
if (theme) {
|
|
||||||
toggleThemeMode(String(localStorage.getItem('theme')))
|
|
||||||
} else if (window?.matchMedia('(prefers-color-scheme: dark)')?.matches) {
|
|
||||||
toggleThemeMode('dark')
|
|
||||||
}
|
|
||||||
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.addEventListener('change', e => {
|
|
||||||
toggleThemeMode(e?.matches ? 'dark' : 'light')
|
|
||||||
})
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.removeEventListener('change', e => {
|
|
||||||
toggleThemeMode(e?.matches ? 'dark' : 'light')
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
|
<SideBar />
|
||||||
<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,24 @@ 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 } =
|
||||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
useContext(BeeContext)
|
||||||
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
const { beeDebugApi } = useContext(SettingsContext)
|
||||||
const { health, isLoadingHealth } = useApiHealth()
|
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
|
||||||
const { isLoading, totalsent, totalreceived, accounting, isLoadingUncashed, error } = useAccounting()
|
|
||||||
|
|
||||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
const { accounting, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances)
|
||||||
return (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
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">
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { ReactElement, useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/'
|
||||||
|
import { ArrowRight, ArrowDropUp } from '@material-ui/icons/'
|
||||||
|
import { NodeAddresses, Topology } from '@ethersphere/bee-js'
|
||||||
|
import { ROUTES } from '../../routes'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
flex: '1 1 auto',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
color: '#2145a0',
|
||||||
|
backgroundColor: '#e1effe',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
nodeAddresses: NodeAddresses | null
|
||||||
|
nodeTopology: Topology | null
|
||||||
|
userBeeVersion?: string
|
||||||
|
isLatestBeeVersion: boolean
|
||||||
|
isOk: boolean
|
||||||
|
latestUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusCard({
|
||||||
|
userBeeVersion,
|
||||||
|
nodeAddresses,
|
||||||
|
nodeTopology,
|
||||||
|
isLatestBeeVersion,
|
||||||
|
latestUrl,
|
||||||
|
}: Props): ReactElement | null {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<boolean>(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent className={classes.root}>
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<span style={{ marginRight: '20px' }}>Discovered Nodes: {nodeTopology?.population}</span>
|
||||||
|
<span style={{ marginRight: '20px' }}>
|
||||||
|
<span>Connected Peers: </span>
|
||||||
|
<Link to={ROUTES.PEERS}>{nodeTopology?.connected}</Link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
|
<span>AGENT: </span>
|
||||||
|
<a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank">
|
||||||
|
Bee
|
||||||
|
</a>{' '}
|
||||||
|
<span>{userBeeVersion || '-'}</span>
|
||||||
|
{isLatestBeeVersion ? (
|
||||||
|
<Chip
|
||||||
|
style={{ marginLeft: '7px', color: '#2145a0' }}
|
||||||
|
size="small"
|
||||||
|
label="latest"
|
||||||
|
className={classes.status}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button size="small" variant="outlined" href={latestUrl}>
|
||||||
|
update
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
|
<span>PUBLIC KEY: </span>
|
||||||
|
<span>{nodeAddresses?.publicKey ? nodeAddresses.publicKey : '-'}</span>
|
||||||
|
</Typography>
|
||||||
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
|
<span>PSS PUBLIC KEY: </span>
|
||||||
|
<span>{nodeAddresses?.pssPublicKey ? nodeAddresses.pssPublicKey : '-'}</span>
|
||||||
|
</Typography>
|
||||||
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
|
<span>OVERLAY ADDRESS (PEER ID): </span>
|
||||||
|
<span>{nodeAddresses?.overlay ? nodeAddresses.overlay : '-'}</span>
|
||||||
|
</Typography>
|
||||||
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
|
<Typography component="div" onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}>
|
||||||
|
<Button color="primary" style={{ padding: 0, marginTop: '6px' }}>
|
||||||
|
{underlayAddressesVisible ? (
|
||||||
|
<ArrowDropUp style={{ fontSize: '12px' }} />
|
||||||
|
) : (
|
||||||
|
<ArrowRight style={{ fontSize: '12px' }} />
|
||||||
|
)}
|
||||||
|
<span>Underlay Addresses</span>
|
||||||
|
</Button>
|
||||||
|
</Typography>
|
||||||
|
{underlayAddressesVisible && (
|
||||||
|
<div>
|
||||||
|
{nodeAddresses?.underlay.map(item => (
|
||||||
|
<li key={item}>{item}</li>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatusCard
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { ReactElement, useContext } from 'react'
|
||||||
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
|
import StatusCard from './StatusCard'
|
||||||
|
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
||||||
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
display: 'grid',
|
||||||
|
rowGap: theme.spacing(3),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function Status(): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const {
|
||||||
|
status,
|
||||||
|
latestUserVersion,
|
||||||
|
isLatestBeeVersion,
|
||||||
|
latestBeeVersionUrl,
|
||||||
|
topology,
|
||||||
|
nodeAddresses,
|
||||||
|
chequebookAddress,
|
||||||
|
} = useContext(BeeContext)
|
||||||
|
|
||||||
|
if (!status.all) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<StatusCard
|
||||||
|
userBeeVersion={latestUserVersion}
|
||||||
|
isLatestBeeVersion={isLatestBeeVersion}
|
||||||
|
isOk={status.all}
|
||||||
|
nodeTopology={topology}
|
||||||
|
latestUrl={latestBeeVersionUrl}
|
||||||
|
nodeAddresses={nodeAddresses}
|
||||||
|
/>
|
||||||
|
{nodeAddresses && chequebookAddress && (
|
||||||
|
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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,178 +0,0 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react'
|
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
|
||||||
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 DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
|
|
||||||
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
|
|
||||||
import VersionCheck from './SetupSteps/VersionCheck'
|
|
||||||
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
|
|
||||||
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
|
|
||||||
import PeerConnection from './SetupSteps/PeerConnection'
|
|
||||||
import { StatusChequebookHook } from '../../hooks/status'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
root: {
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
marginRight: theme.spacing(1),
|
|
||||||
},
|
|
||||||
actionsContainer: {
|
|
||||||
margin: theme.spacing(2),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
interface Step {
|
|
||||||
label: string
|
|
||||||
isOk: boolean
|
|
||||||
isLoading: boolean
|
|
||||||
component: ReactElement
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
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 [activeStep, setActiveStep] = useState(-1)
|
|
||||||
|
|
||||||
const steps: Step[] = [
|
|
||||||
{
|
|
||||||
label: 'Connected to Node DebugAPI',
|
|
||||||
isOk: debugApiConnection.isOk,
|
|
||||||
isLoading: debugApiConnection.isLoading,
|
|
||||||
component: <DebugConnectionCheck {...debugApiConnection} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Running latest Bee version',
|
|
||||||
isOk: nodeVersion.isOk,
|
|
||||||
isLoading: nodeVersion.isLoading,
|
|
||||||
component: <VersionCheck {...nodeVersion} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Connected to Ethereum Blockchain',
|
|
||||||
isOk: ethereumConnection.isOk,
|
|
||||||
isLoading: ethereumConnection.isLoading,
|
|
||||||
component: <EthereumConnectionCheck {...ethereumConnection} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Deployed and Funded Chequebook',
|
|
||||||
isOk: chequebook.isOk,
|
|
||||||
isLoading: chequebook.isLoading,
|
|
||||||
component: <ChequebookDeployFund ethereumAddress={ethereumConnection.nodeAddresses?.ethereum} {...chequebook} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Connected to Node API',
|
|
||||||
isOk: apiConnection.isOk,
|
|
||||||
isLoading: apiConnection.isLoading,
|
|
||||||
component: <NodeConnectionCheck {...apiConnection} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Connected to Peers',
|
|
||||||
isOk: topology.isOk,
|
|
||||||
isLoading: topology.isLoading,
|
|
||||||
component: <PeerConnection {...topology} />,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// If the user already changed the active step we don't want to overwrite it
|
|
||||||
if (activeStep >= 0 && activeStep < steps.length) return
|
|
||||||
|
|
||||||
// If any step is not fully loaded yet return
|
|
||||||
if (!steps.every(step => !step.isLoading)) return
|
|
||||||
|
|
||||||
// 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
|
|
||||||
for (let i = 0; i < steps.length; ++i) {
|
|
||||||
if (!steps[i].isOk) {
|
|
||||||
setActiveStep(i)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [steps])
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
setActiveStep(prevActiveStep => prevActiveStep + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
setActiveStep(prevActiveStep => prevActiveStep - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper className={classes.root}>
|
|
||||||
<Typography variant="h5" gutterBottom>
|
|
||||||
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>
|
|
||||||
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
|
|
||||||
{steps.map(({ label, isOk, component, isLoading }, index) => (
|
|
||||||
<Step key={label}>
|
|
||||||
<StepLabel
|
|
||||||
disabled={isLoading}
|
|
||||||
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
|
|
||||||
StepIconComponent={() => {
|
|
||||||
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
|
|
||||||
|
|
||||||
if (isOk) return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
|
|
||||||
|
|
||||||
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StepButton
|
|
||||||
disabled={isLoading}
|
|
||||||
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
|
|
||||||
style={{ justifyContent: 'space-between' }}
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<div style={{ marginTop: '5px' }}>{label}</div>
|
|
||||||
<div style={{ marginLeft: '12px' }}>
|
|
||||||
{index === activeStep ? <ExpandLessSharp /> : <ExpandMoreSharp />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</StepButton>
|
|
||||||
</StepLabel>
|
|
||||||
<StepContent>
|
|
||||||
<Typography component="div">{component}</Typography>
|
|
||||||
<div className={classes.actionsContainer}>
|
|
||||||
<div>
|
|
||||||
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="primary" onClick={handleNext} className={classes.button}>
|
|
||||||
{index < steps.length - 1 ? 'Next' : 'Finish'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</StepContent>
|
|
||||||
</Step>
|
|
||||||
))}
|
|
||||||
</Stepper>
|
|
||||||
</Paper>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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' }}>
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
|
||||||
import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/'
|
|
||||||
import { CheckCircle, Error, ArrowRight, ArrowDropUp } from '@material-ui/icons/'
|
|
||||||
import { NodeAddresses, Topology } from '@ethersphere/bee-js'
|
|
||||||
|
|
||||||
const useStyles = makeStyles(() =>
|
|
||||||
createStyles({
|
|
||||||
root: {
|
|
||||||
display: 'flex',
|
|
||||||
flex: '1 1 auto',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
color: '#2145a0',
|
|
||||||
backgroundColor: '#e1effe',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
nodeAddresses: NodeAddresses | null
|
|
||||||
nodeTopology: Topology | null
|
|
||||||
userBeeVersion?: string
|
|
||||||
isLatestBeeVersion: boolean
|
|
||||||
isOk: boolean
|
|
||||||
latestUrl: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatusCard({
|
|
||||||
userBeeVersion,
|
|
||||||
nodeAddresses,
|
|
||||||
nodeTopology,
|
|
||||||
isOk,
|
|
||||||
isLatestBeeVersion,
|
|
||||||
latestUrl,
|
|
||||||
}: Props): ReactElement | null {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<boolean>(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardContent className={classes.root}>
|
|
||||||
<Typography component="h5" variant="h5" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
{isOk && (
|
|
||||||
<div>
|
|
||||||
<CheckCircle style={{ color: '#32c48d', marginRight: '7px' }} />
|
|
||||||
<span>Your Bee node is running as expected</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isOk && (
|
|
||||||
<div>
|
|
||||||
<Error style={{ color: '#c9201f', marginRight: '7px' }} />
|
|
||||||
<span>Could not connect to Bee Node</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
{isOk && (
|
|
||||||
<>
|
|
||||||
<div style={{ marginBottom: '20px' }}>
|
|
||||||
<span style={{ marginRight: '20px' }}>Discovered Nodes: {nodeTopology?.population}</span>
|
|
||||||
<span style={{ marginRight: '20px' }}>
|
|
||||||
<span>Connected Peers: </span>
|
|
||||||
<Link to="/peers/">{nodeTopology?.connected}</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
|
||||||
<span>AGENT: </span>
|
|
||||||
<a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank">
|
|
||||||
Bee
|
|
||||||
</a>{' '}
|
|
||||||
<span>{userBeeVersion || '-'}</span>
|
|
||||||
{isLatestBeeVersion ? (
|
|
||||||
<Chip
|
|
||||||
style={{ marginLeft: '7px', color: '#2145a0' }}
|
|
||||||
size="small"
|
|
||||||
label="latest"
|
|
||||||
className={classes.status}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Button size="small" variant="outlined" href={latestUrl}>
|
|
||||||
update
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
|
||||||
<span>PUBLIC KEY: </span>
|
|
||||||
<span>{nodeAddresses?.publicKey ? nodeAddresses.publicKey : '-'}</span>
|
|
||||||
</Typography>
|
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
|
||||||
<span>PSS PUBLIC KEY: </span>
|
|
||||||
<span>{nodeAddresses?.pssPublicKey ? nodeAddresses.pssPublicKey : '-'}</span>
|
|
||||||
</Typography>
|
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
|
||||||
<span>OVERLAY ADDRESS (PEER ID): </span>
|
|
||||||
<span>{nodeAddresses?.overlay ? nodeAddresses.overlay : '-'}</span>
|
|
||||||
</Typography>
|
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
|
||||||
<Typography component="div" onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}>
|
|
||||||
<Button color="primary" style={{ padding: 0, marginTop: '6px' }}>
|
|
||||||
{underlayAddressesVisible ? (
|
|
||||||
<ArrowDropUp style={{ fontSize: '12px' }} />
|
|
||||||
) : (
|
|
||||||
<ArrowRight style={{ fontSize: '12px' }} />
|
|
||||||
)}
|
|
||||||
<span>Underlay Addresses</span>
|
|
||||||
</Button>
|
|
||||||
</Typography>
|
|
||||||
{underlayAddressesVisible && (
|
|
||||||
<div>
|
|
||||||
{nodeAddresses?.underlay.map(item => (
|
|
||||||
<li key={item}>{item}</li>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StatusCard
|
|
||||||
+131
-55
@@ -1,76 +1,152 @@
|
|||||||
import { ReactElement } from 'react'
|
import { ReactElement, useState, 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 { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
|
||||||
|
import { CheckCircle, Error, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
|
||||||
|
|
||||||
import NodeSetupWorkflow from './NodeSetupWorkflow'
|
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
|
||||||
import StatusCard from './StatusCard'
|
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
|
||||||
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
import VersionCheck from './SetupSteps/VersionCheck'
|
||||||
import {
|
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
|
||||||
useStatusEthereumConnection,
|
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
|
||||||
useStatusNodeVersion,
|
import PeerConnection from './SetupSteps/PeerConnection'
|
||||||
useStatusDebugConnection,
|
import { Context } from '../../providers/Bee'
|
||||||
useStatusConnection,
|
|
||||||
useStatusTopology,
|
|
||||||
useStatusChequebook,
|
|
||||||
} from '../../hooks/status'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'grid',
|
},
|
||||||
rowGap: theme.spacing(3),
|
button: {
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
},
|
||||||
|
actionsContainer: {
|
||||||
|
margin: theme.spacing(2),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export default function Status(): ReactElement {
|
interface Step {
|
||||||
|
label: string
|
||||||
|
isOk: boolean
|
||||||
|
component: ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NodeSetupWorkflow(): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const [activeStep, setActiveStep] = useState(-1)
|
||||||
|
|
||||||
const nodeVersion = useStatusNodeVersion()
|
const {
|
||||||
const ethereumConnection = useStatusEthereumConnection()
|
status,
|
||||||
const debugApiConnection = useStatusDebugConnection()
|
isLoading,
|
||||||
const apiConnection = useStatusConnection()
|
latestUserVersion,
|
||||||
const topology = useStatusTopology()
|
latestPublishedVersion,
|
||||||
const chequebook = useStatusChequebook()
|
isLatestBeeVersion,
|
||||||
|
latestBeeVersionUrl,
|
||||||
|
topology,
|
||||||
|
nodeAddresses,
|
||||||
|
chequebookAddress,
|
||||||
|
} = useContext(Context)
|
||||||
|
|
||||||
const checks = [nodeVersion, ethereumConnection, debugApiConnection, apiConnection, topology, chequebook]
|
const steps: Step[] = [
|
||||||
|
{
|
||||||
|
label: 'Connected to Node DebugAPI',
|
||||||
|
isOk: status.debugApiConnection,
|
||||||
|
component: <DebugConnectionCheck isOk={status.debugApiConnection} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Running latest Bee version',
|
||||||
|
isOk: status.version,
|
||||||
|
component: (
|
||||||
|
<VersionCheck
|
||||||
|
isOk={status.version}
|
||||||
|
isLatestBeeVersion={isLatestBeeVersion}
|
||||||
|
userVersion={latestUserVersion}
|
||||||
|
latestVersion={latestPublishedVersion}
|
||||||
|
latestUrl={latestBeeVersionUrl}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Connected to xDai Blockchain',
|
||||||
|
isOk: status.blockchainConnection,
|
||||||
|
component: <EthereumConnectionCheck isOk={status.blockchainConnection} nodeAddresses={nodeAddresses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Deployed and Funded Chequebook',
|
||||||
|
isOk: status.chequebook,
|
||||||
|
component: (
|
||||||
|
<ChequebookDeployFund chequebookAddress={chequebookAddress?.chequebookAddress} isOk={status.chequebook} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Connected to Node API',
|
||||||
|
isOk: status.apiConnection,
|
||||||
|
component: <NodeConnectionCheck isOk={status.apiConnection} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Connected to Peers',
|
||||||
|
isOk: status.topology,
|
||||||
|
component: <PeerConnection isOk={status.topology} topology={topology} />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// If any check data are still loading
|
const handleNext = () => {
|
||||||
if (!checks.every(c => !c.isLoading)) {
|
setActiveStep(prevActiveStep => prevActiveStep + 1)
|
||||||
return (
|
}
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
const handleBack = () => {
|
||||||
</Container>
|
setActiveStep(prevActiveStep => prevActiveStep - 1)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<Paper className={classes.root}>
|
||||||
<StatusCard
|
<Typography variant="h5" gutterBottom>
|
||||||
userBeeVersion={nodeVersion.userVersion}
|
Node Setup
|
||||||
isLatestBeeVersion={nodeVersion.isLatestBeeVersion}
|
</Typography>
|
||||||
isOk={checks.every(c => c.isOk)}
|
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
|
||||||
nodeTopology={topology.topology}
|
{steps.map(({ label, isOk, component }, index) => (
|
||||||
latestUrl={nodeVersion.latestUrl}
|
<Step key={label}>
|
||||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
<StepLabel
|
||||||
/>
|
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
|
||||||
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
|
StepIconComponent={() => {
|
||||||
<EthereumAddressCard
|
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
|
||||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
|
||||||
isLoadingNodeAddresses={ethereumConnection.isLoading}
|
if (isOk) return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
|
||||||
chequebookAddress={chequebook.chequebookAddress}
|
|
||||||
isLoadingChequebookAddress={chequebook.isLoading}
|
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
|
||||||
/>
|
}}
|
||||||
)}
|
>
|
||||||
<NodeSetupWorkflow
|
<StepButton
|
||||||
nodeVersion={nodeVersion}
|
disabled={isLoading}
|
||||||
ethereumConnection={ethereumConnection}
|
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
|
||||||
debugApiConnection={debugApiConnection}
|
style={{ justifyContent: 'space-between' }}
|
||||||
apiConnection={apiConnection}
|
>
|
||||||
topology={topology}
|
<div style={{ display: 'flex' }}>
|
||||||
chequebook={chequebook}
|
<div style={{ marginTop: '5px' }}>{label}</div>
|
||||||
/>
|
<div style={{ marginLeft: '12px' }}>
|
||||||
|
{index === activeStep ? <ExpandLessSharp /> : <ExpandMoreSharp />}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</StepButton>
|
||||||
|
</StepLabel>
|
||||||
|
<StepContent>
|
||||||
|
<Typography component="div">{component}</Typography>
|
||||||
|
<div className={classes.actionsContainer}>
|
||||||
|
<div>
|
||||||
|
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" color="primary" onClick={handleNext} className={classes.button}>
|
||||||
|
{index < steps.length - 1 ? 'Next' : 'Finish'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StepContent>
|
||||||
|
</Step>
|
||||||
|
))}
|
||||||
|
</Stepper>
|
||||||
|
</Paper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ReactElement } from 'react'
|
||||||
|
import { Switch } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { Route } from 'react-router-dom'
|
||||||
|
|
||||||
|
import Info from './pages/info'
|
||||||
|
import Status from './pages/status'
|
||||||
|
import Files from './pages/files'
|
||||||
|
import Peers from './pages/peers'
|
||||||
|
import Accounting from './pages/accounting'
|
||||||
|
import Settings from './pages/settings'
|
||||||
|
import Stamps from './pages/stamps'
|
||||||
|
|
||||||
|
export enum ROUTES {
|
||||||
|
INFO = '/',
|
||||||
|
FILES = '/files',
|
||||||
|
PEERS = '/peers',
|
||||||
|
ACCOUNTING = '/accounting',
|
||||||
|
SETTINGS = '/settings',
|
||||||
|
STAMPS = '/stamps',
|
||||||
|
STATUS = '/status',
|
||||||
|
}
|
||||||
|
|
||||||
|
const BaseRouter = (): ReactElement => (
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={ROUTES.INFO} component={Info} />
|
||||||
|
<Route exact path={ROUTES.FILES} component={Files} />
|
||||||
|
<Route exact path={ROUTES.PEERS} component={Peers} />
|
||||||
|
<Route exact path={ROUTES.ACCOUNTING} component={Accounting} />
|
||||||
|
<Route exact path={ROUTES.SETTINGS} component={Settings} />
|
||||||
|
<Route exact path={ROUTES.STAMPS} component={Stamps} />
|
||||||
|
<Route exact path={ROUTES.STATUS} component={Status} />
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default BaseRouter
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import type { JSXElementConstructor, ReactElement } from 'react'
|
|
||||||
import { Route, RouteComponentProps } from 'react-router-dom'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
component: JSXElementConstructor<RouteComponentProps>
|
|
||||||
layout: JSXElementConstructor<RouteComponentProps>
|
|
||||||
exact?: boolean
|
|
||||||
path: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppRoute = ({ component: Component, layout: Layout, exact, path }: Props): ReactElement => (
|
|
||||||
<Route
|
|
||||||
exact={exact}
|
|
||||||
path={path}
|
|
||||||
render={props => (
|
|
||||||
<Layout {...props}>
|
|
||||||
<Component {...props} />
|
|
||||||
</Layout>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default AppRoute
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import type { ReactElement } from 'react'
|
|
||||||
import { Switch } from 'react-router-dom'
|
|
||||||
|
|
||||||
import AppRoute from './AppRoute'
|
|
||||||
|
|
||||||
// layouts
|
|
||||||
import Dashboard from '../layout/Dashboard'
|
|
||||||
|
|
||||||
// pages
|
|
||||||
import Status from '../pages/status'
|
|
||||||
import Files from '../pages/files'
|
|
||||||
import Peers from '../pages/peers'
|
|
||||||
import Accounting from '../pages/accounting'
|
|
||||||
import Settings from '../pages/settings'
|
|
||||||
import Stamps from '../pages/stamps'
|
|
||||||
|
|
||||||
const BaseRouter = (): ReactElement => (
|
|
||||||
<Switch>
|
|
||||||
<AppRoute exact path="/" layout={Dashboard} component={Status} />
|
|
||||||
<AppRoute exact path="/files/" layout={Dashboard} component={Files} />
|
|
||||||
<AppRoute exact path="/peers/" layout={Dashboard} component={Peers} />
|
|
||||||
<AppRoute exact path="/accounting/" layout={Dashboard} component={Accounting} />
|
|
||||||
<AppRoute exact path="/settings/" layout={Dashboard} component={Settings} />
|
|
||||||
<AppRoute exact path="/stamps/" layout={Dashboard} component={Stamps} />
|
|
||||||
</Switch>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default BaseRouter
|
|
||||||
@@ -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()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
+9
-26
@@ -9,6 +9,12 @@ declare module '@material-ui/core/styles/createPalette' {
|
|||||||
|
|
||||||
// Overwriting default components styles
|
// Overwriting default components styles
|
||||||
const componentsOverrides = (theme: Theme) => ({
|
const componentsOverrides = (theme: Theme) => ({
|
||||||
|
MuiDrawer: {
|
||||||
|
paper: {
|
||||||
|
width: 300,
|
||||||
|
backgroundColor: '#212121',
|
||||||
|
},
|
||||||
|
},
|
||||||
MuiTab: {
|
MuiTab: {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
@@ -55,7 +61,7 @@ const propsOverrides = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lightTheme = createMuiTheme({
|
export const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
background: {
|
background: {
|
||||||
@@ -75,28 +81,5 @@ export const lightTheme = createMuiTheme({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const darkTheme = createMuiTheme({
|
theme.overrides = componentsOverrides(theme)
|
||||||
palette: {
|
theme.props = propsOverrides
|
||||||
type: 'dark',
|
|
||||||
background: {
|
|
||||||
default: '#0d1117',
|
|
||||||
paper: '#161b22',
|
|
||||||
},
|
|
||||||
primary: {
|
|
||||||
light: orange.A200,
|
|
||||||
main: '#dd7700',
|
|
||||||
dark: orange[800],
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
main: '#1f2937',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
typography: {
|
|
||||||
fontFamily: ['Work Sans', 'Montserrat', 'Nunito', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'].join(','),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
darkTheme.overrides = componentsOverrides(darkTheme)
|
|
||||||
darkTheme.props = propsOverrides
|
|
||||||
lightTheme.overrides = componentsOverrides(lightTheme)
|
|
||||||
lightTheme.props = propsOverrides
|
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ describe('utils', () => {
|
|||||||
wrongValues.forEach(v => {
|
wrongValues.forEach(v => {
|
||||||
test(`testing ${v}`, () => {
|
test(`testing ${v}`, () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
expect(() => makeBigNumber((v as unknown) as any)).toThrow()
|
expect(() => makeBigNumber(v as unknown as any)).toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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