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_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||
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
|
||||
+1
-1
@@ -2,5 +2,5 @@ REACT_APP_BEE_HOST=http://localhost:1633
|
||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||
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
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
|
||||
@@ -42,10 +42,10 @@ jobs:
|
||||
- name: Commit linting
|
||||
uses: wagoid/commitlint-github-action@v2
|
||||
|
||||
# - name: Code linting
|
||||
# run: npm run lint:check
|
||||
# env:
|
||||
# CI: true
|
||||
- name: Code linting
|
||||
run: npm run lint:check
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
+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.**
|
||||
|
||||
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 |
|
||||
@@ -67,6 +69,8 @@ git clone git@github.com:ethersphere/bee-dashboard.git
|
||||
|
||||
cd bee-dashboard
|
||||
|
||||
npm i
|
||||
|
||||
npm start
|
||||
```
|
||||
|
||||
@@ -82,8 +86,10 @@ There are some ways you can make this module better:
|
||||
|
||||
## Maintainers
|
||||
|
||||
- [nugaon](https://github.com/nugaon)
|
||||
- [vojtechsimetka](https://github.com/vojtechsimetka)
|
||||
- [Cafe137](https://github.com/Cafe137)
|
||||
|
||||
See what "Maintainer" means [here](https://github.com/ethersphere/repo-maintainer).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Generated
+11026
-6882
File diff suppressed because it is too large
Load Diff
+44
-44
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"keywords": [
|
||||
"bee",
|
||||
@@ -24,52 +24,52 @@
|
||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersphere/bee-js": "^1.0.0",
|
||||
"@material-ui/core": "^4.11.4",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"@types/react-router": "^5.1.13",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"feather-icons": "^4.28.0",
|
||||
"formik": "^2.2.8",
|
||||
"formik-material-ui": "^3.0.1",
|
||||
"material-ui-dropzone": "^3.5.0",
|
||||
"notistack": "^1.0.9",
|
||||
"opener": "^1.5.2",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-identicons": "^1.2.5",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-syntax-highlighter": "^15.4.3",
|
||||
"semver": "^7.3.2",
|
||||
"serve-handler": "^6.1.3"
|
||||
"@ethersphere/bee-js": "1.2.1",
|
||||
"@material-ui/core": "4.12.3",
|
||||
"@material-ui/icons": "4.11.2",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"@types/react-router": "5.1.13",
|
||||
"@types/react-router-dom": "5.1.7",
|
||||
"axios": "0.21.1",
|
||||
"bignumber.js": "9.0.1",
|
||||
"feather-icons": "4.28.0",
|
||||
"formik": "2.2.8",
|
||||
"formik-material-ui": "3.0.1",
|
||||
"material-ui-dropzone": "3.5.0",
|
||||
"notistack": "1.0.9",
|
||||
"opener": "1.5.2",
|
||||
"qrcode.react": "1.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-copy-to-clipboard": "5.0.3",
|
||||
"react-dom": "17.0.2",
|
||||
"react-feather": "2.0.9",
|
||||
"react-identicons": "1.2.5",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-syntax-highlighter": "15.4.3",
|
||||
"semver": "7.3.2",
|
||||
"serve-handler": "6.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^13.1.5",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/qrcode.react": "^1.0.1",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-syntax-highlighter": "^13.5.0",
|
||||
"@types/semver": "^7.3.6",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-prettier": "^8.2.0",
|
||||
"eslint-plugin-jest": "^24.3.5",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"prettier": "^2.2.1",
|
||||
"@testing-library/jest-dom": "5.12.0",
|
||||
"@testing-library/react": "11.2.6",
|
||||
"@testing-library/user-event": "13.1.5",
|
||||
"@types/jest": "26.0.22",
|
||||
"@types/node": "14.14.41",
|
||||
"@types/qrcode.react": "1.0.1",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-copy-to-clipboard": "5.0.0",
|
||||
"@types/react-dom": "17.0.3",
|
||||
"@types/react-syntax-highlighter": "13.5.0",
|
||||
"@types/semver": "7.3.6",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-jest": "24.4.0",
|
||||
"eslint-plugin-prettier": "3.4.1",
|
||||
"eslint-plugin-react": "7.24.0",
|
||||
"prettier": "2.3.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.2.4",
|
||||
"web-vitals": "^1.1.1"
|
||||
"typescript": "4.2.4",
|
||||
"web-vitals": "1.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
|
||||
+1
-1
@@ -10,6 +10,6 @@ declare module 'react-identicons' {
|
||||
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
|
||||
}
|
||||
|
||||
+21
-32
@@ -1,53 +1,42 @@
|
||||
import { ReactElement, useEffect, useState } from 'react'
|
||||
import { ReactElement } from 'react'
|
||||
import { BrowserRouter as Router } from 'react-router-dom'
|
||||
import './App.css'
|
||||
|
||||
import { ThemeProvider } from '@material-ui/styles'
|
||||
import { ThemeProvider } from '@material-ui/core/styles'
|
||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||
import { SnackbarProvider } from 'notistack'
|
||||
|
||||
import BaseRouter from './routes/routes'
|
||||
import { lightTheme, darkTheme } from './theme'
|
||||
import BaseRouter from './routes'
|
||||
import Dashboard from './layout/Dashboard'
|
||||
import { theme } from './theme'
|
||||
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 [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 (
|
||||
const App = (): ReactElement => (
|
||||
<div className="App">
|
||||
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<SettingsProvider>
|
||||
<BeeProvider>
|
||||
<StampsProvider>
|
||||
<PlatformProvider>
|
||||
<SnackbarProvider>
|
||||
<Router>
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Router>
|
||||
<Dashboard>
|
||||
<BaseRouter />
|
||||
</Router>
|
||||
</Dashboard>
|
||||
</>
|
||||
</Router>
|
||||
</SnackbarProvider>
|
||||
</PlatformProvider>
|
||||
</StampsProvider>
|
||||
</BeeProvider>
|
||||
</SettingsProvider>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
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 { Alert, AlertTitle } from '@material-ui/lab'
|
||||
import Collapse from '@material-ui/core/Collapse'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
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'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
@@ -18,12 +18,12 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
|
||||
export default function VersionAlert(): ReactElement | null {
|
||||
const classes = useStyles()
|
||||
const { isLoading, userVersion } = useStatusNodeVersion()
|
||||
const { isLoading, latestUserVersionExact } = useContext(Context)
|
||||
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 (
|
||||
<Collapse in={!isExactlySupportedBeeVersion && open}>
|
||||
@@ -44,9 +44,9 @@ export default function VersionAlert(): ReactElement | null {
|
||||
}
|
||||
>
|
||||
<AlertTitle>Warning</AlertTitle>
|
||||
Your Bee node version (<code>{userVersion}</code>) does not exactly match the Bee version we tested the Bee
|
||||
Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality may not
|
||||
work properly.
|
||||
Your Bee node version (<code>{latestUserVersionExact}</code>) does not exactly match the Bee version we tested
|
||||
the Bee Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality
|
||||
may not work properly.
|
||||
</Alert>
|
||||
</div>
|
||||
</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 Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import { Container, CircularProgress } from '@material-ui/core'
|
||||
import { useSnackbar } from 'notistack'
|
||||
|
||||
import { beeDebugApi } from '../services/bee'
|
||||
|
||||
import { ReactElement, useState, useContext } from 'react'
|
||||
import { Context as SettingsContext } from '../providers/Settings'
|
||||
import EthereumAddress from './EthereumAddress'
|
||||
|
||||
interface Props {
|
||||
@@ -17,10 +15,11 @@ interface Props {
|
||||
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 [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true)
|
||||
@@ -31,16 +30,18 @@ export default function DepositModal({ peerId, uncashedAmount }: Props): ReactEl
|
||||
}
|
||||
|
||||
const handleCashout = () => {
|
||||
if (!beeDebugApi) return
|
||||
|
||||
if (peerId) {
|
||||
setLoadingCashout(true)
|
||||
beeDebugApi.chequebook
|
||||
.peerCashout(peerId)
|
||||
beeDebugApi
|
||||
.cashoutLastCheque(peerId)
|
||||
.then(res => {
|
||||
setOpen(false)
|
||||
enqueueSnackbar(
|
||||
<span>
|
||||
Successfully cashed out cheque. Transaction
|
||||
<EthereumAddress hideBlockie transaction address={res} network={'goerli'} />
|
||||
<EthereumAddress hideBlockie transaction address={res} />
|
||||
</span>,
|
||||
{ variant: 'success' },
|
||||
)
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { withStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||
import { Tabs, Tab, Box, Typography } from '@material-ui/core'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import TabsContainer from './TabsContainer'
|
||||
import CodeBlock from './CodeBlock'
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode
|
||||
index: number
|
||||
value: number
|
||||
}
|
||||
import { Context } from '../providers/Platform'
|
||||
|
||||
interface Props {
|
||||
linux: string
|
||||
@@ -15,133 +9,23 @@ interface Props {
|
||||
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 {
|
||||
const [value, setValue] = React.useState(0)
|
||||
|
||||
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
|
||||
const { platform, setPlatform } = useContext(Context)
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box style={{ marginTop: '-12px' }}>
|
||||
<Typography component="div">{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const AntTabs = withStyles({
|
||||
root: {
|
||||
borderBottom: '1px solid #e8e8e8',
|
||||
<TabsContainer
|
||||
index={platform}
|
||||
indexChanged={setPlatform}
|
||||
values={[
|
||||
{
|
||||
label: 'Linux',
|
||||
component: <CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.linux} />,
|
||||
},
|
||||
indicator: {
|
||||
backgroundColor: '#3f51b5',
|
||||
{
|
||||
label: 'macOS',
|
||||
component: <CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.mac} />,
|
||||
},
|
||||
})(Tabs)
|
||||
|
||||
interface StyledTabProps {
|
||||
label: string
|
||||
}
|
||||
|
||||
const AntTab = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
minWidth: 72,
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
marginRight: theme.spacing(4),
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
].join(','),
|
||||
'&:hover': {
|
||||
color: '#3f51b5',
|
||||
opacity: 1,
|
||||
},
|
||||
'&$selected': {
|
||||
color: '#3f51b5',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
},
|
||||
'&:focus': {
|
||||
color: '#3f51b5',
|
||||
},
|
||||
},
|
||||
selected: {},
|
||||
}),
|
||||
)((props: StyledTabProps) => <Tab disableRipple {...props} />)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
|
||||
<AntTab label="Linux" {...a11yProps(0)} />
|
||||
<AntTab label="MacOS" {...a11yProps(1)} />
|
||||
</AntTabs>
|
||||
<TabPanel value={value} index={0}>
|
||||
<CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.linux} />
|
||||
</TabPanel>
|
||||
<TabPanel value={value} index={1}>
|
||||
<CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.mac} />
|
||||
</TabPanel>
|
||||
</div>
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { TextField, Button, CircularProgress, Container } from '@material-ui/core'
|
||||
import { TextField, Button } from '@material-ui/core'
|
||||
|
||||
interface Props {
|
||||
defaultHost?: string
|
||||
hostName: string
|
||||
setHost: (host: string) => void
|
||||
}
|
||||
|
||||
export default function ConnectToHost(props: Props): ReactElement {
|
||||
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
|
||||
const [connectingToHost, setConnectingToHost] = useState(false)
|
||||
const [host, setHost] = useState('')
|
||||
|
||||
const handleNewHostConnection = () => {
|
||||
if (host) {
|
||||
setConnectingToHost(true)
|
||||
sessionStorage.setItem(props.hostName, host)
|
||||
props.setHost(host)
|
||||
toggleHostInputVisibility(!hostInputVisible)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
// FIXME: this should be broken up
|
||||
/* eslint-disable no-nested-ternary */
|
||||
hostInputVisible ? (
|
||||
{hostInputVisible ? (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<TextField
|
||||
defaultValue={props.defaultHost}
|
||||
@@ -46,17 +40,11 @@ export default function ConnectToHost(props: Props): ReactElement {
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
) : connectingToHost ? (
|
||||
<Container style={{ textAlign: 'center', padding: '0px' }}>
|
||||
<CircularProgress size={20} />
|
||||
</Container>
|
||||
) : (
|
||||
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size="small" variant="outlined">
|
||||
Change host
|
||||
</Button>
|
||||
)
|
||||
/* eslint-enable no-nested-ternary */
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { ReactElement } from 'react'
|
||||
|
||||
interface Props {
|
||||
address: string | undefined
|
||||
network?: string
|
||||
hideBlockie?: boolean
|
||||
transaction?: boolean
|
||||
truncate?: boolean
|
||||
@@ -37,9 +36,9 @@ export default function EthereumAddress(props: Props): ReactElement {
|
||||
}
|
||||
: { marginRight: '7px' }
|
||||
}
|
||||
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${
|
||||
props.transaction ? 'tx' : 'address'
|
||||
}/${props.address}`}
|
||||
href={`${process.env.REACT_APP_BLOCKCHAIN_EXPLORER_URL}/${props.transaction ? 'tx' : 'address'}/${
|
||||
props.address
|
||||
}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||
import { Card, CardContent, Typography } from '@material-ui/core/'
|
||||
|
||||
import EthereumAddress from '../components/EthereumAddress'
|
||||
import { Skeleton } from '@material-ui/lab'
|
||||
|
||||
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
|
||||
|
||||
@@ -28,9 +27,7 @@ const useStyles = makeStyles(() =>
|
||||
|
||||
interface Props {
|
||||
nodeAddresses: NodeAddresses | null
|
||||
isLoadingNodeAddresses: boolean
|
||||
chequebookAddress: ChequebookAddressResponse | null
|
||||
isLoadingChequebookAddress: boolean
|
||||
}
|
||||
|
||||
function EthereumAddressCard(props: Props): ReactElement {
|
||||
@@ -38,36 +35,23 @@ function EthereumAddressCard(props: Props): ReactElement {
|
||||
|
||||
return (
|
||||
<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}>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Ethereum Address
|
||||
</Typography>
|
||||
<EthereumAddress address={props.nodeAddresses?.ethereum} network={'goerli'} />
|
||||
<EthereumAddress address={props.nodeAddresses?.ethereum} />
|
||||
</CardContent>
|
||||
</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}>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Chequebook Contract Address
|
||||
</Typography>
|
||||
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} network={'goerli'} />
|
||||
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
|
||||
</CardContent>
|
||||
</div>
|
||||
)}
|
||||
</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 { Link, RouteComponentProps } from 'react-router-dom'
|
||||
import type { ReactElement } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
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 { 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 { Health } from '@ethersphere/bee-js'
|
||||
|
||||
const drawerWidth = 240
|
||||
import Logo from '../assets/logo.svg'
|
||||
|
||||
const navBarItems = [
|
||||
{
|
||||
label: 'Status',
|
||||
id: 'status',
|
||||
path: '/',
|
||||
icon: Activity,
|
||||
label: 'Info',
|
||||
path: ROUTES.INFO,
|
||||
icon: Home,
|
||||
},
|
||||
{
|
||||
label: 'Files',
|
||||
id: 'files',
|
||||
path: '/files/',
|
||||
path: ROUTES.FILES,
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
label: 'Stamps',
|
||||
id: 'stamps',
|
||||
path: '/stamps/',
|
||||
path: ROUTES.STAMPS,
|
||||
icon: Layers,
|
||||
},
|
||||
{
|
||||
label: 'Accounting',
|
||||
id: 'accounting',
|
||||
path: '/accounting/',
|
||||
path: ROUTES.ACCOUNTING,
|
||||
icon: DollarSign,
|
||||
},
|
||||
{
|
||||
label: 'Peers',
|
||||
id: 'peers',
|
||||
path: '/peers/',
|
||||
path: ROUTES.PEERS,
|
||||
icon: Share2,
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
id: 'settings',
|
||||
path: '/settings/',
|
||||
path: ROUTES.SETTINGS,
|
||||
icon: Settings,
|
||||
},
|
||||
]
|
||||
@@ -53,122 +47,82 @@ const navBarItems = [
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
appBar: {
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
marginLeft: drawerWidth,
|
||||
flexWrap: 'nowrap',
|
||||
minHeight: '100vh',
|
||||
paddingTop: theme.spacing(8),
|
||||
paddingBottom: theme.spacing(8),
|
||||
},
|
||||
logo: {
|
||||
padding: 1,
|
||||
marginTop: 20,
|
||||
marginLeft: theme.spacing(8),
|
||||
marginRight: theme.spacing(8),
|
||||
},
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
icon: {
|
||||
height: theme.spacing(4),
|
||||
},
|
||||
drawerPaper: {
|
||||
width: drawerWidth,
|
||||
iconSmall: {
|
||||
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 {
|
||||
themeMode: string
|
||||
health: boolean
|
||||
nodeHealth: Health | null
|
||||
}
|
||||
|
||||
export default function SideBar(props: Props): ReactElement {
|
||||
export default function SideBar(): ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Drawer
|
||||
className={classes.drawer}
|
||||
variant="permanent"
|
||||
classes={{
|
||||
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' }}
|
||||
/>
|
||||
<Drawer variant="permanent">
|
||||
<Grid container direction="column" justifyContent="space-between" className={classes.root}>
|
||||
<Grid className={classes.logo}>
|
||||
<Link to={ROUTES.INFO}>
|
||||
<img alt="swarm" src={Logo} />
|
||||
</Link>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<List>
|
||||
{navBarItems.map(item => (
|
||||
<Link to={item.path} key={item.id} style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||
<ListItem
|
||||
button
|
||||
selected={props.location.pathname === item.path}
|
||||
className={props.location.pathname === item.path ? classes.activeSideBarItem : ''}
|
||||
>
|
||||
<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 : ''}
|
||||
{navBarItems.map(p => (
|
||||
<Link to={p.path} key={p.path} className={classes.link}>
|
||||
<SideBarItem
|
||||
key={p.path}
|
||||
iconStart={<p.icon className={classes.icon} />}
|
||||
path={p.path}
|
||||
label={p.label}
|
||||
/>
|
||||
</ListItem>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
<Divider className={classes.divider} />
|
||||
<List>
|
||||
<MUILink href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" style={{ textDecoration: 'none' }}>
|
||||
<ListItem button>
|
||||
<ListItemText primary={'Docs'} />
|
||||
<OpenInNewSharp fontSize="small" />
|
||||
</ListItem>
|
||||
<MUILink href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" className={classes.link}>
|
||||
<SideBarItem
|
||||
iconStart={<BookOpen className={classes.icon} />}
|
||||
iconEnd={<OpenInNewSharp className={classes.iconSmall} />}
|
||||
label={<span>Docs</span>}
|
||||
/>
|
||||
</MUILink>
|
||||
</List>
|
||||
<div style={{ position: 'fixed', bottom: 0, width: 'inherit', padding: '10px' }}>
|
||||
<ListItem>
|
||||
<div style={{ marginRight: '30px' }}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: props.health ? '#32c48d' : '#c9201f',
|
||||
marginRight: '7px',
|
||||
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>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Link to={ROUTES.STATUS} className={classes.link}>
|
||||
<SideBarStatus path={ROUTES.STATUS} />
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</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 { makeStyles } from '@material-ui/core/styles'
|
||||
import { Skeleton } from '@material-ui/lab'
|
||||
import type { ReactElement } from 'react'
|
||||
import { Title } from './Title'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
minWidth: 275,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
},
|
||||
pos: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
statistic?: string
|
||||
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()
|
||||
|
||||
return (
|
||||
@@ -36,9 +31,7 @@ export default function StatCard({ loading, label, statistic }: Props): ReactEle
|
||||
)}
|
||||
{!loading && (
|
||||
<>
|
||||
<Typography className={classes.title} color="textSecondary" gutterBottom>
|
||||
{label}
|
||||
</Typography>
|
||||
<Title label={label} tooltip={tooltip} />
|
||||
<Typography variant="h5" component="h2">
|
||||
{statistic}
|
||||
</Typography>
|
||||
|
||||
@@ -15,13 +15,7 @@ function TabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
<div role="tabpanel" hidden={value !== index} {...other}>
|
||||
{value === index && (
|
||||
<Box p={3}>
|
||||
<Typography>{children}</Typography>
|
||||
@@ -44,25 +38,30 @@ interface TabsValues {
|
||||
|
||||
interface Props {
|
||||
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 [value, setValue] = React.useState<number>(0)
|
||||
const [value, setValue] = React.useState<number>(index || 0)
|
||||
|
||||
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 (
|
||||
<div className={classes.root}>
|
||||
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
|
||||
{values.map(({ label }, index) => (
|
||||
<Tab key={index} label={label} />
|
||||
<Tabs value={v} onChange={handleChange}>
|
||||
{values.map(({ label }, idx) => (
|
||||
<Tab key={idx} label={label} />
|
||||
))}
|
||||
</Tabs>
|
||||
{values.map(({ component }, index) => (
|
||||
<TabPanel key={index} value={value} index={index}>
|
||||
{values.map(({ component }, idx) => (
|
||||
<TabPanel key={idx} value={v} index={idx}>
|
||||
{component}
|
||||
</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 { Grid } from '@material-ui/core/'
|
||||
import type { ReactElement } from 'react'
|
||||
import { pickThreshold, ThresholdValues } from '../utils/threshold'
|
||||
import StatCard from './StatCard'
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean
|
||||
interface RootProps {
|
||||
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 container spacing={3}>
|
||||
<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 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 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>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { Card, CardContent, Typography } from '@material-ui/core/'
|
||||
import { ROUTES } from '../routes'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
@@ -26,7 +27,7 @@ export default function TroubleshootConnectionCard(): ReactElement {
|
||||
</Typography>
|
||||
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
|
||||
<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">
|
||||
Swarm Bee Docs
|
||||
</a>
|
||||
|
||||
@@ -21,7 +21,7 @@ interface Props {
|
||||
action: (amount: bigint) => Promise<string>
|
||||
}
|
||||
|
||||
export default function WithdrawModal({
|
||||
export default function WithdrawDepositModal({
|
||||
successMessage,
|
||||
errorMessage,
|
||||
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 { beeDebugApi } from '../services/bee'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import { Context as SettingsContext } from '../providers/Settings'
|
||||
|
||||
import WDModal from '../components/WDModal'
|
||||
import WithdrawDepositModal from '../components/WithdrawDepositModal'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
export default function DepositModal(): ReactElement {
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
return (
|
||||
<WDModal
|
||||
<WithdrawDepositModal
|
||||
successMessage="Successful deposit."
|
||||
errorMessage="Error with depositing"
|
||||
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
||||
label="Deposit"
|
||||
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 { beeDebugApi } from '../services/bee'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import { Context as SettingsContext } from '../providers/Settings'
|
||||
|
||||
import WDModal from '../components/WDModal'
|
||||
import WithdrawDepositModal from '../components/WithdrawDepositModal'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
export default function WithdrawModal(): ReactElement {
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
return (
|
||||
<WDModal
|
||||
<WithdrawDepositModal
|
||||
successMessage="Successful withdrawl."
|
||||
errorMessage="Error with withdrawing."
|
||||
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
||||
label="Withdraw"
|
||||
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 { Token } from '../models/Token'
|
||||
import { beeDebugApi } from '../services/bee'
|
||||
import { Balance, Settlement, useApiPeerBalances, useApiSettlements } from './apiHooks'
|
||||
import { makeRetriablePromise, unwrapPromiseSettlements } from '../utils'
|
||||
import { Balance, Settlements, Settlement } from '../types'
|
||||
|
||||
interface UseAccountingHook {
|
||||
isLoading: boolean
|
||||
isLoadingUncashed: boolean
|
||||
error: Error | null
|
||||
totalsent: Token
|
||||
totalreceived: Token
|
||||
accounting: Accounting[] | null
|
||||
}
|
||||
|
||||
@@ -76,39 +72,34 @@ function mergeAccounting(
|
||||
)
|
||||
}
|
||||
|
||||
export const useAccounting = (): UseAccountingHook => {
|
||||
const settlements = useApiSettlements()
|
||||
const balances = useApiPeerBalances()
|
||||
|
||||
const [err, setErr] = useState<Error | null>(null)
|
||||
export const useAccounting = (
|
||||
beeDebugApi: BeeDebug | null,
|
||||
settlements: Settlements | null,
|
||||
balances: Balance[] | null,
|
||||
): UseAccountingHook => {
|
||||
const [isLoadingUncashed, setIsloadingUncashed] = useState<boolean>(false)
|
||||
const [uncashedAmounts, setUncashedAmounts] = useState<LastCashoutActionResponse[] | undefined>(undefined)
|
||||
|
||||
const error = balances.error || settlements.error || err
|
||||
|
||||
useEffect(() => {
|
||||
// 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)
|
||||
const promises = settlements.settlements.settlements
|
||||
const promises = settlements.settlements
|
||||
.filter(({ received }) => received.toBigNumber.gt('0'))
|
||||
.map(({ peer }) => beeDebugApi.chequebook.getPeerLastCashout(peer))
|
||||
.map(({ peer }) => makeRetriablePromise(() => beeDebugApi.getLastCashoutAction(peer)))
|
||||
|
||||
Promise.all(promises)
|
||||
.then(setUncashedAmounts)
|
||||
.catch(setErr)
|
||||
.finally(() => setIsloadingUncashed(false))
|
||||
}, [settlements, isLoadingUncashed, uncashedAmounts, error])
|
||||
Promise.allSettled(promises).then(settlements => {
|
||||
const results = unwrapPromiseSettlements(settlements)
|
||||
setUncashedAmounts(results.fulfilled)
|
||||
setIsloadingUncashed(false)
|
||||
})
|
||||
}, [settlements, isLoadingUncashed, uncashedAmounts])
|
||||
|
||||
const accounting = mergeAccounting(balances.peerBalances, settlements.settlements?.settlements, uncashedAmounts)
|
||||
const accounting = mergeAccounting(balances, settlements?.settlements, uncashedAmounts)
|
||||
|
||||
return {
|
||||
isLoading: settlements.isLoadingSettlements || balances.isLoadingPeerBalances,
|
||||
isLoadingUncashed,
|
||||
error,
|
||||
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 {
|
||||
NodeAddresses,
|
||||
ChequebookAddressResponse,
|
||||
LastChequesResponse,
|
||||
Health,
|
||||
Peer,
|
||||
Topology,
|
||||
LastChequesForPeerResponse,
|
||||
} from '@ethersphere/bee-js'
|
||||
|
||||
import { beeDebugApi, beeApi } from '../services/bee'
|
||||
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 {
|
||||
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 AlertVersion from '../components/AlertVersion'
|
||||
import { Container, CircularProgress } from '@material-ui/core'
|
||||
|
||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
import SideBar from '../components/SideBar'
|
||||
import NavBar from '../components/NavBar'
|
||||
|
||||
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks'
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
import { Context } from '../providers/Bee'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
content: {
|
||||
marginLeft: '240px',
|
||||
marginLeft: 300,
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing(3),
|
||||
@@ -22,46 +21,28 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
}),
|
||||
)
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
interface Props {
|
||||
children?: ReactElement
|
||||
}
|
||||
|
||||
const Dashboard = (props: Props): ReactElement => {
|
||||
const classes = useStyles()
|
||||
|
||||
const [themeMode, toggleThemeMode] = useState('light')
|
||||
|
||||
// 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')
|
||||
})
|
||||
}, [])
|
||||
const { isLoading } = useContext(Context)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
|
||||
<NavBar themeMode={themeMode} />
|
||||
<SideBar />
|
||||
<ErrorBoundary>
|
||||
<main className={classes.content}>
|
||||
<AlertVersion />
|
||||
{props.children}
|
||||
{isLoading ? (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</main>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ReactElement } from 'react'
|
||||
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||
import { Card, CardContent, Typography, Theme } from '@material-ui/core/'
|
||||
import { Skeleton } from '@material-ui/lab'
|
||||
import WithdrawModal from '../../containers/WithdrawModal'
|
||||
import DepositModal from '../../containers/DepositModal'
|
||||
|
||||
@@ -45,12 +44,11 @@ interface ChequebookBalance {
|
||||
interface Props {
|
||||
chequebookAddress: ChequebookAddressResponse | null
|
||||
chequebookBalance: ChequebookBalance | null
|
||||
totalsent: Token
|
||||
totalreceived: Token
|
||||
isLoading: boolean
|
||||
totalsent?: Token
|
||||
totalreceived?: Token
|
||||
}
|
||||
|
||||
function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }: Props): ReactElement {
|
||||
function AccountCard({ totalreceived, totalsent, chequebookBalance }: Props): ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
@@ -66,7 +64,6 @@ function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }:
|
||||
</div>
|
||||
|
||||
<Card className={classes.root}>
|
||||
{!isLoading && (
|
||||
<CardContent className={classes.gridContainer}>
|
||||
<div>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
@@ -85,18 +82,10 @@ function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }:
|
||||
Total Sent / Received
|
||||
</Typography>
|
||||
<Typography variant="h5">
|
||||
{totalsent.toFixedDecimal()} / {totalreceived.toFixedDecimal()} BZZ
|
||||
{totalsent?.toFixedDecimal()} / {totalreceived?.toFixedDecimal()} BZZ
|
||||
</Typography>
|
||||
</div>
|
||||
</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>
|
||||
</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 { Container, CircularProgress } from '@material-ui/core'
|
||||
|
||||
import AccountCard from '../accounting/AccountCard'
|
||||
import BalancesTable from './BalancesTable'
|
||||
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||
|
||||
import {
|
||||
useApiNodeAddresses,
|
||||
useApiChequebookAddress,
|
||||
useApiChequebookBalance,
|
||||
useApiHealth,
|
||||
useDebugApiHealth,
|
||||
} from '../../hooks/apiHooks'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { useAccounting } from '../../hooks/accounting'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
@@ -29,44 +22,24 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
export default function Accounting(): ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
||||
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
||||
const { health, isLoadingHealth } = useApiHealth()
|
||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||
const { isLoading, totalsent, totalreceived, accounting, isLoadingUncashed, error } = useAccounting()
|
||||
const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements, peerBalances } =
|
||||
useContext(BeeContext)
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
||||
return (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
const { accounting, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances)
|
||||
|
||||
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
||||
if (!status.all) return <TroubleshootConnectionCard />
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AccountCard
|
||||
chequebookAddress={chequebookAddress}
|
||||
isLoading={isLoadingChequebookAddress || isLoading || isLoadingChequebookBalance}
|
||||
chequebookBalance={chequebookBalance}
|
||||
totalsent={totalsent}
|
||||
totalreceived={totalreceived}
|
||||
totalsent={settlements?.totalSent}
|
||||
totalreceived={settlements?.totalReceived}
|
||||
/>
|
||||
<EthereumAddressCard
|
||||
nodeAddresses={nodeAddresses}
|
||||
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} />}
|
||||
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
|
||||
<BalancesTable accounting={accounting} isLoadingUncashed={isLoadingUncashed} />
|
||||
</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 { Paper, InputBase, IconButton, FormHelperText } from '@material-ui/core'
|
||||
import { Search } from '@material-ui/icons'
|
||||
import { apiHost } from '../../constants'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Utils } from '@ethersphere/bee-js'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
@@ -28,6 +28,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
|
||||
export default function Files(): ReactElement {
|
||||
const classes = useStyles()
|
||||
const { apiUrl } = useContext(SettingsContext)
|
||||
|
||||
const [referenceInput, setReferenceInput] = useState('')
|
||||
const [referenceError, setReferenceError] = useState<Error | null>(null)
|
||||
@@ -50,7 +51,7 @@ export default function Files(): ReactElement {
|
||||
onChange={handleReferenceChange}
|
||||
/>
|
||||
<IconButton
|
||||
href={`${apiHost}/bzz/${referenceInput}`}
|
||||
href={`${apiUrl}/bzz/${referenceInput}`}
|
||||
target="_blank"
|
||||
disabled={referenceError !== null || !referenceInput}
|
||||
className={classes.iconButton}
|
||||
|
||||
@@ -8,7 +8,7 @@ import UploadSizeAlert from '../../components/AlertUploadSize'
|
||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||
import PeerDetailDrawer from '../../components/PeerDetail'
|
||||
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
|
||||
import { beeApi } from '../../services/bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
||||
import SelectStamp from './SelectStamp'
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function Files(): ReactElement {
|
||||
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
|
||||
|
||||
const { isLoading, error, stamps } = useContext(Context)
|
||||
const { beeApi } = useContext(SettingsContext)
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
// Choose a postage stamp that has the lowest usage
|
||||
@@ -40,8 +41,11 @@ export default function Files(): ReactElement {
|
||||
|
||||
const uploadFile = () => {
|
||||
if (file === null || selectedStamp === null) return
|
||||
|
||||
if (!beeApi) return
|
||||
|
||||
setIsUploadingFile(true)
|
||||
beeApi.files
|
||||
beeApi
|
||||
.uploadFile(selectedStamp.batchID, file)
|
||||
.then(hash => {
|
||||
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 { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
|
||||
import { Context } from '../../providers/Bee'
|
||||
import Download from './Download'
|
||||
import Upload from './Upload'
|
||||
import TabsContainer from '../../components/TabsContainer'
|
||||
|
||||
export default function Files(): ReactElement {
|
||||
const { health, isLoadingHealth } = useApiHealth()
|
||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||
const { status } = useContext(Context)
|
||||
|
||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
||||
return (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
if (!health || nodeHealth?.status !== 'ok') return <TroubleshootConnectionCard />
|
||||
if (!status.all) return <TroubleshootConnectionCard />
|
||||
|
||||
return (
|
||||
<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 {
|
||||
Table,
|
||||
@@ -10,12 +10,11 @@ import {
|
||||
Button,
|
||||
Paper,
|
||||
Tooltip,
|
||||
Container,
|
||||
CircularProgress,
|
||||
} from '@material-ui/core'
|
||||
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'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
@@ -26,47 +25,43 @@ const useStyles = makeStyles({
|
||||
|
||||
interface Props {
|
||||
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 {
|
||||
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) => {
|
||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: '', loading: true }])
|
||||
beeDebugApi.connectivity
|
||||
.ping(peerId)
|
||||
const pingPeer = (peerId: string) => {
|
||||
setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: '', loading: true } }))
|
||||
beeDebugApi
|
||||
?.pingPeer(peerId)
|
||||
.then(res => {
|
||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: res.rtt, loading: false }])
|
||||
setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: res.rtt, loading: false } }))
|
||||
})
|
||||
.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 (
|
||||
<div>
|
||||
<TableContainer component={Paper}>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Index</TableCell>
|
||||
@@ -75,7 +70,7 @@ function PeerTable(props: Props): ReactElement {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.peers.map((peer: Peer, idx: number) => (
|
||||
{props.peers?.map((peer: Peer, idx: number) => (
|
||||
<TableRow key={peer.address}>
|
||||
<TableCell component="th" scope="row">
|
||||
{idx + 1}
|
||||
@@ -83,21 +78,8 @@ function PeerTable(props: Props): ReactElement {
|
||||
<TableCell>{peer.address}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Tooltip title="Ping node">
|
||||
<Button color="primary" onClick={() => PingPeer(peer.address)}>
|
||||
{
|
||||
// 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 color="primary" onClick={() => pingPeer(peer.address)}>
|
||||
{getPingState(peerLatency, peer)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
import { Container, CircularProgress } from '@material-ui/core/'
|
||||
import PeerTable from './PeerTable'
|
||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||
|
||||
import { useApiNodeTopology, useApiNodePeers, useDebugApiHealth } from '../../hooks/apiHooks'
|
||||
import { Context } from '../../providers/Bee'
|
||||
import TopologyStats from '../../components/TopologyStats'
|
||||
import { ReactElement } from 'react'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
|
||||
export default function Peers(): ReactElement {
|
||||
const topology = useApiNodeTopology()
|
||||
const debugHealth = useDebugApiHealth()
|
||||
const peers = useApiNodePeers()
|
||||
const { topology, peers, status } = useContext(Context)
|
||||
|
||||
if (debugHealth.isLoadingNodeHealth) {
|
||||
return (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
if (debugHealth.error) {
|
||||
if (!status.all) {
|
||||
return <TroubleshootConnectionCard />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopologyStats {...topology} />
|
||||
<PeerTable {...peers} />
|
||||
<TopologyStats topology={topology} />
|
||||
<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 { Context as SettingsContext } from '../../providers/Settings'
|
||||
|
||||
export default function Settings(): ReactElement {
|
||||
const [refreshVisibility, toggleRefreshVisibility] = useState(false)
|
||||
const [host, setHost] = useState('')
|
||||
const [debugHost, setDebugHost] = useState('')
|
||||
const { apiUrl, apiDebugUrl, setApiUrl, setDebugApiUrl } = useContext(SettingsContext)
|
||||
const [host, setHost] = useState(apiUrl)
|
||||
const [debugHost, setDebugHost] = useState(apiDebugUrl)
|
||||
|
||||
const handleNewHostConnection = () => {
|
||||
if (host) {
|
||||
sessionStorage.setItem('api_host', host)
|
||||
const submit = () => {
|
||||
if (host !== apiUrl) setApiUrl(host)
|
||||
|
||||
if (debugHost !== apiDebugUrl) setDebugApiUrl(debugHost)
|
||||
}
|
||||
|
||||
if (debugHost) {
|
||||
sessionStorage.setItem('debug_api_host', debugHost)
|
||||
}
|
||||
|
||||
if (host || debugHost) {
|
||||
toggleRefreshVisibility(!refreshVisibility)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
const touched = host !== apiUrl || debugHost !== apiDebugUrl
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -34,16 +28,13 @@ export default function Settings(): ReactElement {
|
||||
placeholder="ex: 127.0.0.0.1:1633"
|
||||
helperText="Enter node host override / port"
|
||||
fullWidth
|
||||
defaultValue={
|
||||
sessionStorage.getItem('api_host') ? sessionStorage.getItem('api_host') : process.env.REACT_APP_BEE_HOST
|
||||
}
|
||||
defaultValue={apiUrl}
|
||||
margin="normal"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
onChange={e => {
|
||||
setHost(e.target.value)
|
||||
toggleRefreshVisibility(true)
|
||||
}}
|
||||
variant="filled"
|
||||
/>
|
||||
@@ -55,14 +46,9 @@ export default function Settings(): ReactElement {
|
||||
placeholder="ex: 127.0.0.0.1:1635"
|
||||
helperText="Enter node debug host override / port"
|
||||
fullWidth
|
||||
defaultValue={
|
||||
sessionStorage.getItem('debug_api_host')
|
||||
? sessionStorage.getItem('debug_api_host')
|
||||
: process.env.REACT_APP_BEE_DEBUG_HOST
|
||||
}
|
||||
defaultValue={apiDebugUrl}
|
||||
onChange={e => {
|
||||
setDebugHost(e.target.value)
|
||||
toggleRefreshVisibility(true)
|
||||
}}
|
||||
margin="normal"
|
||||
InputLabelProps={{
|
||||
@@ -71,9 +57,9 @@ export default function Settings(): ReactElement {
|
||||
variant="filled"
|
||||
/>
|
||||
</Paper>
|
||||
{refreshVisibility ? (
|
||||
{touched ? (
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<Button variant="outlined" color="primary" onClick={() => handleNewHostConnection()}>
|
||||
<Button variant="outlined" color="primary" onClick={submit}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { FormikHelpers, Form, Field, Formik } from 'formik'
|
||||
import { TextField } from 'formik-material-ui'
|
||||
import { beeApi } from '../../services/bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context } from '../../providers/Stamps'
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||
import { useSnackbar } from 'notistack'
|
||||
@@ -54,6 +54,7 @@ export default function FormDialog({ label }: Props): ReactElement {
|
||||
const classes = useStyles()
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const { refresh } = useContext(Context)
|
||||
const { beeApi } = useContext(SettingsContext)
|
||||
const handleClickOpen = () => setOpen(true)
|
||||
const handleClose = () => setOpen(false)
|
||||
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
|
||||
if (!values.depth || !values.amount) return
|
||||
|
||||
if (!beeApi) return
|
||||
|
||||
const amount = BigInt(values.amount)
|
||||
const depth = Number.parseInt(values.depth)
|
||||
const options = values.label ? { label: values.label } : undefined
|
||||
await beeApi.stamps.buyPostageStamp(amount, depth, options)
|
||||
await beeApi.createPostageBatch(amount.toString(), depth, options)
|
||||
actions.resetForm()
|
||||
await refresh()
|
||||
handleClose()
|
||||
|
||||
@@ -5,10 +5,9 @@ import { Container, CircularProgress } from '@material-ui/core'
|
||||
import StampsTable from './StampsTable'
|
||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||
import CreatePostageStampModal from './CreatePostageStampModal'
|
||||
import LastUpdate from '../../components/LastUpdate'
|
||||
|
||||
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
|
||||
import { Context } from '../../providers/Stamps'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -32,16 +31,15 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
export default function Accounting(): ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
const { health, isLoadingHealth } = useApiHealth()
|
||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||
const { stamps, isLoading, error, lastUpdate, start, stop } = useContext(Context)
|
||||
const beeContext = useContext(BeeContext)
|
||||
const { stamps, isLoading, error, start, stop } = useContext(Context)
|
||||
useEffect(() => {
|
||||
start()
|
||||
|
||||
return () => stop()
|
||||
}, [])
|
||||
|
||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
||||
if (beeContext.isLoading) {
|
||||
return (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress />
|
||||
@@ -49,7 +47,7 @@ export default function Accounting(): ReactElement {
|
||||
)
|
||||
}
|
||||
|
||||
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
||||
if (!beeContext.status.all) return <TroubleshootConnectionCard />
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
@@ -62,7 +60,6 @@ export default function Accounting(): ReactElement {
|
||||
<>
|
||||
<div className={classes.actions}>
|
||||
<CreatePostageStampModal />
|
||||
<LastUpdate date={lastUpdate} />
|
||||
<div style={{ height: '5px' }}>{isLoading && <CircularProgress />}</div>
|
||||
</div>
|
||||
<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 DepositModal from '../../../containers/DepositModal'
|
||||
import type { ReactElement } from 'react'
|
||||
import type { StatusChequebookHook } from '../../../hooks/status'
|
||||
|
||||
interface Props extends StatusChequebookHook {
|
||||
ethereumAddress?: string
|
||||
interface Props extends StatusHookCommon {
|
||||
chequebookAddress?: string
|
||||
}
|
||||
|
||||
const ChequebookDeployFund = ({
|
||||
isLoading,
|
||||
chequebookAddress,
|
||||
chequebookBalance,
|
||||
ethereumAddress,
|
||||
}: Props): ReactElement | null => {
|
||||
if (isLoading) return null
|
||||
|
||||
const ChequebookDeployFund = ({ chequebookAddress, isOk }: Props): ReactElement | null => {
|
||||
return (
|
||||
<div>
|
||||
<p style={{ marginBottom: '20px', display: 'flex' }}>
|
||||
{chequebookAddress?.chequebookAddress && <DepositModal />}
|
||||
</p>
|
||||
<p style={{ marginBottom: '20px', display: 'flex' }}>{chequebookAddress && <DepositModal />}</p>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
{!(chequebookAddress?.chequebookAddress && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) && (
|
||||
{!isOk && (
|
||||
<div>
|
||||
<span>
|
||||
Your chequebook is either not deployed or funded. Join{' '}
|
||||
<a href="https://discord.gg/ykCupZMuww">our discord channel</a>, get verified and send a message{' '}
|
||||
<pre>sprinkle {ethereumAddress || '<YOUR BEE NODE ETH ADDRESS>'}</pre> in the <pre>#faucet-request</pre>{' '}
|
||||
channel to get Goerli ETH and Goerli BZZ token.
|
||||
Your chequebook is either not deployed or funded. To run the node you will need xDAI and xBZZ on the xDai
|
||||
network. You may need to aquire BZZ through (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and
|
||||
bridge it to the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To
|
||||
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>
|
||||
</div>
|
||||
)}
|
||||
@@ -36,7 +28,7 @@ const ChequebookDeployFund = ({
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Chequebook Address
|
||||
</Typography>
|
||||
<EthereumAddress address={chequebookAddress?.chequebookAddress} network={'goerli'} />
|
||||
<EthereumAddress address={chequebookAddress} />
|
||||
</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 MuiAlert from '@material-ui/lab/Alert'
|
||||
import { ExpandMoreSharp } from '@material-ui/icons/'
|
||||
|
||||
import ConnectToHost from '../../../components/ConnectToHost'
|
||||
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||
import { debugApiHost } from '../../../constants'
|
||||
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
|
||||
type Props = StatusHookCommon
|
||||
|
||||
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
|
||||
if (isLoading) return null
|
||||
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
|
||||
const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext)
|
||||
|
||||
const changeDebugApiUrl = (
|
||||
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
|
||||
<span style={{ marginRight: '15px' }}>
|
||||
Debug API (<Typography variant="button">{debugApiHost}</Typography>)
|
||||
Debug API (<Typography variant="button">{apiDebugUrl}</Typography>)
|
||||
</span>
|
||||
<ConnectToHost hostName={'debug_api_host'} defaultHost={debugApiHost} />
|
||||
<ConnectToHost
|
||||
setHost={(host: string) => {
|
||||
console.log(host) // eslint-disable-line
|
||||
setDebugApiUrl(host)
|
||||
}}
|
||||
defaultHost={apiDebugUrl}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -31,7 +37,7 @@ export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactEl
|
||||
|
||||
<div>
|
||||
<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.
|
||||
<Accordion style={{ marginTop: '20px' }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
|
||||
|
||||
@@ -4,37 +4,31 @@ import EthereumAddress from '../../../components/EthereumAddress'
|
||||
|
||||
type Props = StatusEthereumConnectionHook
|
||||
|
||||
export default function EthereumConnectionCheck({ isLoading, isOk, nodeAddresses }: Props): ReactElement | null {
|
||||
if (isLoading) return null
|
||||
|
||||
export default function EthereumConnectionCheck({ isOk, nodeAddresses }: Props): ReactElement | null {
|
||||
if (isOk) {
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Node Address
|
||||
</Typography>
|
||||
<EthereumAddress address={nodeAddresses?.ethereum} network={'goerli'} />
|
||||
<EthereumAddress address={nodeAddresses?.ethereum} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<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{' '}
|
||||
<a href="https://github.com/goerli/testnet" rel="noreferrer" target="_blank">
|
||||
your own Goerli node
|
||||
<a href="https://www.xdaichain.com/" rel="noreferrer" target="_blank">
|
||||
your own xDai node
|
||||
</a>
|
||||
, or use a provider such as{' '}
|
||||
<a href="https://rpc.slock.it/goerli" rel="noreferrer" target="_blank">
|
||||
rpc.slock.it/goerli
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<a href="https://infura.io/" rel="noreferrer" target="_blank">
|
||||
Infura
|
||||
, or use a provider instead - we recommend{' '}
|
||||
<a href="https://getblock.io/" rel="noreferrer" target="_blank">
|
||||
Getblock
|
||||
</a>
|
||||
. By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, simply change
|
||||
your <strong>--swap-endpoint</strong> in your configuration file.
|
||||
. By default, Bee expects a local node at http://localhost:8545. To use a provider instead, simply change the{' '}
|
||||
<strong>swap-endpoint</strong> in your configuration file.
|
||||
</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 { ExpandMoreSharp } from '@material-ui/icons/'
|
||||
|
||||
import ConnectToHost from '../../../components/ConnectToHost'
|
||||
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||
import { apiHost } from '../../../constants'
|
||||
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
|
||||
type Props = StatusHookCommon
|
||||
|
||||
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
|
||||
if (isLoading) return null
|
||||
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
|
||||
const { setApiUrl, apiUrl } = useContext(SettingsContext)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', marginBottom: '25px' }}>
|
||||
<span style={{ marginRight: '15px' }}>
|
||||
Node API (<Typography variant="button">{apiHost}</Typography>)
|
||||
Node API (<Typography variant="button">{apiUrl}</Typography>)
|
||||
</span>
|
||||
<ConnectToHost hostName="api_host" defaultHost={apiHost} />
|
||||
<ConnectToHost setHost={setApiUrl} defaultHost={apiUrl} />
|
||||
</div>
|
||||
<div>
|
||||
{!isOk && (
|
||||
<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.
|
||||
<Accordion style={{ marginTop: '20px' }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
|
||||
|
||||
@@ -3,9 +3,7 @@ import { Typography } from '@material-ui/core/'
|
||||
|
||||
type Props = StatusTopologyHook
|
||||
|
||||
export default function PeerConnection({ isLoading, isOk, topology }: Props): ReactElement | null {
|
||||
if (isLoading) return null
|
||||
|
||||
export default function PeerConnection({ isOk, topology }: Props): ReactElement | null {
|
||||
const peers = (
|
||||
<div style={{ display: 'flex', marginTop: '15px' }}>
|
||||
<div style={{ marginRight: '30px' }}>
|
||||
|
||||
@@ -4,15 +4,7 @@ import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||
|
||||
type Props = StatusNodeVersionHook
|
||||
|
||||
export default function VersionCheck({
|
||||
isLoading,
|
||||
isOk,
|
||||
userVersion,
|
||||
latestVersion,
|
||||
latestUrl,
|
||||
}: Props): ReactElement | null {
|
||||
if (isLoading) return null
|
||||
|
||||
export default function VersionCheck({ isOk, userVersion, latestVersion, latestUrl }: Props): ReactElement | null {
|
||||
const version = (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<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 { Container, CircularProgress } from '@material-ui/core'
|
||||
import { ReactElement, useState, useContext } 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, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
|
||||
|
||||
import NodeSetupWorkflow from './NodeSetupWorkflow'
|
||||
import StatusCard from './StatusCard'
|
||||
import EthereumAddressCard from '../../components/EthereumAddressCard'
|
||||
import {
|
||||
useStatusEthereumConnection,
|
||||
useStatusNodeVersion,
|
||||
useStatusDebugConnection,
|
||||
useStatusConnection,
|
||||
useStatusTopology,
|
||||
useStatusChequebook,
|
||||
} from '../../hooks/status'
|
||||
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 { Context } from '../../providers/Bee'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
padding: theme.spacing(2),
|
||||
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 [activeStep, setActiveStep] = useState(-1)
|
||||
|
||||
const nodeVersion = useStatusNodeVersion()
|
||||
const ethereumConnection = useStatusEthereumConnection()
|
||||
const debugApiConnection = useStatusDebugConnection()
|
||||
const apiConnection = useStatusConnection()
|
||||
const topology = useStatusTopology()
|
||||
const chequebook = useStatusChequebook()
|
||||
const {
|
||||
status,
|
||||
isLoading,
|
||||
latestUserVersion,
|
||||
latestPublishedVersion,
|
||||
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
|
||||
if (!checks.every(c => !c.isLoading)) {
|
||||
return (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
)
|
||||
const handleNext = () => {
|
||||
setActiveStep(prevActiveStep => prevActiveStep + 1)
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
setActiveStep(prevActiveStep => prevActiveStep - 1)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<StatusCard
|
||||
userBeeVersion={nodeVersion.userVersion}
|
||||
isLatestBeeVersion={nodeVersion.isLatestBeeVersion}
|
||||
isOk={checks.every(c => c.isOk)}
|
||||
nodeTopology={topology.topology}
|
||||
latestUrl={nodeVersion.latestUrl}
|
||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
||||
/>
|
||||
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
|
||||
<EthereumAddressCard
|
||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
||||
isLoadingNodeAddresses={ethereumConnection.isLoading}
|
||||
chequebookAddress={chequebook.chequebookAddress}
|
||||
isLoadingChequebookAddress={chequebook.isLoading}
|
||||
/>
|
||||
)}
|
||||
<NodeSetupWorkflow
|
||||
nodeVersion={nodeVersion}
|
||||
ethereumConnection={ethereumConnection}
|
||||
debugApiConnection={debugApiConnection}
|
||||
apiConnection={apiConnection}
|
||||
topology={topology}
|
||||
chequebook={chequebook}
|
||||
/>
|
||||
<Paper className={classes.root}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Node Setup
|
||||
</Typography>
|
||||
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
|
||||
{steps.map(({ label, isOk, component }, index) => (
|
||||
<Step key={label}>
|
||||
<StepLabel
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
|
||||
import { beeApi } from '../services/bee'
|
||||
import { createContext, ReactChild, ReactElement, useEffect, useState, useContext } from 'react'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
export interface EnrichedPostageBatch extends PostageBatch {
|
||||
usage: number
|
||||
@@ -48,6 +48,7 @@ function enrichStamp(postageBatch: PostageBatch): EnrichedPostageBatch {
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const { beeApi } = useContext(SettingsContext)
|
||||
const [stamps, setStamps] = useState<EnrichedPostageBatch[] | null>(initialValues.stamps)
|
||||
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||
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
|
||||
if (isLoading) return
|
||||
|
||||
if (!beeApi) return
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const stamps = await beeApi.stamps.getPostageStamps()
|
||||
const stamps = await beeApi.getAllPostageBatch()
|
||||
|
||||
setStamps(stamps.map(enrichStamp))
|
||||
setLastUpdate(Date.now())
|
||||
|
||||
Vendored
-1
@@ -6,7 +6,6 @@ interface LatestBeeRelease {
|
||||
}
|
||||
|
||||
interface StatusHookCommon {
|
||||
isLoading: 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
|
||||
const componentsOverrides = (theme: Theme) => ({
|
||||
MuiDrawer: {
|
||||
paper: {
|
||||
width: 300,
|
||||
backgroundColor: '#212121',
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
@@ -55,7 +61,7 @@ const propsOverrides = {
|
||||
},
|
||||
}
|
||||
|
||||
export const lightTheme = createMuiTheme({
|
||||
export const theme = createMuiTheme({
|
||||
palette: {
|
||||
type: 'light',
|
||||
background: {
|
||||
@@ -75,28 +81,5 @@ export const lightTheme = createMuiTheme({
|
||||
},
|
||||
})
|
||||
|
||||
export const darkTheme = createMuiTheme({
|
||||
palette: {
|
||||
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
|
||||
theme.overrides = componentsOverrides(theme)
|
||||
theme.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 => {
|
||||
test(`testing ${v}`, () => {
|
||||
// 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}`)
|
||||
}
|
||||
|
||||
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