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 { 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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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 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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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' }}>
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
interface StatusHookCommon {
|
||||||
isLoading: boolean
|
|
||||||
isOk: 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