feat: initial Proof of Concept UI (#1)
* initial dashboard layout * add node status card * add accounting section, pull peer data * add file functionality with bee-js, first iteration of accounts page * Add balances and chequebook table * add blockie / identicon for addresses * add basic settlements table * implement theme overrides * cleanup logging * Add troubleshooting block * add initial dark theme support, add copy to clipboard, QR code support * show active element on sidebar * remove duplicate status page and make status page index * Update package.json Co-authored-by: Vojtech Simetka <vojtech@simetka.cz> * Update public/index.html Co-authored-by: Vojtech Simetka <vojtech@simetka.cz> * Update src/pages/accounting/AccountCard.tsx Co-authored-by: Vojtech Simetka <vojtech@simetka.cz> * change bee api client to use beeJS library * add initial setup workflow * breakout ethereum address component, define initial setup workflow * add types to responses, add additional node troubleshooting info to workflow * make setup steps nonlinear and interactive * make host endpoint dynamic on setup * split out api calls into custom hooks, add component loading indicators * add depost / withdrawl functionality, show transactions in BZZ * add multiOS code support troubleshooting, check for balance in chequebook on setup * add ability to change apis in settings page * show file loading status * Style active sidebar item * reload on theme change * modify troubleshooting verbage, add cashout functionality and details, * facilitate file upload with beeJS * update readme to show UI samples * remove nnPeersWatermark from peers page * split node steps into separate components, make status page visible at anytime * minor UI/UX enhancements * format accounting page * remove WIP wallet connection code * Update src/components/CashoutModal.tsx Co-authored-by: Vojtech Simetka <vojtech@simetka.cz> * use bigint for deposits/withdrawls * revise status card * clean up unused imports and variables * add api status to sidebar * obfuscate pages with troubleshooting component when apis not connected * add localhost OS detection for troubleshooting code * cleanup extra logos * monospace BZZ in tables * hide troubleshooting page while loading API status * Remove ability to remove peers * add null types to API responses Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>
@@ -0,0 +1,7 @@
|
||||
PORT=3001
|
||||
REACT_APP_BEE_HOST=http://localhost:1633
|
||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||
REACT_APP_BEE_DISCORD_HOST=https://discord.gg/eKr9XPv7
|
||||
REACT_APP_ETHERSCAN_HOST=etherscan.io
|
||||
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
||||
@@ -0,0 +1,4 @@
|
||||
REACT_APP_BEE_HOST=http://localhost:1633
|
||||
REACT_APP_BEE_DEBUG_HOST=http://localhost:1635
|
||||
REACT_APP_BEE_DOCS_HOST=https://docs.ethswarm.org/docs/
|
||||
REACT_APP_BEE_GITHUB_REPO_URL=https://api.github.com/repos/ethersphere/bee
|
||||
@@ -11,6 +11,13 @@
|
||||
|
||||
**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.**
|
||||
|
||||

|
||||
|
||||
| Node Setup | Browse Files | Accounting | Peers | Settings |
|
||||
|-------|---------|-------|----------|------|
|
||||
|  |  |  |  |  |
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install](#install)
|
||||
|
||||
@@ -21,20 +21,38 @@
|
||||
"url": "https://github.com/ethersphere/bee-status.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersphere/bee-js": "^0.5.0",
|
||||
"@ethersphere/bee-js": "^0.5.1",
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"@types/react-router": "^5.1.12",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"feather-icons": "^4.28.0",
|
||||
"material-ui-dropzone": "^3.5.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-identicons": "^1.2.5",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-syntax-highlighter": "^15.4.3",
|
||||
"web3": "^1.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"@testing-library/user-event": "^12.8.1",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/qrcode.react": "^1.0.1",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-syntax-highlighter": "^13.5.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.1.2",
|
||||
"typescript": "^4.2.3",
|
||||
"web-vitals": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Bee node status"
|
||||
@@ -15,6 +19,7 @@
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
||||
@@ -1,38 +1,3 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
font-family: "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,94 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {BrowserRouter as Router} from 'react-router-dom';
|
||||
import './App.css';
|
||||
|
||||
import { createMuiTheme } from '@material-ui/core/styles';
|
||||
import { ThemeProvider } from '@material-ui/styles';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
|
||||
import BaseRouter from './routes/routes';
|
||||
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ethereum: {};
|
||||
web3: {};
|
||||
}
|
||||
}
|
||||
|
||||
const lightTheme = createMuiTheme({
|
||||
typography: {
|
||||
fontFamily: [
|
||||
'Montserrat',
|
||||
'Nunito',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif'
|
||||
].join(','),
|
||||
}
|
||||
});
|
||||
|
||||
const darkTheme = createMuiTheme({
|
||||
palette: {
|
||||
type: "dark",
|
||||
background: {
|
||||
default: '#0d1117', //'#111827',
|
||||
paper: '#161b22', //'#1f2937',
|
||||
},
|
||||
primary: {
|
||||
// light: will be calculated from palette.primary.main,
|
||||
main: '#5e72e4' //'#3f51b5',
|
||||
// dark: will be calculated from palette.primary.main,
|
||||
// contrastText: will be calculated to contrast with palette.primary.main
|
||||
},
|
||||
secondary: {
|
||||
main: '#1f2937',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: [
|
||||
'Montserrat',
|
||||
'Nunito',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif'
|
||||
].join(','),
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function App() {
|
||||
const [themeMode, toggleThemeMode] = useState('light');
|
||||
|
||||
useEffect(() => {
|
||||
let theme = localStorage.getItem('theme')
|
||||
|
||||
if (theme) {
|
||||
toggleThemeMode(String(localStorage.getItem('theme')))
|
||||
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
toggleThemeMode('dark')
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
toggleThemeMode(e.matches ? "dark" : "light")
|
||||
});
|
||||
|
||||
return () => window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', e => {
|
||||
toggleThemeMode(e.matches ? "dark" : "light")
|
||||
})
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
||||
<CssBaseline />
|
||||
<Router>
|
||||
<BaseRouter />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 230.07104 58.680001"
|
||||
version="1.1"
|
||||
id="svg28566"
|
||||
sodipodi:docname="swarm-logo.svg"
|
||||
inkscape:version="1.0.1 (3bc2e81, 2020-09-07)"
|
||||
width="230.07104"
|
||||
height="58.68">
|
||||
<metadata
|
||||
id="metadata28570">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Swarm Logo &amp; Lettering 4</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview28568"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.1325"
|
||||
inkscape:cx="123.33"
|
||||
inkscape:cy="35.939998"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_2"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<defs
|
||||
id="defs28540">
|
||||
<style
|
||||
id="style28538">.cls-1{fill:#fafafa;}.cls-2{fill:#242424;}</style>
|
||||
</defs>
|
||||
<title
|
||||
id="title28542">Swarm Logo &amp; Lettering 4</title>
|
||||
<g
|
||||
id="Layer_2"
|
||||
data-name="Layer 2"
|
||||
transform="translate(-76.67,-71.05)">
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 206.24,90.06 -3.54,24.65 c 0,0 -0.06,0 -0.07,0 l -3.1,-24.65 c 0,0 0,0 0,0 h -8.82 l -3.11,24.65 c 0,0 -0.06,0 -0.07,0 l -3.54,-24.65 h -5.38 c 0,0 0,0 0,0 L 183,118.6 h 8.8 L 195.05,94 c 0,0 0.06,0 0.07,0 l 3.21,24.61 h 8.81 l 4.49,-28.53 h -5.37 c 0,0 -0.02,-0.08 -0.02,-0.02 z"
|
||||
id="path28546" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 305,91.39 a 6.52,6.52 0 0 0 -5.19,-2.14 6.74,6.74 0 0 0 -6.33,3.75 v 0 a 5.85,5.85 0 0 0 -1.86,-2.62 5.61,5.61 0 0 0 -3.62,-1.13 6.26,6.26 0 0 0 -3.55,1 5.78,5.78 0 0 0 -2,2.45 v -2.64 c 0,0 0,0 0,0 h -5.24 c 0,0 0,0 0,0 v 28.53 h 5.24 v -19.5 a 5.72,5.72 0 0 1 1,-3.5 3.14,3.14 0 0 1 2.7,-1.25 2.85,2.85 0 0 1 2.3,1 4.08,4.08 0 0 1 0.89,2.73 v 20.5 c 0,0 0,0 0,0 h 5.25 V 99.42 a 6.08,6.08 0 0 1 1,-3.89 3.22,3.22 0 0 1 2.61,-1.19 2.75,2.75 0 0 1 2.44,1 4.9,4.9 0 0 1 0.81,2.92 v 20.28 c 0,0 0,0 0,0 h 5.28 V 96.86 A 8.18,8.18 0 0 0 305,91.39 Z"
|
||||
id="path28548" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 243.47,115.52 -3.32,3.71 h -0.05 l -5.86,-5.07 c 0,0 0,0 0,0 l -7,4.09 a 7.38,7.38 0 0 1 -3.77,1 7.91,7.91 0 0 1 -5.34,-2.13 7.28,7.28 0 0 1 -2.3,-5.45 7.54,7.54 0 0 1 3.82,-6.58 L 233.76,97 c 0,0 0,0 0,0 v -1.8 h -16.53 c 0,0 0,0 0,0 v -5 c 0,0 0,0 0,0 h 21.57 v 21.36 c 0,0 0,0 0,0 l 4.62,4 z m -18.8,-1.66 9.09,-5.28 c 0,0 0,0 0,0 v -5.7 a 0.03,0 0 0 0 -0.06,0 l -11.58,6.65 a 2.46,2.46 0 0 0 -1.29,2.2 2.59,2.59 0 0 0 2.53,2.53 2.51,2.51 0 0 0 1.31,-0.4 z"
|
||||
id="path28550" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 268.75,92 a 9.1,9.1 0 0 0 -6.67,-2.78 9,9 0 0 0 -7.48,3.64 l -3.34,-3.06 a 0.025,0 0 0 0 -0.05,0 l -3.29,3.67 4.74,4.3 v 15.7 h -4.71 c 0,0 0,0 0,0 v 4.93 h 17 c 0,0 0,0 0,0 v -4.93 h -7.19 V 98.93 a 4.61,4.61 0 0 1 4,-4.66 4.45,4.45 0 0 1 4.87,4.42 v 1.64 c 0,0 0,0 0,0 h 4.93 c 0,0 0,0 0,0 V 98.69 A 9.1,9.1 0 0 0 268.75,92 Z"
|
||||
id="path28552" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 173.32,106.74 a 5.41,5.41 0 0 0 -1.5,-2 6.58,6.58 0 0 0 -2.36,-1.44 15.31,15.31 0 0 0 -2.72,-1 c -0.94,-0.25 -2,-0.52 -3.08,-0.73 a 15.43,15.43 0 0 1 -4.88,-1.64 2.85,2.85 0 0 1 -1.51,-2.47 2.81,2.81 0 0 1 1.35,-2.59 5.91,5.91 0 0 1 4.4,-0.76 8.68,8.68 0 0 1 3.86,2.11 c 0.23,0.19 2.51,2.3 2.47,2.35 l 3.29,-3.73 c 0,0 -1.58,-1.6 -2.33,-2.26 a 13,13 0 0 0 -6.31,-3.24 12.18,12.18 0 0 0 -7.23,1.1 9.58,9.58 0 0 0 -1.67,1.09 7.57,7.57 0 0 0 -2.88,6.1 c 0,2.6 0.82,4.45 2.5,5.66 a 17.33,17.33 0 0 0 6.73,2.73 25.41,25.41 0 0 1 5.53,1.65 2.29,2.29 0 0 1 1.59,2.23 3.36,3.36 0 0 1 -1.62,3 8.35,8.35 0 0 1 -8.24,0 19.32,19.32 0 0 1 -2.85,-2.27 c -0.71,-0.6 -1.38,-1.25 -2.07,-1.89 v 0 l -3.2,3.85 c 0,0 1.56,1.46 1.7,1.58 a 15.66,15.66 0 0 0 7.41,4.21 15.26,15.26 0 0 0 3.16,0.32 12.45,12.45 0 0 0 7.85,-2.45 8.17,8.17 0 0 0 3.27,-6.83 6.14,6.14 0 0 0 -0.66,-2.68 z"
|
||||
id="path28554" />
|
||||
<polygon
|
||||
class="cls-2"
|
||||
points="76.67,122.17 90.14,129.73 103.61,122.17 103.61,107.08 90.14,99.51 76.67,107.08 "
|
||||
id="polygon28556" />
|
||||
<polygon
|
||||
class="cls-2"
|
||||
points="121.2,71.05 114.08,75.05 114.07,75.11 114.07,83.1 121.2,87.1 121.26,87.13 128.38,83.14 128.38,75.08 "
|
||||
id="polygon28558" />
|
||||
<polygon
|
||||
class="cls-2"
|
||||
points="134.44,107.08 120.97,99.51 107.5,107.08 107.5,122.17 120.97,129.73 134.44,122.17 "
|
||||
id="polygon28560" />
|
||||
<polygon
|
||||
class="cls-2"
|
||||
points="105.45,73.16 92,80.72 92,95.81 105.47,103.37 118.94,95.81 118.94,89.8 112.38,86.12 110.61,85.13 110.61,83.1 110.61,76.24 "
|
||||
id="polygon28562" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 468 142" xml:space="preserve"><style>.st1,.st2,.st3,.st4{opacity:.45;enable-background:new}.st2,.st3,.st4{opacity:.6}.st3,.st4{opacity:.3}.st4{opacity:.8}</style><g id="XMLID_41_"><g id="XMLID_1_"><g id="Swarm_typeface"><g id="XMLID_42_" opacity=".8"><path id="XMLID_31_" d="M193.7 90c0 9.1-9.5 13.4-17.4 13.4-8.5 0-17.8-3.3-17.8-13.2 0-1.7.1-3 2.2-3 1 0 1.4 1.3 1.4 2.1 0 8.5 6.8 10.6 14.2 10.6 5.8 0 13.8-2.8 13.8-9.8 0-13-31-5.3-31-20.3 0-8.6 9.6-10.8 16.5-10.8 8.1 0 17.1 3 17.1 12.6 0 1-1 1.7-1.9 1.7-1.1 0-1.7-1-1.7-1.9-.3-2.1-.6-3.5-2.1-5.2-2.6-3-7.7-3.6-11.5-3.6-4.5 0-12.9 1-12.9 7.1.1 10.9 31.1 3.5 31.1 20.3z"/><path id="XMLID_30_" d="M264.4 60.9c0 .8-13.2 37.6-14.5 40.9-.3 1-1 1.1-1.9 1.1-.9 0-1.6-.1-1.9-1.1l-12.9-36-12.9 36c-.3 1-1 1.1-1.9 1.1-.9 0-1.7-.1-1.9-1.1-1.3-3.3-14.5-40.1-14.5-40.9 0-1 .8-1.8 1.8-1.8.8 0 1.4.4 1.7 1.1l13 36.4 13-36.4c.3-.9 1-1.1 1.8-1.1.9 0 1.6.3 1.9 1.1l12.9 36.4 13-36.4c.3-.8.9-1.1 1.7-1.1.8 0 1.6.8 1.6 1.8z"/><path id="XMLID_45_" d="M315.3 60.9v39.2c0 1-.8 1.8-1.8 1.8s-1.8-.8-1.8-1.8v-8.2c-3.6 6.6-10.3 11.2-18 11.2-12.1 0-20.7-10.8-20.7-22.4s8.6-22.4 20.7-22.4c7.7 0 14.4 4.6 18 11.2v-8.6c0-.9.8-1.8 1.8-1.8s1.8.9 1.8 1.8zm-4.5 19.8c0-9.6-6.9-18.8-17.1-18.8-10 0-17.1 9.1-17.1 18.8s7.1 18.8 17.1 18.8c10.2 0 17.1-9.2 17.1-18.8z"/><path id="XMLID_27_" d="M352.4 61.3c0 1.1-.6 1.8-1.7 1.9-10.5 1.6-15.3 10.2-15.3 20.2v17.2c0 1-.8 1.8-1.8 1.8-1.1 0-1.8-.8-1.8-1.8V61.4c0-1 .8-1.8 1.8-1.8 1.1 0 1.8.8 1.8 1.8v8c3-5 8.9-9.8 15.1-9.8.8 0 1.9.6 1.9 1.7z"/><path id="XMLID_2_" d="M430.6 77.7v23c0 1-.9 1.8-1.8 1.8-1 0-1.8-.8-1.8-1.8v-23c0-7.7-4.4-15.3-13-15.3-10.9 0-15 11.6-15 20.6v17.8c0 1-.9 1.8-1.8 1.8-1 0-1.8-.8-1.8-1.8v-23c0-7.7-4.4-15.3-13-15.3-10.9 0-15.4 8.6-15.1 20.1 0 .3.1.8 0 .9v17.4c0 1-.8 1.8-1.8 1.8s-1.8-.8-1.8-1.8V61.4c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8V68c3.1-5.5 8.6-9.1 15.1-9.1 7.3 0 13 4.6 15.3 11.5 3-6.7 8.8-11.5 16.2-11.5 10.6-.1 16.7 9.1 16.7 18.8z"/></g></g></g><g id="Swarm_Icon"><path id="XMLID_26_" class="st1" d="M121.3 27v12.4l-11.1-6.2V20.7z"/><path id="XMLID_25_" class="st2" d="M110.2 33.2v12.4l11.1-6.2V27z"/><path id="XMLID_24_" class="st3" d="M37.4 102.7l22.1 12.4 22.1-12.4-22.1-12.5z"/><path id="XMLID_23_" class="st1" d="M59.5 90.2v24.9l-22.1-12.4V77.8z"/><path id="XMLID_22_" class="st1" d="M81.6 77.8v24.9L59.5 90.2V65.4z"/><path id="XMLID_21_" class="st2" d="M59.5 90.2v24.9l22.1-12.4V77.8z"/><path id="XMLID_20_" class="st2" d="M37.4 77.8v24.9l22.1-12.5V65.4z"/><path id="XMLID_19_" class="st4" d="M84 48.4l11 6.2 11.1-6.2L95 42.2z"/><path id="XMLID_18_" class="st4" d="M37.4 77.8l22.1 12.4 22.1-12.4-22.1-12.4z"/><path id="XMLID_17_" class="st3" d="M86.9 102.7l22.1 12.4 22.1-12.4L109 90.2z"/><path id="XMLID_16_" class="st1" d="M109 90.2v24.9l-22.1-12.4V77.8z"/><path id="XMLID_15_" class="st1" d="M131.1 77.8v24.9L109 90.2V65.4z"/><path id="XMLID_14_" class="st2" d="M109 90.2v24.9l22.1-12.4V77.8z"/><path id="XMLID_13_" class="st2" d="M86.9 77.8v24.9L109 90.2V65.4z"/><path id="XMLID_12_" class="st4" d="M86.9 77.8L109 90.2l22.1-12.4L109 65.4z"/><path id="XMLID_11_" class="st2" d="M84 35.9v12.5l11-6.2V29.7z"/><path id="XMLID_10_" class="st3" d="M61.9 60.8L84 73.2l22.1-12.4L84 48.4z"/><path id="XMLID_9_" class="st1" d="M84 48.4v24.8L61.9 60.8V35.9z"/><path id="XMLID_8_" class="st2" d="M61.9 35.9v24.9L84 48.4V23.5z"/><path id="XMLID_7_" class="st2" d="M95 54.6V42.2l-11 6.2v24.8L95 67l11.1-6.2V48.4z"/><g id="XMLID_5_"><path id="XMLID_6_" class="st1" d="M95 29.7l-11-6.2v24.9l22.1 12.4V48.4L95 42.2z"/></g><path id="XMLID_4_" class="st4" d="M84 23.5l11 6.2-11 6.2v12.5L61.9 35.9z"/><path id="XMLID_3_" class="st4" d="M99.2 27l11 6.2 11.1-6.2-11.1-6.3z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import { Snackbar } from '@material-ui/core';
|
||||
|
||||
import { beeDebugApi } from '../services/bee';
|
||||
|
||||
export default function DepositModal() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [peerId, setPeerId] = React.useState('');
|
||||
|
||||
const [showToast, setToastVisibility] = React.useState(false);
|
||||
const [toastContent, setToastContent] = React.useState('');
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleCashout = () => {
|
||||
if (peerId) {
|
||||
beeDebugApi.chequebook.peerCashout(peerId)
|
||||
.then(res => {
|
||||
setOpen(false);
|
||||
handleToast(`Successfully cashed out cheque. Transaction ${res.data.transactionHash}`)
|
||||
})
|
||||
.catch(error => {
|
||||
handleToast('Error with cashout')
|
||||
})
|
||||
} else {
|
||||
handleToast('Peer Id invalid')
|
||||
}
|
||||
};
|
||||
|
||||
const handleToast = (text: string) => {
|
||||
setToastContent(text)
|
||||
setToastVisibility(true);
|
||||
setTimeout(
|
||||
() => setToastVisibility(false),
|
||||
7000
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{marginLeft:'7px'}}>
|
||||
Cashout
|
||||
</Button>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
open={showToast}
|
||||
message={toastContent}
|
||||
/>
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText style={{marginTop: '20px'}}>
|
||||
Specify the peer Id of the peer you would like to cashout.
|
||||
</DialogContentText>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="peerId"
|
||||
type="text"
|
||||
placeholder='Peer Id'
|
||||
fullWidth
|
||||
onChange={(e) => setPeerId(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleCashout} color="primary">
|
||||
Cashout
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { IconButton, Snackbar } from '@material-ui/core';
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard';
|
||||
import { Clipboard } from 'react-feather';
|
||||
|
||||
interface IProps {
|
||||
value: string,
|
||||
}
|
||||
|
||||
export default function ClipboardCopy(props: IProps) {
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
setCopied(true);
|
||||
setTimeout(
|
||||
() => setCopied(false),
|
||||
3000
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div style={{marginRight:'3px', marginLeft:'3px'}}>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
open={copied}
|
||||
message="Copied"
|
||||
/>
|
||||
<IconButton color="primary" size='small' onClick={handleCopy}>
|
||||
<CopyToClipboard text={props.value}
|
||||
>
|
||||
<Clipboard style={{height:'20px'}}/>
|
||||
</CopyToClipboard>
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { hybrid } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||
|
||||
interface IProps {
|
||||
code: string,
|
||||
language: string,
|
||||
showLineNumbers?: boolean,
|
||||
}
|
||||
|
||||
const CodeBlock = (props: IProps) => {
|
||||
return (
|
||||
<div style={{textAlign:'left'}}>
|
||||
<SyntaxHighlighter
|
||||
language={props.language}
|
||||
style={hybrid}
|
||||
showLineNumbers={props.showLineNumbers}
|
||||
>
|
||||
{props.code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeBlock;
|
||||
@@ -0,0 +1,158 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { withStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import { Tabs, Tab, Box, Typography } from '@material-ui/core';
|
||||
import CodeBlock from './CodeBlock';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: any;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
linux: string;
|
||||
mac: string;
|
||||
showLineNumbers?: boolean
|
||||
}
|
||||
|
||||
function a11yProps(index: any) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
function getOS() {
|
||||
var userAgent = window.navigator.userAgent,
|
||||
platform = window.navigator.platform,
|
||||
macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
|
||||
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
|
||||
iosPlatforms = ['iPhone', 'iPad', 'iPod'],
|
||||
os = null;
|
||||
|
||||
if (macosPlatforms.indexOf(platform) !== -1) {
|
||||
os = 'macOS';
|
||||
} else if (iosPlatforms.indexOf(platform) !== -1) {
|
||||
os = 'iOS';
|
||||
} else if (windowsPlatforms.indexOf(platform) !== -1) {
|
||||
os = 'windows';
|
||||
} else if (/Android/.test(userAgent)) {
|
||||
os = 'android';
|
||||
} else if (!os && /Linux/.test(platform)) {
|
||||
os = 'linux';
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
export default function CodeBlockTabs(props: IProps) {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let os = getOS()
|
||||
if(os === 'windows') {
|
||||
setValue(0)
|
||||
} else if (os === 'linux') {
|
||||
setValue(0)
|
||||
} else if (os === 'macOS') {
|
||||
setValue(1)
|
||||
}
|
||||
|
||||
}, [])
|
||||
|
||||
function TabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box style={{ marginTop: '-12px' }}>
|
||||
<Typography>{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const AntTabs = withStyles({
|
||||
root: {
|
||||
borderBottom: '1px solid #e8e8e8',
|
||||
},
|
||||
indicator: {
|
||||
backgroundColor: '#3f51b5',
|
||||
},
|
||||
})(Tabs);
|
||||
|
||||
interface StyledTabProps {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const AntTab = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
minWidth: 72,
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
marginRight: theme.spacing(4),
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
].join(','),
|
||||
'&:hover': {
|
||||
color: '#3f51b5',
|
||||
opacity: 1,
|
||||
},
|
||||
'&$selected': {
|
||||
color: '#3f51b5',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
},
|
||||
'&:focus': {
|
||||
color: '#3f51b5',
|
||||
},
|
||||
},
|
||||
selected: {},
|
||||
}),
|
||||
)((props: StyledTabProps) => <Tab disableRipple {...props} />);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
|
||||
<AntTab label="Linux" {...a11yProps(0)} />
|
||||
<AntTab label="MacOS" {...a11yProps(1)} />
|
||||
</AntTabs>
|
||||
<TabPanel value={value} index={0}>
|
||||
<CodeBlock
|
||||
showLineNumbers={props.showLineNumbers}
|
||||
language='bash'
|
||||
code={props.linux}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={value} index={1}>
|
||||
<CodeBlock
|
||||
showLineNumbers={props.showLineNumbers}
|
||||
language='bash'
|
||||
code={props.mac}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import React, { useState } from 'react';
|
||||
import { TextField, Button, CircularProgress, Container } from '@material-ui/core';
|
||||
|
||||
interface IProps {
|
||||
defaultHost?: string,
|
||||
hostName: string,
|
||||
}
|
||||
|
||||
export default function ConnectToHost(props: IProps) {
|
||||
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
|
||||
const [connectingToHost, setConnectingToHost] = useState(false)
|
||||
const [host, setHost] = useState('')
|
||||
|
||||
const handleNewHostConnection = () => {
|
||||
if (host) {
|
||||
setConnectingToHost(true)
|
||||
sessionStorage.setItem(props.hostName, host)
|
||||
toggleHostInputVisibility(!hostInputVisible)
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{hostInputVisible ?
|
||||
<div style={{display:'flex'}}>
|
||||
<TextField
|
||||
defaultValue={props.defaultHost}
|
||||
label="Enter host"
|
||||
variant="outlined"
|
||||
size='small'
|
||||
onChange={(e) => setHost(e.target.value)}
|
||||
style={{marginRight:'15px', minWidth:'300px'}}
|
||||
/>
|
||||
<Button onClick={() => handleNewHostConnection()} size='small' variant="outlined">Connect</Button>
|
||||
<Button style={{marginLeft:'7px'}} onClick={() => toggleHostInputVisibility(!hostInputVisible)} size='small'>Cancel</Button>
|
||||
</div>
|
||||
:
|
||||
connectingToHost ?
|
||||
<Container style={{textAlign:'center', padding:'0px'}}>
|
||||
<CircularProgress size={20} />
|
||||
</Container>
|
||||
:
|
||||
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size='small' variant="outlined">Change host</Button>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import { Snackbar } from '@material-ui/core';
|
||||
|
||||
import { beeDebugApi } from '../services/bee';
|
||||
|
||||
export default function DepositModal() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [amount, setAmount] = React.useState(BigInt(0));
|
||||
const [showToast, setToastVisibility] = React.useState(false);
|
||||
const [toastContent, setToastContent] = React.useState('');
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (amount > 0) {
|
||||
beeDebugApi.chequebook.deposit(amount)
|
||||
.then(res => {
|
||||
setOpen(false);
|
||||
handleToast(`Successful Deposit. Transaction ${res.data.transactionHash}`)
|
||||
})
|
||||
.catch(error => {
|
||||
handleToast('Error with Deposit')
|
||||
})
|
||||
} else {
|
||||
handleToast('Must be amount of greater than 0')
|
||||
}
|
||||
};
|
||||
|
||||
const handleToast = (text: string) => {
|
||||
setToastContent(text)
|
||||
setToastVisibility(true);
|
||||
setTimeout(
|
||||
() => setToastVisibility(false),
|
||||
7000
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" color="primary" onClick={handleClickOpen} style={{marginLeft:'7px'}}>
|
||||
Deposit
|
||||
</Button>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
open={showToast}
|
||||
message={toastContent}
|
||||
/>
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Deposit Funds</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Specify the amount you would like to deposit to your node.
|
||||
</DialogContentText>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
type="number"
|
||||
placeholder='Amount'
|
||||
fullWidth
|
||||
onChange={(e) => setAmount(BigInt(e.target.value))}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleWithdraw} color="primary">
|
||||
Deposit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Typography } from '@material-ui/core/';
|
||||
import QRCodeModal from './QRCodeModal';
|
||||
import ClipboardCopy from './ClipboardCopy';
|
||||
|
||||
// @ts-ignore
|
||||
import Identicon from 'react-identicons';
|
||||
|
||||
interface IProps {
|
||||
address: string | undefined,
|
||||
network?: string,
|
||||
hideBlockie?: boolean,
|
||||
transaction?: boolean,
|
||||
truncate?: boolean,
|
||||
}
|
||||
|
||||
export default function EthereumAddress(props: IProps) {
|
||||
return (
|
||||
<Typography component="p" variant="subtitle1">
|
||||
{props.address ?
|
||||
<div style={{display:'flex'}}>
|
||||
{props.hideBlockie ?
|
||||
null
|
||||
:
|
||||
<div style={{paddingTop:'5px', marginRight: '10px', }}>
|
||||
<Identicon size='20' string={props.address} />
|
||||
</div>}
|
||||
<div>
|
||||
<a
|
||||
style={props.truncate ?
|
||||
{ marginRight:'7px', maxWidth:'200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display:'block'}
|
||||
:
|
||||
{ marginRight:'7px'}
|
||||
}
|
||||
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${props.transaction ? 'tx' : 'address' }/${props.address}`}
|
||||
target='_blank'
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ props.address }
|
||||
</a>
|
||||
</div>
|
||||
<QRCodeModal
|
||||
value={ props.address }
|
||||
label={'Ethereum Address'}
|
||||
/>
|
||||
<ClipboardCopy
|
||||
value={ props.address }
|
||||
/>
|
||||
</div>
|
||||
: '-' }
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Theme, createStyles, makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
import { Card, CardContent, Typography } from '@material-ui/core/';
|
||||
|
||||
import EthereumAddress from '../components/EthereumAddress';
|
||||
import { Skeleton } from '@material-ui/lab';
|
||||
|
||||
import type { ChequebookAddressResponse } from '@ethersphere/bee-js';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: 'flex',
|
||||
marginTop: '20px',
|
||||
},
|
||||
details: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
content: {
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
status: {
|
||||
color: '#fff',
|
||||
backgroundColor: '#76a9fa',
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
interface NodeAddresses {
|
||||
overlay: string,
|
||||
underlay: string[],
|
||||
ethereum: string,
|
||||
public_key: string,
|
||||
pss_public_key: string
|
||||
}
|
||||
|
||||
interface IProps{
|
||||
nodeAddresses: NodeAddresses | null,
|
||||
isLoadingNodeAddresses: boolean,
|
||||
chequebookAddress: ChequebookAddressResponse | null,
|
||||
isLoadingChequebookAddress: boolean,
|
||||
}
|
||||
|
||||
function EthereumAddressCard(props: IProps) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className={classes.root}>
|
||||
{props.isLoadingNodeAddresses ?
|
||||
<div style={{padding: '16px'}}>
|
||||
<Skeleton width={300} height={30} animation="wave" />
|
||||
<Skeleton width={300} height={50} animation="wave" />
|
||||
</div>
|
||||
:
|
||||
<div className={classes.details}>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<span>Ethereum Address</span>
|
||||
</Typography>
|
||||
<EthereumAddress
|
||||
address={props.nodeAddresses?.ethereum}
|
||||
network={'goerli'}
|
||||
/>
|
||||
</CardContent>
|
||||
</div>}
|
||||
{props.isLoadingChequebookAddress ?
|
||||
<div style={{padding: '16px'}}>
|
||||
<Skeleton width={300} height={30} animation="wave" />
|
||||
<Skeleton width={300} height={50} animation="wave" />
|
||||
</div>
|
||||
:
|
||||
<div className={classes.details}>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<span>Chequebook Contract Address</span>
|
||||
</Typography>
|
||||
<EthereumAddress
|
||||
address={props.chequebookAddress?.chequebookaddress}
|
||||
network={'goerli'}
|
||||
/>
|
||||
</CardContent>
|
||||
</div>}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EthereumAddressCard
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
|
||||
import { AppBar, Toolbar, Chip, IconButton } from '@material-ui/core/';
|
||||
|
||||
import { Sun, Moon } from 'react-feather';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
appBar: {
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
marginLeft: drawerWidth,
|
||||
background:'linear-gradient(35deg,#fb6340,#fbb140)!important'
|
||||
},
|
||||
network: {
|
||||
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
export default function SideBar(props: any) {
|
||||
const [darkMode, toggleDarkMode] = useState(false);
|
||||
|
||||
const switchTheme = () => {
|
||||
let theme = localStorage.getItem('theme')
|
||||
if (theme) {
|
||||
localStorage.setItem('theme', theme === 'light' ? 'dark' : 'light')
|
||||
} else {
|
||||
localStorage.setItem('theme', darkMode ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
toggleDarkMode(!darkMode)
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar style={{display: 'flex'}}>
|
||||
<Chip
|
||||
style={{ marginLeft: '7px'}}
|
||||
size="small"
|
||||
label='Goerli'
|
||||
className={classes.network}
|
||||
/>
|
||||
<div style={{width:'100%'}}>
|
||||
<div style={{float:'right'}} >
|
||||
<IconButton style={{marginRight:'10px'}} aria-label="dark-mode" onClick={() => switchTheme()}>
|
||||
{props.themeMode === 'dark' ?
|
||||
<Moon />
|
||||
:
|
||||
<Sun />
|
||||
}
|
||||
</IconButton>
|
||||
{/* <Chip
|
||||
label="Connect Wallet"
|
||||
color="primary"
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React,{ useState } from 'react';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { IconButton, Dialog, DialogTitle } from '@material-ui/core';
|
||||
import { FilterCenterFocusSharp } from '@material-ui/icons';
|
||||
|
||||
interface IProps {
|
||||
value: string,
|
||||
label: string,
|
||||
}
|
||||
|
||||
export default function QRCodeModal(props: IProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton color="primary" size='small' onClick={handleOpen}>
|
||||
<FilterCenterFocusSharp/>
|
||||
</IconButton>
|
||||
<Dialog onClose={handleClose} aria-labelledby="simple-dialog-title" open={open}>
|
||||
<div style={{padding: '30px', textAlign: 'center'}}>
|
||||
<DialogTitle id="simple-dialog-title">{ props.label }</DialogTitle>
|
||||
<QRCode
|
||||
value={props.value}
|
||||
size={150}
|
||||
bgColor={"#ffffff"}
|
||||
fgColor={"#000000"}
|
||||
level={"L"}
|
||||
includeMargin={false}
|
||||
renderAs={"svg"}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import { Paper, InputBase, IconButton } from '@material-ui/core';
|
||||
import { Search } from '@material-ui/icons';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
padding: '2px 4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 400,
|
||||
},
|
||||
input: {
|
||||
marginLeft: theme.spacing(1),
|
||||
flex: 1,
|
||||
},
|
||||
iconButton: {
|
||||
padding: 10,
|
||||
},
|
||||
divider: {
|
||||
height: 28,
|
||||
margin: 4,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
interface IProps {
|
||||
|
||||
}
|
||||
|
||||
export default function SearchBar(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Paper component="form" className={classes.root}>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
|
||||
inputProps={{ 'aria-label': 'search google maps' }}
|
||||
/>
|
||||
<IconButton type="submit" className={classes.iconButton} aria-label="search">
|
||||
<Search />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
|
||||
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink} from '@material-ui/core';
|
||||
import { OpenInNewSharp } from '@material-ui/icons';
|
||||
import { Activity, FileText, DollarSign, Share2, Settings } from 'react-feather';
|
||||
|
||||
import SwarmLogo from '../assets/swarm-logo-2.svg';
|
||||
import SwarmLogoWhite from '../assets/swarm-logo-2-white.png';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const navBarItems = [
|
||||
{
|
||||
'label': 'Status',
|
||||
'id': 'status',
|
||||
'path': '/',
|
||||
'icon': 'activity'
|
||||
},
|
||||
{
|
||||
'label': 'Files',
|
||||
'id': 'files',
|
||||
'path': '/files/',
|
||||
'icon': 'file-text'
|
||||
},
|
||||
{
|
||||
'label': 'Accounting',
|
||||
'id': 'accounting',
|
||||
'path': '/accounting/',
|
||||
'icon': 'dollar-sign'
|
||||
},
|
||||
{
|
||||
'label': 'Peers',
|
||||
'id': 'peers',
|
||||
'path': '/peers/',
|
||||
'icon': 'share-2'
|
||||
},
|
||||
{
|
||||
'label': 'Settings',
|
||||
'id': 'settings',
|
||||
'path': '/settings/',
|
||||
'icon': 'settings'
|
||||
}
|
||||
]
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
appBar: {
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
marginLeft: drawerWidth,
|
||||
backgroundColor:'#dd7200'
|
||||
},
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
},
|
||||
drawerPaper: {
|
||||
width: drawerWidth,
|
||||
},
|
||||
activeSideBar: {
|
||||
color: '#fb6340',
|
||||
},
|
||||
activeSideBarItem: {
|
||||
borderLeft: '4px solid #fb6340',
|
||||
},
|
||||
toolbar: theme.mixins.toolbar,
|
||||
}),
|
||||
);
|
||||
|
||||
const getIcon = (iconPath: string) => {
|
||||
switch (iconPath) {
|
||||
case 'activity':
|
||||
return <Activity style={{height: '20px'}} />
|
||||
case 'file-text':
|
||||
return <FileText style={{height: '20px'}} />
|
||||
case 'dollar-sign':
|
||||
return <DollarSign style={{height: '20px'}} />
|
||||
case 'share-2':
|
||||
return <Share2 style={{height: '20px'}} />
|
||||
case 'settings':
|
||||
return <Settings style={{height: '20px'}} />
|
||||
}
|
||||
}
|
||||
|
||||
export default function SideBar(props: any) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Drawer
|
||||
className={classes.drawer}
|
||||
variant="permanent"
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
anchor="left"
|
||||
>
|
||||
<div className={classes.toolbar} style={{ textAlign:'center' }}>
|
||||
<Link to='/'>
|
||||
<img alt='swarm' src={props.themeMode === 'light' ? SwarmLogo : SwarmLogoWhite} style={{maxHeight: '60px', alignItems:'center'}} />
|
||||
</Link>
|
||||
</div>
|
||||
<Divider />
|
||||
<List>
|
||||
{navBarItems.map(item => (
|
||||
<Link to={item.path} key={item.id} style={{ color:'inherit', textDecoration:'none'}}>
|
||||
<ListItem button selected={props.location.pathname === item.path} className={props.location.pathname === item.path ? classes.activeSideBarItem : ''}>
|
||||
<ListItemIcon className={props.location.pathname === item.path ? classes.activeSideBar : ''}>
|
||||
{ getIcon(item.icon) }
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.label} className={props.location.pathname === item.path ? classes.activeSideBar : ''} />
|
||||
</ListItem>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<MUILink href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" style={{textDecoration:'none'}}>
|
||||
<ListItem button>
|
||||
<ListItemText primary={'Docs'} />
|
||||
<OpenInNewSharp fontSize="small" />
|
||||
</ListItem>
|
||||
</MUILink>
|
||||
</List>
|
||||
<div style={{position:'fixed', bottom: 0, width: 'inherit', padding: '10px'}}>
|
||||
<ListItem>
|
||||
<div style={{marginRight:'30px'}}>
|
||||
<div style={{backgroundColor: props.health ? '#32c48d' : '#c9201f', marginRight: '7px', height: '10px',width: '10px', borderRadius: '50%', display: 'inline-block'}} />
|
||||
<span>API</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{backgroundColor: props.nodeHealth?.status === 'ok' ? '#32c48d' : '#c9201f', marginRight: '7px', height: '10px',width: '10px', borderRadius: '50%', display: 'inline-block'}} />
|
||||
<span>Debug API</span>
|
||||
</div>
|
||||
</ListItem>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
|
||||
import { makeStyles, } from '@material-ui/core/styles';
|
||||
import { Card, CardContent, Typography } from '@material-ui/core/';
|
||||
import { Skeleton } from '@material-ui/lab';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
minWidth: 275,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
},
|
||||
pos: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
});
|
||||
|
||||
interface IProps {
|
||||
label: string,
|
||||
statistic: string,
|
||||
loading?: boolean,
|
||||
}
|
||||
|
||||
export default function StatCard(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Card className={classes.root}>
|
||||
<CardContent>
|
||||
{props.loading ?
|
||||
<div>
|
||||
<Skeleton width={180} height={25} animation="wave" />
|
||||
<Skeleton width={180} height={35} animation="wave" />
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<Typography className={classes.title} color="textSecondary" gutterBottom>
|
||||
{props.label}
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2">
|
||||
{props.statistic}
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { makeStyles, } from '@material-ui/core/styles';
|
||||
import { Card, CardContent, Typography } from '@material-ui/core/';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
marginTop: '20px'
|
||||
},
|
||||
title: {
|
||||
textAlign:'center',
|
||||
fontSize: 26,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default function TroubleshootConnectionCard() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Card className={classes.root}>
|
||||
<CardContent>
|
||||
<Typography className={classes.title} gutterBottom>
|
||||
Looks like your node is not connected
|
||||
</Typography>
|
||||
<div style={{marginBottom:'20px', textAlign:'center'}}>
|
||||
<strong><Link to='/' >Click to run status checks</Link> on your nodes connection or check out the <a href={process.env.REACT_APP_BEE_DOCS_HOST} target='_blank' >Swarm Bee Docs</a></strong>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom:'20px', textAlign:'center'}}>
|
||||
<p style={{marginTop:'50px'}}>Still not working? Drop us a message on the Ethereum Swarm <a href={process.env.REACT_APP_BEE_DISCORD_HOST} target='_blank' >Discord</a></p>
|
||||
</div>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import { Snackbar } from '@material-ui/core';
|
||||
|
||||
import { beeDebugApi } from '../services/bee';
|
||||
|
||||
export default function WithdrawlModal() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [amount, setAmount] = React.useState(BigInt(0));
|
||||
const [showToast, setToastVisibility] = React.useState(false);
|
||||
const [toastContent, setToastContent] = React.useState('');
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (amount > 0) {
|
||||
beeDebugApi.chequebook.withdraw(amount)
|
||||
.then(res => {
|
||||
setOpen(false);
|
||||
handleToast(`Successful withdrawl. Transaction ${res.data.transactionHash}`)
|
||||
})
|
||||
.catch(error => {
|
||||
handleToast('Error with withdrawl')
|
||||
})
|
||||
} else {
|
||||
handleToast('Must be amount of greater than 0')
|
||||
}
|
||||
};
|
||||
|
||||
const handleToast = (text: string) => {
|
||||
setToastContent(text)
|
||||
setToastVisibility(true);
|
||||
setTimeout(
|
||||
() => setToastVisibility(false),
|
||||
7000
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
||||
Withdraw
|
||||
</Button>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
open={showToast}
|
||||
message={toastContent}
|
||||
/>
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Withdraw Funds</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Specify the amount you would like to withdraw from your node.
|
||||
</DialogContentText>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
type="number"
|
||||
placeholder='Amount'
|
||||
fullWidth
|
||||
onChange={(e) => setAmount(BigInt(e.target.value))}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleWithdraw} color="primary">
|
||||
Withdraw
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import type { NodeAddresses, ChequebookAddressResponse, ChequebookBalanceResponse, BalanceResponse,
|
||||
LastChequesResponse, AllSettlements, LastCashoutActionResponse } from '@ethersphere/bee-js'
|
||||
|
||||
import { beeDebugApi, beeApi } from '../services/bee';
|
||||
|
||||
export const useApiHealth = () => {
|
||||
const [health, setHealth] = useState('')
|
||||
const [isLoadingHealth, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeApi.status.health()
|
||||
.then(res => {
|
||||
setHealth(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { health, isLoadingHealth, error } ;
|
||||
}
|
||||
|
||||
interface NodeHealth {
|
||||
status: string,
|
||||
version: string
|
||||
}
|
||||
|
||||
export const useDebugApiHealth = () => {
|
||||
const [nodeHealth, setNodeHealth] = useState<NodeHealth | null>(null)
|
||||
const [isLoadingNodeHealth, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.status.nodeHealth()
|
||||
.then(res => {
|
||||
setNodeHealth(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { nodeHealth, isLoadingNodeHealth, error } ;
|
||||
}
|
||||
|
||||
interface NodeReadiness {
|
||||
status: string,
|
||||
version: string
|
||||
}
|
||||
|
||||
export const useApiReadiness = () => {
|
||||
const [nodeReadiness, setNodeReadiness] = useState<NodeReadiness | null>(null)
|
||||
const [isLoadingNodeReadiness, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.status.nodeReadiness()
|
||||
.then(res => {
|
||||
setNodeReadiness(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { nodeReadiness, isLoadingNodeReadiness, error } ;
|
||||
}
|
||||
|
||||
export const useApiNodeAddresses = () => {
|
||||
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
||||
const [isLoadingNodeAddresses, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.connectivity.addresses()
|
||||
.then(res => {
|
||||
setNodeAddresses(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { nodeAddresses, isLoadingNodeAddresses, error } ;
|
||||
}
|
||||
|
||||
export const useApiNodeTopology = () => {
|
||||
const [nodeTopology, setNodeTopology] = useState({ baseAddr: '', bins: [""], connected: 0, depth: 0, nnLowWatermark: 0, population: 0, timestamp: ''})
|
||||
const [isLoadingNodeTopology, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.connectivity.topology()
|
||||
.then(res => {
|
||||
setNodeTopology(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { nodeTopology, isLoadingNodeTopology, error } ;
|
||||
}
|
||||
|
||||
export const useApiChequebookAddress = () => {
|
||||
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
|
||||
const [isLoadingChequebookAddress, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.chequebook.address()
|
||||
.then(res => {
|
||||
setChequebookAddress(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { chequebookAddress, isLoadingChequebookAddress, error };
|
||||
}
|
||||
|
||||
export const useApiNodePeers = () => {
|
||||
const [nodePeers, setNodePeers] = useState({ peers: [{ address: '-'}]})
|
||||
const [isLoadingNodePeers, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.connectivity.listPeers()
|
||||
.then(res => {
|
||||
setNodePeers(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { nodePeers, isLoadingNodePeers, error };
|
||||
}
|
||||
|
||||
export const useApiChequebookBalance = () => {
|
||||
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalanceResponse | null>(null)
|
||||
const [isLoadingChequebookBalance, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.chequebook.balance()
|
||||
.then(res => {
|
||||
setChequebookBalance(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { chequebookBalance, isLoadingChequebookBalance, error };
|
||||
}
|
||||
|
||||
export const useApiPeerBalances = () => {
|
||||
const [peerBalances, setPeerBalances] = useState<BalanceResponse | null>(null)
|
||||
const [isLoadingPeerBalances, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.balance.balances()
|
||||
.then(res => {
|
||||
setPeerBalances(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { peerBalances, isLoadingPeerBalances, error };
|
||||
}
|
||||
|
||||
export const useApiPeerCheques = () => {
|
||||
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
||||
const [isLoadingPeerCheques, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.chequebook.getLastCheques()
|
||||
.then(res => {
|
||||
setPeerCheques(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { peerCheques, isLoadingPeerCheques, error };
|
||||
}
|
||||
|
||||
export const useApiPeerLastCheque = (peerId: string) => {
|
||||
const [peerCheque, setPeerCheque] = useState({ peer: '-', chequebook: "",
|
||||
cumulativePayout: 0,
|
||||
beneficiary: "",
|
||||
transactionHash: "",
|
||||
result: {
|
||||
recipient: "",
|
||||
lastPayout: 0,
|
||||
bounced: false
|
||||
}}
|
||||
)
|
||||
const [isLoadingPeerCheque, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.chequebook.getPeerLastCheques(peerId)
|
||||
.then(res => {
|
||||
setPeerCheque(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { peerCheque, isLoadingPeerCheque, error };
|
||||
}
|
||||
|
||||
export const useApiSettlements = () => {
|
||||
const [settlements, setSettlements] = useState<AllSettlements | null>(null)
|
||||
const [isLoadingSettlements, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.settlements.getSettlements()
|
||||
.then(res => {
|
||||
setSettlements(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { settlements, isLoadingSettlements, error };
|
||||
}
|
||||
|
||||
|
||||
export const useApiPingPeer = (peerId: string) => {
|
||||
const [peerRTP, setPeerRTP] = useState<string>('')
|
||||
const [isPingingPeer, setPingingPeer] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setPingingPeer(true)
|
||||
beeDebugApi.connectivity.ping(peerId)
|
||||
.then(res => {
|
||||
setPeerRTP(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setPingingPeer(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { peerRTP, isPingingPeer, error };
|
||||
}
|
||||
|
||||
export const useApiPeerLastCashout = (peerId: string) => {
|
||||
const [peerCashout, setPeerCashout] = useState<LastCashoutActionResponse | null>(null)
|
||||
const [isLoadingPeerCashout, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
beeDebugApi.chequebook.getPeerLastCashout(peerId)
|
||||
.then(res => {
|
||||
setPeerCashout(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { peerCashout, isLoadingPeerCashout, error };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
import SideBar from '../components/SideBar';
|
||||
import NavBar from '../components/NavBar';
|
||||
|
||||
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
toolbar: theme.mixins.toolbar,
|
||||
content: {
|
||||
marginLeft: '240px',
|
||||
marginTop: '64px',
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing(3),
|
||||
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',
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
const Dashboard = (props: any) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const [themeMode, toggleThemeMode] = useState('light');
|
||||
|
||||
const { health, isLoadingHealth } = useApiHealth()
|
||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||
|
||||
useEffect(() => {
|
||||
let theme = localStorage.getItem('theme')
|
||||
|
||||
if (theme) {
|
||||
toggleThemeMode(String(localStorage.getItem('theme')))
|
||||
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
toggleThemeMode('dark')
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
toggleThemeMode(e.matches ? "dark" : "light")
|
||||
});
|
||||
|
||||
return () => window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', e => {
|
||||
toggleThemeMode(e.matches ? "dark" : "light")
|
||||
})
|
||||
}, [])
|
||||
|
||||
let childrenInjectedWithProps = React.cloneElement(props.children, { health, nodeHealth, isLoadingHealth, isLoadingNodeHealth })
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
|
||||
<NavBar themeMode={themeMode} />
|
||||
<main className={classes.content} >
|
||||
{childrenInjectedWithProps}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
@@ -0,0 +1,112 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
|
||||
import { Card, CardContent, Typography, Grid } from '@material-ui/core/';
|
||||
import { Skeleton } from '@material-ui/lab';
|
||||
import WithdrawlModal from '../../components/WithdrawlModal';
|
||||
import DepositModal from '../../components/DepositModal';
|
||||
import CashoutModal from '../../components/CashoutModal';
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common';
|
||||
|
||||
import type { ChequebookAddressResponse } from '@ethersphere/bee-js';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
details: {
|
||||
display: 'flex',
|
||||
},
|
||||
address: {
|
||||
color: 'grey',
|
||||
fontWeight: 400,
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
status: {
|
||||
color: '#fff',
|
||||
backgroundColor: '#76a9fa',
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
interface ChequebookBalance {
|
||||
totalBalance: number,
|
||||
availableBalance: number
|
||||
}
|
||||
|
||||
interface IProps{
|
||||
chequebookAddress: ChequebookAddressResponse | null,
|
||||
isLoadingChequebookAddress: boolean,
|
||||
chequebookBalance: ChequebookBalance | null,
|
||||
isLoadingChequebookBalance: boolean,
|
||||
settlements: any,
|
||||
isLoadingSettlements: boolean,
|
||||
}
|
||||
|
||||
|
||||
function AccountCard(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{justifyContent: 'space-between', display: 'flex'}}>
|
||||
<h2 style={{ marginTop: '0px' }}>Accounting</h2>
|
||||
<div style={{display:'flex'}}>
|
||||
<WithdrawlModal />
|
||||
<DepositModal />
|
||||
<CashoutModal />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className={classes.root}>
|
||||
{ !props.isLoadingChequebookBalance && !props.isLoadingSettlements && props.chequebookBalance ?
|
||||
<div className={classes.details}>
|
||||
<CardContent className={classes.content}>
|
||||
<Grid container spacing={5}>
|
||||
<Grid item>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
Total Balance
|
||||
</Typography>
|
||||
<Typography component="p" variant="h5">
|
||||
{ConvertBalanceToBZZ(props.chequebookBalance.totalBalance)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
Available Balance
|
||||
</Typography>
|
||||
<Typography component="p" variant="h5">
|
||||
{ConvertBalanceToBZZ(props.chequebookBalance.availableBalance)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
Total Sent / Received
|
||||
</Typography>
|
||||
<Typography component="p" variant="h5" >
|
||||
<span style={{marginRight:'7px'}}>{ConvertBalanceToBZZ(props.settlements.totalsent)} / {ConvertBalanceToBZZ(props.settlements.totalreceived)}</span>
|
||||
<span style={{ color: props.settlements.totalsent > props.settlements.totalreceived ? '#c9201f' : '#32c48d' }}>({ConvertBalanceToBZZ(props.settlements.totalsent - props.settlements.totalreceived)})</span>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</div>
|
||||
:
|
||||
<div className={classes.details}>
|
||||
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px'}} />
|
||||
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px'}} />
|
||||
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px'}} />
|
||||
<Skeleton width={180} height={110} animation="wave" />
|
||||
</div>
|
||||
}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountCard;
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper, Container, CircularProgress } from '@material-ui/core';
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
minWidth: 650,
|
||||
},
|
||||
});
|
||||
|
||||
interface PeerBalance {
|
||||
balance: number,
|
||||
peer: string,
|
||||
}
|
||||
|
||||
interface PeerBalances {
|
||||
balances: Array<PeerBalance>
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
peerBalances: PeerBalances | null,
|
||||
loading?: boolean,
|
||||
}
|
||||
|
||||
function BalancesTable(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.loading ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<TableContainer component={Paper}>
|
||||
<Table className={classes.table} size="small" aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Peer</TableCell>
|
||||
<TableCell style={{textAlign:'right'}}>Balance (BZZ)</TableCell>
|
||||
<TableCell align="right"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.peerBalances?.balances.map((peerBalance: PeerBalance, idx: number) => (
|
||||
<TableRow key={peerBalance.peer}>
|
||||
<TableCell>{peerBalance.peer}</TableCell>
|
||||
<TableCell style={{ color: ConvertBalanceToBZZ(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f', textAlign:'right', fontFamily: 'monospace, monospace' }}>
|
||||
{ConvertBalanceToBZZ(peerBalance.balance).toFixed(7).toLocaleString() }
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BalancesTable;
|
||||
@@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper, Container, CircularProgress } from '@material-ui/core';
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common';
|
||||
import EthereumAddress from '../../components/EthereumAddress';
|
||||
import ClipboardCopy from '../../components/ClipboardCopy';
|
||||
import PeerDetailDrawer from './PeerDetailDrawer';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
minWidth: 650,
|
||||
},
|
||||
});
|
||||
|
||||
interface ChequeEvent {
|
||||
beneficiary: string,
|
||||
chequebook: string,
|
||||
payout: number,
|
||||
}
|
||||
|
||||
interface PeerCheque {
|
||||
lastreceived?: ChequeEvent,
|
||||
lastsent?: ChequeEvent,
|
||||
peer: string,
|
||||
}
|
||||
|
||||
interface PeerCheques {
|
||||
lastcheques: Array<PeerCheque>
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
peerCheques: PeerCheques | null,
|
||||
loading?: boolean,
|
||||
}
|
||||
|
||||
function ChequebookTable(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.loading ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<div>
|
||||
<TableContainer component={Paper}>
|
||||
<Table className={classes.table} size="small" aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Peer</TableCell>
|
||||
<TableCell>Last Received</TableCell>
|
||||
<TableCell>Last Sent</TableCell>
|
||||
<TableCell align="right"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.peerCheques?.lastcheques.map((peerCheque: PeerCheque, idx: number) => (
|
||||
<TableRow key={peerCheque.peer}>
|
||||
<TableCell>
|
||||
<div style={{display:'flex'}}>
|
||||
<small>
|
||||
<PeerDetailDrawer
|
||||
peerId={peerCheque.peer}
|
||||
/>
|
||||
</small>
|
||||
<ClipboardCopy value={peerCheque.peer} />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell style={{maxWidth:'200px'}}>
|
||||
<p style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}>
|
||||
{peerCheque.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString() : '-'}
|
||||
</p>
|
||||
<p style={{marginBottom: '0px'}}>
|
||||
<small>{peerCheque.lastreceived ?
|
||||
<EthereumAddress
|
||||
hideBlockie
|
||||
truncate
|
||||
network='goerli'
|
||||
address={peerCheque.lastreceived.beneficiary}
|
||||
/> : null}
|
||||
</small>
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell style={{maxWidth:'200px'}}>
|
||||
<p style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}>
|
||||
{peerCheque.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque.lastsent?.payout).toFixed(7).toLocaleString() : '-'}
|
||||
</p>
|
||||
<p style={{marginBottom: '0px'}}>
|
||||
<small>{peerCheque.lastsent ?
|
||||
<EthereumAddress
|
||||
hideBlockie
|
||||
truncate
|
||||
network='goerli'
|
||||
address={peerCheque.lastsent.beneficiary}
|
||||
/> : null}
|
||||
</small>
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChequebookTable;
|
||||
@@ -0,0 +1,188 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Paper, Container, Drawer, Button, Typography, CircularProgress, Grid } from '@material-ui/core';
|
||||
import ClipboardCopy from '../../components/ClipboardCopy';
|
||||
import { beeDebugApi } from '../../services/bee';
|
||||
import EthereumAddress from '../../components/EthereumAddress';
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common';
|
||||
|
||||
function truncStringPortion(str: string, firstCharCount=10, endCharCount=10) {
|
||||
var convertedStr="";
|
||||
convertedStr+=str.substring(0, firstCharCount);
|
||||
convertedStr += ".".repeat(3);
|
||||
convertedStr+=str.substring(str.length-endCharCount, str.length);
|
||||
return convertedStr;
|
||||
}
|
||||
|
||||
export default function Index(props: any) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [peerCashout, setPeerCashout] = useState({ "peer": "",
|
||||
"chequebook": "",
|
||||
"cumulativePayout": 0,
|
||||
"beneficiary": "",
|
||||
"transactionHash": "",
|
||||
"result": {
|
||||
"recipient": "",
|
||||
"lastPayout": 0,
|
||||
"bounced": false
|
||||
}});
|
||||
|
||||
const [peerCheque, setPeerCheque] = useState({
|
||||
lastreceived: { beneficiary: "", payout: 0, chequebook: "" },
|
||||
lastsent: { beneficiary: "", payout: 0, chequebook: "" },
|
||||
peer: ""
|
||||
})
|
||||
|
||||
const [isLoadingPeerCheque, setIsLoadingPeerCheque] = useState<boolean>(false)
|
||||
const [isLoadingPeerCashout, setIsLoadingPeerCashout] = useState<boolean>(false);
|
||||
|
||||
|
||||
const handleClickOpen = (peerId: string) => {
|
||||
setIsLoadingPeerCashout(true)
|
||||
beeDebugApi.chequebook.getPeerLastCashout(peerId)
|
||||
.then(res => {
|
||||
setPeerCashout(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingPeerCashout(false)
|
||||
})
|
||||
|
||||
setIsLoadingPeerCheque(true)
|
||||
beeDebugApi.chequebook.getPeerLastCheques(peerId)
|
||||
.then(res => {
|
||||
setPeerCheque(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingPeerCheque(false)
|
||||
})
|
||||
|
||||
setOpen(true);
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button color="primary" onClick={() => handleClickOpen(props.peerId)}>{truncStringPortion(props.peerId)}</Button>
|
||||
<Drawer anchor={'right'} open={open} onClose={handleClose}>
|
||||
<div style={{padding:'20px'}}>
|
||||
<Typography variant="h5" gutterBottom style={{display:'flex'}}>
|
||||
<span>Peer: { truncStringPortion(props.peerId) }</span>
|
||||
<ClipboardCopy value={props.peerId} />
|
||||
</Typography>
|
||||
<Paper>
|
||||
{ isLoadingPeerCashout || isLoadingPeerCheque ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<div style={{textAlign:'left', padding:'10px'}}>
|
||||
<h3>Last Cheque</h3>
|
||||
<Grid container spacing={1}>
|
||||
<Grid key={1} item xs={12} sm={12} xl={6}>
|
||||
<h5>Last Sent</h5>
|
||||
<p>
|
||||
Payout:
|
||||
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}> {
|
||||
peerCheque.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque.lastsent?.payout) : '-'
|
||||
}</span>
|
||||
</p>
|
||||
<p>Beneficiary:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCheque.lastsent?.beneficiary}
|
||||
/>
|
||||
</p>
|
||||
<p>Chequebook:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCheque.lastsent?.chequebook}
|
||||
/>
|
||||
</p>
|
||||
</Grid>
|
||||
<Grid key={1} item xs={12} sm={12} xl={6}>
|
||||
<h5>Last Received</h5>
|
||||
<p>
|
||||
Payout:
|
||||
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}> {
|
||||
peerCheque.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque.lastreceived?.payout) : '-'}</span>
|
||||
</p>
|
||||
<p>Beneficiary:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCheque.lastreceived?.beneficiary}
|
||||
/>
|
||||
</p>
|
||||
<p>Chequebook:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCheque.lastreceived?.chequebook}
|
||||
/>
|
||||
</p>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<h3>Last Cashout</h3>
|
||||
{peerCashout.cumulativePayout > 0 ?
|
||||
<div>
|
||||
<p>
|
||||
Cumulative Payout:
|
||||
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}>
|
||||
{peerCashout.cumulativePayout ? ConvertBalanceToBZZ(peerCashout.cumulativePayout) : '-'}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Last Payout:
|
||||
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}> {
|
||||
peerCashout.result.lastPayout ? ConvertBalanceToBZZ(peerCashout.result.lastPayout) : '-'
|
||||
}</span>
|
||||
<span> {peerCashout.result.bounced ? 'Bounced' : ''}</span>
|
||||
</p>
|
||||
<p>Beneficiary:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCashout.beneficiary}
|
||||
/>
|
||||
</p>
|
||||
<p>Chequebook:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCashout.chequebook}
|
||||
/>
|
||||
</p>
|
||||
<p>Recipient:
|
||||
<EthereumAddress
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCashout.result.recipient}
|
||||
/>
|
||||
</p>
|
||||
<p>Transaction:
|
||||
<EthereumAddress
|
||||
transaction
|
||||
network={'goerli'}
|
||||
hideBlockie
|
||||
address={peerCashout.transactionHash}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
: 'None'}
|
||||
</div>
|
||||
}
|
||||
</Paper>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper, Container, CircularProgress } from '@material-ui/core';
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common';
|
||||
|
||||
import type { AllSettlements, Settlements } from '@ethersphere/bee-js'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
minWidth: 650,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
interface IProps {
|
||||
nodeSettlements: AllSettlements | null,
|
||||
loading?: boolean,
|
||||
}
|
||||
|
||||
function SettlementsTable(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.loading ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<TableContainer component={Paper}>
|
||||
<Table className={classes.table} size="small" aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Peer</TableCell>
|
||||
<TableCell>Received (BZZ)</TableCell>
|
||||
<TableCell>Sent (BZZ)</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.nodeSettlements?.settlements.map((item: Settlements, idx: number) => (
|
||||
<TableRow key={item.peer}>
|
||||
<TableCell>{item.peer}</TableCell>
|
||||
<TableCell style={{ fontFamily: 'monospace, monospace'}}>
|
||||
{item.received > 0 ? ConvertBalanceToBZZ(item.received).toFixed(7).toLocaleString() : item.received}
|
||||
</TableCell>
|
||||
<TableCell style={{ fontFamily: 'monospace, monospace'}}>
|
||||
{item.sent > 0 ? ConvertBalanceToBZZ(item.sent).toFixed(7).toLocaleString() : item.sent}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettlementsTable;
|
||||
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { withStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import { Tabs, Tab, Box, Typography, Container, CircularProgress } from '@material-ui/core';
|
||||
|
||||
import AccountCard from '../accounting/AccountCard';
|
||||
import BalancesTable from './BalancesTable';
|
||||
import ChequebookTable from './ChequebookTable';
|
||||
import SettlementsTable from './SettlementsTable';
|
||||
import EthereumAddressCard from '../../components/EthereumAddressCard';
|
||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard';
|
||||
|
||||
import { useApiNodeAddresses, useApiChequebookAddress, useApiChequebookBalance, useApiPeerBalances, useApiPeerCheques, useApiSettlements } from '../../hooks/apiHooks';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: any;
|
||||
value: any;
|
||||
}
|
||||
|
||||
|
||||
function a11yProps(index: any) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Accounting(props: any) {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
||||
const { peerBalances, isLoadingPeerBalances } = useApiPeerBalances()
|
||||
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
||||
|
||||
const { peerCheques, isLoadingPeerCheques } = useApiPeerCheques()
|
||||
const { settlements, isLoadingSettlements } = useApiSettlements()
|
||||
|
||||
|
||||
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 style={{ marginTop: '20px' }}>
|
||||
<Typography>{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const AntTabs = withStyles({
|
||||
root: {
|
||||
borderBottom: '1px solid #e8e8e8',
|
||||
},
|
||||
indicator: {
|
||||
backgroundColor: '#3f51b5',
|
||||
},
|
||||
})(Tabs);
|
||||
|
||||
interface StyledTabProps {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const AntTab = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
minWidth: 72,
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
marginRight: theme.spacing(4),
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
].join(','),
|
||||
'&:hover': {
|
||||
color: '#3f51b5',
|
||||
opacity: 1,
|
||||
},
|
||||
'&$selected': {
|
||||
color: '#3f51b5',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
},
|
||||
'&:focus': {
|
||||
color: '#3f51b5',
|
||||
},
|
||||
},
|
||||
selected: {},
|
||||
}),
|
||||
)((props: StyledTabProps) => <Tab disableRipple {...props} />);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.nodeHealth?.status === 'ok' && props.health ?
|
||||
<div>
|
||||
<AccountCard
|
||||
chequebookAddress={chequebookAddress}
|
||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
||||
chequebookBalance={chequebookBalance}
|
||||
isLoadingChequebookBalance={isLoadingChequebookBalance}
|
||||
settlements={settlements}
|
||||
isLoadingSettlements={isLoadingSettlements}
|
||||
/>
|
||||
<EthereumAddressCard
|
||||
nodeAddresses={nodeAddresses}
|
||||
isLoadingNodeAddresses={isLoadingNodeAddresses}
|
||||
chequebookAddress={chequebookAddress}
|
||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
||||
/>
|
||||
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
|
||||
<AntTab label="Balances" {...a11yProps(0)} />
|
||||
<AntTab label="Chequebook" {...a11yProps(1)} />
|
||||
<AntTab label="Settlements" {...a11yProps(2)} />
|
||||
</AntTabs>
|
||||
<TabPanel value={value} index={0}>
|
||||
<BalancesTable
|
||||
peerBalances={peerBalances}
|
||||
loading={isLoadingPeerBalances}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={value} index={1}>
|
||||
<ChequebookTable
|
||||
peerCheques={peerCheques}
|
||||
loading={isLoadingPeerCheques}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={value} index={2}>
|
||||
<SettlementsTable
|
||||
nodeSettlements={settlements}
|
||||
loading={isLoadingSettlements}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
:
|
||||
props.isLoadingHealth || props.isLoadingNodeHealth ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<TroubleshootConnectionCard />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useState } from 'react';
|
||||
import { beeApi } from '../../services/bee';
|
||||
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import { Paper, InputBase, IconButton, Button, Container, CircularProgress } 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';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
padding: '2px 4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 400,
|
||||
},
|
||||
input: {
|
||||
marginLeft: theme.spacing(1),
|
||||
flex: 1,
|
||||
},
|
||||
iconButton: {
|
||||
padding: 10,
|
||||
},
|
||||
divider: {
|
||||
height: 28,
|
||||
margin: 4,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export default function Files(props: any) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [inputMode, setInputMode] = useState<'browse' | 'upload'>('browse');
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [searchResult, setSearchResult] = useState('');
|
||||
const [loadingSearch, setLoadingSearch] = useState(false);
|
||||
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [uploadReference, setUploadReference] = useState('');
|
||||
const [uploadingFile, setUploadingFile] = useState(false);
|
||||
|
||||
const getFile = () => {
|
||||
setLoadingSearch(true)
|
||||
beeApi.files.downloadFile(searchInput)
|
||||
.then(res => {
|
||||
setSearchResult(new TextDecoder("utf-8").decode(res.data))
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.setAttribute('download', 'file.zip'); //any other extension
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
})
|
||||
.catch(error => {
|
||||
})
|
||||
.finally(() => {
|
||||
setLoadingSearch(false)
|
||||
})
|
||||
}
|
||||
|
||||
const uploadFile = () => {
|
||||
setUploadingFile(true)
|
||||
beeApi.files.uploadFile(files[0])
|
||||
.then(hash => {
|
||||
setUploadReference(hash)
|
||||
setFiles([])
|
||||
})
|
||||
.catch(error => {
|
||||
})
|
||||
.finally(() => {
|
||||
setUploadingFile(false)
|
||||
})
|
||||
}
|
||||
|
||||
const handleChange = (files: any) => {
|
||||
if (files) {
|
||||
setFiles(files)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.nodeHealth?.status === 'ok' && props.health ?
|
||||
<Container maxWidth="sm">
|
||||
<div style={{marginBottom: '7px'}}>
|
||||
<Button color="primary" style={{marginRight: '7px'}} onClick={() => setInputMode('browse')}>Browse</Button>
|
||||
<Button color="primary" onClick={() => setInputMode('upload')}>Upload</Button>
|
||||
</div>
|
||||
{inputMode === 'browse' ?
|
||||
<Paper component="form" className={classes.root}>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
|
||||
inputProps={{ 'aria-label': 'search swarm nodes' }}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
/>
|
||||
<IconButton onClick={() => getFile()} className={classes.iconButton} aria-label="search">
|
||||
<Search />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
:
|
||||
<div>
|
||||
{uploadingFile ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<div>
|
||||
{uploadReference ?
|
||||
<Paper component="form" className={classes.root} style={{marginBottom:'15px', display: 'flex'}}>
|
||||
<span>{uploadReference}</span>
|
||||
<ClipboardCopy
|
||||
value={uploadReference}
|
||||
/>
|
||||
</Paper>
|
||||
:
|
||||
null
|
||||
}
|
||||
<DropzoneArea
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div style={{marginTop:'15px'}}>
|
||||
<Button onClick={() => uploadFile()} className={classes.iconButton}>
|
||||
Upload
|
||||
</Button>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
{loadingSearch ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<div style={{padding:'20px'}} >
|
||||
{searchResult}
|
||||
</div>
|
||||
}
|
||||
</Container>
|
||||
:
|
||||
props.isLoadingHealth || props.isLoadingNodeHealth ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<TroubleshootConnectionCard
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useState } from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Button, Paper, Tooltip, Container, CircularProgress } from '@material-ui/core';
|
||||
import { Cancel, Autorenew } from '@material-ui/icons';
|
||||
|
||||
import { beeDebugApi } from '../../services/bee';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
minWidth: 650,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
function PeerTable(props: any) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [peerLatency, setPeerLatency] = useState([{ peerId: '', rtt: '', loading: false }]);
|
||||
const [removingPeer, setRemovingPeer] = useState(false);
|
||||
|
||||
const PingPeer = async (peerId: string) => {
|
||||
|
||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: '', loading: true }])
|
||||
beeDebugApi.connectivity.ping(peerId)
|
||||
.then(res => {
|
||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: res.data.rtt, loading: false }])
|
||||
})
|
||||
.catch(error => {
|
||||
setPeerLatency([...peerLatency, { peerId: peerId, rtt: 'error', loading: false }])
|
||||
})
|
||||
}
|
||||
|
||||
const removePeer = (peerId: string) => {
|
||||
setRemovingPeer(true)
|
||||
beeDebugApi.connectivity.removePeer(peerId)
|
||||
.then(res => {
|
||||
window.location.reload()
|
||||
setRemovingPeer(false)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setRemovingPeer(false)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.loading ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<TableContainer component={Paper}>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Index</TableCell>
|
||||
<TableCell>Peer Id</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.nodePeers.peers.map((peer: any, idx: number) => (
|
||||
<TableRow key={peer.address}>
|
||||
<TableCell component="th" scope="row">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell>{peer.address}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Tooltip title="Ping node">
|
||||
<Button color="primary" onClick={() => PingPeer(peer.address)} >
|
||||
{peerLatency.find(item => item.peerId === peer.address) ?
|
||||
peerLatency.filter(item => item.peerId === peer.address)[0].loading ? <CircularProgress size={20} /> :
|
||||
peerLatency.filter(item => item.peerId === peer.address)[0].rtt :
|
||||
<Autorenew />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* <Tooltip title="Remove peer">
|
||||
<Button color="primary" onClick={() => removePeer(peer.address)} >
|
||||
<Cancel />
|
||||
</Button>
|
||||
</Tooltip> */}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PeerTable
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Grid, Container, CircularProgress } from '@material-ui/core/';
|
||||
|
||||
import StatCard from '../../components/StatCard';
|
||||
import PeerTable from './PeerTable';
|
||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard';
|
||||
|
||||
import { useApiNodeTopology, useApiNodePeers } from '../../hooks/apiHooks';
|
||||
|
||||
export default function Peers(props: any) {
|
||||
const { nodeTopology, isLoadingNodeTopology } = useApiNodeTopology()
|
||||
const { nodePeers, isLoadingNodePeers } = useApiNodePeers()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{props.nodeHealth?.status === 'ok' && props.health ?
|
||||
<div>
|
||||
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
||||
<StatCard
|
||||
label='Connected Peers'
|
||||
statistic={nodeTopology.connected.toString()}
|
||||
loading={isLoadingNodeTopology}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid key={2} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
||||
<StatCard
|
||||
label='Population'
|
||||
statistic={nodeTopology.population.toString()}
|
||||
loading={isLoadingNodeTopology}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
|
||||
<StatCard
|
||||
label='Depth'
|
||||
statistic={nodeTopology.depth.toString()}
|
||||
loading={isLoadingNodeTopology}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<PeerTable
|
||||
nodePeers={nodePeers}
|
||||
loading={isLoadingNodePeers}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
props.isLoadingHealth || props.isLoadingNodeHealth ?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<TroubleshootConnectionCard />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Paper, Container, TextField, Typography, Button } from '@material-ui/core';
|
||||
|
||||
export default function Settings() {
|
||||
|
||||
const [refreshVisibility, toggleRefreshVisibility] = useState(false)
|
||||
const [host, setHost] = useState('')
|
||||
const [debugHost, setDebugHost] = useState('')
|
||||
|
||||
const handleNewHostConnection = () => {
|
||||
if (host) {
|
||||
sessionStorage.setItem('api_host', host)
|
||||
}
|
||||
if (debugHost) {
|
||||
sessionStorage.setItem('debug_api_host', debugHost)
|
||||
}
|
||||
if (host || debugHost) {
|
||||
toggleRefreshVisibility(!refreshVisibility)
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Settings
|
||||
</Typography>
|
||||
<Paper>
|
||||
<TextField
|
||||
id="filled-full-width"
|
||||
label="API Endpoint"
|
||||
style={{ margin: 0 }}
|
||||
placeholder="ex: 127.0.0.0.1:1633"
|
||||
helperText="Enter node host override / port"
|
||||
fullWidth
|
||||
defaultValue={sessionStorage.getItem('api_host') ? sessionStorage.getItem('api_host') : process.env.REACT_APP_BEE_HOST}
|
||||
margin="normal"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setHost(e.target.value)
|
||||
toggleRefreshVisibility(true)
|
||||
}}
|
||||
variant="filled"
|
||||
/>
|
||||
</Paper>
|
||||
<Paper style={{marginTop:'20px'}}>
|
||||
<TextField
|
||||
id="filled-full-width"
|
||||
label="Debug API Endpoint"
|
||||
style={{ margin: 0 }}
|
||||
placeholder="ex: 127.0.0.0.1:1635"
|
||||
helperText="Enter node debug host override / port"
|
||||
fullWidth
|
||||
defaultValue={sessionStorage.getItem('debug_api_host') ? sessionStorage.getItem('debug_api_host') : process.env.REACT_APP_BEE_DEBUG_HOST}
|
||||
onChange={(e) => {
|
||||
setDebugHost(e.target.value)
|
||||
toggleRefreshVisibility(true)
|
||||
}}
|
||||
margin="normal"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
variant="filled"
|
||||
/>
|
||||
</Paper>
|
||||
{refreshVisibility ?
|
||||
<div style={{marginTop:'20px'}}>
|
||||
<Button variant='outlined' color='primary' onClick={() => handleNewHostConnection()}>Save</Button>
|
||||
</div>
|
||||
: null}
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/';
|
||||
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp } from '@material-ui/icons/';
|
||||
|
||||
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck';
|
||||
import VersionCheck from './SetupSteps/VersionCheck';
|
||||
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck';
|
||||
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund';
|
||||
import PeerConnection from './SetupSteps/PeerConnection';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: '100%',
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
actionsContainer: {
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
resetContainer: {
|
||||
padding: theme.spacing(5),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
function getSteps() {
|
||||
return [
|
||||
'Node Connection Check',
|
||||
'Version Check',
|
||||
'Connect to Ethereum Blockchain',
|
||||
'Deploy and Fund Chequebook',
|
||||
'Connect to Peers',
|
||||
];
|
||||
}
|
||||
|
||||
function getStepContent(step: number, props: any) {
|
||||
|
||||
switch (step) {
|
||||
case 0:
|
||||
return <NodeConnectionCheck {...props} />;
|
||||
case 1:
|
||||
return <VersionCheck {...props} />;
|
||||
case 2:
|
||||
return <EthereumConnectionCheck {...props} />;
|
||||
case 3:
|
||||
return <ChequebookDeployFund {...props} />;
|
||||
default:
|
||||
return <PeerConnection {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default function NodeSetupWorkflow(props: any) {
|
||||
const classes = useStyles();
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [completed, setCompleted] = useState<{ [k: number]: boolean }>({});
|
||||
const steps = getSteps();
|
||||
|
||||
const evaluateNodeStatus = () => {
|
||||
if (props.nodeHealth?.status === 'ok' && props.nodeApiHealth) {
|
||||
handleComplete(0)
|
||||
setActiveStep(1)
|
||||
}
|
||||
|
||||
if (props.beeRelease && props.beeRelease.name === `v${props.nodeHealth?.version?.split('-')[0]}`) {
|
||||
handleComplete(1)
|
||||
setActiveStep(2)
|
||||
}
|
||||
|
||||
if (props.nodeAddresses?.ethereum) {
|
||||
handleComplete(2)
|
||||
setActiveStep(3)
|
||||
}
|
||||
|
||||
if (props.chequebookAddress?.chequebookaddress && props.chequebookBalance.totalBalance > 0) {
|
||||
handleComplete(3)
|
||||
setActiveStep(4)
|
||||
}
|
||||
|
||||
if (props.nodeTopology.connected && props.nodeTopology.connected > 0) {
|
||||
handleComplete(4)
|
||||
setActiveStep(5)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
evaluateNodeStatus()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
evaluateNodeStatus()
|
||||
}, [props])
|
||||
|
||||
const handleNext = () => {
|
||||
setActiveStep((prevActiveStep) => prevActiveStep + 1);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setActiveStep((prevActiveStep) => prevActiveStep - 1);
|
||||
};
|
||||
|
||||
const handleSetupComplete = () => {
|
||||
props.setStatusChecksVisible(false)
|
||||
};
|
||||
|
||||
const handleComplete = (index: number) => {
|
||||
const newCompleted = completed;
|
||||
newCompleted[index] = true;
|
||||
setCompleted(newCompleted);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Node Setup
|
||||
<span style={{marginLeft:'25px'}}>
|
||||
<Button variant='outlined' size='small' onClick={() => window.location.reload()}><Sync/><span style={{marginLeft:'7px'}}>Refresh Checks</span></Button>
|
||||
</span>
|
||||
</Typography>
|
||||
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
|
||||
{steps.map((label, index) => (
|
||||
<Step key={label}>
|
||||
<StepLabel
|
||||
onClick={() => setActiveStep(index === activeStep ? 5 : index)}
|
||||
StepIconComponent={() => {
|
||||
if(completed[index])
|
||||
return <CheckCircle style={{color:'#32c48d', height: '25px', cursor:'pointer'}} />
|
||||
else {
|
||||
return <Error style={{color:'#c9201f', height: '25px', cursor:'pointer'}} />
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StepButton onClick={() => setActiveStep(index === activeStep ? 5 : index)} style={{justifyContent:'space-between'}}>
|
||||
<div style={{display:'flex'}}>
|
||||
<div style={{marginTop:'5px'}}>{label}</div>
|
||||
<div style={{marginLeft:'12px'}}>{index === activeStep ? <ExpandLessSharp /> : <ExpandMoreSharp />}</div>
|
||||
</div>
|
||||
</StepButton>
|
||||
</StepLabel>
|
||||
<StepContent>
|
||||
<Typography>{getStepContent(index, props)}</Typography>
|
||||
<div className={classes.actionsContainer}>
|
||||
<div>
|
||||
<Button
|
||||
disabled={activeStep === 0}
|
||||
onClick={handleBack}
|
||||
className={classes.button}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleNext}
|
||||
className={classes.button}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</StepContent>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
{Object.values(completed).filter(value => value).length === 5 ? (
|
||||
<Paper square elevation={0} className={classes.resetContainer}>
|
||||
<Typography>Bee setup complete! Welcome to the swarm and the internet of decentralized storage</Typography>
|
||||
<Button
|
||||
onClick={handleBack}
|
||||
className={classes.button}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleSetupComplete} variant="contained" color="primary" className={classes.button}>
|
||||
Complete
|
||||
</Button>
|
||||
</Paper>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@material-ui/core/';
|
||||
import { CheckCircle, Warning } from '@material-ui/icons/';
|
||||
import EthereumAddress from '../../../components/EthereumAddress';
|
||||
import DepositModal from '../../../components/DepositModal';
|
||||
import CodeBlockTabs from '../../../components/CodeBlockTabs';
|
||||
|
||||
export default function ChequebookDeployFund(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<p style={{marginBottom:'20px', display:'flex'}}>
|
||||
<span style={{ marginRight:'40px'}} >Deploy chequebook and fund with BZZ</span>
|
||||
{props.chequebookAddress?.chequebookaddress ?
|
||||
<DepositModal />
|
||||
: null }
|
||||
</p>
|
||||
<div style={{ marginBottom:'10px' }}>
|
||||
{props.chequebookAddress?.chequebookaddress && props.chequebookBalance && props.chequebookBalance?.totalBalance > 0 ?
|
||||
<div>
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your chequebook is deployed and funded!</span>
|
||||
</div>
|
||||
:
|
||||
props.loadingChequebookAddress || props.isLoadingChequebookBalance ?
|
||||
null
|
||||
:
|
||||
<div>
|
||||
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your chequebook is either not deployed or funded. Run the below commands to get your address and deposit ETH. Then visit the BZZaar here <a href='#'>https://bzz.ethswarm.org/?transaction=buy&amount=10&slippage=30&receiver=[ENTER_ADDRESS_HERE]</a> to get BZZ</span>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`bee-get-addr`}
|
||||
mac={`bee-get-addr`}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<span>Chequebook Address</span>
|
||||
</Typography>
|
||||
<EthereumAddress
|
||||
address={props.chequebookAddress?.chequebookaddress}
|
||||
network={'goerli'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@material-ui/core/';
|
||||
import { CheckCircle, Warning } from '@material-ui/icons/';
|
||||
import EthereumAddress from '../../../components/EthereumAddress';
|
||||
|
||||
export default function EthereumConnectionCheck(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<p>Connect to the ethereum blockchain.</p>
|
||||
<div style={{ marginBottom:'10px' }}>
|
||||
{props.nodeAddresses?.ethereum ?
|
||||
<div>
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your connected to the Ethereum network</span>
|
||||
</div>
|
||||
:
|
||||
props.loadingNodeAddresses ?
|
||||
null
|
||||
:
|
||||
<div>
|
||||
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your not connected to the Ethereum network. </span>
|
||||
<p>Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your chequebook contract. You can run <a href='https://github.com/goerli/testnet' rel='noreferrer' target='_blank'>your own Goerli node</a>, or use a provider such as <a href='https://rpc.slock.it/goerli' rel='noreferrer' target='_blank'>rpc.slock.it/goerli</a> or <a href='https://infura.io/' rel='noreferrer' target='_blank'>Infura</a>.
|
||||
|
||||
By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, simply change your <strong>--swap-endpoint</strong> in your configuration file.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<span>Node Address</span>
|
||||
</Typography>
|
||||
<EthereumAddress
|
||||
address={props.nodeAddresses?.ethereum}
|
||||
network={'goerli'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import React from 'react'
|
||||
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/';
|
||||
import MuiAlert from '@material-ui/lab/Alert';
|
||||
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/';
|
||||
|
||||
import ConnectToHost from '../../../components/ConnectToHost';
|
||||
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||
|
||||
export default function NodeConnectionCheck(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<p>Connect to Bee Node APIs</p>
|
||||
<div style={{display:'flex', marginBottom: '25px'}}>
|
||||
{ props.nodeApiHealth ?
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
|
||||
:
|
||||
<Error style={{color:'#c9201f', marginRight: '7px', height: '18px'}} />
|
||||
}
|
||||
<span style={{marginRight:'15px'}}>Node API (<a href='#'>{props.apiHost}</a>)</span>
|
||||
<ConnectToHost hostName='api_host' defaultHost={props.apiHost} />
|
||||
</div>
|
||||
<div>
|
||||
{ !props.nodeApiHealth ?
|
||||
<Typography variant="body2" gutterBottom style={{margin: '15px'}}>
|
||||
We cannot connect to your nodes API at <a href='#'>{props.apiHost}</a>. Please check the following to troubleshoot your issue.
|
||||
<Accordion style={{marginTop:'20px'}}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreSharp />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography>Troubleshoot</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>
|
||||
<ol>
|
||||
<li>Check the status of your node by running the below command to see if your node is running.</li>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`sudo systemctl status bee`}
|
||||
mac={`brew services status swarm-bee`}
|
||||
/>
|
||||
<li>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 command to start your bee node</li>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`sudo systemctl start bee`}
|
||||
mac={`brew services start swarm-bee`}
|
||||
/>
|
||||
<li>Run the commands to validate your node is running and see the log output.</li>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
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`}
|
||||
/>
|
||||
</ol>
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Typography>
|
||||
:
|
||||
null}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{display:'flex', marginBottom: '25px'}}>
|
||||
{ props.nodeHealth?.status === 'ok' ?
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
|
||||
:
|
||||
<Error style={{color:'#c9201f', marginRight: '7px', height: '18px'}} />
|
||||
}
|
||||
<span style={{marginRight:'15px'}}>Debug API (<a href='#'>{props.debugApiHost}</a>)</span>
|
||||
<ConnectToHost hostName={'debug_api_host'} defaultHost={props.debugApiHost} />
|
||||
</div>
|
||||
<div>
|
||||
{ props.nodeHealth?.status !== 'ok' ?
|
||||
<Typography variant="body2" gutterBottom style={{margin: '15px'}}>
|
||||
We cannot connect to your nodes debug API at <a href='#'>{props.debugApiHost}</a>. Please check the following to troubleshoot your issue.
|
||||
<Accordion style={{marginTop:'20px'}}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreSharp />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography>Troubleshoot</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>
|
||||
<ol>
|
||||
<li>Check the status of your node by running the below command to see if your node is running.</li>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`sudo systemctl status bee`}
|
||||
mac={`brew services status swarm-bee`}
|
||||
/>
|
||||
<li>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 to start your bee node</li>
|
||||
<MuiAlert style={{marginTop:'10px', marginBottom:'10px'}} elevation={6} variant="filled" severity="error">
|
||||
Your debug node API should never be completely open to the internet. If you want to connect remotely, make sure your firewall settings are set to only allow specific trusted IP addresses and block all other ports. A simple google search for "what is my ip" will show you your computers public IP address to allow.
|
||||
</MuiAlert>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`sudo systemctl start bee`}
|
||||
mac={`brew services start swarm-bee`}
|
||||
/>
|
||||
<li>Run the commands to validate your node is running and see the log output.</li>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
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`}
|
||||
/>
|
||||
<li>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 <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 the configuration run the restart command below for changes to take effect.</li>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`sudo vi /etc/bee/bee.yaml\nsudo systemctl restart bee`}
|
||||
mac={`sudo vi /etc/bee/bee.yaml \nbrew services restart swarm-bee`}
|
||||
/>
|
||||
</ol>
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Typography>
|
||||
:
|
||||
null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@material-ui/core/';
|
||||
import { CheckCircle, Warning } from '@material-ui/icons/';
|
||||
|
||||
export default function PeerConnection(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<p>Connect to Peers</p>
|
||||
<div style={{ marginBottom:'10px' }}>
|
||||
{props.nodeTopology.connected && props.nodeTopology.connected > 0 ?
|
||||
<div>
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your connected to {props.nodeTopology.connected} peers!</span>
|
||||
</div>
|
||||
:
|
||||
props.loadingNodeTopology ?
|
||||
null
|
||||
:
|
||||
<div>
|
||||
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your node is not connected to any peers</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div style={{display:'flex'}}>
|
||||
<div style={{marginRight:'30px'}}>
|
||||
<Typography variant="subtitle1" gutterBottom color="textSecondary">
|
||||
<span>Connected Peers</span>
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2">
|
||||
{ props.nodeTopology.connected }
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="subtitle1" gutterBottom color="textSecondary">
|
||||
<span>Discovered Nodes</span>
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2">
|
||||
{ props.nodeTopology.population }
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@material-ui/core/';
|
||||
import { CheckCircle, Warning } from '@material-ui/icons/';
|
||||
import CodeBlockTabs from '../../../components/CodeBlockTabs';
|
||||
|
||||
export default function VersionCheck(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<p>Check to make sure the latest version of <a href='https://github.com/ethersphere/bee' rel='noreferrer' target='_blank'>Bee</a> is running</p>
|
||||
{props.beeRelease && props.beeRelease.name === `v${props.nodeReadiness?.version?.split('-')[0]}` ?
|
||||
<div>
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your running the latest version of Bee</span>
|
||||
</div>
|
||||
:
|
||||
props.loadingBeeRelease ?
|
||||
null
|
||||
:
|
||||
<div>
|
||||
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
|
||||
<span>Your Bee version is out of date. Please update to the <a href={props.beeRelease.html_url} rel='noreferrer' target='_blank'>latest</a> before continuing. Rerun the installation script below to upgrade. Reference the docs for help with updating. <a href='https://docs.ethswarm.org/docs/installation/manual#upgrading-bee' rel='noreferrer' target='_blank'>Docs</a></span>
|
||||
<CodeBlockTabs
|
||||
showLineNumbers
|
||||
linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${props.beeRelease.name}/bee_${props.nodeReadiness?.version?.split('-')[0]}_amd64.deb\nsudo dpkg -i bee_${props.nodeReadiness?.version?.split('-')[0]}_amd64.deb`}
|
||||
mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div style={{display:'flex'}}>
|
||||
<div style={{marginRight:'30px'}}>
|
||||
<p><span>Current Version</span></p>
|
||||
<Typography component="h5" variant="h5">
|
||||
<span>{props.nodeReadiness?.version ? ` v${props.nodeReadiness?.version?.split('-')[0]}` : '-'}</span>
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<p><span>Latest Version</span></p>
|
||||
<Typography component="h5" variant="h5">
|
||||
<span>{props.beeRelease && props.beeRelease.name ? props.beeRelease.name : '-'}</span>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
|
||||
import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/';
|
||||
import { CheckCircle, Error, ArrowRight, ArrowDropUp } from '@material-ui/icons/';
|
||||
import { Skeleton } from '@material-ui/lab';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
details: {
|
||||
display: 'flex',
|
||||
flex: '1 0 auto',
|
||||
|
||||
flexDirection: 'column',
|
||||
},
|
||||
content: {
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
status: {
|
||||
color: '#2145a0',
|
||||
backgroundColor: '#e1effe',
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
interface NodeHealth {
|
||||
status?: string,
|
||||
version?: string
|
||||
}
|
||||
|
||||
interface NodeReadiness {
|
||||
status?: string,
|
||||
version?: string
|
||||
}
|
||||
|
||||
interface NodeAddresses {
|
||||
overlay: string,
|
||||
underlay: string[],
|
||||
ethereum: string,
|
||||
public_key: string,
|
||||
pss_public_key: string
|
||||
}
|
||||
|
||||
interface NodeTopology {
|
||||
baseAddr: string,
|
||||
bins: string[],
|
||||
connected: number,
|
||||
depth: number,
|
||||
nnLowWatermark: number,
|
||||
population: number,
|
||||
timestamp: string,
|
||||
}
|
||||
|
||||
|
||||
interface IProps{
|
||||
nodeHealth: NodeHealth,
|
||||
loadingNodeHealth: boolean,
|
||||
nodeReadiness: NodeReadiness | null,
|
||||
loadingNodeReadiness: boolean,
|
||||
beeRelease: any,
|
||||
loadingBeeRelease: boolean,
|
||||
nodeAddresses: NodeAddresses,
|
||||
nodeTopology: NodeTopology,
|
||||
loadingNodeTopology: boolean,
|
||||
setStatusChecksVisible: any,
|
||||
}
|
||||
|
||||
function StatusCard(props: IProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<Boolean>(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className={classes.root}>
|
||||
{ !props.loadingNodeHealth && props.nodeHealth ?
|
||||
<div className={classes.details}>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography component="h5" variant="h5" style={{display:'flex', justifyContent:'space-between'}}>
|
||||
{ props.nodeHealth.status === 'ok' ?
|
||||
<div>
|
||||
<CheckCircle style={{color:'#32c48d', marginRight: '7px'}} />
|
||||
<span>Connected to Bee Node</span>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<Error style={{color:'#c9201f', marginRight: '7px'}} />
|
||||
<span>Could not connect to Bee Node</span>
|
||||
</div>
|
||||
}
|
||||
<Button variant='outlined' color='primary' size='small' style={{marginLeft:'12px'}} onClick={() => props.setStatusChecksVisible(true)}>View Status Checks</Button>
|
||||
</Typography>
|
||||
<div style={{marginBottom: '20px' }}>
|
||||
<span style={{marginRight:'20px'}}>Discovered Nodes: { props.nodeTopology.population }</span>
|
||||
<span style={{marginRight:'20px'}}>
|
||||
<span>Connected Peers: </span>
|
||||
<Link to='/peers/'>
|
||||
{ props.nodeTopology.connected }
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
<span>AGENT: </span>
|
||||
<a href='https://github.com/ethersphere/bee' rel='noreferrer' target='_blank'>Bee</a>
|
||||
<span>{props.nodeReadiness?.version ? ` v${props.nodeReadiness.version}` : '-'}</span>
|
||||
{props.beeRelease && props.beeRelease.name === `v${props.nodeReadiness?.version?.split('-')[0]}` ?
|
||||
<Chip
|
||||
style={{ marginLeft: '7px', color: '#2145a0' }}
|
||||
size="small"
|
||||
label='latest'
|
||||
className={classes.status}
|
||||
/>
|
||||
:
|
||||
props.loadingBeeRelease ?
|
||||
''
|
||||
:
|
||||
<a href='#'>update</a>
|
||||
}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
<span>PUBLIC KEY: </span>
|
||||
<span>{ props.nodeAddresses.public_key ? props.nodeAddresses.public_key : '-' }</span>
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
<span>PSS PUBLIC KEY: </span>
|
||||
<span>{ props.nodeAddresses.pss_public_key ? props.nodeAddresses.pss_public_key : '-' }</span>
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
<Typography style={{marginTop:'20px'}}>
|
||||
<span>OVERLAY ADDRESS (PEER ID): </span>
|
||||
<span>{ props.nodeAddresses.overlay ? props.nodeAddresses.overlay : '-' }</span>
|
||||
</Typography>
|
||||
<Typography onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}>
|
||||
<Button color="primary" style={{padding: 0, marginTop:'6px'}}>
|
||||
{ underlayAddressesVisible ?
|
||||
<ArrowDropUp style={{fontSize:'12px'}} /> :
|
||||
<ArrowRight style={{fontSize:'12px'}} />
|
||||
}
|
||||
<span>Underlay Addresses</span>
|
||||
</Button>
|
||||
</Typography>
|
||||
{underlayAddressesVisible ?
|
||||
<div>
|
||||
|
||||
{ props.nodeAddresses.underlay ?
|
||||
props.nodeAddresses.underlay.map(item => (<li>{item}</li>))
|
||||
: '-' }
|
||||
</div>
|
||||
: null}
|
||||
</Typography>
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
:
|
||||
<div style={{padding: '16px'}}>
|
||||
<Skeleton width={650} height={32} animation="wave" />
|
||||
<Skeleton width={650} height={24} animation="wave" />
|
||||
<Skeleton width={650} height={24} animation="wave" />
|
||||
</div>
|
||||
}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatusCard
|
||||
@@ -0,0 +1,141 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import axios from 'axios';
|
||||
import { Container, CircularProgress } from '@material-ui/core';
|
||||
|
||||
import NodeSetupWorkflow from './NodeSetupWorkflow';
|
||||
import StatusCard from './StatusCard';
|
||||
import EthereumAddressCard from '../../components/EthereumAddressCard';
|
||||
import { useApiHealth, useDebugApiHealth, useApiReadiness, useApiNodeAddresses, useApiChequebookAddress, useApiNodeTopology, useApiChequebookBalance } from '../../hooks/apiHooks';
|
||||
|
||||
export default function Status() {
|
||||
const [beeRelease, setBeeRelease] = useState({ name: ''});
|
||||
const [isLoadingBeeRelease, setIsLoadingBeeRelease] = useState<boolean>(false);
|
||||
|
||||
const [apiHost, setApiHost] = useState('');
|
||||
const [debugApiHost, setDebugApiHost] = useState('');
|
||||
|
||||
const [statusChecksVisible, setStatusChecksVisible] = useState<boolean>(false);
|
||||
|
||||
const { health, isLoadingHealth } = useApiHealth()
|
||||
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
|
||||
const { nodeReadiness, isLoadingNodeReadiness } = useApiReadiness()
|
||||
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
|
||||
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
|
||||
const { nodeTopology, isLoadingNodeTopology } = useApiNodeTopology()
|
||||
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
|
||||
|
||||
|
||||
const fetchLatestBeeRelease = async () => {
|
||||
setIsLoadingBeeRelease(true)
|
||||
axios.get(`${process.env.REACT_APP_BEE_GITHUB_REPO_URL}/releases/latest`)
|
||||
.then(res => {
|
||||
setBeeRelease(res.data)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingBeeRelease(false)
|
||||
})
|
||||
}
|
||||
|
||||
const fetchApiHost = () => {
|
||||
let apiHost
|
||||
|
||||
if (sessionStorage.getItem('api_host')) {
|
||||
apiHost = String(sessionStorage.getItem('api_host') || '')
|
||||
} else {
|
||||
apiHost = String(process.env.REACT_APP_BEE_HOST)
|
||||
}
|
||||
setApiHost(apiHost)
|
||||
}
|
||||
|
||||
const fetchDebugApiHost = () => {
|
||||
let debugApiHost
|
||||
|
||||
if (sessionStorage.getItem('debug_api_host')) {
|
||||
debugApiHost = String(sessionStorage.getItem('debug_api_host') || '')
|
||||
} else {
|
||||
debugApiHost = String(process.env.REACT_APP_BEE_DEBUG_HOST)
|
||||
}
|
||||
setDebugApiHost(debugApiHost)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchApiHost()
|
||||
fetchDebugApiHost()
|
||||
fetchLatestBeeRelease()
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{nodeHealth?.status === 'ok' &&
|
||||
health &&
|
||||
beeRelease &&
|
||||
beeRelease.name === `v${nodeHealth?.version?.split('-')[0]}` &&
|
||||
nodeAddresses?.ethereum &&
|
||||
chequebookAddress?.chequebookaddress && chequebookBalance && chequebookBalance?.totalBalance > 0 &&
|
||||
nodeTopology.connected && nodeTopology.connected > 0 &&
|
||||
!statusChecksVisible ?
|
||||
<div>
|
||||
<StatusCard
|
||||
nodeHealth={nodeHealth}
|
||||
loadingNodeHealth={isLoadingNodeHealth}
|
||||
nodeReadiness={nodeReadiness}
|
||||
loadingNodeReadiness={isLoadingNodeReadiness}
|
||||
beeRelease={beeRelease}
|
||||
loadingBeeRelease={isLoadingBeeRelease}
|
||||
nodeAddresses={nodeAddresses}
|
||||
loadingNodeTopology={isLoadingNodeTopology}
|
||||
nodeTopology={nodeTopology}
|
||||
setStatusChecksVisible={setStatusChecksVisible}
|
||||
/>
|
||||
<EthereumAddressCard
|
||||
nodeAddresses={nodeAddresses}
|
||||
isLoadingNodeAddresses={isLoadingNodeAddresses}
|
||||
chequebookAddress={chequebookAddress}
|
||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
( isLoadingNodeHealth || isLoadingHealth || isLoadingNodeReadiness || isLoadingChequebookAddress ||
|
||||
isLoadingNodeTopology || isLoadingBeeRelease || isLoadingNodeAddresses || isLoadingBeeRelease || isLoadingChequebookBalance
|
||||
)
|
||||
?
|
||||
<Container style={{textAlign:'center', padding:'50px'}}>
|
||||
<CircularProgress />
|
||||
</Container>
|
||||
:
|
||||
<NodeSetupWorkflow
|
||||
beeRelease={beeRelease}
|
||||
isLoadingBeeRelease={isLoadingBeeRelease}
|
||||
|
||||
nodeHealth={nodeHealth}
|
||||
isLoadingNodeHealth={isLoadingNodeHealth}
|
||||
|
||||
nodeReadiness={nodeReadiness}
|
||||
isLoadingNodeReadiness={isLoadingNodeReadiness}
|
||||
|
||||
nodeAddresses={nodeAddresses}
|
||||
isLoadingNodeAddresses={isLoadingNodeAddresses}
|
||||
|
||||
nodeTopology={nodeTopology}
|
||||
isLoadingNodeTopology={isLoadingNodeTopology}
|
||||
|
||||
nodeApiHealth={health}
|
||||
isLoadingHealth={isLoadingHealth}
|
||||
|
||||
chequebookAddress={chequebookAddress}
|
||||
isLoadingChequebookAddress={isLoadingChequebookAddress}
|
||||
|
||||
chequebookBalance={chequebookBalance}
|
||||
isLoadingChequebookBalance={isLoadingChequebookBalance}
|
||||
|
||||
apiHost={apiHost}
|
||||
debugApiHost={debugApiHost}
|
||||
setStatusChecksVisible={setStatusChecksVisible}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
|
||||
const AppRoute = ( {component: Component, layout: Layout, ...rest }) => (
|
||||
<Route {...rest} render={props => (
|
||||
<Layout {...props}>
|
||||
<Component {...props} />
|
||||
</Layout>
|
||||
)} />
|
||||
)
|
||||
|
||||
export default AppRoute;
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Switch } from 'react-router-dom';
|
||||
|
||||
import AppRoute from './AppRoute';
|
||||
|
||||
// layouts
|
||||
import Dashboard from '../layout/Dashboard';
|
||||
|
||||
// pages
|
||||
import Status from '../pages/status/index';
|
||||
import Files from '../pages/files/index';
|
||||
import Peers from '../pages/peers/index';
|
||||
import Accounting from '../pages/accounting/index';
|
||||
import Settings from '../pages/settings/index';
|
||||
|
||||
|
||||
const BaseRouter = (props: any) => (
|
||||
<Switch>
|
||||
<AppRoute exact path='/' layout={ Dashboard } component={Status}/>
|
||||
<AppRoute exact path='/files/' layout={ Dashboard } component={Files}/>
|
||||
<AppRoute exact path='/peers/' layout={ Dashboard } component={Peers}/>
|
||||
<AppRoute exact path='/accounting/' layout={ Dashboard } component={Accounting}/>
|
||||
<AppRoute exact path='/settings/' layout={ Dashboard } component={Settings}/>
|
||||
</Switch>
|
||||
);
|
||||
|
||||
export default BaseRouter;
|
||||
@@ -0,0 +1,143 @@
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import { Bee } from "@ethersphere/bee-js";
|
||||
|
||||
const beeJSClient = () => {
|
||||
let apiHost
|
||||
|
||||
if (sessionStorage.getItem('api_host')) {
|
||||
apiHost = String(sessionStorage.getItem('api_host') || '')
|
||||
} else {
|
||||
apiHost = process.env.REACT_APP_BEE_HOST
|
||||
}
|
||||
|
||||
return new Bee(`${apiHost}`)
|
||||
}
|
||||
|
||||
const beeApiClient = (): AxiosInstance => {
|
||||
let apiHost
|
||||
|
||||
if (sessionStorage.getItem('api_host')) {
|
||||
apiHost = String(sessionStorage.getItem('api_host') || '')
|
||||
} else {
|
||||
apiHost = process.env.REACT_APP_BEE_HOST
|
||||
}
|
||||
|
||||
return axios.create({
|
||||
baseURL: apiHost
|
||||
})
|
||||
}
|
||||
|
||||
const beeDebugApiClient = (): AxiosInstance => {
|
||||
let debugApiHost
|
||||
|
||||
if (sessionStorage.getItem('debug_api_host')) {
|
||||
debugApiHost = String(sessionStorage.getItem('debug_api_host') || '')
|
||||
} else {
|
||||
debugApiHost = process.env.REACT_APP_BEE_DEBUG_HOST
|
||||
}
|
||||
|
||||
return axios.create({
|
||||
baseURL: debugApiHost
|
||||
})
|
||||
}
|
||||
|
||||
export const beeApi = {
|
||||
status: {
|
||||
health() {
|
||||
return beeApiClient().get('/')
|
||||
}
|
||||
},
|
||||
files: {
|
||||
uploadFile(file: any) {
|
||||
return beeJSClient().uploadFile(file)
|
||||
},
|
||||
uploadData(file: any) {
|
||||
return beeJSClient().uploadData(file)
|
||||
},
|
||||
downloadFile(hash: string) {
|
||||
return beeJSClient().downloadFile(hash)
|
||||
},
|
||||
downloadData(hash: string) {
|
||||
return beeJSClient().downloadData(hash)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const beeDebugApi = {
|
||||
status: {
|
||||
nodeHealth() {
|
||||
return beeDebugApiClient().get(`/health`)
|
||||
},
|
||||
nodeReadiness() {
|
||||
return beeDebugApiClient().get(`/readiness`)
|
||||
},
|
||||
},
|
||||
connectivity: {
|
||||
addresses() {
|
||||
return beeDebugApiClient().get(`/addresses`)
|
||||
},
|
||||
listPeers() {
|
||||
return beeDebugApiClient().get(`/peers`)
|
||||
},
|
||||
blockListedPeers() {
|
||||
return beeDebugApiClient().get(`/blocklist`)
|
||||
},
|
||||
removePeer(peerId: string) {
|
||||
return beeDebugApiClient().delete(`/peers/${peerId}`)
|
||||
},
|
||||
topology() {
|
||||
return beeDebugApiClient().get(`/topology`)
|
||||
},
|
||||
ping(peerId: string) {
|
||||
return beeDebugApiClient().post(`/pingpong/${peerId}`)
|
||||
}
|
||||
},
|
||||
balance: {
|
||||
balances() {
|
||||
return beeDebugApiClient().get(`/balances`)
|
||||
},
|
||||
peerBalance(peerId: string) {
|
||||
return beeDebugApiClient().get(`/balances/${peerId}`)
|
||||
},
|
||||
consumed() {
|
||||
return beeDebugApiClient().get(`/consumed`)
|
||||
},
|
||||
peerConsumed(peerId: string) {
|
||||
return beeDebugApiClient().get(`/consumed/${peerId}`)
|
||||
}
|
||||
},
|
||||
chequebook: {
|
||||
address() {
|
||||
return beeDebugApiClient().get(`/chequebook/address`)
|
||||
},
|
||||
balance() {
|
||||
return beeDebugApiClient().get(`/chequebook/balance`)
|
||||
},
|
||||
getLastCheques() {
|
||||
return beeDebugApiClient().get(`/chequebook/cheque`)
|
||||
},
|
||||
peerCashout(peerId: string) {
|
||||
return beeDebugApiClient().post(`/chequebook/cashout/${peerId}`)
|
||||
},
|
||||
getPeerLastCashout(peerId: string) {
|
||||
return beeDebugApiClient().get(`/chequebook/cashout/${peerId}`)
|
||||
},
|
||||
getPeerLastCheques(peerId: string) {
|
||||
return beeDebugApiClient().get(`/chequebook/cheque/${peerId}`)
|
||||
},
|
||||
withdraw(amount: bigint) {
|
||||
return beeDebugApiClient().post(`/chequebook/withdraw?amount=${amount}`)
|
||||
},
|
||||
deposit(amount: bigint) {
|
||||
return beeDebugApiClient().post(`/chequebook/deposit?amount=${amount}`)
|
||||
},
|
||||
},
|
||||
settlements: {
|
||||
getSettlements() {
|
||||
return beeDebugApiClient().get(`/settlements`)
|
||||
},
|
||||
peerSettlement(peerId: string) {
|
||||
return beeDebugApiClient().get(`/settlements/${peerId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export const ConvertBalanceToBZZ = (amount: number) => {
|
||||
return amount / (10 ** 16)
|
||||
}
|
||||
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 75 KiB |