diff --git a/app.config.js b/app.config.js index 7eb5da50..4ac3cca7 100644 --- a/app.config.js +++ b/app.config.js @@ -95,5 +95,16 @@ module.exports = { }, // Display alert banner for the developer preview deployment - showPreviewAlert: process.env.NEXT_PUBLIC_SHOW_PREVIEW_ALERT || 'false' + showPreviewAlert: process.env.NEXT_PUBLIC_SHOW_PREVIEW_ALERT || 'false', + + networkAlertConfig: { + // Refresh interval for network status - 30 sec + refreshInterval: 30000, + // Margin of error for block count (how much difference between min / max block numbers before showing an alert) + errorMargin: 10, + // Map chainIds to their respective status endpoints + statusEndpoints: { + 100: 'https://status.genx.delta-dao.com/api/check-blocks' + } + } } diff --git a/src/@context/MarketMetadata/_types.ts b/src/@context/MarketMetadata/_types.ts index 0815b994..89998700 100644 --- a/src/@context/MarketMetadata/_types.ts +++ b/src/@context/MarketMetadata/_types.ts @@ -36,6 +36,16 @@ export interface AppConfig { roughTxGasEstimate: number } showPreviewAlert: string + networkAlertConfig: { + // Refresh interval for network status - 30 sec + refreshInterval: number + // Margin of error for block count (how much difference between min / max block numbers before showing an alert) + errorMargin: number + // Map chainIds to their respective status endpoints + statusEndpoints: { + [chainId: number]: string + } + } } export interface SiteContent { siteTitle: string diff --git a/src/components/@shared/NetworkStatus/index.tsx b/src/components/@shared/NetworkStatus/index.tsx new file mode 100644 index 00000000..8d65ef78 --- /dev/null +++ b/src/components/@shared/NetworkStatus/index.tsx @@ -0,0 +1,84 @@ +import { ReactElement, useCallback, useEffect, useState } from 'react' +import { useNetwork } from 'wagmi' +import { useMarketMetadata } from '../../../@context/MarketMetadata' +import Alert from '../atoms/Alert' +import axios from 'axios' +import { LoggerInstance } from '@oceanprotocol/lib' + +export default function NetworkStatus({ + className +}: { + className?: string +}): ReactElement { + const [showNetworkAlert, setShowNetworkAlert] = useState(true) + const [network, setNetwork] = useState() + const { appConfig } = useMarketMetadata() + const { chain } = useNetwork() + + const { networkAlertConfig } = appConfig + + const fetchNetworkStatus = useCallback( + async (chainId: number) => { + if (!chainId) return + setNetwork(chain?.name) + const apiEndpoint = networkAlertConfig.statusEndpoints[chainId] + if (!apiEndpoint) return + LoggerInstance.log(`[NetworkStatus] retrieving network status`, { + apiEndpoint + }) + try { + const result = await axios.get(apiEndpoint) + const { Nodes } = result.data + const { nodes }: { nodes: { [node: string]: number } } = Nodes + let minBlock: number + let maxBlock: number + Object.values(nodes).forEach((block) => { + if (!minBlock || block < minBlock) minBlock = block + if (!maxBlock || block > maxBlock) maxBlock = block + }) + const hasError = maxBlock - minBlock > networkAlertConfig.errorMargin + setShowNetworkAlert(hasError) + LoggerInstance.log(`[NetworkStatus] network status updated:`, { + minBlock, + maxBlock, + hasError + }) + } catch (error) { + LoggerInstance.error( + `[NetworkStatus] could not retrieve network status:`, + error.message + ) + } + }, + [networkAlertConfig, chain] + ) + + useEffect(() => { + if (!chain?.id) return + + fetchNetworkStatus(chain?.id) + + // init periodic refresh for network status + const networkStatusInterval = setInterval( + () => fetchNetworkStatus(chain?.id), + networkAlertConfig.refreshInterval + ) + + return () => { + clearInterval(networkStatusInterval) + } + }, [chain, fetchNetworkStatus]) + + return ( + showNetworkAlert && ( + setShowNetworkAlert(false)} + className={className} + /> + ) + ) +} diff --git a/src/components/@shared/Page/PageHeader.module.css b/src/components/@shared/Page/PageHeader.module.css index 93d22399..187c79cd 100644 --- a/src/components/@shared/Page/PageHeader.module.css +++ b/src/components/@shared/Page/PageHeader.module.css @@ -1,6 +1,5 @@ .header { margin-bottom: var(--spacer); - max-width: 50rem; } .title { @@ -81,3 +80,7 @@ margin-top: calc(var(--spacer) / 2); max-width: 30rem; } + +.networkAlert { + margin: var(--spacer) auto; +} diff --git a/src/components/@shared/Page/PageHeader.tsx b/src/components/@shared/Page/PageHeader.tsx index 1c633ee0..82790d5f 100644 --- a/src/components/@shared/Page/PageHeader.tsx +++ b/src/components/@shared/Page/PageHeader.tsx @@ -1,10 +1,11 @@ -import { ReactElement } from 'react' -import classNames from 'classnames/bind' -import styles from './PageHeader.module.css' -import Markdown from '@shared/Markdown' import SearchBar from '@components/Header/SearchBar' import BrandLogo from '@images/brand-logo.svg' import GaiaXLogo from '@images/gaia-x-logo.svg' +import Markdown from '@shared/Markdown' +import classNames from 'classnames/bind' +import { ReactElement } from 'react' +import NetworkStatus from '../NetworkStatus' +import styles from './PageHeader.module.css' const cx = classNames.bind(styles) @@ -46,7 +47,10 @@ export default function PageHeader({ ) : ( -

{title}

+
+

{title}

+ +
)} {description && !isHome && (