feat: bee provider caching the state of the app and refreshing periodically (#172)

* feat: bee provider caching the state of the app and refreshing periodically

* chore: added error handling
This commit is contained in:
Vojtech Simetka
2021-08-18 11:10:12 +02:00
committed by GitHub
parent dcec6e0188
commit 2624cf04c9
25 changed files with 469 additions and 733 deletions
+15 -12
View File
@@ -10,6 +10,7 @@ import BaseRouter from './routes/routes'
import { lightTheme, darkTheme } from './theme'
import { Provider as StampsProvider } from './providers/Stamps'
import { Provider as PlatformProvider } from './providers/Platform'
import { Provider as BeeProvider } from './providers/Bee'
const App = (): ReactElement => {
const [themeMode, toggleThemeMode] = useState('light')
@@ -36,18 +37,20 @@ const App = (): ReactElement => {
return (
<div className="App">
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
<StampsProvider>
<PlatformProvider>
<SnackbarProvider>
<>
<CssBaseline />
<Router>
<BaseRouter />
</Router>
</>
</SnackbarProvider>
</PlatformProvider>
</StampsProvider>
<BeeProvider>
<StampsProvider>
<PlatformProvider>
<SnackbarProvider>
<>
<CssBaseline />
<Router>
<BaseRouter />
</Router>
</>
</SnackbarProvider>
</PlatformProvider>
</StampsProvider>
</BeeProvider>
</ThemeProvider>
</div>
)
+8 -8
View File
@@ -1,10 +1,10 @@
import { ReactElement, useState } from 'react'
import { ReactElement, useState, useContext } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Alert, AlertTitle } from '@material-ui/lab'
import Collapse from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton'
import CloseIcon from '@material-ui/icons/Close'
import { useStatusNodeVersion } from '../hooks/status'
import { Context } from '../providers/Bee'
import { SUPPORTED_BEE_VERSION_EXACT } from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
@@ -18,12 +18,12 @@ const useStyles = makeStyles((theme: Theme) =>
export default function VersionAlert(): ReactElement | null {
const classes = useStyles()
const { isLoading, userVersion } = useStatusNodeVersion()
const { isLoading, latestUserVersionExact } = useContext(Context)
const [open, setOpen] = useState<boolean>(true)
const isExactlySupportedBeeVersion = SUPPORTED_BEE_VERSION_EXACT === userVersion
const isExactlySupportedBeeVersion = SUPPORTED_BEE_VERSION_EXACT === latestUserVersionExact
if (isLoading || !userVersion) return null
if (isLoading || !latestUserVersionExact) return null
return (
<Collapse in={!isExactlySupportedBeeVersion && open}>
@@ -44,9 +44,9 @@ export default function VersionAlert(): ReactElement | null {
}
>
<AlertTitle>Warning</AlertTitle>
Your Bee node version (<code>{userVersion}</code>) does not exactly match the Bee version we tested the Bee
Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality may not
work properly.
Your Bee node version (<code>{latestUserVersionExact}</code>) does not exactly match the Bee version we tested
the Bee Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality
may not work properly.
</Alert>
</div>
</Collapse>
+17 -33
View File
@@ -4,7 +4,6 @@ import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography } from '@material-ui/core/'
import EthereumAddress from '../components/EthereumAddress'
import { Skeleton } from '@material-ui/lab'
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
@@ -28,9 +27,7 @@ const useStyles = makeStyles(() =>
interface Props {
nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
chequebookAddress: ChequebookAddressResponse | null
isLoadingChequebookAddress: boolean
}
function EthereumAddressCard(props: Props): ReactElement {
@@ -38,36 +35,23 @@ function EthereumAddressCard(props: Props): ReactElement {
return (
<Card className={classes.root}>
{props.isLoadingNodeAddresses ? (
<div style={{ padding: '16px' }}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
) : (
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Ethereum Address
</Typography>
<EthereumAddress address={props.nodeAddresses?.ethereum} />
</CardContent>
</div>
)}
{props.isLoadingChequebookAddress ? (
<div style={{ padding: '16px' }}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
) : (
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Chequebook Contract Address
</Typography>
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
</CardContent>
</div>
)}
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Ethereum Address
</Typography>
<EthereumAddress address={props.nodeAddresses?.ethereum} />
</CardContent>
</div>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Chequebook Contract Address
</Typography>
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
</CardContent>
</div>
</Card>
)
}
+6
View File
@@ -9,6 +9,8 @@ import { Activity, FileText, DollarSign, Share2, Settings, Layers } from 'react-
import SwarmLogoOrange from '../assets/swarm-logo-orange.svg'
import { Health } from '@ethersphere/bee-js'
import LastUpdate from './LastUpdate'
const drawerWidth = 240
const navBarItems = [
@@ -85,6 +87,7 @@ interface Props extends RouteComponentProps {
themeMode: string
health: boolean
nodeHealth: Health | null
lastUpdate: number | null
}
export default function SideBar(props: Props): ReactElement {
@@ -167,6 +170,9 @@ export default function SideBar(props: Props): ReactElement {
<span>Debug API</span>
</div>
</ListItem>
<div style={{ width: '100%', textAlign: 'center' }}>
<LastUpdate date={props.lastUpdate} />
</div>
</div>
</Drawer>
</div>
+4 -13
View File
@@ -5,9 +5,7 @@ import { pickThreshold, ThresholdValues } from '../utils/threshold'
import StatCard from './StatCard'
interface RootProps {
isLoading: boolean
topology: Topology | null
error: Error | null // FIXME: should display error
}
interface Props extends RootProps {
@@ -29,26 +27,25 @@ const TopologyStats = (props: RootProps): ReactElement => {
)
}
const Indicator = ({ isLoading, thresholds }: Props): ReactElement => {
const Indicator = ({ thresholds }: Props): ReactElement => {
const maximumTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.maximumScore, 0)
const actualTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.score, 0)
const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%'
return (
<div style={{ marginBottom: '20px' }}>
<StatCard label="Overall Health Indicator" statistic={percentageText} loading={isLoading} />
<StatCard label="Overall Health Indicator" statistic={percentageText} />
</div>
)
}
const Metrics = ({ isLoading, topology, thresholds }: Props): ReactElement => (
const Metrics = ({ topology, thresholds }: Props): ReactElement => (
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
<Grid container spacing={3}>
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard
label="Connected Peers"
statistic={topology?.connected.toString()}
loading={isLoading}
tooltip={thresholds.connectedPeers.explanation}
/>
</Grid>
@@ -56,17 +53,11 @@ const Metrics = ({ isLoading, topology, thresholds }: Props): ReactElement => (
<StatCard
label="Population"
statistic={topology?.population.toString()}
loading={isLoading}
tooltip={thresholds.population.explanation}
/>
</Grid>
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard
label="Depth"
statistic={topology?.depth.toString()}
loading={isLoading}
tooltip={thresholds.depth.explanation}
/>
<StatCard label="Depth" statistic={topology?.depth.toString()} tooltip={thresholds.depth.explanation} />
</Grid>
</Grid>
</Grid>
+1 -313
View File
@@ -1,229 +1,9 @@
import { useState, useEffect } from 'react'
import {
NodeAddresses,
ChequebookAddressResponse,
LastChequesResponse,
Health,
Peer,
Topology,
LastChequesForPeerResponse,
} from '@ethersphere/bee-js'
import { beeDebugApi, beeApi } from '../services/bee'
import { beeDebugApi } from '../services/bee'
import axios from 'axios'
import { Token } from '../models/Token'
export interface HealthHook {
health: boolean
isLoadingHealth: boolean
error: Error | null
}
export const useApiHealth = (): HealthHook => {
const [health, setHealth] = useState<boolean>(false)
const [isLoadingHealth, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeApi.status
.health()
.then(res => {
setHealth(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { health, isLoadingHealth, error }
}
export interface DebugHealthHook {
nodeHealth: Health | null
isLoadingNodeHealth: boolean
error: Error | null
}
export const useDebugApiHealth = (): DebugHealthHook => {
const [nodeHealth, setNodeHealth] = useState<Health | null>(null)
const [isLoadingNodeHealth, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.status
.nodeHealth()
.then(res => {
setNodeHealth(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { nodeHealth, isLoadingNodeHealth, error }
}
export interface NodeAddressesHook {
nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
error: Error | null
}
export const useApiNodeAddresses = (): NodeAddressesHook => {
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
const [isLoadingNodeAddresses, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity
.addresses()
.then(res => {
setNodeAddresses(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { nodeAddresses, isLoadingNodeAddresses, error }
}
export interface NodeTopologyHook {
topology: Topology | null
isLoading: boolean
error: Error | null
}
export const useApiNodeTopology = (): NodeTopologyHook => {
const [topology, setNodeTopology] = useState<Topology | null>(null)
const [isLoading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity
.topology()
.then(res => {
setNodeTopology(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { topology, isLoading, error }
}
export interface ChequebookAddressHook {
chequebookAddress: ChequebookAddressResponse | null
isLoadingChequebookAddress: boolean
error: Error | null
}
export const useApiChequebookAddress = (): ChequebookAddressHook => {
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [isLoadingChequebookAddress, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.address()
.then(res => {
setChequebookAddress(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { chequebookAddress, isLoadingChequebookAddress, error }
}
export interface NodePeersHook {
peers: Peer[] | null
isLoading: boolean
error: Error | null
}
export const useApiNodePeers = (): NodePeersHook => {
const [peers, setPeers] = useState<Peer[] | null>(null)
const [isLoading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity
.listPeers()
.then(res => {
setPeers(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peers, isLoading, error }
}
export interface ChequebookBalance {
totalBalance: Token
availableBalance: Token
}
export interface ChequebookBalanceHook {
chequebookBalance: ChequebookBalance | null
isLoadingChequebookBalance: boolean
error: Error | null
}
export const useApiChequebookBalance = (): ChequebookBalanceHook => {
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalance | null>(null)
const [isLoadingChequebookBalance, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.balance()
.then(({ totalBalance, availableBalance }) => {
const balance = {
totalBalance: new Token(totalBalance),
availableBalance: new Token(availableBalance),
}
setChequebookBalance(balance)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { chequebookBalance, isLoadingChequebookBalance, error }
}
export interface Balance {
peer: string
balance: Token
@@ -260,64 +40,6 @@ export const useApiPeerBalances = (): PeerBalanceHook => {
return { peerBalances, isLoadingPeerBalances, error }
}
export interface PeerChequesHook {
peerCheques: LastChequesResponse | null
isLoadingPeerCheques: boolean
error: Error | null
}
export const useApiPeerCheques = (): PeerChequesHook => {
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [isLoadingPeerCheques, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.getLastCheques()
.then(res => {
setPeerCheques(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peerCheques, isLoadingPeerCheques, error }
}
export interface PeerLastChequesHook {
peerCheque: LastChequesForPeerResponse | null
isLoadingPeerCheque: boolean
error: Error | null
}
export const useApiPeerLastCheque = (peerId: string): PeerLastChequesHook => {
const [peerCheque, setPeerCheque] = useState<LastChequesForPeerResponse | null>(null)
const [isLoadingPeerCheque, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.getPeerLastCheques(peerId)
.then(res => {
setPeerCheque(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [peerId])
return { peerCheque, isLoadingPeerCheque, error }
}
export interface Settlement {
peer: string
received: Token
@@ -368,40 +90,6 @@ export const useApiSettlements = (): SettlementsHook => {
return { settlements, isLoadingSettlements, error }
}
export interface LastCashout {
peer: string
uncashedAmount: Token
}
export interface PeerLastCashoutHook {
peerCashout: LastCashout | null
isLoadingPeerCashout: boolean
error: Error | null
}
export const useApiPeerLastCashout = (peerId: string): PeerLastCashoutHook => {
const [peerCashout, setPeerCashout] = useState<LastCashout | null>(null)
const [isLoadingPeerCashout, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.getPeerLastCashout(peerId)
.then(({ peer, uncashedAmount }) => {
setPeerCashout({ peer, uncashedAmount: new Token(uncashedAmount.toString()) })
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [peerId])
return { peerCashout, isLoadingPeerCashout, error }
}
export interface LatestBeeReleaseHook {
latestBeeRelease: LatestBeeRelease | null
isLoadingLatestBeeRelease: boolean
-101
View File
@@ -1,101 +0,0 @@
import { ChequebookAddressResponse } from '@ethersphere/bee-js'
import {
ChequebookBalance,
useApiChequebookAddress,
useApiChequebookBalance,
useApiHealth,
useApiNodeAddresses,
useApiNodeTopology,
useDebugApiHealth,
useLatestBeeRelease,
} from './apiHooks'
import semver from 'semver'
import { engines } from '../../package.json'
export interface StatusChequebookHook extends StatusHookCommon {
chequebookBalance: ChequebookBalance | null
chequebookAddress: ChequebookAddressResponse | null
}
export const useStatusNodeVersion = (): StatusNodeVersionHook => {
const { latestBeeRelease, isLoadingLatestBeeRelease } = useLatestBeeRelease()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const latestVersion = semver.coerce(latestBeeRelease?.name)?.version
const latestUserVersion = semver.coerce(nodeHealth?.version)?.version
const isLatestBeeVersion = Boolean(
latestVersion &&
latestUserVersion &&
semver.satisfies(latestVersion, latestUserVersion, {
includePrerelease: true,
}),
)
return {
isLoading: isLoadingNodeHealth || isLoadingLatestBeeRelease,
isOk: Boolean(
nodeHealth &&
semver.satisfies(nodeHealth.version, engines.bee, {
includePrerelease: true,
}),
),
userVersion: nodeHealth?.version,
latestVersion,
latestUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
isLatestBeeVersion,
}
}
export const useStatusEthereumConnection = (): StatusEthereumConnectionHook => {
const { isLoadingNodeAddresses, nodeAddresses } = useApiNodeAddresses()
return {
isLoading: isLoadingNodeAddresses,
isOk: Boolean(nodeAddresses?.ethereum),
nodeAddresses,
}
}
export const useStatusDebugConnection = (): StatusHookCommon => {
const { isLoadingNodeHealth, nodeHealth } = useDebugApiHealth()
return {
isLoading: isLoadingNodeHealth,
isOk: Boolean(nodeHealth?.status === 'ok'),
}
}
export const useStatusConnection = (): StatusHookCommon => {
const { isLoadingHealth, health } = useApiHealth()
return {
isLoading: isLoadingHealth,
isOk: health,
}
}
export const useStatusTopology = (): StatusTopologyHook => {
const { topology, isLoading } = useApiNodeTopology()
return {
isLoading,
isOk: Boolean(topology?.connected && topology?.connected > 0),
topology,
}
}
export const useStatusChequebook = (): StatusChequebookHook => {
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
return {
isLoading: isLoadingChequebookAddress || isLoadingChequebookBalance,
isOk:
Boolean(chequebookAddress?.chequebookAddress) &&
chequebookBalance !== null &&
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0),
chequebookBalance,
chequebookAddress,
}
}
+19 -6
View File
@@ -1,13 +1,15 @@
import { useState, useEffect, ReactElement } from 'react'
import { useState, useEffect, useContext, ReactElement } from 'react'
import ErrorBoundary from '../components/ErrorBoundary'
import AlertVersion from '../components/AlertVersion'
import { Container, CircularProgress } from '@material-ui/core'
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
import SideBar from '../components/SideBar'
import NavBar from '../components/NavBar'
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks'
import { Context } from '../providers/Bee'
import { RouteComponentProps } from 'react-router'
const useStyles = makeStyles((theme: Theme) =>
@@ -32,8 +34,7 @@ const Dashboard = (props: Props): ReactElement => {
const [themeMode, toggleThemeMode] = useState('light')
// FIXME: handle errrors and loading
const { health } = useApiHealth()
const { nodeHealth } = useDebugApiHealth()
const { isLoading, lastUpdate, apiHealth, debugApiHealth } = useContext(Context)
useEffect(() => {
const theme = localStorage.getItem('theme')
@@ -56,12 +57,24 @@ const Dashboard = (props: Props): ReactElement => {
return (
<div>
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
<SideBar
{...props}
themeMode={themeMode}
health={apiHealth}
nodeHealth={debugApiHealth}
lastUpdate={lastUpdate}
/>
<NavBar themeMode={themeMode} />
<ErrorBoundary>
<main className={classes.content}>
<AlertVersion />
{props.children}
{isLoading ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
props.children
)}
</main>
</ErrorBoundary>
</div>
+24 -35
View File
@@ -2,7 +2,6 @@ import { ReactElement } from 'react'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography, Theme } from '@material-ui/core/'
import { Skeleton } from '@material-ui/lab'
import WithdrawModal from '../../containers/WithdrawModal'
import DepositModal from '../../containers/DepositModal'
@@ -45,12 +44,11 @@ interface ChequebookBalance {
interface Props {
chequebookAddress: ChequebookAddressResponse | null
chequebookBalance: ChequebookBalance | null
totalsent: Token
totalreceived: Token
isLoading: boolean
totalsent?: Token
totalreceived?: Token
}
function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }: Props): ReactElement {
function AccountCard({ totalreceived, totalsent, chequebookBalance }: Props): ReactElement {
const classes = useStyles()
return (
@@ -66,37 +64,28 @@ function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }:
</div>
<Card className={classes.root}>
{!isLoading && (
<CardContent className={classes.gridContainer}>
<div>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Balance
</Typography>
<Typography variant="h5">{chequebookBalance?.totalBalance.toFixedDecimal()} BZZ</Typography>
</div>
<div>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Available Uncommitted Balance
</Typography>
<Typography variant="h5">{chequebookBalance?.availableBalance.toFixedDecimal()} BZZ</Typography>
</div>
<div>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Sent / Received
</Typography>
<Typography variant="h5">
{totalsent.toFixedDecimal()} / {totalreceived.toFixedDecimal()} BZZ
</Typography>
</div>
</CardContent>
)}
{isLoading && (
<div className={classes.gridContainer}>
<Skeleton width={180} height={110} animation="wave" />
<Skeleton width={180} height={110} animation="wave" />
<Skeleton width={180} height={110} animation="wave" />
<CardContent className={classes.gridContainer}>
<div>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Balance
</Typography>
<Typography variant="h5">{chequebookBalance?.totalBalance.toFixedDecimal()} BZZ</Typography>
</div>
)}
<div>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Available Uncommitted Balance
</Typography>
<Typography variant="h5">{chequebookBalance?.availableBalance.toFixedDecimal()} BZZ</Typography>
</div>
<div>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Sent / Received
</Typography>
<Typography variant="h5">
{totalsent?.toFixedDecimal()} / {totalreceived?.toFixedDecimal()} BZZ
</Typography>
</div>
</CardContent>
</Card>
</div>
)
+9 -33
View File
@@ -1,19 +1,12 @@
import type { ReactElement } from 'react'
import { ReactElement, useContext } from 'react'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
import { Container, CircularProgress } from '@material-ui/core'
import { Container } from '@material-ui/core'
import AccountCard from '../accounting/AccountCard'
import BalancesTable from './BalancesTable'
import EthereumAddressCard from '../../components/EthereumAddressCard'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import {
useApiNodeAddresses,
useApiChequebookAddress,
useApiChequebookBalance,
useApiHealth,
useDebugApiHealth,
} from '../../hooks/apiHooks'
import { Context } from '../../providers/Bee'
import { useAccounting } from '../../hooks/accounting'
const useStyles = makeStyles((theme: Theme) =>
@@ -29,38 +22,21 @@ const useStyles = makeStyles((theme: Theme) =>
export default function Accounting(): ReactElement {
const classes = useStyles()
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { isLoading, totalsent, totalreceived, accounting, isLoadingUncashed, error } = useAccounting()
const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements } = useContext(Context)
if (isLoadingHealth || isLoadingNodeHealth) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
const { accounting, isLoadingUncashed, error } = useAccounting()
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
if (!status.all) return <TroubleshootConnectionCard />
return (
<div className={classes.root}>
<AccountCard
chequebookAddress={chequebookAddress}
isLoading={isLoadingChequebookAddress || isLoading || isLoadingChequebookBalance}
chequebookBalance={chequebookBalance}
totalsent={totalsent}
totalreceived={totalreceived}
/>
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
totalsent={settlements?.totalSent}
totalreceived={settlements?.totalReceived}
/>
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
{error && (
<Container style={{ textAlign: 'center', padding: '50px' }}>
Error loading accounting details: {error.message}
+5 -14
View File
@@ -1,26 +1,17 @@
import { ReactElement } from 'react'
import { ReactElement, useContext } from 'react'
import { Container, CircularProgress } from '@material-ui/core'
import { Container } from '@material-ui/core'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
import { Context } from '../../providers/Bee'
import Download from './Download'
import Upload from './Upload'
import TabsContainer from '../../components/TabsContainer'
export default function Files(): ReactElement {
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { status } = useContext(Context)
if (isLoadingHealth || isLoadingNodeHealth) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (!health || nodeHealth?.status !== 'ok') return <TroubleshootConnectionCard />
if (!status.all) return <TroubleshootConnectionCard />
return (
<Container maxWidth="sm">
+1 -20
View File
@@ -10,7 +10,6 @@ import {
Button,
Paper,
Tooltip,
Container,
CircularProgress,
} from '@material-ui/core'
import { Autorenew } from '@material-ui/icons'
@@ -26,8 +25,6 @@ const useStyles = makeStyles({
interface Props {
peers: Peer[] | null
isLoading: boolean
error: Error | null
}
function PeerTable(props: Props): ReactElement {
@@ -47,22 +44,6 @@ function PeerTable(props: Props): ReactElement {
})
}
if (props.isLoading) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (props.error || props.peers === null) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<p>Failed to load peers</p>
</Container>
)
}
return (
<div>
<TableContainer component={Paper}>
@@ -75,7 +56,7 @@ function PeerTable(props: Props): ReactElement {
</TableRow>
</TableHead>
<TableBody>
{props.peers.map((peer: Peer, idx: number) => (
{props.peers?.map((peer: Peer, idx: number) => (
<TableRow key={peer.address}>
<TableCell component="th" scope="row">
{idx + 1}
+6 -17
View File
@@ -1,32 +1,21 @@
import { Container, CircularProgress } from '@material-ui/core/'
import PeerTable from './PeerTable'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { useApiNodeTopology, useApiNodePeers, useDebugApiHealth } from '../../hooks/apiHooks'
import { Context } from '../../providers/Bee'
import TopologyStats from '../../components/TopologyStats'
import { ReactElement } from 'react'
import { ReactElement, useContext } from 'react'
export default function Peers(): ReactElement {
const topology = useApiNodeTopology()
const debugHealth = useDebugApiHealth()
const peers = useApiNodePeers()
const { topology, peers, status } = useContext(Context)
if (debugHealth.isLoadingNodeHealth) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (debugHealth.error) {
if (!status.all) {
return <TroubleshootConnectionCard />
}
return (
<>
<TopologyStats {...topology} />
<PeerTable {...peers} />
<TopologyStats topology={topology} />
<PeerTable peers={peers} />
</>
)
}
+4 -5
View File
@@ -7,8 +7,8 @@ import TroubleshootConnectionCard from '../../components/TroubleshootConnectionC
import CreatePostageStampModal from './CreatePostageStampModal'
import LastUpdate from '../../components/LastUpdate'
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
import { Context } from '../../providers/Stamps'
import { Context as BeeContext } from '../../providers/Bee'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -32,8 +32,7 @@ const useStyles = makeStyles((theme: Theme) =>
export default function Accounting(): ReactElement {
const classes = useStyles()
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const beeContext = useContext(BeeContext)
const { stamps, isLoading, error, lastUpdate, start, stop } = useContext(Context)
useEffect(() => {
start()
@@ -41,7 +40,7 @@ export default function Accounting(): ReactElement {
return () => stop()
}, [])
if (isLoadingHealth || isLoadingNodeHealth) {
if (beeContext.isLoading) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
@@ -49,7 +48,7 @@ export default function Accounting(): ReactElement {
)
}
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard />
if (!beeContext.status.all) return <TroubleshootConnectionCard />
return (
<div className={classes.root}>
+40 -42
View File
@@ -1,4 +1,4 @@
import { ReactElement, useEffect, useState } from 'react'
import { ReactElement, useEffect, useState, useContext } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
@@ -9,7 +9,7 @@ import VersionCheck from './SetupSteps/VersionCheck'
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
import PeerConnection from './SetupSteps/PeerConnection'
import { StatusChequebookHook } from '../../hooks/status'
import { Context } from '../../providers/Bee'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -30,66 +30,65 @@ const useStyles = makeStyles((theme: Theme) =>
interface Step {
label: string
isOk: boolean
isLoading: boolean
component: ReactElement
}
interface Props {
nodeVersion: StatusNodeVersionHook
ethereumConnection: StatusEthereumConnectionHook
debugApiConnection: StatusHookCommon
apiConnection: StatusHookCommon
topology: StatusTopologyHook
chequebook: StatusChequebookHook
}
export default function NodeSetupWorkflow({
nodeVersion,
ethereumConnection,
debugApiConnection,
apiConnection,
topology,
chequebook,
}: Props): ReactElement {
export default function NodeSetupWorkflow(): ReactElement {
const classes = useStyles()
const [activeStep, setActiveStep] = useState(-1)
const {
status,
isLoading,
latestUserVersion,
latestPublishedVersion,
isLatestBeeVersion,
latestBeeVersionUrl,
topology,
nodeAddresses,
chequebookAddress,
} = useContext(Context)
const steps: Step[] = [
{
label: 'Connected to Node DebugAPI',
isOk: debugApiConnection.isOk,
isLoading: debugApiConnection.isLoading,
component: <DebugConnectionCheck {...debugApiConnection} />,
isOk: status.debugApiConnection,
component: <DebugConnectionCheck isOk={status.debugApiConnection} />,
},
{
label: 'Running latest Bee version',
isOk: nodeVersion.isOk,
isLoading: nodeVersion.isLoading,
component: <VersionCheck {...nodeVersion} />,
isOk: status.version,
component: (
<VersionCheck
isOk={status.version}
isLatestBeeVersion={isLatestBeeVersion}
userVersion={latestUserVersion}
latestVersion={latestPublishedVersion}
latestUrl={latestBeeVersionUrl}
/>
),
},
{
label: 'Connected to Ethereum Blockchain',
isOk: ethereumConnection.isOk,
isLoading: ethereumConnection.isLoading,
component: <EthereumConnectionCheck {...ethereumConnection} />,
label: 'Connected to xDai Blockchain',
isOk: status.blockchainConnection,
component: <EthereumConnectionCheck isOk={status.blockchainConnection} nodeAddresses={nodeAddresses} />,
},
{
label: 'Deployed and Funded Chequebook',
isOk: chequebook.isOk,
isLoading: chequebook.isLoading,
component: <ChequebookDeployFund ethereumAddress={ethereumConnection.nodeAddresses?.ethereum} {...chequebook} />,
isOk: status.chequebook,
component: (
<ChequebookDeployFund chequebookAddress={chequebookAddress?.chequebookAddress} isOk={status.chequebook} />
),
},
{
label: 'Connected to Node API',
isOk: apiConnection.isOk,
isLoading: apiConnection.isLoading,
component: <NodeConnectionCheck {...apiConnection} />,
isOk: status.apiConnection,
component: <NodeConnectionCheck isOk={status.apiConnection} />,
},
{
label: 'Connected to Peers',
isOk: topology.isOk,
isLoading: topology.isLoading,
component: <PeerConnection {...topology} />,
isOk: status.topology,
component: <PeerConnection isOk={status.topology} topology={topology} />,
},
]
@@ -98,7 +97,7 @@ export default function NodeSetupWorkflow({
if (activeStep >= 0 && activeStep < steps.length) return
// If any step is not fully loaded yet return
if (!steps.every(step => !step.isLoading)) return
if (!isLoading) return
// Select first step that is not OK
// This is deliberately a for loop (and not forEach) so that we can terminate the useEffect from within the cycle
@@ -131,10 +130,9 @@ export default function NodeSetupWorkflow({
</span>
</Typography>
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
{steps.map(({ label, isOk, component, isLoading }, index) => (
{steps.map(({ label, isOk, component }, index) => (
<Step key={label}>
<StepLabel
disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
StepIconComponent={() => {
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
@@ -2,22 +2,17 @@ import { Typography } from '@material-ui/core/'
import EthereumAddress from '../../../components/EthereumAddress'
import DepositModal from '../../../containers/DepositModal'
import type { ReactElement } from 'react'
import type { StatusChequebookHook } from '../../../hooks/status'
interface Props extends StatusChequebookHook {
ethereumAddress?: string
interface Props extends StatusHookCommon {
chequebookAddress?: string
}
const ChequebookDeployFund = ({ isLoading, chequebookAddress, chequebookBalance }: Props): ReactElement | null => {
if (isLoading) return null
const ChequebookDeployFund = ({ chequebookAddress, isOk }: Props): ReactElement | null => {
return (
<div>
<p style={{ marginBottom: '20px', display: 'flex' }}>
{chequebookAddress?.chequebookAddress && <DepositModal />}
</p>
<p style={{ marginBottom: '20px', display: 'flex' }}>{chequebookAddress && <DepositModal />}</p>
<div style={{ marginBottom: '10px' }}>
{!(chequebookAddress?.chequebookAddress && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) && (
{!isOk && (
<div>
<span>
Your chequebook is either not deployed or funded. To run the node you will need xDAI and xBZZ on the xDai
@@ -33,7 +28,7 @@ const ChequebookDeployFund = ({ isLoading, chequebookAddress, chequebookBalance
<Typography variant="subtitle1" gutterBottom>
Chequebook Address
</Typography>
<EthereumAddress address={chequebookAddress?.chequebookAddress} />
<EthereumAddress address={chequebookAddress} />
</div>
)
}
@@ -9,9 +9,7 @@ import { debugApiHost } from '../../../constants'
type Props = StatusHookCommon
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
if (isLoading) return null
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
const changeDebugApiUrl = (
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
<span style={{ marginRight: '15px' }}>
@@ -4,9 +4,7 @@ import EthereumAddress from '../../../components/EthereumAddress'
type Props = StatusEthereumConnectionHook
export default function EthereumConnectionCheck({ isLoading, isOk, nodeAddresses }: Props): ReactElement | null {
if (isLoading) return null
export default function EthereumConnectionCheck({ isOk, nodeAddresses }: Props): ReactElement | null {
if (isOk) {
return (
<div>
@@ -8,9 +8,7 @@ import { apiHost } from '../../../constants'
type Props = StatusHookCommon
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
if (isLoading) return null
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
return (
<div>
<div style={{ display: 'flex', marginBottom: '25px' }}>
@@ -3,9 +3,7 @@ import { Typography } from '@material-ui/core/'
type Props = StatusTopologyHook
export default function PeerConnection({ isLoading, isOk, topology }: Props): ReactElement | null {
if (isLoading) return null
export default function PeerConnection({ isOk, topology }: Props): ReactElement | null {
const peers = (
<div style={{ display: 'flex', marginTop: '15px' }}>
<div style={{ marginRight: '30px' }}>
+1 -9
View File
@@ -4,15 +4,7 @@ import CodeBlockTabs from '../../../components/CodeBlockTabs'
type Props = StatusNodeVersionHook
export default function VersionCheck({
isLoading,
isOk,
userVersion,
latestVersion,
latestUrl,
}: Props): ReactElement | null {
if (isLoading) return null
export default function VersionCheck({ isOk, userVersion, latestVersion, latestUrl }: Props): ReactElement | null {
const version = (
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}>
+20 -48
View File
@@ -1,18 +1,10 @@
import { ReactElement } from 'react'
import { Container, CircularProgress } from '@material-ui/core'
import { ReactElement, useContext } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import NodeSetupWorkflow from './NodeSetupWorkflow'
import StatusCard from './StatusCard'
import EthereumAddressCard from '../../components/EthereumAddressCard'
import {
useStatusEthereumConnection,
useStatusNodeVersion,
useStatusDebugConnection,
useStatusConnection,
useStatusTopology,
useStatusChequebook,
} from '../../hooks/status'
import { Context } from '../../providers/Bee'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -27,50 +19,30 @@ const useStyles = makeStyles((theme: Theme) =>
export default function Status(): ReactElement {
const classes = useStyles()
const nodeVersion = useStatusNodeVersion()
const ethereumConnection = useStatusEthereumConnection()
const debugApiConnection = useStatusDebugConnection()
const apiConnection = useStatusConnection()
const topology = useStatusTopology()
const chequebook = useStatusChequebook()
const checks = [nodeVersion, ethereumConnection, debugApiConnection, apiConnection, topology, chequebook]
// If any check data are still loading
if (!checks.every(c => !c.isLoading)) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
const {
status,
latestUserVersion,
isLatestBeeVersion,
latestBeeVersionUrl,
topology,
nodeAddresses,
chequebookAddress,
} = useContext(Context)
return (
<div className={classes.root}>
<StatusCard
userBeeVersion={nodeVersion.userVersion}
isLatestBeeVersion={nodeVersion.isLatestBeeVersion}
isOk={checks.every(c => c.isOk)}
nodeTopology={topology.topology}
latestUrl={nodeVersion.latestUrl}
nodeAddresses={ethereumConnection.nodeAddresses}
userBeeVersion={latestUserVersion}
isLatestBeeVersion={isLatestBeeVersion}
isOk={status.all}
nodeTopology={topology}
latestUrl={latestBeeVersionUrl}
nodeAddresses={nodeAddresses}
/>
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
<EthereumAddressCard
nodeAddresses={ethereumConnection.nodeAddresses}
isLoadingNodeAddresses={ethereumConnection.isLoading}
chequebookAddress={chequebook.chequebookAddress}
isLoadingChequebookAddress={chequebook.isLoading}
/>
{nodeAddresses && chequebookAddress && (
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
)}
<NodeSetupWorkflow
nodeVersion={nodeVersion}
ethereumConnection={ethereumConnection}
debugApiConnection={debugApiConnection}
apiConnection={apiConnection}
topology={topology}
chequebook={chequebook}
/>
<NodeSetupWorkflow />
</div>
)
}
+256
View File
@@ -0,0 +1,256 @@
import type { ChequebookBalance, Balance, Settlements } from '../types'
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
import { beeApi, beeDebugApi } from '../services/bee'
import { Token } from '../models/Token'
import semver from 'semver'
import { engines } from '../../package.json'
import type {
NodeAddresses,
ChequebookAddressResponse,
LastChequesResponse,
Health,
Peer,
Topology,
} from '@ethersphere/bee-js'
import { useLatestBeeRelease } from '../hooks/apiHooks'
interface Status {
all: boolean
version: boolean
blockchainConnection: boolean
debugApiConnection: boolean
apiConnection: boolean
topology: boolean
chequebook: boolean
}
interface ContextInterface {
status: Status
latestPublishedVersion?: string
latestUserVersion?: string
latestUserVersionExact?: string
isLatestBeeVersion: boolean
latestBeeVersionUrl: string
error: Error | null
apiHealth: boolean
debugApiHealth: Health | null
nodeAddresses: NodeAddresses | null
topology: Topology | null
chequebookAddress: ChequebookAddressResponse | null
peers: Peer[] | null
chequebookBalance: ChequebookBalance | null
peerBalances: Balance[] | null
peerCheques: LastChequesResponse | null
settlements: Settlements | null
latestBeeRelease: LatestBeeRelease | null
isLoading: boolean
isRefreshing: boolean
lastUpdate: number | null
start: (frequency?: number) => void
stop: () => void
refresh: () => Promise<void>
}
const initialValues: ContextInterface = {
status: {
all: false,
version: false,
blockchainConnection: false,
debugApiConnection: false,
apiConnection: false,
topology: false,
chequebook: false,
},
latestPublishedVersion: undefined,
latestUserVersion: undefined,
latestUserVersionExact: undefined,
isLatestBeeVersion: false,
latestBeeVersionUrl: 'https://github.com/ethersphere/bee/releases/latest',
error: null,
apiHealth: false,
debugApiHealth: null,
nodeAddresses: null,
topology: null,
chequebookAddress: null,
peers: null,
chequebookBalance: null,
peerBalances: null,
peerCheques: null,
settlements: null,
latestBeeRelease: null,
isLoading: true,
isRefreshing: false,
lastUpdate: null,
start: () => {}, // eslint-disable-line
stop: () => {}, // eslint-disable-line
refresh: () => Promise.reject(),
}
export const Context = createContext<ContextInterface>(initialValues)
export const Consumer = Context.Consumer
interface Props {
children: ReactChild
}
function getStatus(
latestBeeRelease: LatestBeeRelease | null,
debugApiHealth: Health | null,
nodeAddresses: NodeAddresses | null,
apiHealth: boolean,
topology: Topology | null,
chequebookAddress: ChequebookAddressResponse | null,
chequebookBalance: ChequebookBalance | null,
error: Error | null,
): Status {
const status = {
version: Boolean(
debugApiHealth &&
semver.satisfies(debugApiHealth.version, engines.bee, {
includePrerelease: true,
}),
),
blockchainConnection: Boolean(nodeAddresses?.ethereum),
debugApiConnection: Boolean(debugApiHealth?.status === 'ok'),
apiConnection: apiHealth,
topology: Boolean(topology?.connected && topology?.connected > 0),
chequebook:
Boolean(chequebookAddress?.chequebookAddress) &&
chequebookBalance !== null &&
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0),
}
return { ...status, all: !error && Object.values(status).every(v => v) }
}
export function Provider({ children }: Props): ReactElement {
const [apiHealth, setApiHealth] = useState<boolean>(false)
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
const [topology, setNodeTopology] = useState<Topology | null>(null)
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [peers, setPeers] = useState<Peer[] | null>(null)
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalance | null>(null)
const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null)
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [settlements, setSettlements] = useState<Settlements | null>(null)
const { latestBeeRelease } = useLatestBeeRelease()
const [error, setError] = useState<Error | null>(initialValues.error)
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
const [isRefreshing, setIsRefreshing] = useState<boolean>(initialValues.isRefreshing)
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
const [frequency, setFrequency] = useState<number | null>(30000)
const latestPublishedVersion = semver.coerce(latestBeeRelease?.name)?.version
const latestUserVersion = semver.coerce(debugApiHealth?.version)?.version
const latestUserVersionExact = debugApiHealth?.version
const refresh = async () => {
// Don't want to refresh when already refreshing
if (isRefreshing) return
try {
setIsRefreshing(true)
setApiHealth(await beeApi.status.health())
setDebugApiHealth(await beeDebugApi.status.nodeHealth())
setNodeAddresses(await beeDebugApi.connectivity.addresses())
setNodeTopology(await beeDebugApi.connectivity.topology())
setChequebookAddress(await beeDebugApi.chequebook.address())
setPeers(await beeDebugApi.connectivity.listPeers())
const { totalBalance, availableBalance } = await beeDebugApi.chequebook.balance()
setChequebookBalance({
totalBalance: new Token(totalBalance),
availableBalance: new Token(availableBalance),
})
const { balances } = await beeDebugApi.balance.balances()
setPeerBalances(balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) })))
setPeerCheques(await beeDebugApi.chequebook.getLastCheques())
const { totalReceived, settlements, totalSent } = await beeDebugApi.settlements.getSettlements()
setSettlements({
totalReceived: new Token(totalReceived),
totalSent: new Token(totalSent),
settlements: settlements.map(({ peer, received, sent }) => ({
peer,
received: new Token(received),
sent: new Token(sent),
})),
})
setLastUpdate(Date.now())
} catch (e) {
setError(e)
} finally {
setIsLoading(false)
setIsRefreshing(false)
}
}
const start = (freq = 30000) => setFrequency(freq)
const stop = () => setFrequency(null)
// Start the update loop
useEffect(() => {
refresh()
// Start autorefresh only if the frequency is set
if (frequency) {
const interval = setInterval(refresh, frequency)
return () => clearInterval(interval)
}
}, [frequency])
return (
<Context.Provider
value={{
status: getStatus(
latestBeeRelease,
debugApiHealth,
nodeAddresses,
apiHealth,
topology,
chequebookAddress,
chequebookBalance,
error,
),
latestUserVersion,
latestUserVersionExact,
latestPublishedVersion,
isLatestBeeVersion: Boolean(
latestPublishedVersion &&
latestUserVersion &&
semver.satisfies(latestPublishedVersion, latestUserVersion, {
includePrerelease: true,
}),
),
latestBeeVersionUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
error,
apiHealth,
debugApiHealth,
nodeAddresses,
topology,
chequebookAddress,
peers,
chequebookBalance,
peerBalances,
peerCheques,
settlements,
latestBeeRelease,
isLoading,
isRefreshing,
lastUpdate,
start,
stop,
refresh,
}}
>
{children}
</Context.Provider>
)
}
-1
View File
@@ -6,7 +6,6 @@ interface LatestBeeRelease {
}
interface StatusHookCommon {
isLoading: boolean
isOk: boolean
}
+23
View File
@@ -0,0 +1,23 @@
import type { Token } from './models/Token'
export interface ChequebookBalance {
totalBalance: Token
availableBalance: Token
}
export interface Balance {
peer: string
balance: Token
}
export interface Settlement {
peer: string
received: Token
sent: Token
}
export interface Settlements {
totalReceived: Token
totalSent: Token
settlements: Settlement[]
}