From 5d0fbf705dfed6738980c751a9654199d60a3787 Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Wed, 13 Apr 2022 18:09:30 +0500 Subject: [PATCH] feat: optional status checks (e.g. connected peers > 0 or funded chequebook) (#331) * feat: make some check optional (e.g. connected peers > 0 or funded chequebook) * fix: alter setup step text to better describe what needs to be done * refactor: rename isOk from boolean value to checkState enum * fix: add checking for any error --- src/components/SideBarStatus.tsx | 6 +- src/components/StatusIcon.tsx | 23 +++- src/pages/accounting/index.tsx | 4 +- src/pages/feeds/index.tsx | 4 +- src/pages/files/Upload.tsx | 4 +- src/pages/info/index.tsx | 4 +- src/pages/stamps/index.tsx | 4 +- .../SetupSteps/ChequebookDeployFund.tsx | 54 ++++++--- .../SetupSteps/DebugConnectionCheck.tsx | 10 +- .../SetupSteps/EthereumConnectionCheck.tsx | 8 +- .../status/SetupSteps/NodeConnectionCheck.tsx | 10 +- .../status/SetupSteps/PeerConnection.tsx | 26 ++-- src/pages/status/SetupSteps/VersionCheck.tsx | 8 +- src/providers/Bee.tsx | 113 +++++++++++------- src/react-app-env.d.ts | 17 --- src/types.ts | 19 +++ 16 files changed, 190 insertions(+), 124 deletions(-) diff --git a/src/components/SideBarStatus.tsx b/src/components/SideBarStatus.tsx index 46728b5..d230230 100644 --- a/src/components/SideBarStatus.tsx +++ b/src/components/SideBarStatus.tsx @@ -66,11 +66,9 @@ export default function SideBarItem({ path }: Props): ReactElement { disableRipple > - + - {`Node ${status.all ? 'OK' : 'Error'}`}} - /> + {`Node ${status.all}`}} /> {status.all ? null : } diff --git a/src/components/StatusIcon.tsx b/src/components/StatusIcon.tsx index bab8969..6d52150 100644 --- a/src/components/StatusIcon.tsx +++ b/src/components/StatusIcon.tsx @@ -1,23 +1,40 @@ import type { ReactElement } from 'react' import { CircularProgress } from '@material-ui/core' +import { CheckState } from '../providers/Bee' interface Props { - isOk: boolean + checkState: CheckState isLoading?: boolean size?: number | string className?: string } -export default function StatusIcon({ isOk, size, className, isLoading }: Props): ReactElement { +export default function StatusIcon({ checkState, size, className, isLoading }: Props): ReactElement { const s = size || '1rem' if (isLoading) return + let backgroundColor: string + switch (checkState) { + case CheckState.OK: + backgroundColor = '#1de600' + break + case CheckState.WARNING: + backgroundColor = 'orange' + break + case CheckState.ERROR: + backgroundColor = '#ff3a52' + break + default: + // Default is error + backgroundColor = '#ff3a52' + } + return ( + if (status.all === CheckState.ERROR) return return (
diff --git a/src/pages/feeds/index.tsx b/src/pages/feeds/index.tsx index 209d9a7..662fc09 100644 --- a/src/pages/feeds/index.tsx +++ b/src/pages/feeds/index.tsx @@ -8,7 +8,7 @@ import ExpandableListItemActions from '../../components/ExpandableListItemAction import ExpandableListItemKey from '../../components/ExpandableListItemKey' import { SwarmButton } from '../../components/SwarmButton' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' -import { Context as BeeContext } from '../../providers/Bee' +import { CheckState, Context as BeeContext } from '../../providers/Bee' import { Context as IdentityContext, Identity } from '../../providers/Feeds' import { ROUTES } from '../../routes' import { formatEnum } from '../../utils' @@ -60,7 +60,7 @@ export default function Feeds(): ReactElement { setShowDelete(true) } - if (!status.all) return + if (status.all === CheckState.ERROR) return return (
diff --git a/src/pages/files/Upload.tsx b/src/pages/files/Upload.tsx index 8bad5c7..6d26f4e 100644 --- a/src/pages/files/Upload.tsx +++ b/src/pages/files/Upload.tsx @@ -6,7 +6,7 @@ import { DocumentationText } from '../../components/DocumentationText' import { HistoryHeader } from '../../components/HistoryHeader' import { ProgressIndicator } from '../../components/ProgressIndicator' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' -import { Context as BeeContext } from '../../providers/Bee' +import { CheckState, Context as BeeContext } from '../../providers/Bee' import { Context as IdentityContext, Identity } from '../../providers/Feeds' import { Context as FileContext } from '../../providers/File' import { Context as SettingsContext } from '../../providers/Settings' @@ -43,7 +43,7 @@ export function Upload(): ReactElement { refresh() }, []) // eslint-disable-line react-hooks/exhaustive-deps - if (!status.all) return + if (status.all === CheckState.ERROR) return if (!files.length) { setFiles([]) diff --git a/src/pages/info/index.tsx b/src/pages/info/index.tsx index fd01a27..4cf6447 100644 --- a/src/pages/info/index.tsx +++ b/src/pages/info/index.tsx @@ -2,7 +2,7 @@ import { ReactElement, useContext } from 'react' import { Button } from '@material-ui/core' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' -import { Context as BeeContext } from '../../providers/Bee' +import { CheckState, Context as BeeContext } from '../../providers/Bee' import ExpandableList from '../../components/ExpandableList' import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItemKey from '../../components/ExpandableListItemKey' @@ -20,7 +20,7 @@ export default function Status(): ReactElement { nodeInfo, } = useContext(BeeContext) - if (!status.all) return + if (status.all === CheckState.ERROR) return return (
diff --git a/src/pages/stamps/index.tsx b/src/pages/stamps/index.tsx index 221673e..a5f449a 100644 --- a/src/pages/stamps/index.tsx +++ b/src/pages/stamps/index.tsx @@ -5,7 +5,7 @@ import { PlusSquare } from 'react-feather' import { useNavigate } from 'react-router' import { SwarmButton } from '../../components/SwarmButton' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' -import { Context as BeeContext } from '../../providers/Bee' +import { CheckState, Context as BeeContext } from '../../providers/Bee' import { Context as StampsContext } from '../../providers/Stamps' import { ROUTES } from '../../routes' import StampsTable from './StampsTable' @@ -41,7 +41,7 @@ export default function Stamp(): ReactElement { return () => stop() }, [status]) // eslint-disable-line react-hooks/exhaustive-deps - if (!status.all) return + if (status.all === CheckState.ERROR) return function navigateToNewStamp() { navigate(ROUTES.STAMPS_NEW) diff --git a/src/pages/status/SetupSteps/ChequebookDeployFund.tsx b/src/pages/status/SetupSteps/ChequebookDeployFund.tsx index 70610fd..c3572b8 100644 --- a/src/pages/status/SetupSteps/ChequebookDeployFund.tsx +++ b/src/pages/status/SetupSteps/ChequebookDeployFund.tsx @@ -1,41 +1,59 @@ import { useContext } from 'react' import DepositModal from '../../../containers/DepositModal' -import type { ReactElement } from 'react' +import type { ReactElement, ReactNode } 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' +import { CheckState, Context } from '../../../providers/Bee' const ChequebookDeployFund = (): ReactElement | null => { const { status, isLoading, chequebookAddress } = useContext(Context) - const { isOk, isEnabled } = status.chequebook + const { checkState, isEnabled } = status.chequebook if (!isEnabled) return null + let text: ReactNode + + switch (checkState) { + case CheckState.OK: + text = 'Your chequebook is deployed and funded' + break + case CheckState.WARNING: + text = ( + <> + Your chequebook is not funded. Please deposit some xBZZ to your chequebook address. You may need to aquire BZZ + (e.g. bzz.exchange) and bridge it to the xDai network through the{' '} + omni bridge. To pay the transaction fees, you will also need + xDAI token. You can purchase DAI on the network and bridge it to xDai network through the{' '} + xDai Bridge. See the{' '} + official xDai website for more information. + + ) + break + default: + text = ( + <> + Your chequebook is either not deployed nor funded. To run the node you will need xDAI and xBZZ on the xDai + network. You may need to aquire BZZ (e.g. bzz.exchange) and bridge it to + the xDai network through the omni bridge. To pay the + transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it to xDai + network through the xDai Bridge. See the{' '} + official xDai website for more information. + + ) + } + return ( - Chequebook Deployment & Funding + Chequebook Deployment & Funding } > - - {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 - network. You may need to aquire BZZ (e.g. bzz.exchange) and bridge it to - the xDai network through the omni bridge. To pay the - transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it to xDai - network through the xDai Bridge. See the{' '} - official xDai website for more information. - - )} - + {text} {chequebookAddress && ( <> diff --git a/src/pages/status/SetupSteps/DebugConnectionCheck.tsx b/src/pages/status/SetupSteps/DebugConnectionCheck.tsx index 1c203ee..b6d961c 100644 --- a/src/pages/status/SetupSteps/DebugConnectionCheck.tsx +++ b/src/pages/status/SetupSteps/DebugConnectionCheck.tsx @@ -6,13 +6,13 @@ import ExpandableListItem from '../../../components/ExpandableListItem' import ExpandableListItemInput from '../../../components/ExpandableListItemInput' import ExpandableListItemNote from '../../../components/ExpandableListItemNote' import StatusIcon from '../../../components/StatusIcon' -import { Context } from '../../../providers/Bee' +import { CheckState, Context } from '../../../providers/Bee' import { Context as SettingsContext } from '../../../providers/Settings' export default function NodeConnectionCheck(): ReactElement | null { const { status, isLoading } = useContext(Context) const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext) - const { isOk, isEnabled } = status.debugApiConnection + const { checkState, isEnabled } = status.debugApiConnection if (!isEnabled) return null @@ -20,18 +20,18 @@ export default function NodeConnectionCheck(): ReactElement | null { - Connection to Bee Debug API + Connection to Bee Debug API } > - {isOk + {checkState === CheckState.OK ? 'The connection to the Bee nodes debug API has been successful' : 'We cannot connect to your nodes debug API. Please check the following to troubleshoot your issue.'} - {!isOk && ( + {checkState === CheckState.ERROR && ( - Connection to Blockchain + Connection to Blockchain } > - {isOk ? ( + {checkState === CheckState.OK ? ( 'Your node is connected to the xDai blockchain' ) : ( <> diff --git a/src/pages/status/SetupSteps/NodeConnectionCheck.tsx b/src/pages/status/SetupSteps/NodeConnectionCheck.tsx index a8d41d2..9669c17 100644 --- a/src/pages/status/SetupSteps/NodeConnectionCheck.tsx +++ b/src/pages/status/SetupSteps/NodeConnectionCheck.tsx @@ -7,12 +7,12 @@ 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' +import { CheckState, Context } from '../../../providers/Bee' export default function NodeConnectionCheck(): ReactElement | null { const { setApiUrl, apiUrl } = useContext(SettingsContext) const { status, isLoading } = useContext(Context) - const { isEnabled, isOk } = status.apiConnection + const { isEnabled, checkState } = status.apiConnection if (!isEnabled) return null @@ -20,17 +20,17 @@ export default function NodeConnectionCheck(): ReactElement | null { - Connection to Bee API + Connection to Bee API } > - {isOk + {checkState === CheckState.OK ? '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.'} - {!isOk && ( + {checkState === CheckState.ERROR && ( - Connection to Peers + Connection to Peers } > - - {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.'} - + {text} diff --git a/src/pages/status/SetupSteps/VersionCheck.tsx b/src/pages/status/SetupSteps/VersionCheck.tsx index 7249864..f1d57dc 100644 --- a/src/pages/status/SetupSteps/VersionCheck.tsx +++ b/src/pages/status/SetupSteps/VersionCheck.tsx @@ -4,11 +4,11 @@ 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' +import { CheckState, Context } from '../../../providers/Bee' export default function VersionCheck(): ReactElement | null { const { status, isLoading, latestUserVersion, latestPublishedVersion, latestBeeVersionUrl } = useContext(Context) - const { isEnabled, isOk } = status.version + const { isEnabled, checkState } = status.version if (!isEnabled) return null @@ -16,12 +16,12 @@ export default function VersionCheck(): ReactElement | null { - Bee Version + Bee Version } > - {isOk ? ( + {checkState === CheckState.OK ? ( 'You are running the latest version of Bee.' ) : ( <> diff --git a/src/providers/Bee.tsx b/src/providers/Bee.tsx index bf80d3d..df9a352 100644 --- a/src/providers/Bee.tsx +++ b/src/providers/Bee.tsx @@ -17,13 +17,19 @@ import { Token } from '../models/Token' import type { Balance, ChequebookBalance, Settlements } from '../types' import { Context as SettingsContext } from './Settings' +export enum CheckState { + OK = 'OK', + WARNING = 'Warning', + ERROR = 'Error', +} + interface StatusItem { isEnabled: boolean - isOk: boolean + checkState: CheckState } interface Status { - all: boolean + all: CheckState version: StatusItem blockchainConnection: StatusItem debugApiConnection: StatusItem @@ -63,13 +69,13 @@ interface ContextInterface { const initialValues: ContextInterface = { status: { - all: false, - version: { isEnabled: false, isOk: false }, - blockchainConnection: { isEnabled: false, isOk: false }, - debugApiConnection: { isEnabled: false, isOk: false }, - apiConnection: { isEnabled: false, isOk: false }, - topology: { isEnabled: false, isOk: false }, - chequebook: { isEnabled: false, isOk: false }, + all: CheckState.ERROR, + version: { isEnabled: false, checkState: CheckState.ERROR }, + blockchainConnection: { isEnabled: false, checkState: CheckState.ERROR }, + debugApiConnection: { isEnabled: false, checkState: CheckState.ERROR }, + apiConnection: { isEnabled: false, checkState: CheckState.ERROR }, + topology: { isEnabled: false, checkState: CheckState.ERROR }, + chequebook: { isEnabled: false, checkState: CheckState.ERROR }, }, latestPublishedVersion: undefined, latestUserVersion: undefined, @@ -115,45 +121,62 @@ function getStatus( chequebookBalance: ChequebookBalance | null, error: Error | null, ): Status { - const status = { - version: { - isEnabled: true, - isOk: Boolean( - debugApiHealth && - semver.satisfies(debugApiHealth.version, engines.bee, { - includePrerelease: true, - }), - ), - }, - blockchainConnection: { - isEnabled: true, - isOk: Boolean(nodeAddresses?.ethereum), - }, - debugApiConnection: { - isEnabled: true, - isOk: Boolean(debugApiHealth?.status === 'ok'), - }, - apiConnection: { - isEnabled: true, - isOk: apiHealth, - }, - topology: { - isEnabled: Boolean(nodeInfo && [BeeModes.FULL, BeeModes.LIGHT, BeeModes.ULTRA_LIGHT].includes(nodeInfo.beeMode)), - isOk: Boolean(topology?.connected && topology?.connected > 0), - }, - chequebook: { - isEnabled: Boolean(nodeInfo && [BeeModes.FULL, BeeModes.LIGHT].includes(nodeInfo.beeMode)), - isOk: - Boolean(chequebookAddress?.chequebookAddress) && - chequebookBalance !== null && - chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0), - }, + const status: Status = { ...initialValues.status } + + // Version check + status.version.isEnabled = true + status.version.checkState = + debugApiHealth && + semver.satisfies(debugApiHealth.version, engines.bee, { + includePrerelease: true, + }) + ? CheckState.OK + : CheckState.ERROR + + // Blockchain connection check + status.blockchainConnection.isEnabled = true + status.blockchainConnection.checkState = Boolean(debugApiHealth?.status === 'ok') ? CheckState.OK : CheckState.ERROR + + // Debug API connection check + status.debugApiConnection.isEnabled = true + status.debugApiConnection.checkState = Boolean(debugApiHealth?.status === 'ok') ? CheckState.OK : CheckState.ERROR + + // API connection check + status.apiConnection.isEnabled = true + status.apiConnection.checkState = apiHealth ? CheckState.OK : CheckState.ERROR + + // Topology check + if (nodeInfo && [BeeModes.FULL, BeeModes.LIGHT, BeeModes.ULTRA_LIGHT].includes(nodeInfo.beeMode)) { + status.topology.isEnabled = true + status.topology.checkState = topology?.connected && topology?.connected > 0 ? CheckState.OK : CheckState.WARNING } - return { - ...status, - all: !error && Object.values(status).every(({ isEnabled, isOk }) => !isEnabled || (isEnabled && isOk)), + // Chequebook check + if (error || (nodeInfo && [BeeModes.FULL, BeeModes.LIGHT].includes(nodeInfo.beeMode))) { + status.chequebook.isEnabled = true + + if ( + chequebookAddress?.chequebookAddress && + chequebookBalance !== null && + chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0) + ) { + status.chequebook.checkState = CheckState.OK + } else if (chequebookAddress?.chequebookAddress) status.chequebook.checkState = CheckState.WARNING + else status.chequebook.checkState = CheckState.OK } + + // Determine overall status + if (Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.ERROR)) { + status.all = CheckState.ERROR + } else if ( + Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.WARNING) + ) { + status.all = CheckState.WARNING + } else { + status.all = CheckState.OK + } + + return status } export function Provider({ children }: Props): ReactElement { diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 924899c..2739368 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -5,23 +5,6 @@ interface LatestBeeRelease { html_url: string } -interface StatusHookCommon { - isOk: boolean -} - -interface StatusNodeVersionHook extends StatusHookCommon { - userVersion?: string - latestVersion?: string - latestUrl: string - isLatestBeeVersion: boolean -} -interface StatusEthereumConnectionHook extends StatusHookCommon { - nodeAddresses: NodeAddresses | null -} -interface StatusTopologyHook extends StatusHookCommon { - topology: Topology | null -} - interface SwarmMetadata { size: number name: string diff --git a/src/types.ts b/src/types.ts index 8fd9a7f..ef872be 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,23 @@ +import type { NodeAddresses, Topology } from '@ethersphere/bee-js' import type { Token } from './models/Token' +import { CheckState } from './providers/Bee' + +export interface StatusHookCommon { + checkState: CheckState +} + +export interface StatusNodeVersionHook extends StatusHookCommon { + userVersion?: string + latestVersion?: string + latestUrl: string + isLatestBeeVersion: boolean +} +export interface StatusEthereumConnectionHook extends StatusHookCommon { + nodeAddresses: NodeAddresses | null +} +export interface StatusTopologyHook extends StatusHookCommon { + topology: Topology | null +} export interface ChequebookBalance { totalBalance: Token