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 { lightTheme, darkTheme } from './theme'
import { Provider as StampsProvider } from './providers/Stamps' import { Provider as StampsProvider } from './providers/Stamps'
import { Provider as PlatformProvider } from './providers/Platform' import { Provider as PlatformProvider } from './providers/Platform'
import { Provider as BeeProvider } from './providers/Bee'
const App = (): ReactElement => { const App = (): ReactElement => {
const [themeMode, toggleThemeMode] = useState('light') const [themeMode, toggleThemeMode] = useState('light')
@@ -36,18 +37,20 @@ const App = (): ReactElement => {
return ( return (
<div className="App"> <div className="App">
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}> <ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
<StampsProvider> <BeeProvider>
<PlatformProvider> <StampsProvider>
<SnackbarProvider> <PlatformProvider>
<> <SnackbarProvider>
<CssBaseline /> <>
<Router> <CssBaseline />
<BaseRouter /> <Router>
</Router> <BaseRouter />
</> </Router>
</SnackbarProvider> </>
</PlatformProvider> </SnackbarProvider>
</StampsProvider> </PlatformProvider>
</StampsProvider>
</BeeProvider>
</ThemeProvider> </ThemeProvider>
</div> </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 { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Alert, AlertTitle } from '@material-ui/lab' import { Alert, AlertTitle } from '@material-ui/lab'
import Collapse from '@material-ui/core/Collapse' import Collapse from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import CloseIcon from '@material-ui/icons/Close' 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' import { SUPPORTED_BEE_VERSION_EXACT } from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
@@ -18,12 +18,12 @@ const useStyles = makeStyles((theme: Theme) =>
export default function VersionAlert(): ReactElement | null { export default function VersionAlert(): ReactElement | null {
const classes = useStyles() const classes = useStyles()
const { isLoading, userVersion } = useStatusNodeVersion() const { isLoading, latestUserVersionExact } = useContext(Context)
const [open, setOpen] = useState<boolean>(true) 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 ( return (
<Collapse in={!isExactlySupportedBeeVersion && open}> <Collapse in={!isExactlySupportedBeeVersion && open}>
@@ -44,9 +44,9 @@ export default function VersionAlert(): ReactElement | null {
} }
> >
<AlertTitle>Warning</AlertTitle> <AlertTitle>Warning</AlertTitle>
Your Bee node version (<code>{userVersion}</code>) does not exactly match the Bee version we tested the Bee Your Bee node version (<code>{latestUserVersionExact}</code>) does not exactly match the Bee version we tested
Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality may not the Bee Dashboard against (<code>{SUPPORTED_BEE_VERSION_EXACT}</code>). Please note that some functionality
work properly. may not work properly.
</Alert> </Alert>
</div> </div>
</Collapse> </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 { Card, CardContent, Typography } from '@material-ui/core/'
import EthereumAddress from '../components/EthereumAddress' import EthereumAddress from '../components/EthereumAddress'
import { Skeleton } from '@material-ui/lab'
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js' import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
@@ -28,9 +27,7 @@ const useStyles = makeStyles(() =>
interface Props { interface Props {
nodeAddresses: NodeAddresses | null nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
chequebookAddress: ChequebookAddressResponse | null chequebookAddress: ChequebookAddressResponse | null
isLoadingChequebookAddress: boolean
} }
function EthereumAddressCard(props: Props): ReactElement { function EthereumAddressCard(props: Props): ReactElement {
@@ -38,36 +35,23 @@ function EthereumAddressCard(props: Props): ReactElement {
return ( return (
<Card className={classes.root}> <Card className={classes.root}>
{props.isLoadingNodeAddresses ? ( <div className={classes.details}>
<div style={{ padding: '16px' }}> <CardContent className={classes.content}>
<Skeleton width={300} height={30} animation="wave" /> <Typography variant="subtitle1" gutterBottom>
<Skeleton width={300} height={50} animation="wave" /> Ethereum Address
</div> </Typography>
) : ( <EthereumAddress address={props.nodeAddresses?.ethereum} />
<div className={classes.details}> </CardContent>
<CardContent className={classes.content}> </div>
<Typography variant="subtitle1" gutterBottom>
Ethereum Address <div className={classes.details}>
</Typography> <CardContent className={classes.content}>
<EthereumAddress address={props.nodeAddresses?.ethereum} /> <Typography variant="subtitle1" gutterBottom>
</CardContent> Chequebook Contract Address
</div> </Typography>
)} <EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
{props.isLoadingChequebookAddress ? ( </CardContent>
<div style={{ padding: '16px' }}> </div>
<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>
)}
</Card> </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 SwarmLogoOrange from '../assets/swarm-logo-orange.svg'
import { Health } from '@ethersphere/bee-js' import { Health } from '@ethersphere/bee-js'
import LastUpdate from './LastUpdate'
const drawerWidth = 240 const drawerWidth = 240
const navBarItems = [ const navBarItems = [
@@ -85,6 +87,7 @@ interface Props extends RouteComponentProps {
themeMode: string themeMode: string
health: boolean health: boolean
nodeHealth: Health | null nodeHealth: Health | null
lastUpdate: number | null
} }
export default function SideBar(props: Props): ReactElement { export default function SideBar(props: Props): ReactElement {
@@ -167,6 +170,9 @@ export default function SideBar(props: Props): ReactElement {
<span>Debug API</span> <span>Debug API</span>
</div> </div>
</ListItem> </ListItem>
<div style={{ width: '100%', textAlign: 'center' }}>
<LastUpdate date={props.lastUpdate} />
</div>
</div> </div>
</Drawer> </Drawer>
</div> </div>
+4 -13
View File
@@ -5,9 +5,7 @@ import { pickThreshold, ThresholdValues } from '../utils/threshold'
import StatCard from './StatCard' import StatCard from './StatCard'
interface RootProps { interface RootProps {
isLoading: boolean
topology: Topology | null topology: Topology | null
error: Error | null // FIXME: should display error
} }
interface Props extends RootProps { 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 maximumTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.maximumScore, 0)
const actualTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.score, 0) const actualTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.score, 0)
const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%' const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%'
return ( return (
<div style={{ marginBottom: '20px' }}> <div style={{ marginBottom: '20px' }}>
<StatCard label="Overall Health Indicator" statistic={percentageText} loading={isLoading} /> <StatCard label="Overall Health Indicator" statistic={percentageText} />
</div> </div>
) )
} }
const Metrics = ({ isLoading, topology, thresholds }: Props): ReactElement => ( const Metrics = ({ topology, thresholds }: Props): ReactElement => (
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}> <Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}> <Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard <StatCard
label="Connected Peers" label="Connected Peers"
statistic={topology?.connected.toString()} statistic={topology?.connected.toString()}
loading={isLoading}
tooltip={thresholds.connectedPeers.explanation} tooltip={thresholds.connectedPeers.explanation}
/> />
</Grid> </Grid>
@@ -56,17 +53,11 @@ const Metrics = ({ isLoading, topology, thresholds }: Props): ReactElement => (
<StatCard <StatCard
label="Population" label="Population"
statistic={topology?.population.toString()} statistic={topology?.population.toString()}
loading={isLoading}
tooltip={thresholds.population.explanation} tooltip={thresholds.population.explanation}
/> />
</Grid> </Grid>
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}> <Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard <StatCard label="Depth" statistic={topology?.depth.toString()} tooltip={thresholds.depth.explanation} />
label="Depth"
statistic={topology?.depth.toString()}
loading={isLoading}
tooltip={thresholds.depth.explanation}
/>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
+1 -313
View File
@@ -1,229 +1,9 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { import { beeDebugApi } from '../services/bee'
NodeAddresses,
ChequebookAddressResponse,
LastChequesResponse,
Health,
Peer,
Topology,
LastChequesForPeerResponse,
} from '@ethersphere/bee-js'
import { beeDebugApi, beeApi } from '../services/bee'
import axios from 'axios' import axios from 'axios'
import { Token } from '../models/Token' 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 { export interface Balance {
peer: string peer: string
balance: Token balance: Token
@@ -260,64 +40,6 @@ export const useApiPeerBalances = (): PeerBalanceHook => {
return { peerBalances, isLoadingPeerBalances, error } 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 { export interface Settlement {
peer: string peer: string
received: Token received: Token
@@ -368,40 +90,6 @@ export const useApiSettlements = (): SettlementsHook => {
return { settlements, isLoadingSettlements, error } 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 { export interface LatestBeeReleaseHook {
latestBeeRelease: LatestBeeRelease | null latestBeeRelease: LatestBeeRelease | null
isLoadingLatestBeeRelease: boolean 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 ErrorBoundary from '../components/ErrorBoundary'
import AlertVersion from '../components/AlertVersion' import AlertVersion from '../components/AlertVersion'
import { Container, CircularProgress } from '@material-ui/core'
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles' import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
import SideBar from '../components/SideBar' import SideBar from '../components/SideBar'
import NavBar from '../components/NavBar' import NavBar from '../components/NavBar'
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks' import { Context } from '../providers/Bee'
import { RouteComponentProps } from 'react-router' import { RouteComponentProps } from 'react-router'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
@@ -32,8 +34,7 @@ const Dashboard = (props: Props): ReactElement => {
const [themeMode, toggleThemeMode] = useState('light') const [themeMode, toggleThemeMode] = useState('light')
// FIXME: handle errrors and loading // FIXME: handle errrors and loading
const { health } = useApiHealth() const { isLoading, lastUpdate, apiHealth, debugApiHealth } = useContext(Context)
const { nodeHealth } = useDebugApiHealth()
useEffect(() => { useEffect(() => {
const theme = localStorage.getItem('theme') const theme = localStorage.getItem('theme')
@@ -56,12 +57,24 @@ const Dashboard = (props: Props): ReactElement => {
return ( return (
<div> <div>
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} /> <SideBar
{...props}
themeMode={themeMode}
health={apiHealth}
nodeHealth={debugApiHealth}
lastUpdate={lastUpdate}
/>
<NavBar themeMode={themeMode} /> <NavBar themeMode={themeMode} />
<ErrorBoundary> <ErrorBoundary>
<main className={classes.content}> <main className={classes.content}>
<AlertVersion /> <AlertVersion />
{props.children} {isLoading ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
props.children
)}
</main> </main>
</ErrorBoundary> </ErrorBoundary>
</div> </div>
+24 -35
View File
@@ -2,7 +2,6 @@ import { ReactElement } from 'react'
import { createStyles, makeStyles } from '@material-ui/core/styles' import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography, Theme } from '@material-ui/core/' import { Card, CardContent, Typography, Theme } from '@material-ui/core/'
import { Skeleton } from '@material-ui/lab'
import WithdrawModal from '../../containers/WithdrawModal' import WithdrawModal from '../../containers/WithdrawModal'
import DepositModal from '../../containers/DepositModal' import DepositModal from '../../containers/DepositModal'
@@ -45,12 +44,11 @@ interface ChequebookBalance {
interface Props { interface Props {
chequebookAddress: ChequebookAddressResponse | null chequebookAddress: ChequebookAddressResponse | null
chequebookBalance: ChequebookBalance | null chequebookBalance: ChequebookBalance | null
totalsent: Token totalsent?: Token
totalreceived: Token totalreceived?: Token
isLoading: boolean
} }
function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }: Props): ReactElement { function AccountCard({ totalreceived, totalsent, chequebookBalance }: Props): ReactElement {
const classes = useStyles() const classes = useStyles()
return ( return (
@@ -66,37 +64,28 @@ function AccountCard({ totalreceived, totalsent, chequebookBalance, isLoading }:
</div> </div>
<Card className={classes.root}> <Card className={classes.root}>
{!isLoading && ( <CardContent className={classes.gridContainer}>
<CardContent className={classes.gridContainer}> <div>
<div> <Typography component="h2" variant="h6" color="primary" gutterBottom>
<Typography component="h2" variant="h6" color="primary" gutterBottom> Total Balance
Total Balance </Typography>
</Typography> <Typography variant="h5">{chequebookBalance?.totalBalance.toFixedDecimal()} BZZ</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" />
</div> </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> </Card>
</div> </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 { 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 AccountCard from '../accounting/AccountCard'
import BalancesTable from './BalancesTable' import BalancesTable from './BalancesTable'
import EthereumAddressCard from '../../components/EthereumAddressCard' import EthereumAddressCard from '../../components/EthereumAddressCard'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context } from '../../providers/Bee'
import {
useApiNodeAddresses,
useApiChequebookAddress,
useApiChequebookBalance,
useApiHealth,
useDebugApiHealth,
} from '../../hooks/apiHooks'
import { useAccounting } from '../../hooks/accounting' import { useAccounting } from '../../hooks/accounting'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
@@ -29,38 +22,21 @@ const useStyles = makeStyles((theme: Theme) =>
export default function Accounting(): ReactElement { export default function Accounting(): ReactElement {
const classes = useStyles() const classes = useStyles()
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress() const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements } = useContext(Context)
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { isLoading, totalsent, totalreceived, accounting, isLoadingUncashed, error } = useAccounting()
if (isLoadingHealth || isLoadingNodeHealth) { const { accounting, isLoadingUncashed, error } = useAccounting()
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard /> if (!status.all) return <TroubleshootConnectionCard />
return ( return (
<div className={classes.root}> <div className={classes.root}>
<AccountCard <AccountCard
chequebookAddress={chequebookAddress} chequebookAddress={chequebookAddress}
isLoading={isLoadingChequebookAddress || isLoading || isLoadingChequebookBalance}
chequebookBalance={chequebookBalance} chequebookBalance={chequebookBalance}
totalsent={totalsent} totalsent={settlements?.totalSent}
totalreceived={totalreceived} totalreceived={settlements?.totalReceived}
/>
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
/> />
<EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
{error && ( {error && (
<Container style={{ textAlign: 'center', padding: '50px' }}> <Container style={{ textAlign: 'center', padding: '50px' }}>
Error loading accounting details: {error.message} 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 TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks' import { Context } from '../../providers/Bee'
import Download from './Download' import Download from './Download'
import Upload from './Upload' import Upload from './Upload'
import TabsContainer from '../../components/TabsContainer' import TabsContainer from '../../components/TabsContainer'
export default function Files(): ReactElement { export default function Files(): ReactElement {
const { health, isLoadingHealth } = useApiHealth() const { status } = useContext(Context)
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
if (isLoadingHealth || isLoadingNodeHealth) { if (!status.all) return <TroubleshootConnectionCard />
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (!health || nodeHealth?.status !== 'ok') return <TroubleshootConnectionCard />
return ( return (
<Container maxWidth="sm"> <Container maxWidth="sm">
+1 -20
View File
@@ -10,7 +10,6 @@ import {
Button, Button,
Paper, Paper,
Tooltip, Tooltip,
Container,
CircularProgress, CircularProgress,
} from '@material-ui/core' } from '@material-ui/core'
import { Autorenew } from '@material-ui/icons' import { Autorenew } from '@material-ui/icons'
@@ -26,8 +25,6 @@ const useStyles = makeStyles({
interface Props { interface Props {
peers: Peer[] | null peers: Peer[] | null
isLoading: boolean
error: Error | null
} }
function PeerTable(props: Props): ReactElement { 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 ( return (
<div> <div>
<TableContainer component={Paper}> <TableContainer component={Paper}>
@@ -75,7 +56,7 @@ function PeerTable(props: Props): ReactElement {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{props.peers.map((peer: Peer, idx: number) => ( {props.peers?.map((peer: Peer, idx: number) => (
<TableRow key={peer.address}> <TableRow key={peer.address}>
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{idx + 1} {idx + 1}
+6 -17
View File
@@ -1,32 +1,21 @@
import { Container, CircularProgress } from '@material-ui/core/'
import PeerTable from './PeerTable' import PeerTable from './PeerTable'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { useApiNodeTopology, useApiNodePeers, useDebugApiHealth } from '../../hooks/apiHooks' import { Context } from '../../providers/Bee'
import TopologyStats from '../../components/TopologyStats' import TopologyStats from '../../components/TopologyStats'
import { ReactElement } from 'react' import { ReactElement, useContext } from 'react'
export default function Peers(): ReactElement { export default function Peers(): ReactElement {
const topology = useApiNodeTopology() const { topology, peers, status } = useContext(Context)
const debugHealth = useDebugApiHealth()
const peers = useApiNodePeers()
if (debugHealth.isLoadingNodeHealth) { if (!status.all) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (debugHealth.error) {
return <TroubleshootConnectionCard /> return <TroubleshootConnectionCard />
} }
return ( return (
<> <>
<TopologyStats {...topology} /> <TopologyStats topology={topology} />
<PeerTable {...peers} /> <PeerTable peers={peers} />
</> </>
) )
} }
+4 -5
View File
@@ -7,8 +7,8 @@ import TroubleshootConnectionCard from '../../components/TroubleshootConnectionC
import CreatePostageStampModal from './CreatePostageStampModal' import CreatePostageStampModal from './CreatePostageStampModal'
import LastUpdate from '../../components/LastUpdate' import LastUpdate from '../../components/LastUpdate'
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
import { Context } from '../../providers/Stamps' import { Context } from '../../providers/Stamps'
import { Context as BeeContext } from '../../providers/Bee'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@@ -32,8 +32,7 @@ const useStyles = makeStyles((theme: Theme) =>
export default function Accounting(): ReactElement { export default function Accounting(): ReactElement {
const classes = useStyles() const classes = useStyles()
const { health, isLoadingHealth } = useApiHealth() const beeContext = useContext(BeeContext)
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { stamps, isLoading, error, lastUpdate, start, stop } = useContext(Context) const { stamps, isLoading, error, lastUpdate, start, stop } = useContext(Context)
useEffect(() => { useEffect(() => {
start() start()
@@ -41,7 +40,7 @@ export default function Accounting(): ReactElement {
return () => stop() return () => stop()
}, []) }, [])
if (isLoadingHealth || isLoadingNodeHealth) { if (beeContext.isLoading) {
return ( return (
<Container style={{ textAlign: 'center', padding: '50px' }}> <Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress /> <CircularProgress />
@@ -49,7 +48,7 @@ export default function Accounting(): ReactElement {
) )
} }
if (nodeHealth?.status !== 'ok' || !health) return <TroubleshootConnectionCard /> if (!beeContext.status.all) return <TroubleshootConnectionCard />
return ( return (
<div className={classes.root}> <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 { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/' import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/' import { 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 EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund' import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
import PeerConnection from './SetupSteps/PeerConnection' import PeerConnection from './SetupSteps/PeerConnection'
import { StatusChequebookHook } from '../../hooks/status' import { Context } from '../../providers/Bee'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@@ -30,66 +30,65 @@ const useStyles = makeStyles((theme: Theme) =>
interface Step { interface Step {
label: string label: string
isOk: boolean isOk: boolean
isLoading: boolean
component: ReactElement component: ReactElement
} }
interface Props { export default function NodeSetupWorkflow(): ReactElement {
nodeVersion: StatusNodeVersionHook
ethereumConnection: StatusEthereumConnectionHook
debugApiConnection: StatusHookCommon
apiConnection: StatusHookCommon
topology: StatusTopologyHook
chequebook: StatusChequebookHook
}
export default function NodeSetupWorkflow({
nodeVersion,
ethereumConnection,
debugApiConnection,
apiConnection,
topology,
chequebook,
}: Props): ReactElement {
const classes = useStyles() const classes = useStyles()
const [activeStep, setActiveStep] = useState(-1) const [activeStep, setActiveStep] = useState(-1)
const {
status,
isLoading,
latestUserVersion,
latestPublishedVersion,
isLatestBeeVersion,
latestBeeVersionUrl,
topology,
nodeAddresses,
chequebookAddress,
} = useContext(Context)
const steps: Step[] = [ const steps: Step[] = [
{ {
label: 'Connected to Node DebugAPI', label: 'Connected to Node DebugAPI',
isOk: debugApiConnection.isOk, isOk: status.debugApiConnection,
isLoading: debugApiConnection.isLoading, component: <DebugConnectionCheck isOk={status.debugApiConnection} />,
component: <DebugConnectionCheck {...debugApiConnection} />,
}, },
{ {
label: 'Running latest Bee version', label: 'Running latest Bee version',
isOk: nodeVersion.isOk, isOk: status.version,
isLoading: nodeVersion.isLoading, component: (
component: <VersionCheck {...nodeVersion} />, <VersionCheck
isOk={status.version}
isLatestBeeVersion={isLatestBeeVersion}
userVersion={latestUserVersion}
latestVersion={latestPublishedVersion}
latestUrl={latestBeeVersionUrl}
/>
),
}, },
{ {
label: 'Connected to Ethereum Blockchain', label: 'Connected to xDai Blockchain',
isOk: ethereumConnection.isOk, isOk: status.blockchainConnection,
isLoading: ethereumConnection.isLoading, component: <EthereumConnectionCheck isOk={status.blockchainConnection} nodeAddresses={nodeAddresses} />,
component: <EthereumConnectionCheck {...ethereumConnection} />,
}, },
{ {
label: 'Deployed and Funded Chequebook', label: 'Deployed and Funded Chequebook',
isOk: chequebook.isOk, isOk: status.chequebook,
isLoading: chequebook.isLoading, component: (
component: <ChequebookDeployFund ethereumAddress={ethereumConnection.nodeAddresses?.ethereum} {...chequebook} />, <ChequebookDeployFund chequebookAddress={chequebookAddress?.chequebookAddress} isOk={status.chequebook} />
),
}, },
{ {
label: 'Connected to Node API', label: 'Connected to Node API',
isOk: apiConnection.isOk, isOk: status.apiConnection,
isLoading: apiConnection.isLoading, component: <NodeConnectionCheck isOk={status.apiConnection} />,
component: <NodeConnectionCheck {...apiConnection} />,
}, },
{ {
label: 'Connected to Peers', label: 'Connected to Peers',
isOk: topology.isOk, isOk: status.topology,
isLoading: topology.isLoading, component: <PeerConnection isOk={status.topology} topology={topology} />,
component: <PeerConnection {...topology} />,
}, },
] ]
@@ -98,7 +97,7 @@ export default function NodeSetupWorkflow({
if (activeStep >= 0 && activeStep < steps.length) return if (activeStep >= 0 && activeStep < steps.length) return
// If any step is not fully loaded yet 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 // 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 // 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> </span>
</Typography> </Typography>
<Stepper nonLinear activeStep={activeStep} orientation="vertical"> <Stepper nonLinear activeStep={activeStep} orientation="vertical">
{steps.map(({ label, isOk, component, isLoading }, index) => ( {steps.map(({ label, isOk, component }, index) => (
<Step key={label}> <Step key={label}>
<StepLabel <StepLabel
disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)} onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
StepIconComponent={() => { StepIconComponent={() => {
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} /> 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 EthereumAddress from '../../../components/EthereumAddress'
import DepositModal from '../../../containers/DepositModal' import DepositModal from '../../../containers/DepositModal'
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
import type { StatusChequebookHook } from '../../../hooks/status'
interface Props extends StatusChequebookHook { interface Props extends StatusHookCommon {
ethereumAddress?: string chequebookAddress?: string
} }
const ChequebookDeployFund = ({ isLoading, chequebookAddress, chequebookBalance }: Props): ReactElement | null => { const ChequebookDeployFund = ({ chequebookAddress, isOk }: Props): ReactElement | null => {
if (isLoading) return null
return ( return (
<div> <div>
<p style={{ marginBottom: '20px', display: 'flex' }}> <p style={{ marginBottom: '20px', display: 'flex' }}>{chequebookAddress && <DepositModal />}</p>
{chequebookAddress?.chequebookAddress && <DepositModal />}
</p>
<div style={{ marginBottom: '10px' }}> <div style={{ marginBottom: '10px' }}>
{!(chequebookAddress?.chequebookAddress && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) && ( {!isOk && (
<div> <div>
<span> <span>
Your chequebook is either not deployed or funded. To run the node you will need xDAI and xBZZ on the xDai 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> <Typography variant="subtitle1" gutterBottom>
Chequebook Address Chequebook Address
</Typography> </Typography>
<EthereumAddress address={chequebookAddress?.chequebookAddress} /> <EthereumAddress address={chequebookAddress} />
</div> </div>
) )
} }
@@ -9,9 +9,7 @@ import { debugApiHost } from '../../../constants'
type Props = StatusHookCommon type Props = StatusHookCommon
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null { export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
if (isLoading) return null
const changeDebugApiUrl = ( const changeDebugApiUrl = (
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}> <div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
<span style={{ marginRight: '15px' }}> <span style={{ marginRight: '15px' }}>
@@ -4,9 +4,7 @@ import EthereumAddress from '../../../components/EthereumAddress'
type Props = StatusEthereumConnectionHook type Props = StatusEthereumConnectionHook
export default function EthereumConnectionCheck({ isLoading, isOk, nodeAddresses }: Props): ReactElement | null { export default function EthereumConnectionCheck({ isOk, nodeAddresses }: Props): ReactElement | null {
if (isLoading) return null
if (isOk) { if (isOk) {
return ( return (
<div> <div>
@@ -8,9 +8,7 @@ import { apiHost } from '../../../constants'
type Props = StatusHookCommon type Props = StatusHookCommon
export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null { export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
if (isLoading) return null
return ( return (
<div> <div>
<div style={{ display: 'flex', marginBottom: '25px' }}> <div style={{ display: 'flex', marginBottom: '25px' }}>
@@ -3,9 +3,7 @@ import { Typography } from '@material-ui/core/'
type Props = StatusTopologyHook type Props = StatusTopologyHook
export default function PeerConnection({ isLoading, isOk, topology }: Props): ReactElement | null { export default function PeerConnection({ isOk, topology }: Props): ReactElement | null {
if (isLoading) return null
const peers = ( const peers = (
<div style={{ display: 'flex', marginTop: '15px' }}> <div style={{ display: 'flex', marginTop: '15px' }}>
<div style={{ marginRight: '30px' }}> <div style={{ marginRight: '30px' }}>
+1 -9
View File
@@ -4,15 +4,7 @@ import CodeBlockTabs from '../../../components/CodeBlockTabs'
type Props = StatusNodeVersionHook type Props = StatusNodeVersionHook
export default function VersionCheck({ export default function VersionCheck({ isOk, userVersion, latestVersion, latestUrl }: Props): ReactElement | null {
isLoading,
isOk,
userVersion,
latestVersion,
latestUrl,
}: Props): ReactElement | null {
if (isLoading) return null
const version = ( const version = (
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}> <div style={{ marginRight: '30px' }}>
+20 -48
View File
@@ -1,18 +1,10 @@
import { ReactElement } from 'react' import { ReactElement, useContext } from 'react'
import { Container, CircularProgress } from '@material-ui/core'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import NodeSetupWorkflow from './NodeSetupWorkflow' import NodeSetupWorkflow from './NodeSetupWorkflow'
import StatusCard from './StatusCard' import StatusCard from './StatusCard'
import EthereumAddressCard from '../../components/EthereumAddressCard' import EthereumAddressCard from '../../components/EthereumAddressCard'
import { import { Context } from '../../providers/Bee'
useStatusEthereumConnection,
useStatusNodeVersion,
useStatusDebugConnection,
useStatusConnection,
useStatusTopology,
useStatusChequebook,
} from '../../hooks/status'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@@ -27,50 +19,30 @@ const useStyles = makeStyles((theme: Theme) =>
export default function Status(): ReactElement { export default function Status(): ReactElement {
const classes = useStyles() const classes = useStyles()
const nodeVersion = useStatusNodeVersion() const {
const ethereumConnection = useStatusEthereumConnection() status,
const debugApiConnection = useStatusDebugConnection() latestUserVersion,
const apiConnection = useStatusConnection() isLatestBeeVersion,
const topology = useStatusTopology() latestBeeVersionUrl,
const chequebook = useStatusChequebook() topology,
nodeAddresses,
const checks = [nodeVersion, ethereumConnection, debugApiConnection, apiConnection, topology, chequebook] chequebookAddress,
} = useContext(Context)
// If any check data are still loading
if (!checks.every(c => !c.isLoading)) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
return ( return (
<div className={classes.root}> <div className={classes.root}>
<StatusCard <StatusCard
userBeeVersion={nodeVersion.userVersion} userBeeVersion={latestUserVersion}
isLatestBeeVersion={nodeVersion.isLatestBeeVersion} isLatestBeeVersion={isLatestBeeVersion}
isOk={checks.every(c => c.isOk)} isOk={status.all}
nodeTopology={topology.topology} nodeTopology={topology}
latestUrl={nodeVersion.latestUrl} latestUrl={latestBeeVersionUrl}
nodeAddresses={ethereumConnection.nodeAddresses} nodeAddresses={nodeAddresses}
/> />
{ethereumConnection.nodeAddresses && chequebook.chequebookAddress && ( {nodeAddresses && chequebookAddress && (
<EthereumAddressCard <EthereumAddressCard nodeAddresses={nodeAddresses} chequebookAddress={chequebookAddress} />
nodeAddresses={ethereumConnection.nodeAddresses}
isLoadingNodeAddresses={ethereumConnection.isLoading}
chequebookAddress={chequebook.chequebookAddress}
isLoadingChequebookAddress={chequebook.isLoading}
/>
)} )}
<NodeSetupWorkflow <NodeSetupWorkflow />
nodeVersion={nodeVersion}
ethereumConnection={ethereumConnection}
debugApiConnection={debugApiConnection}
apiConnection={apiConnection}
topology={topology}
chequebook={chequebook}
/>
</div> </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 { interface StatusHookCommon {
isLoading: boolean
isOk: 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[]
}