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
+85 -163
View File
@@ -1,7 +1,7 @@
import { ReactElement, useEffect, useState } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp } from '@material-ui/icons/'
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp, Autorenew } from '@material-ui/icons/'
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
@@ -9,17 +9,11 @@ import VersionCheck from './SetupSteps/VersionCheck'
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
import PeerConnection from './SetupSteps/PeerConnection'
import type {
ChequebookAddressResponse,
ChequebookBalanceResponse,
Health,
NodeAddresses,
Topology,
} from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: theme.spacing(2),
width: '100%',
},
button: {
@@ -29,147 +23,88 @@ const useStyles = makeStyles((theme: Theme) =>
actionsContainer: {
margin: theme.spacing(2),
},
resetContainer: {
padding: theme.spacing(5),
},
}),
)
function getSteps() {
return [
'Debug Connection Check',
'Version Check',
'Connect to Ethereum Blockchain',
'Deploy and Fund Chequebook',
'Node Connection Check',
'Connect to Peers',
]
interface Step {
label: string
condition: boolean
isLoading: boolean
component: ReactElement
}
interface Props {
nodeHealth: Health | null
nodeApiHealth: boolean
nodeAddresses: NodeAddresses | null
chequebookAddress: ChequebookAddressResponse | null
chequebookBalance: ChequebookBalanceResponse | null
beeRelease: LatestBeeRelease | null
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
nodeVersion: StatusNodeVersionHook
ethereumConnection: StatusEthereumConnectionHook
debugApiConnection: StatusHookCommon
apiConnection: StatusHookCommon
topology: StatusTopologyHook
chequebook: StatusChequebookHook
}
function getStepContent(step: number, props: Props) {
const {
nodeHealth,
debugApiHost,
beeRelease,
isLoadingBeeRelease,
nodeAddresses,
isLoadingNodeAddresses,
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
export default function NodeSetupWorkflow({
nodeVersion,
ethereumConnection,
debugApiConnection,
apiConnection,
topology,
chequebook,
}: Props): ReactElement {
const classes = useStyles()
const [activeStep, setActiveStep] = useState(0)
const [completed, setCompleted] = useState<{ [k: number]: boolean }>({})
const steps = getSteps()
const [activeStep, setActiveStep] = useState(-1)
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(() => {
const handleComplete = (index: number) => {
const newCompleted = completed
newCompleted[index] = true
setCompleted(newCompleted)
}
// If the user already changed the active step we don't want to overwrite it
if (activeStep > 0 && activeStep < steps.length) return
const evaluateNodeStatus = () => {
if (nodeHealth?.status === 'ok') {
handleComplete(0)
setActiveStep(1)
}
// Select first step that is not OK
for (let i = 0; i < steps.length; ++i) {
if (!steps[i].condition) {
setActiveStep(i)
if (beeRelease && beeRelease.name === `v${nodeHealth?.version?.split('-')[0]}`) {
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)
return
}
}
evaluateNodeStatus()
}, [
nodeHealth,
nodeApiHealth,
nodeAddresses,
chequebookAddress,
beeRelease,
chequebookBalance,
nodeTopology,
completed,
])
}, [steps])
const handleNext = () => {
setActiveStep(prevActiveStep => prevActiveStep + 1)
@@ -179,13 +114,9 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
setActiveStep(prevActiveStep => prevActiveStep - 1)
}
const handleSetupComplete = () => {
setStatusChecksVisible(false)
}
return (
<div className={classes.root}>
<Typography variant="h4" gutterBottom>
<Paper className={classes.root}>
<Typography variant="h5" gutterBottom>
Node Setup
<span style={{ marginLeft: '25px' }}>
<Button variant="outlined" size="small" onClick={() => window.location.reload()}>
@@ -195,20 +126,22 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
</span>
</Typography>
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
{steps.map((label, index) => (
{steps.map(({ label, condition, component, isLoading }, index) => (
<Step key={label}>
<StepLabel
onClick={() => setActiveStep(index === activeStep ? 6 : index)}
disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
StepIconComponent={() => {
if (completed[index]) {
return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
} else {
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
}
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
if (condition) return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
}}
>
<StepButton
onClick={() => setActiveStep(index === activeStep ? 6 : index)}
disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
style={{ justifyContent: 'space-between' }}
>
<div style={{ display: 'flex' }}>
@@ -220,14 +153,14 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
</StepButton>
</StepLabel>
<StepContent>
<Typography component="div">{getStepContent(index, props)}</Typography>
<Typography component="div">{component}</Typography>
<div className={classes.actionsContainer}>
<div>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
Back
</Button>
<Button variant="contained" color="primary" onClick={handleNext} className={classes.button}>
Next
{index < steps.length - 1 ? 'Next' : 'Finish'}
</Button>
</div>
</div>
@@ -235,17 +168,6 @@ export default function NodeSetupWorkflow(props: Props): ReactElement {
</Step>
))}
</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>
) : null}
</div>
</Paper>
)
}