feat: reviewed and simplified the node status check (#63)

* refactor: status page nested ternary logic

* refactor: move the fetch latest bee release to a hook

* refactor: solved node status rerendering, improved performance and clarity

* refactor: step components now use unified hooks interface

* style: removed component margins, layout should be handled by pages
This commit is contained in:
Vojtech Simetka
2021-04-09 15:09:17 +02:00
committed by GitHub
parent 5608b91966
commit 9e4e58f160
14 changed files with 656 additions and 764 deletions
+3 -7
View File
@@ -12,7 +12,9 @@ const useStyles = makeStyles(() =>
createStyles({ createStyles({
root: { root: {
display: 'flex', display: 'flex',
marginTop: '20px', alignItems: 'center',
justifyContent: 'space-around',
flexWrap: 'wrap',
}, },
details: { details: {
display: 'flex', display: 'flex',
@@ -21,10 +23,6 @@ const useStyles = makeStyles(() =>
content: { content: {
flex: '1 0 auto', flex: '1 0 auto',
}, },
status: {
color: '#fff',
backgroundColor: '#76a9fa',
},
}), }),
) )
@@ -39,7 +37,6 @@ function EthereumAddressCard(props: Props): ReactElement {
const classes = useStyles() const classes = useStyles()
return ( return (
<div>
<Card className={classes.root}> <Card className={classes.root}>
{props.isLoadingNodeAddresses ? ( {props.isLoadingNodeAddresses ? (
<div style={{ padding: '16px' }}> <div style={{ padding: '16px' }}>
@@ -72,7 +69,6 @@ function EthereumAddressCard(props: Props): ReactElement {
</div> </div>
)} )}
</Card> </Card>
</div>
) )
} }
+29
View File
@@ -15,6 +15,7 @@ import {
} from '@ethersphere/bee-js' } from '@ethersphere/bee-js'
import { beeDebugApi, beeApi } from '../services/bee' import { beeDebugApi, beeApi } from '../services/bee'
import axios from 'axios'
export interface HealthHook { export interface HealthHook {
health: boolean health: boolean
@@ -361,3 +362,31 @@ export const useApiPeerLastCashout = (peerId: string): PeerLastCashoutHook => {
return { peerCashout, isLoadingPeerCashout, error } return { peerCashout, isLoadingPeerCashout, error }
} }
export interface LatestBeeReleaseHook {
latestBeeRelease: LatestBeeRelease | null
isLoadingLatestBeeRelease: boolean
error: Error | null
}
export const useLatestBeeRelease = (): LatestBeeReleaseHook => {
const [latestBeeRelease, setLatestBeeRelease] = useState<LatestBeeRelease | null>(null)
const [isLoadingLatestBeeRelease, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
axios
.get(`${process.env.REACT_APP_BEE_GITHUB_REPO_URL}/releases/latest`)
.then(res => {
setLatestBeeRelease(res.data)
})
.catch((error: Error) => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { latestBeeRelease, isLoadingLatestBeeRelease, error }
}
+73
View File
@@ -0,0 +1,73 @@
import {
useApiChequebookAddress,
useApiChequebookBalance,
useApiHealth,
useApiNodeAddresses,
useApiNodeTopology,
useDebugApiHealth,
useLatestBeeRelease,
} from './apiHooks'
export const useStatusNodeVersion = (): StatusNodeVersionHook => {
const { latestBeeRelease, isLoadingLatestBeeRelease } = useLatestBeeRelease()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
return {
isLoading: isLoadingNodeHealth || isLoadingLatestBeeRelease,
isOk: Boolean(latestBeeRelease && latestBeeRelease.name === `v${nodeHealth?.version?.split('-')[0]}`),
userVersion: nodeHealth?.version?.split('-')[0] || '-',
latestVersion: latestBeeRelease?.name.substring(1) || '-',
latestUrl: latestBeeRelease?.html_url || 'https://github.com/ethersphere/bee/releases/latest',
}
}
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 > 0,
chequebookBalance,
chequebookAddress,
}
}
+15 -3
View File
@@ -1,5 +1,5 @@
import { ReactElement, useState, ChangeEvent, ReactChild } from 'react' import { ReactElement, useState, ChangeEvent, ReactChild } from 'react'
import { withStyles, Theme, createStyles } from '@material-ui/core/styles' import { withStyles, Theme, createStyles, makeStyles } from '@material-ui/core/styles'
import { Tabs, Tab, Box, Typography, Container, CircularProgress } from '@material-ui/core' import { Tabs, Tab, Box, Typography, Container, CircularProgress } from '@material-ui/core'
import AccountCard from '../accounting/AccountCard' import AccountCard from '../accounting/AccountCard'
@@ -33,8 +33,19 @@ function a11yProps(index: number) {
} }
} }
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: '100%',
display: 'grid',
rowGap: theme.spacing(3),
},
}),
)
export default function Accounting(): ReactElement { export default function Accounting(): ReactElement {
const [value, setValue] = useState(0) const [value, setValue] = useState(0)
const classes = useStyles()
const handleChange = (event: ChangeEvent<unknown>, newValue: number) => { const handleChange = (event: ChangeEvent<unknown>, newValue: number) => {
setValue(newValue) setValue(newValue)
@@ -115,7 +126,6 @@ export default function Accounting(): ReactElement {
color: '#3f51b5', color: '#3f51b5',
}, },
}, },
selected: {},
}), }),
)((props: StyledTabProps) => <Tab disableRipple {...props} />) )((props: StyledTabProps) => <Tab disableRipple {...props} />)
@@ -125,7 +135,7 @@ export default function Accounting(): ReactElement {
// FIXME: this should be broken up // FIXME: this should be broken up
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
nodeHealth?.status === 'ok' && health ? ( nodeHealth?.status === 'ok' && health ? (
<div> <div className={classes.root}>
<AccountCard <AccountCard
chequebookAddress={chequebookAddress} chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress} isLoadingChequebookAddress={isLoadingChequebookAddress}
@@ -140,6 +150,7 @@ export default function Accounting(): ReactElement {
chequebookAddress={chequebookAddress} chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress} isLoadingChequebookAddress={isLoadingChequebookAddress}
/> />
<div>
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example"> <AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
<AntTab label="Balances" {...a11yProps(0)} /> <AntTab label="Balances" {...a11yProps(0)} />
<AntTab label="Chequebook" {...a11yProps(1)} /> <AntTab label="Chequebook" {...a11yProps(1)} />
@@ -155,6 +166,7 @@ export default function Accounting(): ReactElement {
<SettlementsTable nodeSettlements={settlements} loading={isLoadingSettlements} /> <SettlementsTable nodeSettlements={settlements} loading={isLoadingSettlements} />
</TabPanel> </TabPanel>
</div> </div>
</div>
) : isLoadingHealth || isLoadingNodeHealth ? ( ) : isLoadingHealth || isLoadingNodeHealth ? (
<Container style={{ textAlign: 'center', padding: '50px' }}> <Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress /> <CircularProgress />
+83 -161
View File
@@ -1,7 +1,7 @@
import { ReactElement, useEffect, useState } from 'react' import { ReactElement, useEffect, useState } 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 } from '@material-ui/icons/' import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck' import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck' import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
@@ -9,17 +9,11 @@ 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 type {
ChequebookAddressResponse,
ChequebookBalanceResponse,
Health,
NodeAddresses,
Topology,
} from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
root: { root: {
padding: theme.spacing(2),
width: '100%', width: '100%',
}, },
button: { button: {
@@ -29,147 +23,88 @@ const useStyles = makeStyles((theme: Theme) =>
actionsContainer: { actionsContainer: {
margin: theme.spacing(2), margin: theme.spacing(2),
}, },
resetContainer: {
padding: theme.spacing(5),
},
}), }),
) )
function getSteps() { interface Step {
return [ label: string
'Debug Connection Check', condition: boolean
'Version Check', isLoading: boolean
'Connect to Ethereum Blockchain', component: ReactElement
'Deploy and Fund Chequebook',
'Node Connection Check',
'Connect to Peers',
]
} }
interface Props { interface Props {
nodeHealth: Health | null nodeVersion: StatusNodeVersionHook
nodeApiHealth: boolean ethereumConnection: StatusEthereumConnectionHook
nodeAddresses: NodeAddresses | null debugApiConnection: StatusHookCommon
chequebookAddress: ChequebookAddressResponse | null apiConnection: StatusHookCommon
chequebookBalance: ChequebookBalanceResponse | null topology: StatusTopologyHook
beeRelease: LatestBeeRelease | null chequebook: StatusChequebookHook
nodeTopology: Topology | null
isLoadingBeeRelease: boolean
isLoadingNodeHealth: boolean
isLoadingNodeAddresses: boolean
isLoadingNodeTopology: boolean
isLoadingHealth: boolean
isLoadingChequebookAddress: boolean
isLoadingChequebookBalance: boolean
setStatusChecksVisible: (value: boolean) => void
apiHost: string
debugApiHost: string
} }
function getStepContent(step: number, props: Props) { export default function NodeSetupWorkflow({
const { nodeVersion,
nodeHealth, ethereumConnection,
debugApiHost, debugApiConnection,
beeRelease, apiConnection,
isLoadingBeeRelease, topology,
nodeAddresses, chequebook,
isLoadingNodeAddresses, }: Props): ReactElement {
isLoadingChequebookBalance,
chequebookAddress,
chequebookBalance,
isLoadingChequebookAddress,
nodeApiHealth,
apiHost,
isLoadingNodeTopology,
nodeTopology,
} = props
switch (step) {
case 0:
return <DebugConnectionCheck nodeHealth={nodeHealth} debugApiHost={debugApiHost} />
case 1:
return <VersionCheck nodeHealth={nodeHealth} beeRelease={beeRelease} isLoadingBeeRelease={isLoadingBeeRelease} />
case 2:
return <EthereumConnectionCheck nodeAddresses={nodeAddresses} isLoadingNodeAddresses={isLoadingNodeAddresses} />
case 3:
return (
<ChequebookDeployFund
chequebookAddress={chequebookAddress}
chequebookBalance={chequebookBalance}
isLoadingChequebookAddress={isLoadingChequebookAddress}
isLoadingChequebookBalance={isLoadingChequebookBalance}
/>
)
case 4:
return <NodeConnectionCheck nodeApiHealth={nodeApiHealth} apiHost={apiHost} />
default:
return <PeerConnection nodeTopology={nodeTopology} isLoadingNodeTopology={isLoadingNodeTopology} />
}
}
export default function NodeSetupWorkflow(props: Props): ReactElement {
const {
nodeHealth,
nodeApiHealth,
nodeAddresses,
chequebookAddress,
chequebookBalance,
beeRelease,
nodeTopology,
setStatusChecksVisible,
} = props
const classes = useStyles() const classes = useStyles()
const [activeStep, setActiveStep] = useState(0) const [activeStep, setActiveStep] = useState(-1)
const [completed, setCompleted] = useState<{ [k: number]: boolean }>({})
const steps = getSteps() const steps: Step[] = [
{
label: 'Connected to Node DebugAPI',
condition: debugApiConnection.isOk,
isLoading: debugApiConnection.isLoading,
component: <DebugConnectionCheck {...debugApiConnection} />,
},
{
label: 'Running latest Bee version',
condition: nodeVersion.isOk,
isLoading: nodeVersion.isLoading,
component: <VersionCheck {...nodeVersion} />,
},
{
label: 'Connected to Ethereum Blockchain',
condition: ethereumConnection.isOk,
isLoading: ethereumConnection.isLoading,
component: <EthereumConnectionCheck {...ethereumConnection} />,
},
{
label: 'Deployed and Funded Chequebook',
condition: chequebook.isOk,
isLoading: chequebook.isLoading,
component: <ChequebookDeployFund {...chequebook} />,
},
{
label: 'Connected to Node API',
condition: apiConnection.isOk,
isLoading: apiConnection.isLoading,
component: <NodeConnectionCheck {...apiConnection} />,
},
{
label: 'Connected to Peers',
condition: topology.isOk,
isLoading: topology.isLoading,
component: <PeerConnection {...topology} />,
},
]
useEffect(() => { useEffect(() => {
const handleComplete = (index: number) => { // If the user already changed the active step we don't want to overwrite it
const newCompleted = completed if (activeStep > 0 && activeStep < steps.length) return
newCompleted[index] = true
setCompleted(newCompleted)
}
const evaluateNodeStatus = () => { // Select first step that is not OK
if (nodeHealth?.status === 'ok') { for (let i = 0; i < steps.length; ++i) {
handleComplete(0) if (!steps[i].condition) {
setActiveStep(1) setActiveStep(i)
}
if (beeRelease && beeRelease.name === `v${nodeHealth?.version?.split('-')[0]}`) { return
handleComplete(1)
setActiveStep(2)
}
if (nodeAddresses?.ethereum) {
handleComplete(2)
setActiveStep(3)
}
if (chequebookAddress?.chequebookaddress && chequebookBalance && chequebookBalance.totalBalance > 0) {
handleComplete(3)
setActiveStep(4)
}
if (nodeApiHealth) {
handleComplete(4)
setActiveStep(5)
}
if (nodeTopology?.connected && nodeTopology?.connected > 0) {
handleComplete(5)
setActiveStep(6)
} }
} }
evaluateNodeStatus() }, [steps])
}, [
nodeHealth,
nodeApiHealth,
nodeAddresses,
chequebookAddress,
beeRelease,
chequebookBalance,
nodeTopology,
completed,
])
const handleNext = () => { const handleNext = () => {
setActiveStep(prevActiveStep => prevActiveStep + 1) setActiveStep(prevActiveStep => prevActiveStep + 1)
@@ -179,13 +114,9 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
setActiveStep(prevActiveStep => prevActiveStep - 1) setActiveStep(prevActiveStep => prevActiveStep - 1)
} }
const handleSetupComplete = () => {
setStatusChecksVisible(false)
}
return ( return (
<div className={classes.root}> <Paper className={classes.root}>
<Typography variant="h4" gutterBottom> <Typography variant="h5" gutterBottom>
Node Setup Node Setup
<span style={{ marginLeft: '25px' }}> <span style={{ marginLeft: '25px' }}>
<Button variant="outlined" size="small" onClick={() => window.location.reload()}> <Button variant="outlined" size="small" onClick={() => window.location.reload()}>
@@ -195,20 +126,22 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
</span> </span>
</Typography> </Typography>
<Stepper nonLinear activeStep={activeStep} orientation="vertical"> <Stepper nonLinear activeStep={activeStep} orientation="vertical">
{steps.map((label, index) => ( {steps.map(({ label, condition, component, isLoading }, index) => (
<Step key={label}> <Step key={label}>
<StepLabel <StepLabel
onClick={() => setActiveStep(index === activeStep ? 6 : index)} disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
StepIconComponent={() => { StepIconComponent={() => {
if (completed[index]) { if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
} else { if (condition) return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} /> return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
}
}} }}
> >
<StepButton <StepButton
onClick={() => setActiveStep(index === activeStep ? 6 : index)} disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
style={{ justifyContent: 'space-between' }} style={{ justifyContent: 'space-between' }}
> >
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
@@ -220,14 +153,14 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
</StepButton> </StepButton>
</StepLabel> </StepLabel>
<StepContent> <StepContent>
<Typography component="div">{getStepContent(index, props)}</Typography> <Typography component="div">{component}</Typography>
<div className={classes.actionsContainer}> <div className={classes.actionsContainer}>
<div> <div>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}> <Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
Back Back
</Button> </Button>
<Button variant="contained" color="primary" onClick={handleNext} className={classes.button}> <Button variant="contained" color="primary" onClick={handleNext} className={classes.button}>
Next {index < steps.length - 1 ? 'Next' : 'Finish'}
</Button> </Button>
</div> </div>
</div> </div>
@@ -235,17 +168,6 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
</Step> </Step>
))} ))}
</Stepper> </Stepper>
{Object.values(completed).filter(value => value).length === 6 ? (
<Paper square elevation={0} className={classes.resetContainer}>
<Typography>Bee setup complete! Welcome to the swarm and the internet of decentralized storage</Typography>
<Button onClick={handleBack} className={classes.button}>
Back
</Button>
<Button onClick={handleSetupComplete} variant="contained" color="primary" className={classes.button}>
Complete
</Button>
</Paper> </Paper>
) : null}
</div>
) )
} }
@@ -1,38 +1,22 @@
import { Typography } from '@material-ui/core/' import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import EthereumAddress from '../../../components/EthereumAddress' import EthereumAddress from '../../../components/EthereumAddress'
import DepositModal from '../../../components/DepositModal' import DepositModal from '../../../components/DepositModal'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import type { ChequebookAddressResponse, ChequebookBalanceResponse } from '@ethersphere/bee-js'
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
interface Props { type Props = StatusChequebookHook
chequebookAddress: ChequebookAddressResponse | null
chequebookBalance: ChequebookBalanceResponse | null
isLoadingChequebookAddress: boolean
isLoadingChequebookBalance: boolean
}
const ChequebookDeployFund = (props: Props): ReactElement => ( const ChequebookDeployFund = ({ isLoading, chequebookAddress, chequebookBalance }: Props): ReactElement | null => {
if (isLoading) return null
return (
<div> <div>
<p style={{ marginBottom: '20px', display: 'flex' }}> <p style={{ marginBottom: '20px', display: 'flex' }}>
<span style={{ marginRight: '40px' }}>Deploy chequebook and fund with BZZ</span> {chequebookAddress?.chequebookaddress && <DepositModal />}
{props.chequebookAddress?.chequebookaddress ? <DepositModal /> : null}
</p> </p>
<div style={{ marginBottom: '10px' }}> <div style={{ marginBottom: '10px' }}>
{ {!(chequebookAddress?.chequebookaddress && chequebookBalance && chequebookBalance?.totalBalance > 0) && (
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.chequebookAddress?.chequebookaddress &&
props.chequebookBalance &&
props.chequebookBalance?.totalBalance > 0 ? (
<div> <div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your chequebook is deployed and funded!</span>
</div>
) : props.isLoadingChequebookAddress || props.isLoadingChequebookBalance ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span> <span>
Your chequebook is either not deployed or funded. Run the below commands to get your address and deposit Your chequebook is either not deployed or funded. Run the below commands to get your address and deposit
ETH. Then visit the BZZaar here{' '} ETH. Then visit the BZZaar here{' '}
@@ -43,15 +27,14 @@ const ChequebookDeployFund = (props: Props): ReactElement => (
</span> </span>
<CodeBlockTabs showLineNumbers linux={`bee-get-addr`} mac={`bee-get-addr`} /> <CodeBlockTabs showLineNumbers linux={`bee-get-addr`} mac={`bee-get-addr`} />
</div> </div>
) )}
/* eslint-enable no-nested-ternary */
}
</div> </div>
<Typography variant="subtitle1" gutterBottom> <Typography variant="subtitle1" gutterBottom>
Chequebook Address Chequebook Address
</Typography> </Typography>
<EthereumAddress address={props.chequebookAddress?.chequebookaddress} network={'goerli'} /> <EthereumAddress address={chequebookAddress?.chequebookaddress} network={'goerli'} />
</div> </div>
) )
}
export default ChequebookDeployFund export default ChequebookDeployFund
@@ -1,39 +1,38 @@
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/' import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
import MuiAlert from '@material-ui/lab/Alert' import MuiAlert from '@material-ui/lab/Alert'
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/' import { ExpandMoreSharp } from '@material-ui/icons/'
import ConnectToHost from '../../../components/ConnectToHost' import ConnectToHost from '../../../components/ConnectToHost'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import type { Health } from '@ethersphere/bee-js' import { debugApiHost } from '../../../constants'
interface Props { type Props = StatusHookCommon
nodeHealth: Health | null
debugApiHost: string export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
if (isLoading) return null
const changeDebugApiUrl = (
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
<span style={{ marginRight: '15px' }}>
Debug API (<Typography variant="button">{debugApiHost}</Typography>)
</span>
<ConnectToHost hostName={'debug_api_host'} defaultHost={debugApiHost} />
</div>
)
if (isOk) {
return changeDebugApiUrl
} }
export default function NodeConnectionCheck(props: Props): ReactElement {
return ( return (
<div> <div>
<p>Connect to Bee Node Debug API</p> {changeDebugApiUrl}
<div> <div>
<div style={{ display: 'flex', marginBottom: '25px' }}>
{props.nodeHealth?.status === 'ok' ? (
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
) : (
<Error style={{ color: '#c9201f', marginRight: '7px', height: '18px' }} />
)}
<span style={{ marginRight: '15px' }}>
Debug API (<Typography variant="button">{props.debugApiHost}</Typography>)
</span>
<ConnectToHost hostName={'debug_api_host'} defaultHost={props.debugApiHost} />
</div>
<div>
{props.nodeHealth?.status !== 'ok' ? (
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}> <Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
We cannot connect to your nodes debug API at{' '} We cannot connect to your nodes debug API at <Typography variant="button">{debugApiHost}</Typography>. Please
<Typography variant="button">{props.debugApiHost}</Typography>. Please check the following to troubleshoot check the following to troubleshoot your issue.
your issue.
<Accordion style={{ marginTop: '20px' }}> <Accordion style={{ marginTop: '20px' }}>
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header"> <AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
<Typography>Troubleshoot</Typography> <Typography>Troubleshoot</Typography>
@@ -41,18 +40,16 @@ export default function NodeConnectionCheck(props: Props): ReactElement {
<AccordionDetails> <AccordionDetails>
<Typography component="div"> <Typography component="div">
<ol> <ol>
<li> <li>Check the status of your node by running the below command to see if your node is running.</li>
Check the status of your node by running the below command to see if your node is running.
</li>
<CodeBlockTabs <CodeBlockTabs
showLineNumbers showLineNumbers
linux={`sudo systemctl status bee`} linux={`sudo systemctl status bee`}
mac={`brew services status swarm-bee`} mac={`brew services status swarm-bee`}
/> />
<li> <li>
If your node is running, check your firewall settings to make sure that port 1635 (or your If your node is running, check your firewall settings to make sure that port 1635 (or your custom
custom specified port) is bound to localhost. If your node is not running try executing the specified port) is bound to localhost. If your node is not running try executing the below command
below command to start your bee node to start your bee node
</li> </li>
<MuiAlert <MuiAlert
style={{ marginTop: '10px', marginBottom: '10px' }} style={{ marginTop: '10px', marginBottom: '10px' }}
@@ -61,9 +58,9 @@ export default function NodeConnectionCheck(props: Props): ReactElement {
severity="error" severity="error"
> >
Your debug node API should never be completely open to the internet. If you want to connect Your debug node API should never be completely open to the internet. If you want to connect
remotely, make sure your firewall settings are set to only allow specific trusted IP addresses remotely, make sure your firewall settings are set to only allow specific trusted IP addresses and
and block all other ports. A simple google search for &quot;what is my ip&quot; will show you block all other ports. A simple google search for &quot;what is my ip&quot; will show you your
your computers public IP address to allow. computers public IP address to allow.
</MuiAlert> </MuiAlert>
<CodeBlockTabs <CodeBlockTabs
showLineNumbers showLineNumbers
@@ -77,11 +74,11 @@ export default function NodeConnectionCheck(props: Props): ReactElement {
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`} mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
/> />
<li> <li>
Lastly, check your nodes configuration settings to validate the debug API is enabled and the Lastly, check your nodes configuration settings to validate the debug API is enabled and the Cross
Cross Origin Resource Sharing (CORS) setting is configured to allow your host. Config parameter{' '} Origin Resource Sharing (CORS) setting is configured to allow your host. Config parameter{' '}
<strong>debug-api-enable</strong> must be set to <strong>true</strong> and{' '} <strong>debug-api-enable</strong> must be set to <strong>true</strong> and{' '}
<strong>cors-allowed-origins</strong> must be set to your host domain or IP. If edits are made <strong>cors-allowed-origins</strong> must be set to your host domain or IP. If edits are made to
to the configuration run the restart command below for changes to take effect. the configuration run the restart command below for changes to take effect.
</li> </li>
<CodeBlockTabs <CodeBlockTabs
showLineNumbers showLineNumbers
@@ -93,8 +90,6 @@ export default function NodeConnectionCheck(props: Props): ReactElement {
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
</Typography> </Typography>
) : null}
</div>
</div> </div>
</div> </div>
) )
@@ -1,34 +1,27 @@
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
import { Typography } from '@material-ui/core/' import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import EthereumAddress from '../../../components/EthereumAddress' import EthereumAddress from '../../../components/EthereumAddress'
import type { NodeAddresses } from '@ethersphere/bee-js'
interface Props { type Props = StatusEthereumConnectionHook
nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
}
export default function EthereumConnectionCheck(props: Props): ReactElement { export default function EthereumConnectionCheck({ isLoading, isOk, nodeAddresses }: Props): ReactElement | null {
if (isLoading) return null
if (isOk) {
return ( return (
<div> <div>
<p>Connect to the ethereum blockchain.</p> <Typography variant="subtitle1" gutterBottom>
<div style={{ marginBottom: '10px' }}> Node Address
{ </Typography>
// FIXME: this should be broken up <EthereumAddress address={nodeAddresses?.ethereum} network={'goerli'} />
/* eslint-disable no-nested-ternary */
props.nodeAddresses?.ethereum ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your connected to the Ethereum network</span>
</div> </div>
) : props.isLoadingNodeAddresses ? null : ( )
<div> }
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>Your not connected to the Ethereum network. </span> return (
<p> <p>
Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your chequebook
chequebook contract. You can run{' '} contract. You can run{' '}
<a href="https://github.com/goerli/testnet" rel="noreferrer" target="_blank"> <a href="https://github.com/goerli/testnet" rel="noreferrer" target="_blank">
your own Goerli node your own Goerli node
</a> </a>
@@ -40,17 +33,8 @@ export default function EthereumConnectionCheck(props: Props): ReactElement {
<a href="https://infura.io/" rel="noreferrer" target="_blank"> <a href="https://infura.io/" rel="noreferrer" target="_blank">
Infura Infura
</a> </a>
. By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, . By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, simply change
simply change your <strong>--swap-endpoint</strong> in your configuration file. your <strong>--swap-endpoint</strong> in your configuration file.
</p> </p>
</div>
) /* eslint-enable no-nested-ternary */
}
</div>
<Typography variant="subtitle1" gutterBottom>
Node Address
</Typography>
<EthereumAddress address={props.nodeAddresses?.ethereum} network={'goerli'} />
</div>
) )
} }
@@ -1,35 +1,29 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/' import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/' import { ExpandMoreSharp } from '@material-ui/icons/'
import ConnectToHost from '../../../components/ConnectToHost' import ConnectToHost from '../../../components/ConnectToHost'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import { apiHost } from '../../../constants'
interface Props { type Props = StatusHookCommon
nodeApiHealth: boolean
apiHost: string export default function NodeConnectionCheck({ isLoading, isOk }: Props): ReactElement | null {
} if (isLoading) return null
export default function NodeConnectionCheck(props: Props): ReactElement {
return ( return (
<div> <div>
<p>Connect to Bee Node API</p>
<div style={{ display: 'flex', marginBottom: '25px' }}> <div style={{ display: 'flex', marginBottom: '25px' }}>
{props.nodeApiHealth ? (
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
) : (
<Error style={{ color: '#c9201f', marginRight: '7px', height: '18px' }} />
)}
<span style={{ marginRight: '15px' }}> <span style={{ marginRight: '15px' }}>
Node API (<Typography variant="button">{props.apiHost}</Typography>) Node API (<Typography variant="button">{apiHost}</Typography>)
</span> </span>
<ConnectToHost hostName="api_host" defaultHost={props.apiHost} /> <ConnectToHost hostName="api_host" defaultHost={apiHost} />
</div> </div>
<div> <div>
{!props.nodeApiHealth ? ( {!isOk && (
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}> <Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
We cannot connect to your nodes API at <Typography variant="button">{props.apiHost}</Typography>. Please We cannot connect to your nodes API at <Typography variant="button">{apiHost}</Typography>. Please check the
check the following to troubleshoot your issue. following to troubleshoot your issue.
<Accordion style={{ marginTop: '20px' }}> <Accordion style={{ marginTop: '20px' }}>
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header"> <AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
<Typography>Troubleshoot</Typography> <Typography>Troubleshoot</Typography>
@@ -64,7 +58,7 @@ export default function NodeConnectionCheck(props: Props): ReactElement {
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
</Typography> </Typography>
) : null} )}
</div> </div>
</div> </div>
) )
+25 -33
View File
@@ -1,42 +1,19 @@
import React, { ReactElement } from 'react' import type { ReactElement } from 'react'
import { Typography } from '@material-ui/core/' import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import { Topology } from '@ethersphere/bee-js'
interface Props { type Props = StatusTopologyHook
nodeTopology: Topology | null
isLoadingNodeTopology: boolean
}
export default function PeerConnection(props: Props): ReactElement { export default function PeerConnection({ isLoading, isOk, topology }: Props): ReactElement | null {
return ( if (isLoading) return null
<div>
<p>Connect to Peers</p> const peers = (
<div style={{ marginBottom: '10px' }}> <div style={{ display: 'flex', marginTop: '15px' }}>
html_url
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.nodeTopology?.connected && props.nodeTopology?.connected > 0 ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your connected to {props.nodeTopology.connected} peers!</span>
</div>
) : props.isLoadingNodeTopology ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>Your node is not connected to any peers</span>
</div>
) /* eslint-enable no-nested-ternary */
}
</div>
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}> <div style={{ marginRight: '30px' }}>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary"> <Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Connected Peers</span> <span>Connected Peers</span>
</Typography> </Typography>
<Typography component="h2" variant="h5"> <Typography component="h2" variant="h5">
{props.nodeTopology?.connected} {topology?.connected ? topology.connected : '-'}
</Typography> </Typography>
</div> </div>
<div> <div>
@@ -44,10 +21,25 @@ export default function PeerConnection(props: Props): ReactElement {
<span>Discovered Nodes</span> <span>Discovered Nodes</span>
</Typography> </Typography>
<Typography component="h2" variant="h5"> <Typography component="h2" variant="h5">
{props.nodeTopology?.population} {topology?.population ? topology.population : '-'}
</Typography> </Typography>
</div> </div>
</div> </div>
</div> )
if (isOk) {
return (
<>
<span>You are connected to peers!</span>
{peers}
</>
)
}
return (
<>
<span>Your node is not connected to any peers</span>
{peers}
</>
) )
} }
+47 -60
View File
@@ -1,70 +1,26 @@
import React, { ReactElement } from 'react' import type { ReactElement } from 'react'
import { Typography } from '@material-ui/core/' import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import { Health } from '@ethersphere/bee-js'
interface Props { type Props = StatusNodeVersionHook
beeRelease: LatestBeeRelease | null
isLoadingBeeRelease: boolean
nodeHealth: Health | null
}
export default function VersionCheck(props: Props): ReactElement { export default function VersionCheck({
return ( isLoading,
<div> isOk,
<p> userVersion,
Check to make sure the latest version of{' '} latestVersion,
<a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank"> latestUrl,
Bee }: Props): ReactElement | null {
</a>{' '} if (isLoading) return null
is running
</p> const version = (
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.beeRelease && props.beeRelease.name === `v${props.nodeHealth?.version?.split('-')[0]}` ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your running the latest version of Bee</span>
</div>
) : props.isLoadingBeeRelease ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>
Your Bee version is out of date. Please update to the{' '}
<a href={props.beeRelease?.html_url} rel="noreferrer" target="_blank">
latest
</a>{' '}
before continuing. Rerun the installation script below to upgrade. Reference the docs for help with
updating.{' '}
<a
href="https://docs.ethswarm.org/docs/installation/manual#upgrading-bee"
rel="noreferrer"
target="_blank"
>
Docs
</a>
</span>
<CodeBlockTabs
showLineNumbers
linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${
props.beeRelease?.name
}/bee_${props.nodeHealth?.version?.split('-')[0]}_amd64.deb\nsudo dpkg -i bee_${
props.nodeHealth?.version?.split('-')[0]
}_amd64.deb`}
mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`}
/>
</div>
) /* eslint-enable no-nested-ternary */
}
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}> <div style={{ marginRight: '30px' }}>
<p> <p>
<span>Current Version</span> <span>User Version</span>
</p> </p>
<Typography component="h5" variant="h5"> <Typography component="h5" variant="h5">
<span>{props.nodeHealth?.version ? ` v${props.nodeHealth?.version?.split('-')[0]}` : '-'}</span> <span>{userVersion}</span>
</Typography> </Typography>
</div> </div>
<div> <div>
@@ -72,10 +28,41 @@ export default function VersionCheck(props: Props): ReactElement {
<span>Latest Version</span> <span>Latest Version</span>
</p> </p>
<Typography component="h5" variant="h5"> <Typography component="h5" variant="h5">
<span>{props.beeRelease && props.beeRelease.name ? props.beeRelease.name : '-'}</span> <span>{latestVersion}</span>
</Typography> </Typography>
</div> </div>
</div> </div>
</div> )
// Running latest bee version
if (isOk) {
return (
<>
<span>You are running the latest version of Bee</span>
{version}
</>
)
}
// Old version or not connected to bee debug API
return (
<>
<span>
Your Bee version is out of date. Please update to the{' '}
<a href={latestUrl} rel="noreferrer" target="_blank">
latest
</a>{' '}
before continuing. Rerun the installation script below to upgrade. Reference the docs for help with updating.{' '}
<a href="https://docs.ethswarm.org/docs/installation/manual#upgrading-bee" rel="noreferrer" target="_blank">
Docs
</a>
</span>
<CodeBlockTabs
showLineNumbers
linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${latestVersion}/bee_${latestVersion}_amd64.deb\nsudo dpkg -i bee_${latestVersion}_amd64.deb`}
mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`}
/>
{version}
</>
) )
} }
+41 -69
View File
@@ -4,23 +4,15 @@ import { Link } from 'react-router-dom'
import { createStyles, makeStyles } from '@material-ui/core/styles' import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/' import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/'
import { CheckCircle, Error, ArrowRight, ArrowDropUp } from '@material-ui/icons/' import { CheckCircle, Error, ArrowRight, ArrowDropUp } from '@material-ui/icons/'
import { Skeleton } from '@material-ui/lab' import { NodeAddresses, Topology } from '@ethersphere/bee-js'
import type { Health, NodeAddresses, Topology } from '@ethersphere/bee-js'
const useStyles = makeStyles(() => const useStyles = makeStyles(() =>
createStyles({ createStyles({
root: { root: {
display: 'flex', display: 'flex',
}, flex: '1 1 auto',
details: {
display: 'flex',
flex: '1 0 auto',
flexDirection: 'column', flexDirection: 'column',
}, },
content: {
flex: '1 0 auto',
},
status: { status: {
color: '#2145a0', color: '#2145a0',
backgroundColor: '#e1effe', backgroundColor: '#e1effe',
@@ -29,54 +21,48 @@ const useStyles = makeStyles(() =>
) )
interface Props { interface Props {
nodeHealth: Health | null nodeAddresses: NodeAddresses | null
loadingNodeHealth: boolean nodeTopology: Topology | null
beeRelease: LatestBeeRelease | null userBeeVersion: string | null
loadingBeeRelease: boolean latestBeeVersion: string | null
nodeAddresses: NodeAddresses isOk: boolean
nodeTopology: Topology
loadingNodeTopology: boolean
setStatusChecksVisible: (value: boolean) => void
} }
function StatusCard(props: Props): ReactElement { function StatusCard({
userBeeVersion,
nodeAddresses,
nodeTopology,
latestBeeVersion,
isOk,
}: Props): ReactElement | null {
const classes = useStyles() const classes = useStyles()
const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<boolean>(false) const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<boolean>(false)
return ( return (
<div> <Card>
<Card className={classes.root}> <CardContent className={classes.root}>
{!props.loadingNodeHealth && props.nodeHealth ? (
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography component="h5" variant="h5" style={{ display: 'flex', justifyContent: 'space-between' }}> <Typography component="h5" variant="h5" style={{ display: 'flex', justifyContent: 'space-between' }}>
{props.nodeHealth.status === 'ok' ? ( {isOk && (
<div> <div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px' }} /> <CheckCircle style={{ color: '#32c48d', marginRight: '7px' }} />
<span>Connected to Bee Node</span> <span>Your Bee node is running as expected</span>
</div> </div>
) : ( )}
{!isOk && (
<div> <div>
<Error style={{ color: '#c9201f', marginRight: '7px' }} /> <Error style={{ color: '#c9201f', marginRight: '7px' }} />
<span>Could not connect to Bee Node</span> <span>Could not connect to Bee Node</span>
</div> </div>
)} )}
<Button
variant="outlined"
color="primary"
size="small"
style={{ marginLeft: '12px' }}
onClick={() => props.setStatusChecksVisible(true)}
>
View Status Checks
</Button>
</Typography> </Typography>
{isOk && (
<>
<div style={{ marginBottom: '20px' }}> <div style={{ marginBottom: '20px' }}>
<span style={{ marginRight: '20px' }}>Discovered Nodes: {props.nodeTopology.population}</span> <span style={{ marginRight: '20px' }}>Discovered Nodes: {nodeTopology?.population}</span>
<span style={{ marginRight: '20px' }}> <span style={{ marginRight: '20px' }}>
<span>Connected Peers: </span> <span>Connected Peers: </span>
<Link to="/peers/">{props.nodeTopology.connected}</Link> <Link to="/peers/">{nodeTopology?.connected}</Link>
</span> </span>
</div> </div>
<div> <div>
@@ -84,39 +70,32 @@ function StatusCard(props: Props): ReactElement {
<span>AGENT: </span> <span>AGENT: </span>
<a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank"> <a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank">
Bee Bee
</a> </a>{' '}
<span>{props.nodeHealth?.version ? ` v${props.nodeHealth.version}` : '-'}</span> <span>{userBeeVersion || '-'}</span>
{ {userBeeVersion && latestBeeVersion && userBeeVersion === latestBeeVersion ? (
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.beeRelease && props.beeRelease.name === `v${props.nodeHealth?.version?.split('-')[0]}` ? (
<Chip <Chip
style={{ marginLeft: '7px', color: '#2145a0' }} style={{ marginLeft: '7px', color: '#2145a0' }}
size="small" size="small"
label="latest" label="latest"
className={classes.status} className={classes.status}
/> />
) : props.loadingBeeRelease ? (
''
) : ( ) : (
<Typography variant="button">update</Typography> <Typography variant="button">update</Typography>
) )}
/* eslint-enable no-nested-ternary */
}
</Typography> </Typography>
<Typography component="div" variant="subtitle2" gutterBottom> <Typography component="div" variant="subtitle2" gutterBottom>
<span>PUBLIC KEY: </span> <span>PUBLIC KEY: </span>
<span>{props.nodeAddresses.public_key ? props.nodeAddresses.public_key : '-'}</span> <span>{nodeAddresses?.public_key ? nodeAddresses.public_key : '-'}</span>
</Typography> </Typography>
<Typography component="div" variant="subtitle2" gutterBottom> <Typography component="div" variant="subtitle2" gutterBottom>
<span>PSS PUBLIC KEY: </span> <span>PSS PUBLIC KEY: </span>
<span>{props.nodeAddresses.pss_public_key ? props.nodeAddresses.pss_public_key : '-'}</span> <span>{nodeAddresses?.pss_public_key ? nodeAddresses.pss_public_key : '-'}</span>
</Typography> </Typography>
<Typography component="div" variant="subtitle2" gutterBottom> <Typography component="div" variant="subtitle2" gutterBottom>
<Typography component="div" style={{ marginTop: '20px' }}>
<span>OVERLAY ADDRESS (PEER ID): </span> <span>OVERLAY ADDRESS (PEER ID): </span>
<span>{props.nodeAddresses.overlay ? props.nodeAddresses.overlay : '-'}</span> <span>{nodeAddresses?.overlay ? nodeAddresses.overlay : '-'}</span>
</Typography> </Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<Typography component="div" onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}> <Typography component="div" onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}>
<Button color="primary" style={{ padding: 0, marginTop: '6px' }}> <Button color="primary" style={{ padding: 0, marginTop: '6px' }}>
{underlayAddressesVisible ? ( {underlayAddressesVisible ? (
@@ -127,26 +106,19 @@ function StatusCard(props: Props): ReactElement {
<span>Underlay Addresses</span> <span>Underlay Addresses</span>
</Button> </Button>
</Typography> </Typography>
{underlayAddressesVisible ? ( {underlayAddressesVisible && (
<div> <div>
{props.nodeAddresses.underlay {nodeAddresses?.underlay.map(item => (
? props.nodeAddresses.underlay.map(item => <li key={item}>{item}</li>) <li key={item}>{item}</li>
: '-'} ))}
</div>
) : null}
</Typography>
</div>
</CardContent>
</div>
) : (
<div style={{ padding: '16px' }}>
<Skeleton width={650} height={32} animation="wave" />
<Skeleton width={650} height={24} animation="wave" />
<Skeleton width={650} height={24} animation="wave" />
</div> </div>
)} )}
</Card> </Typography>
</div> </div>
</>
)}
</CardContent>
</Card>
) )
} }
+55 -123
View File
@@ -1,143 +1,75 @@
import { useState, useEffect, ReactElement } from 'react' import { ReactElement } from 'react'
import axios from 'axios'
import { Container, CircularProgress } from '@material-ui/core' import { Container, CircularProgress } from '@material-ui/core'
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 {
useApiHealth, useStatusEthereumConnection,
useDebugApiHealth, useStatusNodeVersion,
useApiNodeAddresses, useStatusDebugConnection,
useApiChequebookAddress, useStatusConnection,
useApiNodeTopology, useStatusTopology,
useApiChequebookBalance, useStatusChequebook,
} from '../../hooks/apiHooks' } from '../../hooks/status'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: '100%',
display: 'grid',
rowGap: theme.spacing(3),
},
}),
)
export default function Status(): ReactElement { export default function Status(): ReactElement {
const [beeRelease, setBeeRelease] = useState<LatestBeeRelease | null>(null) const classes = useStyles()
const [isLoadingBeeRelease, setIsLoadingBeeRelease] = useState<boolean>(false)
const [apiHost, setApiHost] = useState('') const nodeVersion = useStatusNodeVersion()
const [debugApiHost, setDebugApiHost] = useState('') const ethereumConnection = useStatusEthereumConnection()
const debugApiConnection = useStatusDebugConnection()
const apiConnection = useStatusConnection()
const topology = useStatusTopology()
const chequebook = useStatusChequebook()
const [statusChecksVisible, setStatusChecksVisible] = useState<boolean>(false) const checks = [nodeVersion, ethereumConnection, debugApiConnection, apiConnection, topology, chequebook]
const { health, isLoadingHealth } = useApiHealth() // If any check data are still loading
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth() if (!checks.every(c => !c.isLoading)) {
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { topology: nodeTopology, isLoading: isLoadingNodeTopology } = useApiNodeTopology()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const fetchLatestBeeRelease = () => {
setIsLoadingBeeRelease(true)
axios
.get(`${process.env.REACT_APP_BEE_GITHUB_REPO_URL}/releases/latest`)
.then(res => {
setBeeRelease(res.data)
})
.catch(() => {
// FIXME: should do something about the error
})
.finally(() => {
setIsLoadingBeeRelease(false)
})
}
const fetchApiHost = () => {
let apiHost
if (sessionStorage.getItem('api_host')) {
apiHost = String(sessionStorage.getItem('api_host') || '')
} else {
apiHost = String(process.env.REACT_APP_BEE_HOST)
}
setApiHost(apiHost)
}
const fetchDebugApiHost = () => {
let debugApiHost
if (sessionStorage.getItem('debug_api_host')) {
debugApiHost = String(sessionStorage.getItem('debug_api_host') || '')
} else {
debugApiHost = String(process.env.REACT_APP_BEE_DEBUG_HOST)
}
setDebugApiHost(debugApiHost)
}
useEffect(() => {
fetchApiHost()
fetchDebugApiHost()
fetchLatestBeeRelease()
}, [])
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
return ( return (
<div>
{nodeHealth?.status === 'ok' &&
health &&
beeRelease &&
beeRelease.name === `v${nodeHealth?.version?.split('-')[0]}` &&
nodeAddresses?.ethereum &&
chequebookAddress?.chequebookaddress &&
chequebookBalance &&
chequebookBalance?.totalBalance > 0 &&
nodeTopology?.connected &&
nodeTopology?.connected > 0 &&
!statusChecksVisible ? (
<div>
<StatusCard
nodeHealth={nodeHealth}
loadingNodeHealth={isLoadingNodeHealth}
beeRelease={beeRelease}
loadingBeeRelease={isLoadingBeeRelease}
nodeAddresses={nodeAddresses}
loadingNodeTopology={isLoadingNodeTopology}
nodeTopology={nodeTopology}
setStatusChecksVisible={setStatusChecksVisible}
/>
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
/>
</div>
) : isLoadingNodeHealth ||
isLoadingHealth ||
isLoadingChequebookAddress ||
isLoadingNodeTopology ||
isLoadingBeeRelease ||
isLoadingNodeAddresses ||
isLoadingBeeRelease ||
isLoadingChequebookBalance ? (
<Container style={{ textAlign: 'center', padding: '50px' }}> <Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress /> <CircularProgress />
</Container> </Container>
) : ( )
<NodeSetupWorkflow }
beeRelease={beeRelease}
isLoadingBeeRelease={isLoadingBeeRelease} return (
nodeHealth={nodeHealth} <div className={classes.root}>
isLoadingNodeHealth={isLoadingNodeHealth} <StatusCard
nodeAddresses={nodeAddresses} userBeeVersion={nodeVersion.userVersion}
isLoadingNodeAddresses={isLoadingNodeAddresses} latestBeeVersion={nodeVersion.latestVersion}
nodeTopology={nodeTopology} isOk={checks.every(c => c.isOk)}
isLoadingNodeTopology={isLoadingNodeTopology} nodeTopology={topology.topology}
nodeApiHealth={health} nodeAddresses={ethereumConnection.nodeAddresses}
isLoadingHealth={isLoadingHealth} />
chequebookAddress={chequebookAddress} {ethereumConnection.nodeAddresses && chequebook.chequebookAddress && (
isLoadingChequebookAddress={isLoadingChequebookAddress} <EthereumAddressCard
chequebookBalance={chequebookBalance} nodeAddresses={ethereumConnection.nodeAddresses}
isLoadingChequebookBalance={isLoadingChequebookBalance} isLoadingNodeAddresses={ethereumConnection.isLoading}
apiHost={apiHost} chequebookAddress={chequebook.chequebookAddress}
debugApiHost={debugApiHost} isLoadingChequebookAddress={chequebook.isLoading}
setStatusChecksVisible={setStatusChecksVisible}
/> />
)} )}
<NodeSetupWorkflow
nodeVersion={nodeVersion}
ethereumConnection={ethereumConnection}
debugApiConnection={debugApiConnection}
apiConnection={apiConnection}
topology={topology}
chequebook={chequebook}
/>
</div> </div>
) )
} }
+21
View File
@@ -4,3 +4,24 @@ interface LatestBeeRelease {
name: string name: string
html_url: string html_url: string
} }
interface StatusHookCommon {
isLoading: boolean
isOk: boolean
}
interface StatusNodeVersionHook extends StatusHookCommon {
userVersion: string
latestVersion: string
latestUrl: string
}
interface StatusEthereumConnectionHook extends StatusHookCommon {
nodeAddresses: NodeAddresses | null
}
interface StatusTopologyHook extends StatusHookCommon {
topology: Topology | null
}
interface StatusChequebookHook extends StatusHookCommon {
chequebookBalance: ChequebookBalanceResponse | null
chequebookAddress: ChequebookAddressResponse | null
}