Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 650d100dd2 | |||
| 960ffb8fdd | |||
| be8b88516b | |||
| 43b3a45d90 | |||
| 20ed3cb387 | |||
| b190cac706 | |||
| 6f645dc6c3 | |||
| af88027ba9 | |||
| 5748c9b609 | |||
| 5ace7629f2 | |||
| 465df17741 | |||
| 3bcf2ac688 | |||
| a2bff60270 | |||
| 353db10080 | |||
| bec84051a9 | |||
| 92c727e5f5 | |||
| 4074a9de5d | |||
| 9fee1aa68a | |||
| 08fdac9366 | |||
| ba9b498488 | |||
| 07f987e069 | |||
| a603a86c1a | |||
| aab0462047 | |||
| ce949d380c | |||
| 7f5fbd3fb6 | |||
| edd4a2fc11 |
+2
-2
@@ -3,5 +3,5 @@ REACT_APP_BEE_HOST=http://localhost:1633
|
|||||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||||
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
||||||
REACT_APP_ETHERSCAN_HOST=etherscan.io
|
REACT_APP_BLOCKCHAIN_EXPLORER_URL=https://blockscout.com/xdai/mainnet
|
||||||
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
REACT_APP_BEE_HOST=http://localhost:1633
|
REACT_APP_BEE_HOST=http://localhost:1633
|
||||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||||
|
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
||||||
|
REACT_APP_BLOCKCHAIN_EXPLORER_URL=https://blockscout.com/xdai/mainnet
|
||||||
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Denote all files that are truly binary and should not be modified.
|
||||||
|
*.png binary
|
||||||
@@ -1,5 +1,70 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* display postage batch usage percentage ([#149](https://www.github.com/ethersphere/bee-dashboard/issues/149)) ([6f645dc](https://www.github.com/ethersphere/bee-dashboard/commit/6f645dc6c357cb9d86cec15e454b361bc871bec6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* clear dropzone state after upload ([#150](https://www.github.com/ethersphere/bee-dashboard/issues/150)) ([b190cac](https://www.github.com/ethersphere/bee-dashboard/commit/b190cac7064ad3dffb770c5a83d3db4a14d39607))
|
||||||
|
|
||||||
|
### [0.3.1](https://www.github.com/ethersphere/bee-dashboard/compare/v0.3.0...v0.3.1) (2021-06-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't display version alert when unable to retrieve version from bee node ([#138](https://www.github.com/ethersphere/bee-dashboard/issues/138)) ([5ace762](https://www.github.com/ethersphere/bee-dashboard/commit/5ace7629f2479499fe975dec8be4187ece6221ed))
|
||||||
|
* typeerror in the postage stamps form ([#137](https://www.github.com/ethersphere/bee-dashboard/issues/137)) ([465df17](https://www.github.com/ethersphere/bee-dashboard/commit/465df177413afba5376682bd23a712066bd0385c))
|
||||||
|
|
||||||
|
## [0.3.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.2.0...v0.3.0) (2021-06-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added Dockerfile ([#75](https://www.github.com/ethersphere/bee-dashboard/issues/75)) ([aab0462](https://www.github.com/ethersphere/bee-dashboard/commit/aab0462047a3fcd87ba258b5486aede922865b1e))
|
||||||
|
* added tolerance to version check and warning if not exact to what we tested ([#133](https://www.github.com/ethersphere/bee-dashboard/issues/133)) ([353db10](https://www.github.com/ethersphere/bee-dashboard/commit/353db10080b85b0e12e13991665297ec262d2806))
|
||||||
|
* postage stamps support ([#115](https://www.github.com/ethersphere/bee-dashboard/issues/115)) ([4074a9d](https://www.github.com/ethersphere/bee-dashboard/commit/4074a9de5dae4aaa1654f7dfdd3e3343eaf2bf9b))
|
||||||
|
* unified notification with notistack ([#127](https://www.github.com/ethersphere/bee-dashboard/issues/127)) ([bec8405](https://www.github.com/ethersphere/bee-dashboard/commit/bec84051a9582bf62a23f2080a6587a9f458b969))
|
||||||
|
* upload files with postage stamps ([#126](https://www.github.com/ethersphere/bee-dashboard/issues/126)) ([92c727e](https://www.github.com/ethersphere/bee-dashboard/commit/92c727e5f5772f612fe04b750ef5373780ccba5c))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add git attributes ([#123](https://www.github.com/ethersphere/bee-dashboard/issues/123)) ([07f987e](https://www.github.com/ethersphere/bee-dashboard/commit/07f987e069cda2f28bc5ebf8958b9b0aa9d875dc))
|
||||||
|
* add prod env variables ([#121](https://www.github.com/ethersphere/bee-dashboard/issues/121)) ([a603a86](https://www.github.com/ethersphere/bee-dashboard/commit/a603a86c1adcfb0dcc9995c95c4ee4411c41c25a))
|
||||||
|
* replace http-serve with serve-handler ([#122](https://www.github.com/ethersphere/bee-dashboard/issues/122)) ([ba9b498](https://www.github.com/ethersphere/bee-dashboard/commit/ba9b498488dca989bbbda6110d0d22753b33ae8c))
|
||||||
|
* troubleshooting on a mac and clearer CORS setup guide ([#131](https://www.github.com/ethersphere/bee-dashboard/issues/131)) ([9fee1aa](https://www.github.com/ethersphere/bee-dashboard/commit/9fee1aa68ac6dbc53615332bc0142a06f3e5f03f))
|
||||||
|
|
||||||
|
## [0.2.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.1.0...v0.2.0) (2021-05-20)
|
||||||
|
|
||||||
|
This release supports the [Bee's 0.6.0 release](https://github.com/ethersphere/bee/releases/tag/v0.6.0) and is fully
|
||||||
|
compatible with it. Due to the amount of breaking changes Bee Dashboard no longer supports the Bee version `0.5.3`.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* update to bee 0.6.0 and bee-js 0.9.0 ([#99](https://www.github.com/ethersphere/bee-dashboard/issues/99)) ([7f5fbd3](https://www.github.com/ethersphere/bee-dashboard/commit/7f5fbd3fb65fe35762cf25ddf7bbaa8b3bd623f9))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* serve npm command path specification ([#101](https://www.github.com/ethersphere/bee-dashboard/issues/101)) ([edd4a2f](https://www.github.com/ethersphere/bee-dashboard/commit/edd4a2fc11219843860861343f0317a5f1268ff0)), closes [#98](https://www.github.com/ethersphere/bee-dashboard/issues/98)
|
||||||
|
|
||||||
## 0.1.0 (2021-04-29)
|
## 0.1.0 (2021-04-29)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
FROM node:15.14-alpine AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN npm ci
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:15.14-alpine AS final
|
||||||
|
RUN npm i -g serve
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /src/build .
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["serve", "-l", "8080"]
|
||||||
@@ -6,7 +6,7 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
> An app which helps users to setup their Bee node and do actions like cash out cheques.
|
> An app which helps users to setup their Bee node and do actions like cash out cheques, upload and download files or manage your postage stamps.
|
||||||
|
|
||||||
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
|
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
|
||||||
|
|
||||||
@@ -21,29 +21,58 @@
|
|||||||
|
|
||||||
- [Install](#install)
|
- [Install](#install)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
|
- [Terminal](#terminal)
|
||||||
|
- [Docker](#docker)
|
||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
|
- [Development](#development)
|
||||||
|
- [Maintainers](#maintainers)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```
|
Install globally with npm. We require Node.js's version of at least 12.x and npm v6.x (or yarn v2.x).
|
||||||
$ npm install -g @ethersphere/bee-dashboard
|
|
||||||
$ bee-dashboard
|
```sh
|
||||||
|
npm install -g @ethersphere/bee-dashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Usage
|
||||||
|
|
||||||
|
:warning: To successfully connect to the Bee node, you will need to enable the Debug API and CORS. You can do so by setting `cors-allowed-origins: ['*']` and `debug-api-enable: true` in the Bee config file and then restart the Bee node. To see where the config file is, consult the [official Bee documentation](https://docs.ethswarm.org/docs/working-with-bee/configuration#configuring-bee-installed-using-a-package-manager)
|
||||||
|
|
||||||
|
### Terminal
|
||||||
|
|
||||||
|
To start use:
|
||||||
|
```sh
|
||||||
|
bee-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
This should open the webpage on [`http://localhost:8080`](http://localhost:8080)
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
To build Docker image and run it, execute the following from inside project directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build . -t bee-dashboard
|
||||||
|
docker run --rm -p 127.0.0.1:8080:8080 bee-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Bee dashboard is now available on [`http://localhost:8080`](http://localhost:8080)
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone git@github.com:ethersphere/bee-dashboard.git
|
git clone git@github.com:ethersphere/bee-dashboard.git
|
||||||
|
|
||||||
cd bee-dashboard
|
cd bee-dashboard
|
||||||
|
|
||||||
npm ci
|
npm i
|
||||||
npm run build
|
|
||||||
npm run serve
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now access Bee Dashboard on [http://localhost:8080/](http://localhost:8080/)
|
The Bee Dashboard runs in development mode on [http://localhost:3031/](http://localhost:3031/)
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
@@ -51,7 +80,7 @@ There are some ways you can make this module better:
|
|||||||
|
|
||||||
- Consult our [open issues](https://github.com/ethersphere/bee-dashboard/issues) and take on one of them
|
- Consult our [open issues](https://github.com/ethersphere/bee-dashboard/issues) and take on one of them
|
||||||
- Help our tests reach 100% coverage!
|
- Help our tests reach 100% coverage!
|
||||||
- Join us in our [Mattermost chat](https://beehive.ethswarm.org/swarm/channels/swarm-javascript) if you have questions or want to give feedback
|
- Join us in our [Discord chat](https://discord.gg/wdghaQsGq5) in the #develop-on-swarm channel if you have questions or want to give feedback
|
||||||
|
|
||||||
## Maintainers
|
## Maintainers
|
||||||
|
|
||||||
|
|||||||
Generated
+455
-367
File diff suppressed because it is too large
Load Diff
+17
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.1.0",
|
"version": "0.5.0",
|
||||||
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bee",
|
"bee",
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersphere/bee-js": "^0.8.1",
|
"@ethersphere/bee-js": "^1.0.0",
|
||||||
"@material-ui/core": "^4.11.3",
|
"@material-ui/core": "^4.11.4",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
"@types/react-router": "^5.1.13",
|
"@types/react-router": "^5.1.13",
|
||||||
@@ -33,8 +33,11 @@
|
|||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bignumber.js": "^9.0.1",
|
"bignumber.js": "^9.0.1",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"http-serve": "^1.0.1",
|
"formik": "^2.2.8",
|
||||||
|
"formik-material-ui": "^3.0.1",
|
||||||
"material-ui-dropzone": "^3.5.0",
|
"material-ui-dropzone": "^3.5.0",
|
||||||
|
"notistack": "^1.0.9",
|
||||||
|
"opener": "^1.5.2",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-copy-to-clipboard": "^5.0.3",
|
"react-copy-to-clipboard": "^5.0.3",
|
||||||
@@ -42,7 +45,9 @@
|
|||||||
"react-feather": "^2.0.9",
|
"react-feather": "^2.0.9",
|
||||||
"react-identicons": "^1.2.5",
|
"react-identicons": "^1.2.5",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-syntax-highlighter": "^15.4.3"
|
"react-syntax-highlighter": "^15.4.3",
|
||||||
|
"semver": "^7.3.2",
|
||||||
|
"serve-handler": "^6.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.12.0",
|
"@testing-library/jest-dom": "^5.12.0",
|
||||||
@@ -55,6 +60,7 @@
|
|||||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
"@types/react-copy-to-clipboard": "^5.0.0",
|
||||||
"@types/react-dom": "^17.0.3",
|
"@types/react-dom": "^17.0.3",
|
||||||
"@types/react-syntax-highlighter": "^13.5.0",
|
"@types/react-syntax-highlighter": "^13.5.0",
|
||||||
|
"@types/semver": "^7.3.6",
|
||||||
"eslint": "^7.24.0",
|
"eslint": "^7.24.0",
|
||||||
"eslint-config-prettier": "^8.2.0",
|
"eslint-config-prettier": "^8.2.0",
|
||||||
"eslint-plugin-jest": "^24.3.5",
|
"eslint-plugin-jest": "^24.3.5",
|
||||||
@@ -70,7 +76,7 @@
|
|||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"serve": "http-serve -o ./build",
|
"serve": "node ./serve.js",
|
||||||
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||||
"lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\""
|
"lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\""
|
||||||
},
|
},
|
||||||
@@ -95,5 +101,10 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0",
|
||||||
|
"npm": ">=6.0.0",
|
||||||
|
"bee": ">=0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"trailingSlash": false,
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source" : "*",
|
||||||
|
"headers" : [{
|
||||||
|
"key" : "Cache-Control",
|
||||||
|
"value" : "max-age=3600"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,15 +1,33 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const serve = require('http-serve')
|
const handler = require('serve-handler');
|
||||||
|
const http = require('http');
|
||||||
const opener = require('opener')
|
const opener = require('opener')
|
||||||
|
|
||||||
const server = serve.createServer({
|
const serverConfig = {
|
||||||
root: path.join(__dirname, 'build')
|
public: path.join(__dirname, 'build'),
|
||||||
|
trailingSlash: false,
|
||||||
|
rewrites: [
|
||||||
|
{ source: "**", destination: "/index.html" },
|
||||||
|
],
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
source: "*",
|
||||||
|
headers: [{
|
||||||
|
key: "Cache-Control",
|
||||||
|
value: "max-age=3600"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer((request, response) => {
|
||||||
|
|
||||||
|
return handler(request, response, serverConfig);
|
||||||
})
|
})
|
||||||
|
|
||||||
server.listen(8080, '127.0.0.1', function () {
|
server.listen(8080, () => {
|
||||||
console.log('Starting up Bee Dashboard on address http://localhost:8080')
|
console.log('Starting up Bee Dashboard on address http://localhost:8080')
|
||||||
console.log('Hit CTRL-C to stop the server')
|
console.log('Hit CTRL-C to stop the server')
|
||||||
opener('http://localhost:8080')
|
opener('http://localhost:8080')
|
||||||
|
|||||||
+12
-4
@@ -4,9 +4,11 @@ import './App.css'
|
|||||||
|
|
||||||
import { ThemeProvider } from '@material-ui/styles'
|
import { ThemeProvider } from '@material-ui/styles'
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
|
import { SnackbarProvider } from 'notistack'
|
||||||
|
|
||||||
import BaseRouter from './routes/routes'
|
import BaseRouter from './routes/routes'
|
||||||
import { lightTheme, darkTheme } from './theme'
|
import { lightTheme, darkTheme } from './theme'
|
||||||
|
import { Provider as StampsProvider } from './providers/Stamps'
|
||||||
|
|
||||||
const App = (): ReactElement => {
|
const App = (): ReactElement => {
|
||||||
const [themeMode, toggleThemeMode] = useState('light')
|
const [themeMode, toggleThemeMode] = useState('light')
|
||||||
@@ -33,10 +35,16 @@ const App = (): ReactElement => {
|
|||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
||||||
<CssBaseline />
|
<StampsProvider>
|
||||||
<Router>
|
<SnackbarProvider>
|
||||||
<BaseRouter />
|
<>
|
||||||
</Router>
|
<CssBaseline />
|
||||||
|
<Router>
|
||||||
|
<BaseRouter />
|
||||||
|
</Router>
|
||||||
|
</>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</StampsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import Collapse from '@material-ui/core/Collapse'
|
||||||
|
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
||||||
|
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||||
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
|
const LIMIT = 100_000_000 // 100 megabytes
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
file: File
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function UploadSizeAlert(props: Props): ReactElement | null {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const aboveLimit = props.file.size >= LIMIT
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapse in={aboveLimit}>
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Alert severity="warning">
|
||||||
|
<AlertTitle>Warning</AlertTitle>
|
||||||
|
The file you are trying to upload is above the recommended size. The chunks may not be synchronised properly
|
||||||
|
over the network.
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { ReactElement, useState } 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 { SUPPORTED_BEE_VERSION_EXACT } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function VersionAlert(): ReactElement | null {
|
||||||
|
const classes = useStyles()
|
||||||
|
const { isLoading, userVersion } = useStatusNodeVersion()
|
||||||
|
const [open, setOpen] = useState<boolean>(true)
|
||||||
|
|
||||||
|
const isExactlySupportedBeeVersion = SUPPORTED_BEE_VERSION_EXACT === userVersion
|
||||||
|
|
||||||
|
if (isLoading || !userVersion) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapse in={!isExactlySupportedBeeVersion && open}>
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Alert
|
||||||
|
severity="warning"
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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.
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@ import DialogActions from '@material-ui/core/DialogActions'
|
|||||||
import DialogContent from '@material-ui/core/DialogContent'
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||||
import { Snackbar, Container, CircularProgress } from '@material-ui/core'
|
import { Container, CircularProgress } from '@material-ui/core'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
import { beeDebugApi } from '../services/bee'
|
import { beeDebugApi } from '../services/bee'
|
||||||
|
|
||||||
@@ -19,8 +20,7 @@ interface Props {
|
|||||||
export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement {
|
export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement {
|
||||||
const [open, setOpen] = useState<boolean>(false)
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
||||||
const [showToast, setToastVisibility] = useState<boolean>(false)
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
const [toastContent, setToastContent] = useState<JSX.Element | null>(null)
|
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -37,37 +37,30 @@ export default function DepositModal({ peerId, uncashedAmount }: Props): ReactEl
|
|||||||
.peerCashout(peerId)
|
.peerCashout(peerId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
handleToast(
|
enqueueSnackbar(
|
||||||
<span>
|
<span>
|
||||||
Successfully cashed out cheque. Transaction
|
Successfully cashed out cheque. Transaction
|
||||||
<EthereumAddress hideBlockie transaction address={res.transactionHash} network={'goerli'} />
|
<EthereumAddress hideBlockie transaction address={res} />
|
||||||
</span>,
|
</span>,
|
||||||
|
{ variant: 'success' },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e: Error) => {
|
||||||
// FIXME: handle errors more gracefully
|
enqueueSnackbar(<span>Error: {e.message}</span>, { variant: 'error' })
|
||||||
handleToast(<span>Error with cashout</span>)
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoadingCashout(false)
|
setLoadingCashout(false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
handleToast(<span>Peer Id invalid</span>)
|
enqueueSnackbar(<span>Peer Id invalid</span>, { variant: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToast = (text: JSX.Element) => {
|
|
||||||
setToastContent(text)
|
|
||||||
setToastVisibility(true)
|
|
||||||
setTimeout(() => setToastVisibility(false), 7000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
|
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
|
||||||
Cashout
|
Cashout
|
||||||
</Button>
|
</Button>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import { IconButton, Snackbar } from '@material-ui/core'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||||
import { Clipboard } from 'react-feather'
|
import { Clipboard } from 'react-feather'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ClipboardCopy(props: Props): ReactElement {
|
export default function ClipboardCopy({ value }: Props): ReactElement {
|
||||||
const [copied, setCopied] = useState(false)
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
const handleCopy = () => enqueueSnackbar(`Copied: ${value}`, { variant: 'success' })
|
||||||
const handleCopy = () => {
|
|
||||||
setCopied(true)
|
|
||||||
setTimeout(() => setCopied(false), 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginRight: '3px', marginLeft: '3px' }}>
|
<div style={{ marginRight: '3px', marginLeft: '3px' }}>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={copied} message="Copied" />
|
|
||||||
<IconButton color="primary" size="small" onClick={handleCopy}>
|
<IconButton color="primary" size="small" onClick={handleCopy}>
|
||||||
<CopyToClipboard text={props.value}>
|
<CopyToClipboard text={value}>
|
||||||
<Clipboard style={{ height: '20px' }} />
|
<Clipboard style={{ height: '20px' }} />
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ReactElement } from 'react'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
address: string | undefined
|
address: string | undefined
|
||||||
network?: string
|
|
||||||
hideBlockie?: boolean
|
hideBlockie?: boolean
|
||||||
transaction?: boolean
|
transaction?: boolean
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
@@ -37,9 +36,9 @@ export default function EthereumAddress(props: Props): ReactElement {
|
|||||||
}
|
}
|
||||||
: { marginRight: '7px' }
|
: { marginRight: '7px' }
|
||||||
}
|
}
|
||||||
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${
|
href={`${process.env.REACT_APP_BLOCKCHAIN_EXPLORER_URL}/${props.transaction ? 'tx' : 'address'}/${
|
||||||
props.transaction ? 'tx' : 'address'
|
props.address
|
||||||
}/${props.address}`}
|
}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Card, CardContent, Typography } from '@material-ui/core/'
|
import { Card, CardContent, Typography } from '@material-ui/core/'
|
||||||
@@ -49,7 +49,7 @@ function EthereumAddressCard(props: Props): ReactElement {
|
|||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Ethereum Address
|
Ethereum Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={props.nodeAddresses?.ethereum} network={'goerli'} />
|
<EthereumAddress address={props.nodeAddresses?.ethereum} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -64,7 +64,7 @@ function EthereumAddressCard(props: Props): ReactElement {
|
|||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Chequebook Contract Address
|
Chequebook Contract Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={props.chequebookAddress?.chequebookaddress} network={'goerli'} />
|
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ReactElement, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
date: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LastUpdate({ date }: Props): ReactElement {
|
||||||
|
const [duration, setDuration] = useState<string>('never')
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
if (!date) setDuration('never')
|
||||||
|
else setDuration(`${((Date.now() - date) / 1000).toFixed()} seconds ago`)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh()
|
||||||
|
const i = setInterval(refresh, 1000)
|
||||||
|
|
||||||
|
return () => clearInterval(i)
|
||||||
|
}, [date])
|
||||||
|
|
||||||
|
return <span>Last Update: {duration}</span>
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, ReactElement } from 'react'
|
import { useState, ReactElement } from 'react'
|
||||||
|
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import { Toolbar, Chip, IconButton } from '@material-ui/core/'
|
import { Toolbar, IconButton } from '@material-ui/core/'
|
||||||
|
|
||||||
import { Sun, Moon } from 'react-feather'
|
import { Sun, Moon } from 'react-feather'
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ const useStyles = makeStyles(() =>
|
|||||||
width: `calc(100% - ${drawerWidth}px)`,
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
marginLeft: drawerWidth,
|
marginLeft: drawerWidth,
|
||||||
},
|
},
|
||||||
network: {},
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -42,16 +41,11 @@ export default function SideBar(props: Props): ReactElement {
|
|||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'fixed' }} className={classes.appBar}>
|
<div style={{ display: 'fixed' }} className={classes.appBar}>
|
||||||
<Toolbar style={{ display: 'flex' }}>
|
<Toolbar style={{ display: 'flex' }}>
|
||||||
<Chip style={{ marginLeft: '7px' }} size="small" label="Goerli" className={classes.network} />
|
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<div style={{ float: 'right' }}>
|
<div style={{ float: 'right' }}>
|
||||||
<IconButton style={{ marginRight: '10px' }} aria-label="dark-mode" onClick={() => switchTheme()}>
|
<IconButton style={{ marginRight: '10px' }} aria-label="dark-mode" onClick={() => switchTheme()}>
|
||||||
{props.themeMode === 'dark' ? <Moon /> : <Sun />}
|
{props.themeMode === 'dark' ? <Moon /> : <Sun />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{/* <Chip
|
|
||||||
label="Connect Wallet"
|
|
||||||
color="primary"
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10)
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
peerId: string
|
peerId: string
|
||||||
|
characterLength?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PeerDetail(props: Props): ReactElement {
|
export default function PeerDetail({ peerId, characterLength }: Props): ReactElement {
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
variant="button"
|
variant="button"
|
||||||
@@ -17,7 +18,7 @@ export default function PeerDetail(props: Props): ReactElement {
|
|||||||
fontFamily: 'monospace, monospace',
|
fontFamily: 'monospace, monospace',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{truncStringPortion(props.peerId)}
|
{truncStringPortion(peerId, characterLength, characterLength)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { Link, RouteComponentProps } from 'react-router-dom'
|
|||||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
||||||
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink } from '@material-ui/core'
|
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink } from '@material-ui/core'
|
||||||
import { OpenInNewSharp } from '@material-ui/icons'
|
import { OpenInNewSharp } from '@material-ui/icons'
|
||||||
import { Activity, FileText, DollarSign, Share2, Settings } from 'react-feather'
|
import { Activity, FileText, DollarSign, Share2, Settings, Layers } from 'react-feather'
|
||||||
|
|
||||||
import SwarmLogoOrange from '../assets/swarm-logo-orange.svg'
|
import SwarmLogoOrange from '../assets/swarm-logo-orange.svg'
|
||||||
import { Health } from '@ethersphere/bee-js'
|
import { Health } from '@ethersphere/bee-js'
|
||||||
@@ -24,6 +24,12 @@ const navBarItems = [
|
|||||||
path: '/files/',
|
path: '/files/',
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Stamps',
|
||||||
|
id: 'stamps',
|
||||||
|
path: '/stamps/',
|
||||||
|
icon: Layers,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Accounting',
|
label: 'Accounting',
|
||||||
id: 'accounting',
|
id: 'accounting',
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import React, { ReactElement, ReactNode } from 'react'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import Tabs from '@material-ui/core/Tabs'
|
||||||
|
import Tab from '@material-ui/core/Tab'
|
||||||
|
import Typography from '@material-ui/core/Typography'
|
||||||
|
import Box from '@material-ui/core/Box'
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: ReactNode
|
||||||
|
index: number
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
{value === index && (
|
||||||
|
<Box p={3}>
|
||||||
|
<Typography>{children}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
root: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
interface TabsValues {
|
||||||
|
component: ReactNode
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
values: TabsValues[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SimpleTabs({ values }: Props): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [value, setValue] = React.useState<number>(0)
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<Record<string, never>>, newValue: number) => {
|
||||||
|
setValue(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
{values.map(({ component }, index) => (
|
||||||
|
<TabPanel key={index} value={value} index={index}>
|
||||||
|
{component}
|
||||||
|
</TabPanel>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,9 +6,10 @@ import DialogActions from '@material-ui/core/DialogActions'
|
|||||||
import DialogContent from '@material-ui/core/DialogContent'
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||||
import { FormHelperText, Snackbar } from '@material-ui/core'
|
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||||
import { Token } from '../models/Token'
|
import { Token } from '../models/Token'
|
||||||
import type { BigNumber } from 'bignumber.js'
|
import type { BigNumber } from 'bignumber.js'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
successMessage: string
|
successMessage: string
|
||||||
@@ -17,7 +18,7 @@ interface Props {
|
|||||||
label: string
|
label: string
|
||||||
max?: BigNumber
|
max?: BigNumber
|
||||||
min?: BigNumber
|
min?: BigNumber
|
||||||
action: (amount: bigint) => Promise<{ transactionHash: string }>
|
action: (amount: bigint) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WithdrawModal({
|
export default function WithdrawModal({
|
||||||
@@ -33,8 +34,7 @@ export default function WithdrawModal({
|
|||||||
const [amount, setAmount] = useState('')
|
const [amount, setAmount] = useState('')
|
||||||
const [amountToken, setAmountToken] = useState<Token | null>(null)
|
const [amountToken, setAmountToken] = useState<Token | null>(null)
|
||||||
const [amountError, setAmountError] = useState<Error | null>(null)
|
const [amountError, setAmountError] = useState<Error | null>(null)
|
||||||
const [showToast, setToastVisibility] = useState(false)
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
const [toastContent, setToastContent] = useState('')
|
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -48,20 +48,14 @@ export default function WithdrawModal({
|
|||||||
if (amountToken === null) return
|
if (amountToken === null) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { transactionHash } = await action(amountToken.toBigInt as bigint)
|
const transactionHash = await action(amountToken.toBigInt as bigint)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
handleToast(`${successMessage} Transaction ${transactionHash}`)
|
enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleToast(`${errorMessage} Error: ${e.message}`)
|
enqueueSnackbar(`${errorMessage} Error: ${e.message}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToast = (text: string) => {
|
|
||||||
setToastContent(text)
|
|
||||||
setToastVisibility(true)
|
|
||||||
setTimeout(() => setToastVisibility(false), 7000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
setAmount(value)
|
setAmount(value)
|
||||||
@@ -83,7 +77,6 @@ export default function WithdrawModal({
|
|||||||
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
|
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -67,10 +67,8 @@ function mergeAccounting(
|
|||||||
// If there are no cheques (and hence last cashout actions), we don't need to sort and can return values right away
|
// If there are no cheques (and hence last cashout actions), we don't need to sort and can return values right away
|
||||||
if (!uncashedAmounts) return Object.values(accounting)
|
if (!uncashedAmounts) return Object.values(accounting)
|
||||||
|
|
||||||
uncashedAmounts?.forEach(({ peer, cumulativePayout }) => {
|
uncashedAmounts?.forEach(({ peer, uncashedAmount }) => {
|
||||||
accounting[peer].uncashedAmount = new Token(
|
accounting[peer].uncashedAmount = new Token(uncashedAmount)
|
||||||
accounting[peer].received.toBigNumber.minus(cumulativePayout.toString()),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return Object.values(accounting).sort((a, b) =>
|
return Object.values(accounting).sort((a, b) =>
|
||||||
@@ -93,9 +91,10 @@ export const useAccounting = (): UseAccountingHook => {
|
|||||||
if (isLoadingUncashed || !settlements.settlements || uncashedAmounts || error) return
|
if (isLoadingUncashed || !settlements.settlements || uncashedAmounts || error) return
|
||||||
|
|
||||||
setIsloadingUncashed(true)
|
setIsloadingUncashed(true)
|
||||||
const promises = settlements.settlements.settlements.map(({ peer }) =>
|
const promises = settlements.settlements.settlements
|
||||||
beeDebugApi.chequebook.getPeerLastCashout(peer),
|
.filter(({ received }) => received.toBigNumber.gt('0'))
|
||||||
)
|
.map(({ peer }) => beeDebugApi.chequebook.getPeerLastCashout(peer))
|
||||||
|
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
.then(setUncashedAmounts)
|
.then(setUncashedAmounts)
|
||||||
.catch(setErr)
|
.catch(setErr)
|
||||||
@@ -109,7 +108,7 @@ export const useAccounting = (): UseAccountingHook => {
|
|||||||
isLoadingUncashed,
|
isLoadingUncashed,
|
||||||
error,
|
error,
|
||||||
accounting,
|
accounting,
|
||||||
totalsent: settlements.settlements?.totalsent || new Token('0'),
|
totalsent: settlements.settlements?.totalSent || new Token('0'),
|
||||||
totalreceived: settlements.settlements?.totalreceived || new Token('0'),
|
totalreceived: settlements.settlements?.totalReceived || new Token('0'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,8 +325,8 @@ export interface Settlement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Settlements {
|
export interface Settlements {
|
||||||
totalreceived: Token
|
totalReceived: Token
|
||||||
totalsent: Token
|
totalSent: Token
|
||||||
settlements: Settlement[]
|
settlements: Settlement[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,10 +345,10 @@ export const useApiSettlements = (): SettlementsHook => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
beeDebugApi.settlements
|
beeDebugApi.settlements
|
||||||
.getSettlements()
|
.getSettlements()
|
||||||
.then(({ totalreceived, settlements, totalsent }) => {
|
.then(({ totalReceived, settlements, totalSent }) => {
|
||||||
const set = {
|
const set = {
|
||||||
totalreceived: new Token(totalreceived),
|
totalReceived: new Token(totalReceived),
|
||||||
totalsent: new Token(totalsent),
|
totalSent: new Token(totalSent),
|
||||||
settlements: settlements.map(({ peer, received, sent }) => ({
|
settlements: settlements.map(({ peer, received, sent }) => ({
|
||||||
peer,
|
peer,
|
||||||
received: new Token(received),
|
received: new Token(received),
|
||||||
@@ -370,7 +370,7 @@ export const useApiSettlements = (): SettlementsHook => {
|
|||||||
|
|
||||||
export interface LastCashout {
|
export interface LastCashout {
|
||||||
peer: string
|
peer: string
|
||||||
cumulativePayout: Token
|
uncashedAmount: Token
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PeerLastCashoutHook {
|
export interface PeerLastCashoutHook {
|
||||||
@@ -388,8 +388,8 @@ export const useApiPeerLastCashout = (peerId: string): PeerLastCashoutHook => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
beeDebugApi.chequebook
|
beeDebugApi.chequebook
|
||||||
.getPeerLastCashout(peerId)
|
.getPeerLastCashout(peerId)
|
||||||
.then(({ peer, cumulativePayout }) => {
|
.then(({ peer, uncashedAmount }) => {
|
||||||
setPeerCashout({ peer, cumulativePayout: new Token(cumulativePayout) })
|
setPeerCashout({ peer, uncashedAmount: new Token(uncashedAmount.toString()) })
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
setError(error)
|
setError(error)
|
||||||
|
|||||||
+23
-4
@@ -9,6 +9,8 @@ import {
|
|||||||
useDebugApiHealth,
|
useDebugApiHealth,
|
||||||
useLatestBeeRelease,
|
useLatestBeeRelease,
|
||||||
} from './apiHooks'
|
} from './apiHooks'
|
||||||
|
import semver from 'semver'
|
||||||
|
import { engines } from '../../package.json'
|
||||||
|
|
||||||
export interface StatusChequebookHook extends StatusHookCommon {
|
export interface StatusChequebookHook extends StatusHookCommon {
|
||||||
chequebookBalance: ChequebookBalance | null
|
chequebookBalance: ChequebookBalance | null
|
||||||
@@ -19,12 +21,29 @@ export const useStatusNodeVersion = (): StatusNodeVersionHook => {
|
|||||||
const { latestBeeRelease, isLoadingLatestBeeRelease } = useLatestBeeRelease()
|
const { latestBeeRelease, isLoadingLatestBeeRelease } = useLatestBeeRelease()
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
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 {
|
return {
|
||||||
isLoading: isLoadingNodeHealth || isLoadingLatestBeeRelease,
|
isLoading: isLoadingNodeHealth || isLoadingLatestBeeRelease,
|
||||||
isOk: Boolean(latestBeeRelease && latestBeeRelease.name === `v${nodeHealth?.version?.split('-')[0]}`),
|
isOk: Boolean(
|
||||||
userVersion: nodeHealth?.version?.split('-')[0] || '-',
|
nodeHealth &&
|
||||||
latestVersion: latestBeeRelease?.name.substring(1) || '-',
|
semver.satisfies(nodeHealth.version, engines.bee, {
|
||||||
|
includePrerelease: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
userVersion: nodeHealth?.version,
|
||||||
|
latestVersion,
|
||||||
latestUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
|
latestUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
|
||||||
|
isLatestBeeVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +92,7 @@ export const useStatusChequebook = (): StatusChequebookHook => {
|
|||||||
return {
|
return {
|
||||||
isLoading: isLoadingChequebookAddress || isLoadingChequebookBalance,
|
isLoading: isLoadingChequebookAddress || isLoadingChequebookBalance,
|
||||||
isOk:
|
isOk:
|
||||||
Boolean(chequebookAddress?.chequebookaddress) &&
|
Boolean(chequebookAddress?.chequebookAddress) &&
|
||||||
chequebookBalance !== null &&
|
chequebookBalance !== null &&
|
||||||
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0),
|
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0),
|
||||||
chequebookBalance,
|
chequebookBalance,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, ReactElement } from 'react'
|
import { useState, useEffect, ReactElement } from 'react'
|
||||||
import ErrorBoundary from '../components/ErrorBoundary'
|
import ErrorBoundary from '../components/ErrorBoundary'
|
||||||
|
import AlertVersion from '../components/AlertVersion'
|
||||||
|
|
||||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
@@ -11,7 +12,6 @@ import { RouteComponentProps } from 'react-router'
|
|||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
toolbar: theme.mixins.toolbar,
|
|
||||||
content: {
|
content: {
|
||||||
marginLeft: '240px',
|
marginLeft: '240px',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@@ -19,20 +19,6 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
paddingBottom: '65px',
|
paddingBottom: '65px',
|
||||||
},
|
},
|
||||||
footer: {
|
|
||||||
marginLeft: '240px',
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
position: 'fixed',
|
|
||||||
bottom: 0,
|
|
||||||
flexGrow: 1,
|
|
||||||
width: '-webkit-fill-available',
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
height: '20px',
|
|
||||||
marginRight: '7px',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,7 +59,10 @@ const Dashboard = (props: Props): ReactElement => {
|
|||||||
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
|
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
|
||||||
<NavBar themeMode={themeMode} />
|
<NavBar themeMode={themeMode} />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<main className={classes.content}>{props.children}</main>
|
<main className={classes.content}>
|
||||||
|
<AlertVersion />
|
||||||
|
{props.children}
|
||||||
|
</main>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper
|
|||||||
|
|
||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||||
import CashoutModal from '../../components/CashoutModal'
|
import CashoutModal from '../../components/CashoutModal'
|
||||||
import PeerDetailDrawer from './PeerDetail'
|
import PeerDetailDrawer from '../../components/PeerDetail'
|
||||||
import { Accounting } from '../../hooks/accounting'
|
import { Accounting } from '../../hooks/accounting'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { ReactElement, useState } 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 { Utils } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
padding: theme.spacing(0.25),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
iconButton: {
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
height: 28,
|
||||||
|
margin: 4,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function Files(): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const [referenceInput, setReferenceInput] = useState('')
|
||||||
|
const [referenceError, setReferenceError] = useState<Error | null>(null)
|
||||||
|
|
||||||
|
const handleReferenceChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
|
setReferenceInput(e.target.value)
|
||||||
|
|
||||||
|
if (Utils.Hex.isHexString(e.target.value, 64) || Utils.Hex.isHexString(e.target.value, 128)) setReferenceError(null)
|
||||||
|
else setReferenceError(new Error('Incorrect format of swarm hash'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Paper className={classes.root}>
|
||||||
|
<InputBase
|
||||||
|
className={classes.input}
|
||||||
|
placeholder="Enter swarm reference e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
|
||||||
|
inputProps={{ 'aria-label': 'retrieve file from swarm' }}
|
||||||
|
value={referenceInput}
|
||||||
|
onChange={handleReferenceChange}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
href={`${apiHost}/bzz/${referenceInput}`}
|
||||||
|
target="_blank"
|
||||||
|
disabled={referenceError !== null || !referenceInput}
|
||||||
|
className={classes.iconButton}
|
||||||
|
aria-label="download"
|
||||||
|
>
|
||||||
|
<Search />
|
||||||
|
</IconButton>
|
||||||
|
</Paper>
|
||||||
|
{referenceError && <FormHelperText error>{referenceError.message}</FormHelperText>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import Button from '@material-ui/core/Button'
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
|
import Menu from '@material-ui/core/Menu'
|
||||||
|
import MenuItem from '@material-ui/core/MenuItem'
|
||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import PeerDetailDrawer from '../../components/PeerDetail'
|
||||||
|
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
stamps: EnrichedPostageBatch[] | null
|
||||||
|
selectedStamp: EnrichedPostageBatch | null
|
||||||
|
setSelected: (stamp: EnrichedPostageBatch) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props): ReactElement | null {
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
|
||||||
|
|
||||||
|
if (!stamps) return null
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => setAnchorEl(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
|
||||||
|
Change
|
||||||
|
</Button>
|
||||||
|
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
|
||||||
|
{stamps.map(stamp => (
|
||||||
|
<MenuItem
|
||||||
|
key={stamp.batchID}
|
||||||
|
onClick={() => {
|
||||||
|
setSelected(stamp)
|
||||||
|
handleClose()
|
||||||
|
}}
|
||||||
|
selected={stamp.batchID === selectedStamp?.batchID}
|
||||||
|
>
|
||||||
|
<ListItemIcon>{stamp.usageText}</ListItemIcon>
|
||||||
|
<PeerDetailDrawer peerId={stamp.batchID} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { Button, CircularProgress, Container } from '@material-ui/core'
|
||||||
|
import Avatar from '@material-ui/core/Avatar'
|
||||||
|
import Chip from '@material-ui/core/Chip'
|
||||||
|
import { DropzoneArea } from 'material-ui-dropzone'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||||
|
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 CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
||||||
|
import SelectStamp from './SelectStamp'
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 1_000_000_000 // 1 gigabyte
|
||||||
|
|
||||||
|
export default function Files(): ReactElement {
|
||||||
|
const [dropzoneKey, setDropzoneKey] = useState(0)
|
||||||
|
const [file, setFile] = useState<File | null>(null)
|
||||||
|
const [uploadReference, setUploadReference] = useState('')
|
||||||
|
const [isUploadingFile, setIsUploadingFile] = useState(false)
|
||||||
|
|
||||||
|
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
|
||||||
|
|
||||||
|
const { isLoading, error, stamps } = useContext(Context)
|
||||||
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
|
// Choose a postage stamp that has the lowest usage
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedStamp && stamps && stamps.length > 0) {
|
||||||
|
const stamp = stamps.reduce((prev, curr) => {
|
||||||
|
if (curr.usage < prev.usage) return curr
|
||||||
|
|
||||||
|
return prev
|
||||||
|
}, stamps[0])
|
||||||
|
|
||||||
|
setSelectedStamp(stamp)
|
||||||
|
}
|
||||||
|
}, [isLoading, error, stamps, selectedStamp])
|
||||||
|
|
||||||
|
const uploadFile = () => {
|
||||||
|
if (file === null || selectedStamp === null) return
|
||||||
|
setIsUploadingFile(true)
|
||||||
|
beeApi.files
|
||||||
|
.uploadFile(selectedStamp.batchID, file)
|
||||||
|
.then(hash => {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
setFile(null)
|
||||||
|
setUploadReference(hash)
|
||||||
|
setDropzoneKey(dropzoneKey + 1)
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
.catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' }))
|
||||||
|
.finally(() => {
|
||||||
|
setIsUploadingFile(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (files?: File[]) => {
|
||||||
|
if (files) {
|
||||||
|
setFile(files[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<DropzoneArea
|
||||||
|
key={'dropzone-' + dropzoneKey}
|
||||||
|
onChange={handleChange}
|
||||||
|
filesLimit={1}
|
||||||
|
maxFileSize={MAX_FILE_SIZE}
|
||||||
|
/>
|
||||||
|
<div style={{ marginTop: '15px' }}>
|
||||||
|
{selectedStamp && (
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<small>
|
||||||
|
with Postage Stamp{' '}
|
||||||
|
<Chip
|
||||||
|
avatar={<Avatar>{selectedStamp.usageText}</Avatar>}
|
||||||
|
label={<PeerDetailDrawer peerId={selectedStamp.batchID} characterLength={6} />}
|
||||||
|
deleteIcon={<ClipboardCopy value={selectedStamp.batchID} />}
|
||||||
|
onDelete={() => {} /* eslint-disable-line*/}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</small>
|
||||||
|
<SelectStamp stamps={stamps} selectedStamp={selectedStamp} setSelected={setSelectedStamp} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!selectedStamp && <CreatePostageStamp />}
|
||||||
|
<Button disabled={!file && isUploadingFile && !selectedStamp} onClick={() => uploadFile()}>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
{file && <UploadSizeAlert file={file} />}
|
||||||
|
{isUploadingFile && (
|
||||||
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
{uploadReference && (
|
||||||
|
<div style={{ marginBottom: '15px', display: 'flex' }}>
|
||||||
|
<span>{uploadReference}</span>
|
||||||
|
<ClipboardCopy value={uploadReference} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+17
-125
@@ -1,82 +1,17 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import { ReactElement } from 'react'
|
||||||
import { beeApi } from '../../services/bee'
|
|
||||||
|
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { Container, CircularProgress } from '@material-ui/core'
|
||||||
import { Paper, InputBase, IconButton, Button, Container, CircularProgress, FormHelperText } from '@material-ui/core'
|
|
||||||
import { Search } from '@material-ui/icons'
|
|
||||||
import { DropzoneArea } from 'material-ui-dropzone'
|
|
||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
|
||||||
|
|
||||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
|
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
|
||||||
import { apiHost } from '../../constants'
|
import Download from './Download'
|
||||||
import { Utils } from '@ethersphere/bee-js'
|
import Upload from './Upload'
|
||||||
|
import TabsContainer from '../../components/TabsContainer'
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
root: {
|
|
||||||
padding: '2px 4px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
marginLeft: theme.spacing(1),
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
iconButton: {
|
|
||||||
padding: 10,
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
height: 28,
|
|
||||||
margin: 4,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export default function Files(): ReactElement {
|
export default function Files(): ReactElement {
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const [inputMode, setInputMode] = useState<'download' | 'upload'>('download')
|
|
||||||
const [referenceInput, setReferenceInput] = useState('')
|
|
||||||
const [referenceError, setReferenceError] = useState<Error | null>(null)
|
|
||||||
const { health, isLoadingHealth } = useApiHealth()
|
const { health, isLoadingHealth } = useApiHealth()
|
||||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||||
|
|
||||||
const [file, setFile] = useState<File | null>(null)
|
|
||||||
const [uploadReference, setUploadReference] = useState('')
|
|
||||||
const [uploadError, setUploadError] = useState<Error | null>(null)
|
|
||||||
const [isUploadingFile, setIsUploadingFile] = useState(false)
|
|
||||||
|
|
||||||
const uploadFile = () => {
|
|
||||||
if (file === null) return
|
|
||||||
setIsUploadingFile(true)
|
|
||||||
setUploadError(null)
|
|
||||||
beeApi.files
|
|
||||||
.uploadFile(file)
|
|
||||||
.then(hash => {
|
|
||||||
setUploadReference(hash)
|
|
||||||
setFile(null)
|
|
||||||
})
|
|
||||||
.catch(setUploadError)
|
|
||||||
.finally(() => {
|
|
||||||
setIsUploadingFile(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (files?: File[]) => {
|
|
||||||
if (files) {
|
|
||||||
setFile(files[0])
|
|
||||||
setUploadReference('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReferenceChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
||||||
setReferenceInput(e.target.value)
|
|
||||||
|
|
||||||
if (Utils.Hex.isHexString(e.target.value, 64)) setReferenceError(null)
|
|
||||||
else setReferenceError(new Error('Incorrect format of swarm hash'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoadingHealth || isLoadingNodeHealth) {
|
if (isLoadingHealth || isLoadingNodeHealth) {
|
||||||
return (
|
return (
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
@@ -89,61 +24,18 @@ export default function Files(): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<div style={{ marginBottom: '7px' }}>
|
<TabsContainer
|
||||||
<Button color="primary" style={{ marginRight: '7px' }} onClick={() => setInputMode('download')}>
|
values={[
|
||||||
Download
|
{
|
||||||
</Button>
|
label: 'download',
|
||||||
<Button color="primary" onClick={() => setInputMode('upload')}>
|
component: <Download />,
|
||||||
Upload
|
},
|
||||||
</Button>
|
{
|
||||||
</div>
|
label: 'upload',
|
||||||
{inputMode === 'download' && (
|
component: <Upload />,
|
||||||
<>
|
},
|
||||||
<Paper className={classes.root}>
|
]}
|
||||||
<InputBase
|
/>
|
||||||
className={classes.input}
|
|
||||||
placeholder="Enter swarm reference e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
|
|
||||||
inputProps={{ 'aria-label': 'retriefe file from swarm' }}
|
|
||||||
value={referenceInput}
|
|
||||||
onChange={handleReferenceChange}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
href={`${apiHost}/files/${referenceInput}`}
|
|
||||||
target="_blank"
|
|
||||||
disabled={referenceError !== null || !referenceInput}
|
|
||||||
className={classes.iconButton}
|
|
||||||
aria-label="download"
|
|
||||||
>
|
|
||||||
<Search />
|
|
||||||
</IconButton>
|
|
||||||
</Paper>
|
|
||||||
{referenceError && <FormHelperText error>{referenceError.message}</FormHelperText>}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{inputMode === 'upload' && (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<DropzoneArea onChange={handleChange} filesLimit={1} />
|
|
||||||
<div style={{ marginTop: '15px' }}>
|
|
||||||
<Button disabled={!file && isUploadingFile} onClick={() => uploadFile()} className={classes.iconButton}>
|
|
||||||
Upload
|
|
||||||
</Button>
|
|
||||||
{isUploadingFile && (
|
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
{uploadReference && (
|
|
||||||
<div style={{ marginBottom: '15px', display: 'flex' }}>
|
|
||||||
<span>{uploadReference}</span>
|
|
||||||
<ClipboardCopy value={uploadReference} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{uploadError && <FormHelperText error>{uploadError.message}</FormHelperText>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export default function Settings(): ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Paper>
|
<Paper>
|
||||||
<TextField
|
<TextField
|
||||||
id="filled-full-width"
|
|
||||||
label="API Endpoint"
|
label="API Endpoint"
|
||||||
style={{ margin: 0 }}
|
style={{ margin: 0 }}
|
||||||
placeholder="ex: 127.0.0.0.1:1633"
|
placeholder="ex: 127.0.0.0.1:1633"
|
||||||
@@ -51,7 +50,6 @@ export default function Settings(): ReactElement {
|
|||||||
</Paper>
|
</Paper>
|
||||||
<Paper style={{ marginTop: '20px' }}>
|
<Paper style={{ marginTop: '20px' }}>
|
||||||
<TextField
|
<TextField
|
||||||
id="filled-full-width"
|
|
||||||
label="Debug API Endpoint"
|
label="Debug API Endpoint"
|
||||||
style={{ margin: 0 }}
|
style={{ margin: 0 }}
|
||||||
placeholder="ex: 127.0.0.0.1:1635"
|
placeholder="ex: 127.0.0.0.1:1635"
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import React, { ReactElement, useContext } from 'react'
|
||||||
|
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 CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
|
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 } from '../../providers/Stamps'
|
||||||
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
|
interface FormValues {
|
||||||
|
depth?: string
|
||||||
|
amount?: string
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
type FormErrors = Partial<FormValues>
|
||||||
|
const initialFormValues: FormValues = {
|
||||||
|
depth: '',
|
||||||
|
amount: '',
|
||||||
|
label: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
wrapper: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
},
|
||||||
|
buttonProgress: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
marginTop: -12,
|
||||||
|
marginBottom: -12,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FormDialog({ label }: Props): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const { refresh } = useContext(Context)
|
||||||
|
const handleClickOpen = () => setOpen(true)
|
||||||
|
const handleClose = () => setOpen(false)
|
||||||
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={initialFormValues}
|
||||||
|
onSubmit={async (values: FormValues, actions: FormikHelpers<FormValues>) => {
|
||||||
|
try {
|
||||||
|
// This is really just a typeguard, the validation pretty much guarantees these will have the right values
|
||||||
|
if (!values.depth || !values.amount) 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)
|
||||||
|
actions.resetForm()
|
||||||
|
await refresh()
|
||||||
|
handleClose()
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar(`Error: ${e.message}`, { variant: 'error' })
|
||||||
|
actions.setSubmitting(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
validate={(values: FormValues) => {
|
||||||
|
const errors: FormErrors = {}
|
||||||
|
|
||||||
|
// Depth
|
||||||
|
if (!values.depth) errors.depth = 'Required field'
|
||||||
|
else {
|
||||||
|
const depth = new BigNumber(values.depth)
|
||||||
|
|
||||||
|
if (!depth.isInteger()) errors.depth = 'Depth must be an integer'
|
||||||
|
else if (depth.isLessThan(16)) errors.depth = 'Minimal depth is 16'
|
||||||
|
else if (depth.isGreaterThan(255)) errors.depth = 'Depth has to be at most 255'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
if (!values.amount) errors.amount = 'Required field'
|
||||||
|
else {
|
||||||
|
const amount = new BigNumber(values.amount)
|
||||||
|
|
||||||
|
if (!amount.isInteger()) errors.amount = 'Amount must be an integer'
|
||||||
|
else if (amount.isLessThanOrEqualTo(0)) errors.amount = 'Amount must be greater than 0'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label
|
||||||
|
if (values.label && !/^[0-9a-z]*$/i.test(values.label)) errors.label = 'Label must be an alphanumeric string'
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ submitForm, isValid, isSubmitting, values }) => (
|
||||||
|
<Form>
|
||||||
|
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
||||||
|
{label || 'Buy Postage Stamp'}
|
||||||
|
{isSubmitting && <CircularProgress size={24} className={classes.buttonProgress} />}
|
||||||
|
</Button>
|
||||||
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
|
<DialogTitle id="form-dialog-title">Purchase new postage stamp</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Provide the depth, amount and optionally the label of the postage stamp. Please refer to the{' '}
|
||||||
|
<a href="https://docs.ethswarm.org/docs/access-the-swarm/keep-your-data-alive" target="blank">
|
||||||
|
official bee docs
|
||||||
|
</a>{' '}
|
||||||
|
to understand these values.
|
||||||
|
</DialogContentText>
|
||||||
|
<Field
|
||||||
|
component={TextField}
|
||||||
|
required
|
||||||
|
name="depth"
|
||||||
|
autoFocus
|
||||||
|
label="Depth"
|
||||||
|
fullWidth
|
||||||
|
className={classes.field}
|
||||||
|
/>
|
||||||
|
<Field component={TextField} required name="amount" label="Amount" fullWidth className={classes.field} />
|
||||||
|
<Field component={TextField} name="label" label="Label" fullWidth className={classes.field} />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<div className={classes.wrapper}>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
disabled={isSubmitting || !isValid || !values.amount || !values.depth}
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
onClick={submitForm}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
{isSubmitting && <CircularProgress size={24} className={classes.buttonProgress} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@material-ui/core'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import type { ReactElement } from 'react'
|
||||||
|
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||||
|
import PeerDetailDrawer from '../../components/PeerDetail'
|
||||||
|
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
table: {
|
||||||
|
minWidth: 650,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
textAlign: 'right',
|
||||||
|
fontFamily: 'monospace, monospace',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
interface Props {
|
||||||
|
postageStamps: EnrichedPostageBatch[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
function StampsTable({ postageStamps }: Props): ReactElement | null {
|
||||||
|
if (postageStamps === null) return null
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table className={classes.table} size="small" aria-label="Balances Table">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Batch ID</TableCell>
|
||||||
|
<TableCell align="right">Usage</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{postageStamps.map(({ batchID, usageText }) => (
|
||||||
|
<TableRow key={batchID}>
|
||||||
|
<TableCell>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<small>
|
||||||
|
<PeerDetailDrawer peerId={batchID} />
|
||||||
|
</small>
|
||||||
|
<ClipboardCopy value={batchID} />
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.values}>{usageText}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StampsTable
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { ReactElement, useContext, useEffect } from 'react'
|
||||||
|
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
|
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'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
display: 'grid',
|
||||||
|
rowGap: theme.spacing(2),
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
columnGap: theme.spacing(1),
|
||||||
|
rowGap: theme.spacing(1),
|
||||||
|
flex: '0 1 auto',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
useEffect(() => {
|
||||||
|
start()
|
||||||
|
|
||||||
|
return () => stop()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (isLoadingHealth || isLoadingNodeHealth) {
|
||||||
|
return (
|
||||||
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
{error && (
|
||||||
|
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
|
Error loading postage stamps details: {error.message}
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
{!error && (
|
||||||
|
<>
|
||||||
|
<div className={classes.actions}>
|
||||||
|
<CreatePostageStampModal />
|
||||||
|
<LastUpdate date={lastUpdate} />
|
||||||
|
<div style={{ height: '5px' }}>{isLoading && <CircularProgress />}</div>
|
||||||
|
</div>
|
||||||
|
<StampsTable postageStamps={stamps} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,27 +8,24 @@ interface Props extends StatusChequebookHook {
|
|||||||
ethereumAddress?: string
|
ethereumAddress?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChequebookDeployFund = ({
|
const ChequebookDeployFund = ({ isLoading, chequebookAddress, chequebookBalance }: Props): ReactElement | null => {
|
||||||
isLoading,
|
|
||||||
chequebookAddress,
|
|
||||||
chequebookBalance,
|
|
||||||
ethereumAddress,
|
|
||||||
}: Props): ReactElement | null => {
|
|
||||||
if (isLoading) return null
|
if (isLoading) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p style={{ marginBottom: '20px', display: 'flex' }}>
|
<p style={{ marginBottom: '20px', display: 'flex' }}>
|
||||||
{chequebookAddress?.chequebookaddress && <DepositModal />}
|
{chequebookAddress?.chequebookAddress && <DepositModal />}
|
||||||
</p>
|
</p>
|
||||||
<div style={{ marginBottom: '10px' }}>
|
<div style={{ marginBottom: '10px' }}>
|
||||||
{!(chequebookAddress?.chequebookaddress && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) && (
|
{!(chequebookAddress?.chequebookAddress && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) && (
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
Your chequebook is either not deployed or funded. Join{' '}
|
Your chequebook is either not deployed or funded. To run the node you will need xDAI and xBZZ on the xDai
|
||||||
<a href="https://discord.gg/ykCupZMuww">our discord channel</a>, get verified and send a message{' '}
|
network. You may need to aquire BZZ through (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and
|
||||||
<pre>sprinkle {ethereumAddress || '<YOUR BEE NODE ETH ADDRESS>'}</pre> in the <pre>#faucet-request</pre>{' '}
|
bridge it to the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To
|
||||||
channel to get Goerli ETH and Goerli BZZ token.
|
pay the transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it
|
||||||
|
to xDai network through the <a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '}
|
||||||
|
<a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -36,7 +33,7 @@ const ChequebookDeployFund = ({
|
|||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Chequebook Address
|
Chequebook Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={chequebookAddress?.chequebookaddress} network={'goerli'} />
|
<EthereumAddress address={chequebookAddress?.chequebookAddress} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,7 @@ export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactEl
|
|||||||
<Typography component="div">
|
<Typography component="div">
|
||||||
<ol>
|
<ol>
|
||||||
<li>Check the status of your node by running the below command to see if your node is running.</li>
|
<li>Check the status of your node by running the below command to see if your node is running.</li>
|
||||||
<CodeBlockTabs
|
<CodeBlockTabs showLineNumbers linux={`sudo systemctl status bee`} mac={`brew services list`} />
|
||||||
showLineNumbers
|
|
||||||
linux={`sudo systemctl status bee`}
|
|
||||||
mac={`brew services status swarm-bee`}
|
|
||||||
/>
|
|
||||||
<li>
|
<li>
|
||||||
If your node is running, check your firewall settings to make sure that port 1635 (or your custom
|
If your node is running, check your firewall settings to make sure that port 1635 (or your custom
|
||||||
specified port) is bound to localhost. If your node is not running try executing the below command
|
specified port) is bound to localhost. If your node is not running try executing the below command
|
||||||
@@ -71,19 +67,20 @@ export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactEl
|
|||||||
<CodeBlockTabs
|
<CodeBlockTabs
|
||||||
showLineNumbers
|
showLineNumbers
|
||||||
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
|
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
|
||||||
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
|
mac={`brew services list \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
|
||||||
/>
|
/>
|
||||||
<li>
|
<li>
|
||||||
Lastly, check your nodes configuration settings to validate the debug API is enabled and the Cross
|
Lastly, check your nodes configuration settings to validate the debug API is enabled and the Cross
|
||||||
Origin Resource Sharing (CORS) setting is configured to allow your host. Config parameter{' '}
|
Origin Resource Sharing (CORS) setting is configured to allow your host. Config parameter{' '}
|
||||||
<strong>debug-api-enable</strong> must be set to <strong>true</strong> and{' '}
|
<strong>debug-api-enable</strong> must be set to <strong>true</strong> and{' '}
|
||||||
<strong>cors-allowed-origins</strong> must be set to your host domain or IP. If edits are made to
|
<strong>cors-allowed-origins</strong> must be set to your host domain or IP (you can also use the
|
||||||
the configuration run the restart command below for changes to take effect.
|
wildcard <code>{"cors-allowed-origins: ['*']"}</code>). If edits are made to the configuration run
|
||||||
|
the restart command below for changes to take effect.
|
||||||
</li>
|
</li>
|
||||||
<CodeBlockTabs
|
<CodeBlockTabs
|
||||||
showLineNumbers
|
showLineNumbers
|
||||||
linux={`sudo vi /etc/bee/bee.yaml\nsudo systemctl restart bee`}
|
linux={`sudo vi /etc/bee/bee.yaml\nsudo systemctl restart bee`}
|
||||||
mac={`sudo vi /etc/bee/bee.yaml \nbrew services restart swarm-bee`}
|
mac={`sudo vi /usr/local/etc/swarm-bee/bee.yaml \nbrew services restart swarm-bee`}
|
||||||
/>
|
/>
|
||||||
</ol>
|
</ol>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -13,28 +13,24 @@ export default function EthereumConnectionCheck({ isLoading, isOk, nodeAddresses
|
|||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Node Address
|
Node Address
|
||||||
</Typography>
|
</Typography>
|
||||||
<EthereumAddress address={nodeAddresses?.ethereum} network={'goerli'} />
|
<EthereumAddress address={nodeAddresses?.ethereum} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your chequebook
|
Your Bee node must have access to the xDai blockchain, so that it can interact and deploy your chequebook
|
||||||
contract. You can run{' '}
|
contract. You can run{' '}
|
||||||
<a href="https://github.com/goerli/testnet" rel="noreferrer" target="_blank">
|
<a href="https://www.xdaichain.com/" rel="noreferrer" target="_blank">
|
||||||
your own Goerli node
|
your own xDai node
|
||||||
</a>
|
</a>
|
||||||
, or use a provider such as{' '}
|
, or use a provider instead - we recommend{' '}
|
||||||
<a href="https://rpc.slock.it/goerli" rel="noreferrer" target="_blank">
|
<a href="https://getblock.io/" rel="noreferrer" target="_blank">
|
||||||
rpc.slock.it/goerli
|
Getblock
|
||||||
</a>{' '}
|
|
||||||
or{' '}
|
|
||||||
<a href="https://infura.io/" rel="noreferrer" target="_blank">
|
|
||||||
Infura
|
|
||||||
</a>
|
</a>
|
||||||
. By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, simply change
|
. By default, Bee expects a local node at http://localhost:8545. To use a provider instead, simply change the{' '}
|
||||||
your <strong>--swap-endpoint</strong> in your configuration file.
|
<strong>swap-endpoint</strong> in your configuration file.
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,7 @@ export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactEl
|
|||||||
<Typography component="div">
|
<Typography component="div">
|
||||||
<ol>
|
<ol>
|
||||||
<li>Check the status of your node by running the below command to see if your node is running.</li>
|
<li>Check the status of your node by running the below command to see if your node is running.</li>
|
||||||
<CodeBlockTabs
|
<CodeBlockTabs showLineNumbers linux={`sudo systemctl status bee`} mac={`brew services list`} />
|
||||||
showLineNumbers
|
|
||||||
linux={`sudo systemctl status bee`}
|
|
||||||
mac={`brew services status swarm-bee`}
|
|
||||||
/>
|
|
||||||
<li>
|
<li>
|
||||||
If your node is running, check your firewall settings to make sure that port 1633 (or your custom
|
If your node is running, check your firewall settings to make sure that port 1633 (or your custom
|
||||||
specified port) is exposed to the internet. If your node is not running try executing the below
|
specified port) is exposed to the internet. If your node is not running try executing the below
|
||||||
@@ -51,7 +47,7 @@ export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactEl
|
|||||||
<CodeBlockTabs
|
<CodeBlockTabs
|
||||||
showLineNumbers
|
showLineNumbers
|
||||||
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
|
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
|
||||||
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
|
mac={`brew services list \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
|
||||||
/>
|
/>
|
||||||
</ol>
|
</ol>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -23,17 +23,19 @@ const useStyles = makeStyles(() =>
|
|||||||
interface Props {
|
interface Props {
|
||||||
nodeAddresses: NodeAddresses | null
|
nodeAddresses: NodeAddresses | null
|
||||||
nodeTopology: Topology | null
|
nodeTopology: Topology | null
|
||||||
userBeeVersion: string | null
|
userBeeVersion?: string
|
||||||
latestBeeVersion: string | null
|
isLatestBeeVersion: boolean
|
||||||
isOk: boolean
|
isOk: boolean
|
||||||
|
latestUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusCard({
|
function StatusCard({
|
||||||
userBeeVersion,
|
userBeeVersion,
|
||||||
nodeAddresses,
|
nodeAddresses,
|
||||||
nodeTopology,
|
nodeTopology,
|
||||||
latestBeeVersion,
|
|
||||||
isOk,
|
isOk,
|
||||||
|
isLatestBeeVersion,
|
||||||
|
latestUrl,
|
||||||
}: Props): ReactElement | null {
|
}: Props): ReactElement | null {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
@@ -72,7 +74,7 @@ function StatusCard({
|
|||||||
Bee
|
Bee
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
<span>{userBeeVersion || '-'}</span>
|
<span>{userBeeVersion || '-'}</span>
|
||||||
{userBeeVersion && latestBeeVersion && userBeeVersion === latestBeeVersion ? (
|
{isLatestBeeVersion ? (
|
||||||
<Chip
|
<Chip
|
||||||
style={{ marginLeft: '7px', color: '#2145a0' }}
|
style={{ marginLeft: '7px', color: '#2145a0' }}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -80,16 +82,18 @@ function StatusCard({
|
|||||||
className={classes.status}
|
className={classes.status}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Typography variant="button">update</Typography>
|
<Button size="small" variant="outlined" href={latestUrl}>
|
||||||
|
update
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
<span>PUBLIC KEY: </span>
|
<span>PUBLIC KEY: </span>
|
||||||
<span>{nodeAddresses?.public_key ? nodeAddresses.public_key : '-'}</span>
|
<span>{nodeAddresses?.publicKey ? nodeAddresses.publicKey : '-'}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
<span>PSS PUBLIC KEY: </span>
|
<span>PSS PUBLIC KEY: </span>
|
||||||
<span>{nodeAddresses?.pss_public_key ? nodeAddresses.pss_public_key : '-'}</span>
|
<span>{nodeAddresses?.pssPublicKey ? nodeAddresses.pssPublicKey : '-'}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="div" variant="subtitle2" gutterBottom>
|
<Typography component="div" variant="subtitle2" gutterBottom>
|
||||||
<span>OVERLAY ADDRESS (PEER ID): </span>
|
<span>OVERLAY ADDRESS (PEER ID): </span>
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ export default function Status(): ReactElement {
|
|||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<StatusCard
|
<StatusCard
|
||||||
userBeeVersion={nodeVersion.userVersion}
|
userBeeVersion={nodeVersion.userVersion}
|
||||||
latestBeeVersion={nodeVersion.latestVersion}
|
isLatestBeeVersion={nodeVersion.isLatestBeeVersion}
|
||||||
isOk={checks.every(c => c.isOk)}
|
isOk={checks.every(c => c.isOk)}
|
||||||
nodeTopology={topology.topology}
|
nodeTopology={topology.topology}
|
||||||
|
latestUrl={nodeVersion.latestUrl}
|
||||||
nodeAddresses={ethereumConnection.nodeAddresses}
|
nodeAddresses={ethereumConnection.nodeAddresses}
|
||||||
/>
|
/>
|
||||||
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
|
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { PostageBatch } from '@ethersphere/bee-js'
|
||||||
|
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
|
||||||
|
import { beeApi } from '../services/bee'
|
||||||
|
|
||||||
|
export interface EnrichedPostageBatch extends PostageBatch {
|
||||||
|
usage: number
|
||||||
|
usageText: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextInterface {
|
||||||
|
stamps: EnrichedPostageBatch[] | null
|
||||||
|
error: Error | null
|
||||||
|
isLoading: boolean
|
||||||
|
lastUpdate: number | null
|
||||||
|
start: (frequency?: number) => void
|
||||||
|
stop: () => void
|
||||||
|
refresh: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: ContextInterface = {
|
||||||
|
stamps: null,
|
||||||
|
error: null,
|
||||||
|
isLoading: 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 enrichStamp(postageBatch: PostageBatch): EnrichedPostageBatch {
|
||||||
|
const { depth, bucketDepth, utilization } = postageBatch
|
||||||
|
|
||||||
|
const usage = utilization / Math.pow(2, depth - bucketDepth)
|
||||||
|
const usageText = `${Math.ceil(usage * 100)}%`
|
||||||
|
|
||||||
|
return {
|
||||||
|
...postageBatch,
|
||||||
|
usage,
|
||||||
|
usageText,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Provider({ children }: Props): ReactElement {
|
||||||
|
const [stamps, setStamps] = useState<EnrichedPostageBatch[] | null>(initialValues.stamps)
|
||||||
|
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||||
|
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||||
|
const [frequency, setFrequency] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
// Don't want to refresh when already refreshing
|
||||||
|
if (isLoading) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const stamps = await beeApi.stamps.getPostageStamps()
|
||||||
|
|
||||||
|
setStamps(stamps.map(enrichStamp))
|
||||||
|
setLastUpdate(Date.now())
|
||||||
|
} catch (e) {
|
||||||
|
setError(e)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={{ stamps, error, isLoading, lastUpdate, start, stop, refresh }}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
Vendored
+3
-2
@@ -11,9 +11,10 @@ interface StatusHookCommon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StatusNodeVersionHook extends StatusHookCommon {
|
interface StatusNodeVersionHook extends StatusHookCommon {
|
||||||
userVersion: string
|
userVersion?: string
|
||||||
latestVersion: string
|
latestVersion?: string
|
||||||
latestUrl: string
|
latestUrl: string
|
||||||
|
isLatestBeeVersion: boolean
|
||||||
}
|
}
|
||||||
interface StatusEthereumConnectionHook extends StatusHookCommon {
|
interface StatusEthereumConnectionHook extends StatusHookCommon {
|
||||||
nodeAddresses: NodeAddresses | null
|
nodeAddresses: NodeAddresses | null
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import AppRoute from './AppRoute'
|
|||||||
import Dashboard from '../layout/Dashboard'
|
import Dashboard from '../layout/Dashboard'
|
||||||
|
|
||||||
// pages
|
// pages
|
||||||
import Status from '../pages/status/index'
|
import Status from '../pages/status'
|
||||||
import Files from '../pages/files/index'
|
import Files from '../pages/files'
|
||||||
import Peers from '../pages/peers/index'
|
import Peers from '../pages/peers'
|
||||||
import Accounting from '../pages/accounting/index'
|
import Accounting from '../pages/accounting'
|
||||||
import Settings from '../pages/settings/index'
|
import Settings from '../pages/settings'
|
||||||
|
import Stamps from '../pages/stamps'
|
||||||
|
|
||||||
const BaseRouter = (): ReactElement => (
|
const BaseRouter = (): ReactElement => (
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -20,6 +21,7 @@ const BaseRouter = (): ReactElement => (
|
|||||||
<AppRoute exact path="/peers/" layout={Dashboard} component={Peers} />
|
<AppRoute exact path="/peers/" layout={Dashboard} component={Peers} />
|
||||||
<AppRoute exact path="/accounting/" layout={Dashboard} component={Accounting} />
|
<AppRoute exact path="/accounting/" layout={Dashboard} component={Accounting} />
|
||||||
<AppRoute exact path="/settings/" layout={Dashboard} component={Settings} />
|
<AppRoute exact path="/settings/" layout={Dashboard} component={Settings} />
|
||||||
|
<AppRoute exact path="/stamps/" layout={Dashboard} component={Stamps} />
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+18
-10
@@ -1,13 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
|
Address,
|
||||||
AllSettlements,
|
AllSettlements,
|
||||||
BalanceResponse,
|
BalanceResponse,
|
||||||
Bee,
|
Bee,
|
||||||
BeeDebug,
|
BeeDebug,
|
||||||
CashoutResponse,
|
|
||||||
ChequebookAddressResponse,
|
ChequebookAddressResponse,
|
||||||
ChequebookBalanceResponse,
|
ChequebookBalanceResponse,
|
||||||
Data,
|
Data,
|
||||||
DepositTokensResponse,
|
|
||||||
FileData,
|
FileData,
|
||||||
Health,
|
Health,
|
||||||
LastCashoutActionResponse,
|
LastCashoutActionResponse,
|
||||||
@@ -16,9 +15,10 @@ import {
|
|||||||
NodeAddresses,
|
NodeAddresses,
|
||||||
Peer,
|
Peer,
|
||||||
PingResponse,
|
PingResponse,
|
||||||
|
PostageBatch,
|
||||||
|
PostageBatchOptions,
|
||||||
Reference,
|
Reference,
|
||||||
Topology,
|
Topology,
|
||||||
WithdrawTokensResponse,
|
|
||||||
} from '@ethersphere/bee-js'
|
} from '@ethersphere/bee-js'
|
||||||
import { apiHost, debugApiHost } from '../constants'
|
import { apiHost, debugApiHost } from '../constants'
|
||||||
|
|
||||||
@@ -33,13 +33,21 @@ export const beeApi = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
uploadFile(file: File): Promise<Reference> {
|
uploadFile(postageBatchId: Address, file: File): Promise<Reference> {
|
||||||
return beeJSClient().uploadFile(file)
|
return beeJSClient().uploadFile(postageBatchId, file)
|
||||||
},
|
},
|
||||||
downloadFile(hash: string | Reference): Promise<FileData<Data>> {
|
downloadFile(hash: string | Reference): Promise<FileData<Data>> {
|
||||||
return beeJSClient().downloadFile(hash)
|
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 = {
|
export const beeDebugApi = {
|
||||||
@@ -77,7 +85,7 @@ export const beeDebugApi = {
|
|||||||
getLastCheques(): Promise<LastChequesResponse> {
|
getLastCheques(): Promise<LastChequesResponse> {
|
||||||
return beeJSDebugClient().getLastCheques()
|
return beeJSDebugClient().getLastCheques()
|
||||||
},
|
},
|
||||||
peerCashout(peerId: string): Promise<CashoutResponse> {
|
peerCashout(peerId: string): Promise<string> {
|
||||||
return beeJSDebugClient().cashoutLastCheque(peerId)
|
return beeJSDebugClient().cashoutLastCheque(peerId)
|
||||||
},
|
},
|
||||||
getPeerLastCashout(peerId: string): Promise<LastCashoutActionResponse> {
|
getPeerLastCashout(peerId: string): Promise<LastCashoutActionResponse> {
|
||||||
@@ -86,11 +94,11 @@ export const beeDebugApi = {
|
|||||||
getPeerLastCheques(peerId: string): Promise<LastChequesForPeerResponse> {
|
getPeerLastCheques(peerId: string): Promise<LastChequesForPeerResponse> {
|
||||||
return beeJSDebugClient().getLastChequesForPeer(peerId)
|
return beeJSDebugClient().getLastChequesForPeer(peerId)
|
||||||
},
|
},
|
||||||
withdraw(amount: bigint): Promise<WithdrawTokensResponse> {
|
withdraw(amount: bigint): Promise<string> {
|
||||||
return beeJSDebugClient().withdrawTokens(amount)
|
return beeJSDebugClient().withdrawTokens(amount.toString())
|
||||||
},
|
},
|
||||||
deposit(amount: bigint): Promise<DepositTokensResponse> {
|
deposit(amount: bigint): Promise<string> {
|
||||||
return beeJSDebugClient().depositTokens(amount)
|
return beeJSDebugClient().depositTokens(amount.toString())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
settlements: {
|
settlements: {
|
||||||
|
|||||||
+60
-2
@@ -1,4 +1,5 @@
|
|||||||
import { createMuiTheme } from '@material-ui/core/styles'
|
import { createMuiTheme, Theme } from '@material-ui/core/styles'
|
||||||
|
import { orange } from '@material-ui/core/colors'
|
||||||
|
|
||||||
declare module '@material-ui/core/styles/createPalette' {
|
declare module '@material-ui/core/styles/createPalette' {
|
||||||
interface TypeBackground {
|
interface TypeBackground {
|
||||||
@@ -6,6 +7,54 @@ declare module '@material-ui/core/styles/createPalette' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overwriting default components styles
|
||||||
|
const componentsOverrides = (theme: Theme) => ({
|
||||||
|
MuiTab: {
|
||||||
|
root: {
|
||||||
|
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: theme.palette.secondary,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
'&$selected': {
|
||||||
|
color: theme.palette.secondary,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
},
|
||||||
|
'&:focus': {
|
||||||
|
color: theme.palette.secondary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTabs: {
|
||||||
|
root: {
|
||||||
|
borderBottom: 'none',
|
||||||
|
},
|
||||||
|
indicator: {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const propsOverrides = {
|
||||||
|
MuiTab: {
|
||||||
|
disableRipple: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const lightTheme = createMuiTheme({
|
export const lightTheme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
@@ -13,7 +62,9 @@ export const lightTheme = createMuiTheme({
|
|||||||
default: '#fafafa',
|
default: '#fafafa',
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
main: '#6a6a6a',
|
light: orange.A200,
|
||||||
|
main: '#dd7700',
|
||||||
|
dark: orange[800],
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: '#333333',
|
main: '#333333',
|
||||||
@@ -32,7 +83,9 @@ export const darkTheme = createMuiTheme({
|
|||||||
paper: '#161b22',
|
paper: '#161b22',
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
|
light: orange.A200,
|
||||||
main: '#dd7700',
|
main: '#dd7700',
|
||||||
|
dark: orange[800],
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: '#1f2937',
|
main: '#1f2937',
|
||||||
@@ -42,3 +95,8 @@ export const darkTheme = createMuiTheme({
|
|||||||
fontFamily: ['Work Sans', 'Montserrat', 'Nunito', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'].join(','),
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user