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:
+15
-12
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Vendored
-1
@@ -6,7 +6,6 @@ interface LatestBeeRelease {
|
||||
}
|
||||
|
||||
interface StatusHookCommon {
|
||||
isLoading: boolean
|
||||
isOk: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
Reference in New Issue
Block a user