feat: status page redesign (#214)

* feat: initial rewrite without status indicators

* feat: status icon as a component and add to the node setup

* feat: added input list item component

* feat: improved the topology status info

* fix: disabled state of the buttons

* chore: removed unused components

* chore: remove debug console log

* fix: deposit modal helper text
This commit is contained in:
Vojtech Simetka
2021-10-06 18:38:54 +02:00
committed by GitHub
parent ecbc116475
commit b666cd2657
20 changed files with 453 additions and 636 deletions
-50
View File
@@ -1,50 +0,0 @@
import React, { ReactElement, useState } from 'react'
import { TextField, Button } from '@material-ui/core'
interface Props {
defaultHost?: string
setHost: (host: string) => void
}
export default function ConnectToHost(props: Props): ReactElement {
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
const [host, setHost] = useState('')
const handleNewHostConnection = () => {
if (host) {
props.setHost(host)
toggleHostInputVisibility(!hostInputVisible)
}
}
return (
<div>
{hostInputVisible ? (
<div style={{ display: 'flex' }}>
<TextField
defaultValue={props.defaultHost}
label="Enter host"
variant="outlined"
size="small"
onChange={e => setHost(e.target.value)}
style={{ marginRight: '15px', minWidth: '300px' }}
/>
<Button onClick={() => handleNewHostConnection()} size="small" variant="outlined">
Connect
</Button>
<Button
style={{ marginLeft: '7px' }}
onClick={() => toggleHostInputVisibility(!hostInputVisible)}
size="small"
>
Cancel
</Button>
</div>
) : (
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size="small" variant="outlined">
Change host
</Button>
)}
</div>
)
}
-59
View File
@@ -1,59 +0,0 @@
import { ReactElement } from 'react'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography } from '@material-ui/core/'
import EthereumAddress from '../components/EthereumAddress'
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
const useStyles = makeStyles(() =>
createStyles({
root: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-around',
flexWrap: 'wrap',
},
details: {
display: 'flex',
flexDirection: 'column',
},
content: {
flex: '1 0 auto',
},
}),
)
interface Props {
nodeAddresses: NodeAddresses | null
chequebookAddress: ChequebookAddressResponse | null
}
function EthereumAddressCard(props: Props): ReactElement {
const classes = useStyles()
return (
<Card className={classes.root}>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Ethereum Address
</Typography>
<EthereumAddress address={props.nodeAddresses?.ethereum} />
</CardContent>
</div>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Chequebook Contract Address
</Typography>
<EthereumAddress address={props.chequebookAddress?.chequebookAddress} />
</CardContent>
</div>
</Card>
)
}
export default EthereumAddressCard
+1 -1
View File
@@ -24,7 +24,7 @@ const useStyles = makeStyles((theme: Theme) =>
) )
interface Props { interface Props {
label?: string label?: ReactNode
value?: ReactNode value?: ReactNode
tooltip?: string tooltip?: string
} }
+103
View File
@@ -0,0 +1,103 @@
import { ReactElement, useState } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import Collapse from '@material-ui/core/Collapse'
import { ListItem, Typography, Grid, IconButton, InputBase, Button } from '@material-ui/core'
import { Edit, Minus, RotateCcw, Check } from 'react-feather'
import ExpandableListItemActions from './ExpandableListItemActions'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
header: {
backgroundColor: theme.palette.background.paper,
marginBottom: theme.spacing(0.25),
borderLeft: `${theme.spacing(0.25)}px solid rgba(0,0,0,0)`,
wordBreak: 'break-word',
},
headerOpen: {
borderLeft: `${theme.spacing(0.25)}px solid ${theme.palette.primary.main}`,
},
copyValue: {
cursor: 'pointer',
padding: theme.spacing(1),
borderRadius: 0,
'&:hover': {
backgroundColor: '#fcf2e8',
color: theme.palette.primary.main,
},
},
content: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
keyMargin: {
marginRight: theme.spacing(1),
},
}),
)
interface Props {
label: string
value: string
onConfirm: (value: string) => void
}
export default function ExpandableListItemKey({ label, value, onConfirm }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState(false)
const [inputValue, setInputValue] = useState(value)
const toggleOpen = () => setOpen(!open)
return (
<>
<ListItem className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
<Grid container direction="column" justifyContent="space-between" alignItems="stretch">
<Grid container direction="row" justifyContent="space-between" alignItems="center">
{label && <Typography variant="body1">{label}</Typography>}
<Typography variant="body2">
<div>
{!open && value}
<IconButton size="small" className={classes.copyValue}>
{open ? (
<Minus onClick={toggleOpen} strokeWidth={1} />
) : (
<Edit onClick={toggleOpen} strokeWidth={1} />
)}
</IconButton>
</div>
</Typography>
</Grid>
<Collapse in={open} timeout="auto" unmountOnExit>
<InputBase
value={inputValue}
onChange={e => setInputValue(e.target.value)}
fullWidth
className={classes.content}
autoFocus
/>
</Collapse>
</Grid>
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<ExpandableListItemActions>
<Button
variant="contained"
disabled={inputValue === value}
startIcon={<Check size="1rem" />}
onClick={() => onConfirm(inputValue)}
>
Save
</Button>
<Button
variant="contained"
disabled={inputValue === value}
startIcon={<RotateCcw size="1rem" />}
onClick={() => setInputValue(value)}
>
Cancel
</Button>
</ExpandableListItemActions>
</Collapse>
</>
)
}
+32
View File
@@ -0,0 +1,32 @@
import { ReactElement, ReactNode } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Typography } from '@material-ui/core'
import ListItem from '@material-ui/core/ListItem'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
header: {
backgroundColor: '#F7F7F7',
marginBottom: theme.spacing(0.25),
},
typography: {
color: '#242424',
},
}),
)
interface Props {
children?: ReactNode | ReactNode[]
}
export default function ExpandableListItemNote({ children }: Props): ReactElement | null {
const classes = useStyles()
return (
<ListItem className={classes.header}>
<Typography variant="body1" className={classes.typography}>
{children}
</Typography>
</ListItem>
)
}
+4 -12
View File
@@ -5,6 +5,7 @@ import { ArrowRight } from 'react-feather'
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles' import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
import { ListItemText, ListItemIcon, ListItem, Typography } from '@material-ui/core' import { ListItemText, ListItemIcon, ListItem, Typography } from '@material-ui/core'
import { Context } from '../providers/Bee' import { Context } from '../providers/Bee'
import StatusIcon from './StatusIcon'
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@@ -50,7 +51,7 @@ interface Props {
} }
export default function SideBarItem({ path }: Props): ReactElement { export default function SideBarItem({ path }: Props): ReactElement {
const { status } = useContext(Context) const { status, isLoading } = useContext(Context)
const classes = useStyles() const classes = useStyles()
const location = useLocation() const location = useLocation()
const isSelected = Boolean(matchPath(location.pathname, { path, exact: true })) const isSelected = Boolean(matchPath(location.pathname, { path, exact: true }))
@@ -62,17 +63,8 @@ export default function SideBarItem({ path }: Props): ReactElement {
selected={isSelected} selected={isSelected}
disableRipple disableRipple
> >
<ListItemIcon> <ListItemIcon style={{ marginLeft: '30px' }}>
<span <StatusIcon isOk={status.all} isLoading={isLoading} />
style={{
backgroundColor: status.all ? '#1de600' : '#ff3a52',
height: '14px',
width: '14px',
borderRadius: '50%',
display: 'inline-block',
marginLeft: 30,
}}
/>
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
primary={<Typography className={classes.smallerText}>{`Node ${status.all ? 'OK' : 'Error'}`}</Typography>} primary={<Typography className={classes.smallerText}>{`Node ${status.all ? 'OK' : 'Error'}`}</Typography>}
-43
View File
@@ -1,43 +0,0 @@
import { Card, CardContent, Typography } from '@material-ui/core/'
import { makeStyles } from '@material-ui/core/styles'
import { Skeleton } from '@material-ui/lab'
import type { ReactElement } from 'react'
import { Title } from './Title'
const useStyles = makeStyles({
root: {
minWidth: 275,
},
})
interface Props {
label: string
statistic?: string
loading?: boolean
tooltip?: string
}
export default function StatCard({ loading, label, statistic, tooltip }: Props): ReactElement {
const classes = useStyles()
return (
<Card className={classes.root}>
<CardContent>
{loading && (
<>
<Skeleton width={180} height={25} animation="wave" />
<Skeleton width={180} height={35} animation="wave" />
</>
)}
{!loading && (
<>
<Title label={label} tooltip={tooltip} />
<Typography variant="h5" component="h2">
{statistic}
</Typography>
</>
)}
</CardContent>
</Card>
)
}
+28
View File
@@ -0,0 +1,28 @@
import type { ReactElement } from 'react'
import { CircularProgress } from '@material-ui/core'
interface Props {
isOk: boolean
isLoading?: boolean
size?: number | string
className?: string
}
export default function StatusIcon({ isOk, size, className, isLoading }: Props): ReactElement {
const s = size || '1rem'
if (isLoading) return <CircularProgress size={s} className={className} />
return (
<span
className={className}
style={{
backgroundColor: isOk ? '#1de600' : '#ff3a52',
height: s,
width: s,
borderRadius: '50%',
display: 'inline-block',
}}
/>
)
}
-41
View File
@@ -1,41 +0,0 @@
import { Grid, Tooltip, Typography } from '@material-ui/core/'
import { makeStyles } from '@material-ui/core/styles'
import { Info } from '@material-ui/icons'
import type { ReactElement } from 'react'
interface TitleProps {
label: string
tooltip?: string
}
const useStyles = makeStyles({
title: {
fontSize: 16,
},
})
export function Title({ label, tooltip }: TitleProps): ReactElement {
const classes = useStyles()
if (!tooltip) {
return (
<Typography className={classes.title} color="textSecondary" gutterBottom>
{label}
</Typography>
)
}
// span is needed as Tooltip expects a non-functional element!
return (
<Tooltip title={tooltip}>
<span>
<Grid container direction="row" justify="space-between">
<Typography className={classes.title} color="textSecondary" gutterBottom>
{label}
</Typography>
<Info />
</Grid>
</span>
</Tooltip>
)
}
+2 -3
View File
@@ -1,7 +1,6 @@
import type { Topology } from '@ethersphere/bee-js' import type { Topology } from '@ethersphere/bee-js'
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
import { pickThreshold, ThresholdValues } from '../utils/threshold' import { pickThreshold, ThresholdValues } from '../utils/threshold'
import ExpandableList from './ExpandableList'
import ExpandableListItem from './ExpandableListItem' import ExpandableListItem from './ExpandableListItem'
interface Props { interface Props {
@@ -20,7 +19,7 @@ const TopologyStats = (props: Props): ReactElement => {
const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%' const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%'
return ( return (
<ExpandableList label="Connectivity" defaultOpen> <>
<ExpandableListItem label="Overall Health Indicator" value={percentageText} /> <ExpandableListItem label="Overall Health Indicator" value={percentageText} />
<ExpandableListItem <ExpandableListItem
label="Connected Peers" label="Connected Peers"
@@ -37,7 +36,7 @@ const TopologyStats = (props: Props): ReactElement => {
value={props.topology?.depth.toString()} value={props.topology?.depth.toString()}
tooltip={thresholds.depth.explanation} tooltip={thresholds.depth.explanation}
/> />
</ExpandableList> </>
) )
} }
+1 -1
View File
@@ -12,7 +12,7 @@ export default function DepositModal(): ReactElement {
<WithdrawDepositModal <WithdrawDepositModal
successMessage="Successful deposit." successMessage="Successful deposit."
errorMessage="Error with depositing" errorMessage="Error with depositing"
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node." dialogMessage="Specify the amount of BZZ you would like to deposit to your node."
label="Deposit" label="Deposit"
icon={<Download size="1rem" />} icon={<Download size="1rem" />}
min={new BigNumber(0)} min={new BigNumber(0)}
+2
View File
@@ -56,7 +56,9 @@ export default function Status(): ReactElement {
<ExpandableListItemKey label="Ethereum address" value={nodeAddresses?.ethereum || ''} /> <ExpandableListItemKey label="Ethereum address" value={nodeAddresses?.ethereum || ''} />
<ExpandableListItemKey label="Chequebook contract address" value={chequebookAddress?.chequebookAddress || ''} /> <ExpandableListItemKey label="Chequebook contract address" value={chequebookAddress?.chequebookAddress || ''} />
</ExpandableList> </ExpandableList>
<ExpandableList label="Connectivity" defaultOpen>
<TopologyStats topology={topology} /> <TopologyStats topology={topology} />
</ExpandableList>
</div> </div>
) )
} }
@@ -1,35 +1,48 @@
import { Typography } from '@material-ui/core/' import { useContext } from 'react'
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 ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
interface Props extends StatusHookCommon { const ChequebookDeployFund = (): ReactElement | null => {
chequebookAddress?: string const { status, isLoading, chequebookAddress } = useContext(Context)
} const isOk = status.chequebook
const ChequebookDeployFund = ({ chequebookAddress, isOk }: Props): ReactElement | null => {
return ( return (
<div> <ExpandableList
<p style={{ marginBottom: '20px', display: 'flex' }}>{chequebookAddress && <DepositModal />}</p> label={
<div style={{ marginBottom: '10px' }}> <>
{!isOk && ( <StatusIcon isOk={isOk} isLoading={isLoading} /> Chequebook Deployment & Funding
<div> </>
<span> }
>
<ExpandableListItemNote>
{isOk ? (
'Your chequebook is deployed and funded'
) : (
<>
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
network. You may need to aquire BZZ (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and bridge it network. You may need to aquire BZZ (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and bridge it to
to the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To pay the the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To pay the
transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it to xDai transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it to xDai
network through the <a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '} network through the <a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '}
<a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information. <a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information.
</span> </>
</div>
)} )}
</div> </ExpandableListItemNote>
<Typography variant="subtitle1" gutterBottom> {chequebookAddress && (
Chequebook Address <>
</Typography> <ExpandableListItemKey label="Chequebook Address" value={chequebookAddress.chequebookAddress} />
<EthereumAddress address={chequebookAddress} /> <ExpandableListItemActions>
</div> <DepositModal />
</ExpandableListItemActions>
</>
)}
</ExpandableList>
) )
} }
@@ -1,57 +1,46 @@
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
import MuiAlert from '@material-ui/lab/Alert' import MuiAlert from '@material-ui/lab/Alert'
import { ExpandMoreSharp } from '@material-ui/icons/'
import ConnectToHost from '../../../components/ConnectToHost'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemInput from '../../../components/ExpandableListItemInput'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context as SettingsContext } from '../../../providers/Settings' import { Context as SettingsContext } from '../../../providers/Settings'
import { Context } from '../../../providers/Bee'
type Props = StatusHookCommon export default function NodeConnectionCheck(): ReactElement | null {
const { status, isLoading } = useContext(Context)
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext) const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext)
const isOk = status.debugApiConnection
const changeDebugApiUrl = (
<div style={{ display: 'flex', marginTop: '25px', marginBottom: '25px' }}>
<span style={{ marginRight: '15px' }}>
Debug API (<Typography variant="button">{apiDebugUrl}</Typography>)
</span>
<ConnectToHost
setHost={(host: string) => {
console.log(host) // eslint-disable-line
setDebugApiUrl(host)
}}
defaultHost={apiDebugUrl}
/>
</div>
)
if (isOk) {
return changeDebugApiUrl
}
return ( return (
<div> <ExpandableList
{changeDebugApiUrl} label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Bee Debug API
</>
}
>
<ExpandableListItemNote>
{isOk
? 'The connection to the Bee nodes deug API has been successful'
: 'We cannot connect to your nodes debug API. Please check the following to troubleshoot your issue.'}
</ExpandableListItemNote>
<ExpandableListItemInput label="Bee Debug API" value={apiDebugUrl} onConfirm={setDebugApiUrl} />
<div> {!isOk && (
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}> <ExpandableList level={1} label="Troubleshoot">
We cannot connect to your nodes debug API at <Typography variant="button">{apiDebugUrl}</Typography>. Please <ExpandableListItem
check the following to troubleshoot your issue. label={
<Accordion style={{ marginTop: '20px' }}>
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
<Typography>Troubleshoot</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography component="div">
<ol> <ol>
<li>Check the status of your node by running the below command to see if your node is running.</li> <li>Check the status of your node by running the below command to see if your node is running.</li>
<CodeBlockTabs showLineNumbers linux={`sudo systemctl status bee`} mac={`brew services list`} /> <CodeBlockTabs showLineNumbers linux={`sudo systemctl status bee`} mac={`brew services list`} />
<li> <li>
If your node is running, check your firewall settings to make sure that port 1635 (or your custom If your node is running, check your firewall settings to make sure that port 1635 (or your custom
specified port) is bound to localhost. If your node is not running try executing the below command specified port) is bound to localhost. If your node is not running try executing the below command to
to start your bee node start your bee node
</li> </li>
<MuiAlert <MuiAlert
style={{ marginTop: '10px', marginBottom: '10px' }} style={{ marginTop: '10px', marginBottom: '10px' }}
@@ -59,10 +48,10 @@ export default function NodeConnectionCheck({ isOk }: Props): ReactElement | nul
variant="filled" variant="filled"
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,
remotely, make sure your firewall settings are set to only allow specific trusted IP addresses and make sure your firewall settings are set to only allow specific trusted IP addresses and block all
block all other ports. A simple google search for &quot;what is my ip&quot; will show you your other ports. A simple google search for &quot;what is my ip&quot; will show you your computers public
computers public IP address to allow. IP address to allow.
</MuiAlert> </MuiAlert>
<CodeBlockTabs <CodeBlockTabs
showLineNumbers showLineNumbers
@@ -80,8 +69,8 @@ export default function NodeConnectionCheck({ isOk }: Props): ReactElement | nul
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 (you can also use the <strong>cors-allowed-origins</strong> must be set to your host domain or IP (you can also use the
wildcard <code>{"cors-allowed-origins: ['*']"}</code>). If edits are made to the configuration run wildcard <code>{"cors-allowed-origins: ['*']"}</code>). If edits are made to the configuration run the
the restart command below for changes to take effect. restart command below for changes to take effect.
</li> </li>
<CodeBlockTabs <CodeBlockTabs
showLineNumbers showLineNumbers
@@ -89,11 +78,10 @@ export default function NodeConnectionCheck({ isOk }: Props): ReactElement | nul
mac={`sudo vi /usr/local/etc/swarm-bee/bee.yaml \nbrew services restart swarm-bee`} mac={`sudo vi /usr/local/etc/swarm-bee/bee.yaml \nbrew services restart swarm-bee`}
/> />
</ol> </ol>
</Typography> }
</AccordionDetails> />
</Accordion> </ExpandableList>
</Typography> )}
</div> </ExpandableList>
</div>
) )
} }
@@ -1,23 +1,27 @@
import type { ReactElement } from 'react' import { ReactElement, useContext } from 'react'
import { Typography } from '@material-ui/core/' import ExpandableList from '../../../components/ExpandableList'
import EthereumAddress from '../../../components/EthereumAddress' import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
type Props = StatusEthereumConnectionHook export default function EthereumConnectionCheck(): ReactElement | null {
const { status, isLoading, nodeAddresses } = useContext(Context)
const isOk = status.blockchainConnection
export default function EthereumConnectionCheck({ isOk, nodeAddresses }: Props): ReactElement | null {
if (isOk) {
return ( return (
<div> <ExpandableList
<Typography variant="subtitle1" gutterBottom> label={
Node Address <>
</Typography> <StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Blockchain
<EthereumAddress address={nodeAddresses?.ethereum} /> </>
</div>
)
} }
>
return ( <ExpandableListItemNote>
<p> {isOk ? (
'Your node is connected to the xDai blockchain'
) : (
<>
Your Bee node must have access to the xDai blockchain, so that it can interact and deploy your chequebook Your Bee node must have access to the xDai blockchain, so that it can interact and deploy your chequebook
contract. You can run{' '} contract. You can run{' '}
<a href="https://www.xdaichain.com/" rel="noreferrer" target="_blank"> <a href="https://www.xdaichain.com/" rel="noreferrer" target="_blank">
@@ -27,8 +31,12 @@ export default function EthereumConnectionCheck({ isOk, nodeAddresses }: Props):
<a href="https://getblock.io/" rel="noreferrer" target="_blank"> <a href="https://getblock.io/" rel="noreferrer" target="_blank">
Getblock Getblock
</a> </a>
. By default, Bee expects a local node at http://localhost:8545. To use a provider instead, simply change the{' '} . By default, Bee expects a local node at http://localhost:8545. To use a provider instead, simply change
<strong>swap-endpoint</strong> in your configuration file. the <strong>swap-endpoint</strong> in your configuration file.
</p> </>
)}
</ExpandableListItemNote>
{nodeAddresses?.ethereum && <ExpandableListItemKey label="Ethereum Address" value={nodeAddresses?.ethereum} />}
</ExpandableList>
) )
} }
@@ -1,35 +1,37 @@
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
import { ExpandMoreSharp } from '@material-ui/icons/'
import ConnectToHost from '../../../components/ConnectToHost'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import { Context as SettingsContext } from '../../../providers/Settings' import { Context as SettingsContext } from '../../../providers/Settings'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import ExpandableListItemInput from '../../../components/ExpandableListItemInput'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
type Props = StatusHookCommon export default function NodeConnectionCheck(): ReactElement | null {
export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null {
const { setApiUrl, apiUrl } = useContext(SettingsContext) const { setApiUrl, apiUrl } = useContext(SettingsContext)
const { status, isLoading } = useContext(Context)
const isOk = status.apiConnection
return ( return (
<div> <ExpandableList
<div style={{ display: 'flex', marginBottom: '25px' }}> label={
<span style={{ marginRight: '15px' }}> <>
Node API (<Typography variant="button">{apiUrl}</Typography>) <StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Bee API
</span> </>
<ConnectToHost setHost={setApiUrl} defaultHost={apiUrl} /> }
</div> >
<div> <ExpandableListItemNote>
{isOk
? 'The connection to the Bee nodes API has been successful'
: 'Could not connect to your Bee nodes API. Please check the troubleshoot below on how you may resolve it.'}
</ExpandableListItemNote>
<ExpandableListItemInput label="Bee API" value={apiUrl} onConfirm={setApiUrl} />
{!isOk && ( {!isOk && (
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}> <ExpandableList level={1} label="Troubleshoot">
We cannot connect to your nodes API at <Typography variant="button">{apiUrl}</Typography>. Please check the <ExpandableListItem
following to troubleshoot your issue. label={
<Accordion style={{ marginTop: '20px' }}>
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
<Typography>Troubleshoot</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography component="div">
<ol> <ol>
<li>Check the status of your node by running the below command to see if your node is running.</li> <li>Check the status of your node by running the below command to see if your node is running.</li>
<CodeBlockTabs showLineNumbers linux={`sudo systemctl status bee`} mac={`brew services list`} /> <CodeBlockTabs showLineNumbers linux={`sudo systemctl status bee`} mac={`brew services list`} />
@@ -50,12 +52,10 @@ export default function NodeConnectionCheck({ isOk }: Props): ReactElement | nul
mac={`brew services list \ntail -f /usr/local/var/log/swarm-bee/bee.log`} mac={`brew services list \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
/> />
</ol> </ol>
</Typography> }
</AccordionDetails> />
</Accordion> </ExpandableList>
</Typography>
)} )}
</div> </ExpandableList>
</div>
) )
} }
+20 -34
View File
@@ -1,43 +1,29 @@
import type { ReactElement } from 'react' import { ReactElement, useContext } from 'react'
import { Typography } from '@material-ui/core/' import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import TopologyStats from '../../../components/TopologyStats'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
type Props = StatusTopologyHook export default function PeerConnection(): ReactElement | null {
const { status, isLoading, topology } = useContext(Context)
const isOk = status.topology
export default function PeerConnection({ isOk, topology }: Props): ReactElement | null {
const peers = (
<div style={{ display: 'flex', marginTop: '15px' }}>
<div style={{ marginRight: '30px' }}>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Connected Peers</span>
</Typography>
<Typography component="h2" variant="h5">
{topology?.connected ? topology.connected : '-'}
</Typography>
</div>
<div>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Discovered Nodes</span>
</Typography>
<Typography component="h2" variant="h5">
{topology?.population ? topology.population : '-'}
</Typography>
</div>
</div>
)
if (isOk) {
return ( return (
<ExpandableList
label={
<> <>
<span>You are connected to peers!</span> <StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Peers
{peers}
</> </>
)
} }
>
<ExpandableListItemNote>
{isOk
? 'You are connected to other Bee nodes'
: 'Your node is not connected to any peers. Please wait a bit if you just started the node, otherwise review your configuration file.'}
</ExpandableListItemNote>
return ( <TopologyStats topology={topology} />
<> </ExpandableList>
<span>Your node is not connected to any peers</span>
{peers}
</>
) )
} }
+26 -39
View File
@@ -1,60 +1,47 @@
import type { ReactElement } from 'react' import { ReactElement, useContext } from 'react'
import { Typography } from '@material-ui/core/'
import CodeBlockTabs from '../../../components/CodeBlockTabs' import CodeBlockTabs from '../../../components/CodeBlockTabs'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
type Props = StatusNodeVersionHook export default function VersionCheck(): ReactElement | null {
const { status, isLoading, latestUserVersion, latestPublishedVersion, latestBeeVersionUrl } = useContext(Context)
const isOk = status.version
export default function VersionCheck({ isOk, userVersion, latestVersion, latestUrl }: Props): ReactElement | null {
const version = (
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}>
<p>
<span>User Version</span>
</p>
<Typography component="h5" variant="h5">
<span>{userVersion}</span>
</Typography>
</div>
<div>
<p>
<span>Latest Version</span>
</p>
<Typography component="h5" variant="h5">
<span>{latestVersion}</span>
</Typography>
</div>
</div>
)
// Running latest bee version
if (isOk) {
return ( return (
<ExpandableList
label={
<> <>
<span>You are running the latest version of Bee</span> <StatusIcon isOk={isOk} isLoading={isLoading} /> Bee Version
{version}
</> </>
)
} }
>
// Old version or not connected to bee debug API <ExpandableListItemNote>
return ( {isOk ? (
'You are running the latest version of Bee.'
) : (
<> <>
<span>
Your Bee version is out of date. Please update to the{' '} Your Bee version is out of date. Please update to the{' '}
<a href={latestUrl} rel="noreferrer" target="_blank"> <a href={latestBeeVersionUrl} rel="noreferrer" target="_blank">
latest latest
</a>{' '} </a>{' '}
before continuing. Rerun the installation script below to upgrade. Reference the docs for help with updating.{' '} before continuing. Rerun the installation script below to upgrade. For more information please see the{' '}
<a href="https://docs.ethswarm.org/docs/installation/manual#upgrading-bee" rel="noreferrer" target="_blank"> <a href="https://docs.ethswarm.org/docs/installation/manual#upgrading-bee" rel="noreferrer" target="_blank">
Docs Docs
</a> </a>
</span> .
<CodeBlockTabs <CodeBlockTabs
showLineNumbers showLineNumbers
linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${latestVersion}/bee_${latestVersion}_amd64.deb\nsudo dpkg -i bee_${latestVersion}_amd64.deb`} linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${latestPublishedVersion}/bee_${latestPublishedVersion}_amd64.deb\nsudo dpkg -i bee_${latestPublishedVersion}_amd64.deb`}
mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`} mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`}
/> />
{version}
</> </>
)}
</ExpandableListItemNote>
<ExpandableListItem label="Your Version" value={latestUserVersion || '-'} />
<ExpandableListItem label="Latest Version" value={latestPublishedVersion || '-'} />
</ExpandableList>
) )
} }
+7 -138
View File
@@ -1,7 +1,4 @@
import { ReactElement, useState, useContext } from 'react' import type { ReactElement } 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, 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,144 +6,16 @@ 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 { Context } from '../../providers/Bee'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: theme.spacing(2),
width: '100%',
},
button: {
marginTop: theme.spacing(1),
marginRight: theme.spacing(1),
},
actionsContainer: {
margin: theme.spacing(2),
},
}),
)
interface Step {
label: string
isOk: boolean
component: ReactElement
}
export default function NodeSetupWorkflow(): ReactElement { export default function NodeSetupWorkflow(): ReactElement {
const classes = useStyles()
const [activeStep, setActiveStep] = useState(-1)
const {
status,
isLoading,
latestUserVersion,
latestPublishedVersion,
isLatestBeeVersion,
latestBeeVersionUrl,
topology,
nodeAddresses,
chequebookAddress,
} = useContext(Context)
const steps: Step[] = [
{
label: 'Connected to Node DebugAPI',
isOk: status.debugApiConnection,
component: <DebugConnectionCheck isOk={status.debugApiConnection} />,
},
{
label: 'Running latest Bee version',
isOk: status.version,
component: (
<VersionCheck
isOk={status.version}
isLatestBeeVersion={isLatestBeeVersion}
userVersion={latestUserVersion}
latestVersion={latestPublishedVersion}
latestUrl={latestBeeVersionUrl}
/>
),
},
{
label: 'Connected to xDai Blockchain',
isOk: status.blockchainConnection,
component: <EthereumConnectionCheck isOk={status.blockchainConnection} nodeAddresses={nodeAddresses} />,
},
{
label: 'Deployed and Funded Chequebook',
isOk: status.chequebook,
component: (
<ChequebookDeployFund chequebookAddress={chequebookAddress?.chequebookAddress} isOk={status.chequebook} />
),
},
{
label: 'Connected to Node API',
isOk: status.apiConnection,
component: <NodeConnectionCheck isOk={status.apiConnection} />,
},
{
label: 'Connected to Peers',
isOk: status.topology,
component: <PeerConnection isOk={status.topology} topology={topology} />,
},
]
const handleNext = () => {
setActiveStep(prevActiveStep => prevActiveStep + 1)
}
const handleBack = () => {
setActiveStep(prevActiveStep => prevActiveStep - 1)
}
return ( return (
<Paper className={classes.root}>
<Typography variant="h5" gutterBottom>
Node Setup
</Typography>
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
{steps.map(({ label, isOk, component }, index) => (
<Step key={label}>
<StepLabel
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
StepIconComponent={() => {
if (isLoading) return <Autorenew style={{ height: '25px', cursor: 'pointer' }} />
if (isOk) return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
}}
>
<StepButton
disabled={isLoading}
onClick={() => setActiveStep(index === activeStep ? steps.length : index)}
style={{ justifyContent: 'space-between' }}
>
<div style={{ display: 'flex' }}>
<div style={{ marginTop: '5px' }}>{label}</div>
<div style={{ marginLeft: '12px' }}>
{index === activeStep ? <ExpandLessSharp /> : <ExpandMoreSharp />}
</div>
</div>
</StepButton>
</StepLabel>
<StepContent>
<Typography component="div">{component}</Typography>
<div className={classes.actionsContainer}>
<div> <div>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}> <DebugConnectionCheck />
Back <VersionCheck />
</Button> <EthereumConnectionCheck />
<Button variant="contained" color="primary" onClick={handleNext} className={classes.button}> <ChequebookDeployFund />
{index < steps.length - 1 ? 'Next' : 'Finish'} <NodeConnectionCheck />
</Button> <PeerConnection />
</div> </div>
</div>
</StepContent>
</Step>
))}
</Stepper>
</Paper>
) )
} }
+3
View File
@@ -61,6 +61,9 @@ const componentsOverrides = (theme: Theme) => ({
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
color: 'white', color: 'white',
}, },
'&:disabled': {
backgroundColor: 'white',
},
}, },
}, },
MuiTab: { MuiTab: {