Compare commits

...

18 Commits

Author SHA1 Message Date
bee-worker 5748c9b609 chore: release 0.3.1 (#141) 2021-06-03 13:22:57 +02:00
Vojtech Simetka 5ace7629f2 fix: don't display version alert when unable to retrieve version from bee node (#138) 2021-06-03 13:19:16 +02:00
Vojtech Simetka 465df17741 fix: typeerror in the postage stamps form (#137) 2021-06-03 13:18:58 +02:00
bee-worker 3bcf2ac688 chore: release 0.3.0 (#113) 2021-06-02 16:08:11 +02:00
Vojtech Simetka a2bff60270 refactor: removed unused useGetPostageStamps hook (#132) 2021-06-02 16:00:18 +02:00
Vojtech Simetka 353db10080 feat: added tolerance to version check and warning if not exact to what we tested (#133)
* feat: added tolerance to version check and warning if not exact to what we expect

* chore: update to bee-js 0.10.0

* chore: updated interfaces that changed in bee-js 0.10.0
2021-06-02 15:59:57 +02:00
Vojtech Simetka bec84051a9 feat: unified notification with notistack (#127)
* feat: unified existing notification with notistack

* chore: replaced with useSnackbar, added missing notifications

* chore: removed FIXME as per PR review
2021-06-02 13:36:39 +02:00
Vojtech Simetka 92c727e5f5 feat: upload files with postage stamps (#126)
* chore: release 0.3.0

* feat: added postage stamp table to list all stamps

* feat: postage stamp modal to purchase stamps

* feat: postage stamps provider

* chore: added formik

* chore: proper form state handling

* chore: revert accidental release inclusion

* chore: polishing identified when developing the upload functionality

* feat: upload files with postage stamps

* style: tabs styles are defined in theme now, addressed other PR comments

* style: removed unused styles

* fix: enable encrypted hashes to download

Co-authored-by: bee-worker <70210089+bee-worker@users.noreply.github.com>
2021-06-02 13:25:49 +02:00
Vojtech Simetka 4074a9de5d feat: postage stamps support (#115)
* chore: release 0.3.0

* feat: added postage stamp table to list all stamps

* feat: postage stamp modal to purchase stamps

* feat: postage stamps provider

* chore: added formik

* chore: proper form state handling

* chore: revert accidental release inclusion

* chore: polishing identified when developing the upload functionality

Co-authored-by: bee-worker <70210089+bee-worker@users.noreply.github.com>
2021-06-02 13:13:27 +02:00
Vojtech Simetka 9fee1aa68a fix: troubleshooting on a mac and clearer CORS setup guide (#131)
* fix: service status check on macOS

* fix: config file location on mac and making the CORS setup clearer

* fix: error in the README cors instructions
2021-06-02 12:13:17 +02:00
Vojtech Simetka 08fdac9366 docs: updated readme with discord link and clearer setup steps (#129)
* docs: updated readme with discord link and clearer setup steps

* chore: typo in README.md

Co-authored-by: Attila Gazso <agazso@gmail.com>

Co-authored-by: Attila Gazso <agazso@gmail.com>
2021-06-02 11:55:09 +02:00
Matt Mertens ba9b498488 fix: replace http-serve with serve-handler (#122)
* fix: replace http-serve with serve package

Replaces the http-serve package to properly handle the single page routing

* feat: add cache invalidation

* fix: add serve command in bin

* fix: remove serve package dependency

* chore: applied PR review suggestions

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz> (+1 squashed commit)
Squashed commits:
[d73baf4] Update serve.js

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>
2021-05-31 09:57:44 +02:00
Matt Mertens 07f987e069 fix: add git attributes (#123) 2021-05-29 01:30:54 +02:00
Matt Mertens a603a86c1a fix: add prod env variables (#121) 2021-05-28 17:43:02 +02:00
Mirko Da Corte aab0462047 feat: added Dockerfile (#75)
* feat: added Dockerfile (#72)

* fix: applying requested fixes on Dockerfile

* fix: updated exposed port on Dockerfile
chore: updated readme with docker instructions

* Update README.md

Co-authored-by: nugaon <50576770+nugaon@users.noreply.github.com>

* fix: fixed codeblock in readme

Co-authored-by: nugaon <50576770+nugaon@users.noreply.github.com>
2021-05-25 16:43:26 +02:00
bee-worker ce949d380c chore: release 0.2.0 (#105) 2021-05-20 18:55:36 +02:00
Vojtech Simetka 7f5fbd3fb6 feat: update to bee 0.6.0 and bee-js 0.9.0 (#99)
* chore: update the interfaces to latest bee-js

* chore: update to latest bee-js

* chore: removed upload page, updated to latest bee-js

* chore: typo in src/pages/files/index.tsx

Co-authored-by: Attila Gazso <agazso@gmail.com>

* chore: update to bee-js 0.9.0

Co-authored-by: Attila Gazso <agazso@gmail.com>
2021-05-20 18:45:35 +02:00
Attila Gazso edd4a2fc11 fix: serve npm command path specification (#101)
When running the Exit 127 command the path is
not changed and it uses the default value ().

Fixes #98
2021-05-20 18:36:20 +02:00
42 changed files with 1545 additions and 627 deletions
+2
View File
@@ -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_ETHERSCAN_HOST=etherscan.io
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
+4
View File
@@ -0,0 +1,4 @@
* text=auto eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
+41
View File
@@ -1,5 +1,46 @@
# Changelog # Changelog
### [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
View File
@@ -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"]
+37 -10
View File
@@ -6,7 +6,7 @@
![](https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square) ![](https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square)
![](https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square) ![](https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square)
> 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,56 @@
- [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 start
npm run build
npm run serve
``` ```
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 +78,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
+455 -349
View File
File diff suppressed because it is too large Load Diff
+17 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ethersphere/bee-dashboard", "name": "@ethersphere/bee-dashboard",
"version": "0.1.0", "version": "0.3.1",
"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": "^0.10.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"
} }
} }
+12
View File
@@ -0,0 +1,12 @@
{
"trailingSlash": false,
"headers": [
{
"source" : "*",
"headers" : [{
"key" : "Cache-Control",
"value" : "max-age=3600"
}]
}
]
}
Executable → Regular
+22 -4
View File
@@ -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')
+8
View File
@@ -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}>
<StampsProvider>
<SnackbarProvider>
<>
<CssBaseline /> <CssBaseline />
<Router> <Router>
<BaseRouter /> <BaseRouter />
</Router> </Router>
</>
</SnackbarProvider>
</StampsProvider>
</ThemeProvider> </ThemeProvider>
</div> </div>
) )
+54
View File
@@ -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>
)
}
+9 -16
View File
@@ -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} network={'goerli'} />
</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>
+7 -11
View File
@@ -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>
+1 -1
View File
@@ -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} network={'goerli'} />
</CardContent> </CardContent>
</div> </div>
)} )}
+23
View File
@@ -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>
}
@@ -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>
) )
} }
+7 -1
View File
@@ -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',
+71
View File
@@ -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>
)
}
+7 -14
View File
@@ -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>
+8 -9
View File
@@ -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'),
} }
} }
+8 -8
View File
@@ -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
View File
@@ -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,
+5 -16
View File
@@ -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>
) )
+1 -1
View File
@@ -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({
+65
View File
@@ -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>}
</>
)
}
+48
View File
@@ -0,0 +1,48 @@
import React, { ReactElement } from 'react'
import Button from '@material-ui/core/Button'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import { PostageBatch } from '@ethersphere/bee-js'
import PeerDetailDrawer from '../../components/PeerDetail'
interface Props {
stamps: PostageBatch[] | null
selectedStamp: PostageBatch | null
setSelected: (stamp: PostageBatch) => 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.utilization}</ListItemIcon>
<PeerDetailDrawer peerId={stamp.batchID} />
</MenuItem>
))}
</Menu>
</div>
)
}
+100
View File
@@ -0,0 +1,100 @@
import { ReactElement, useContext, useEffect, useState } from 'react'
import { beeApi } from '../../services/bee'
import { Button, Container, CircularProgress } from '@material-ui/core'
import { DropzoneArea } from 'material-ui-dropzone'
import ClipboardCopy from '../../components/ClipboardCopy'
import { PostageBatch } from '@ethersphere/bee-js'
import { Context } from '../../providers/Stamps'
import PeerDetailDrawer from '../../components/PeerDetail'
import Chip from '@material-ui/core/Chip'
import Avatar from '@material-ui/core/Avatar'
import SelectStamp from './SelectStamp'
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
import { useSnackbar } from 'notistack'
export default function Files(): ReactElement {
const [file, setFile] = useState<File | null>(null)
const [uploadReference, setUploadReference] = useState('')
const [isUploadingFile, setIsUploadingFile] = useState(false)
const [selectedStamp, setSelectedStamp] = useState<PostageBatch | null>(null)
const { isLoading, error, stamps } = useContext(Context)
const { enqueueSnackbar } = useSnackbar()
// Choose a postage stamp that has the lowest utilization
useEffect(() => {
if (!selectedStamp && stamps && stamps.length > 0) {
const stamp = stamps.reduce((prev, curr) => {
if (curr.utilization < prev.utilization) 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 => {
setUploadReference(hash)
setFile(null)
})
.catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' }))
.finally(() => {
setIsUploadingFile(false)
})
}
const handleChange = (files?: File[]) => {
if (files) {
setFile(files[0])
setUploadReference('')
}
}
return (
<div>
<div>
<DropzoneArea onChange={handleChange} filesLimit={1} />
<div style={{ marginTop: '15px' }}>
{selectedStamp && (
<div style={{ display: 'flex' }}>
<small>
with Postage Stamp{' '}
<Chip
avatar={<Avatar>{selectedStamp.utilization}</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>
{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>
)
}
+16 -124
View File
@@ -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>
) )
} }
-2
View File
@@ -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>
)
}
+55
View File
@@ -0,0 +1,55 @@
import type { ReactElement } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper } from '@material-ui/core'
import ClipboardCopy from '../../components/ClipboardCopy'
import PeerDetailDrawer from '../../components/PeerDetail'
import { PostageBatch } from '@ethersphere/bee-js'
const useStyles = makeStyles({
table: {
minWidth: 650,
},
values: {
textAlign: 'right',
fontFamily: 'monospace, monospace',
},
})
interface Props {
postageStamps: PostageBatch[] | 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">Utilization</TableCell>
</TableRow>
</TableHead>
<TableBody>
{postageStamps.map(({ batchID, utilization }) => (
<TableRow key={batchID}>
<TableCell>
<div style={{ display: 'flex' }}>
<small>
<PeerDetailDrawer peerId={batchID} />
</small>
<ClipboardCopy value={batchID} />
</div>
</TableCell>
<TableCell className={classes.values}>{utilization}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
export default StampsTable
+73
View File
@@ -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>
)
}
@@ -19,10 +19,10 @@ const ChequebookDeployFund = ({
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. Join{' '}
@@ -36,7 +36,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} network={'goerli'} />
</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>
@@ -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>
+11 -7
View File
@@ -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>
+2 -1
View File
@@ -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 && (
+76
View File
@@ -0,0 +1,76 @@
import { PostageBatch } from '@ethersphere/bee-js'
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
import { beeApi } from '../services/bee'
interface ContextInterface {
stamps: PostageBatch[] | 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
}
export function Provider({ children }: Props): ReactElement {
const [stamps, setStamps] = useState<PostageBatch[] | 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)
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>
)
}
+3 -2
View File
@@ -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 -5
View File
@@ -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>
) )
+16 -8
View File
@@ -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, 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,10 +94,10 @@ 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)
}, },
deposit(amount: bigint): Promise<DepositTokensResponse> { deposit(amount: bigint): Promise<string> {
return beeJSDebugClient().depositTokens(amount) return beeJSDebugClient().depositTokens(amount)
}, },
}, },
+60 -2
View File
@@ -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