diff --git a/src/components/StatCard.tsx b/src/components/StatCard.tsx
index 13c1c52..e581c97 100644
--- a/src/components/StatCard.tsx
+++ b/src/components/StatCard.tsx
@@ -1,28 +1,23 @@
-import type { ReactElement } from 'react'
-
-import { makeStyles } from '@material-ui/core/styles'
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,
},
- title: {
- fontSize: 16,
- },
- pos: {
- marginBottom: 12,
- },
})
interface Props {
label: string
statistic?: string
loading?: boolean
+ tooltip?: string
}
-export default function StatCard({ loading, label, statistic }: Props): ReactElement {
+export default function StatCard({ loading, label, statistic, tooltip }: Props): ReactElement {
const classes = useStyles()
return (
@@ -36,9 +31,7 @@ export default function StatCard({ loading, label, statistic }: Props): ReactEle
)}
{!loading && (
<>
-
- {label}
-
+
{statistic}
diff --git a/src/components/Title.tsx b/src/components/Title.tsx
new file mode 100644
index 0000000..9819d70
--- /dev/null
+++ b/src/components/Title.tsx
@@ -0,0 +1,41 @@
+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 (
+
+ {label}
+
+ )
+ }
+
+ // span is needed as Tooltip expects a non-functional element!
+ return (
+
+
+
+
+ {label}
+
+
+
+
+
+ )
+}
diff --git a/src/components/TopologyStats.tsx b/src/components/TopologyStats.tsx
index 023ecfc..6eeceb1 100644
--- a/src/components/TopologyStats.tsx
+++ b/src/components/TopologyStats.tsx
@@ -1,25 +1,72 @@
import type { Topology } from '@ethersphere/bee-js'
import { Grid } from '@material-ui/core/'
import type { ReactElement } from 'react'
+import { pickThreshold, ThresholdValues } from '../utils/threshold'
import StatCard from './StatCard'
-interface Props {
+interface RootProps {
isLoading: boolean
topology: Topology | null
error: Error | null // FIXME: should display error
}
-const TopologyStats = ({ isLoading, topology }: Props): ReactElement => (
+interface Props extends RootProps {
+ thresholds: ThresholdValues
+}
+
+const TopologyStats = (props: RootProps): ReactElement => {
+ const thresholds: ThresholdValues = {
+ connectedPeers: pickThreshold('connectedPeers', props.topology?.connected || 0),
+ population: pickThreshold('population', props.topology?.population || 0),
+ depth: pickThreshold('depth', props.topology?.depth || 0),
+ }
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+const Indicator = ({ isLoading, thresholds }: Props): ReactElement => {
+ const maximumTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.maximumScore, 0)
+ const actualTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.score, 0)
+ const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%'
+
+ return (
+
+
+
+ )
+}
+
+const Metrics = ({ isLoading, topology, thresholds }: Props): ReactElement => (
-
+
-
+
-
+
diff --git a/src/utils/threshold.ts b/src/utils/threshold.ts
new file mode 100644
index 0000000..d7a1baf
--- /dev/null
+++ b/src/utils/threshold.ts
@@ -0,0 +1,105 @@
+const OPTIMAL_CONNECTED_PEERS = 200
+const OPTIMAL_POPULATION = 100_000
+const OPTIMAL_DEPTH = 12
+
+interface Threshold {
+ minimumValue: number
+ explanation: string
+ score: number
+}
+
+type Thresholds = {
+ connectedPeers: Threshold[]
+ population: Threshold[]
+ depth: Threshold[]
+}
+
+type ThresholdValue = {
+ score: number
+ maximumScore: number
+ explanation: string
+}
+
+export type ThresholdValues = {
+ connectedPeers: ThresholdValue
+ population: ThresholdValue
+ depth: ThresholdValue
+}
+
+const GENERIC_ERROR = 'There may be issues with your Bee node or connection.'
+
+const THRESHOLDS: Thresholds = {
+ connectedPeers: [
+ {
+ minimumValue: OPTIMAL_CONNECTED_PEERS,
+ explanation: `Perfect! ${OPTIMAL_CONNECTED_PEERS} or more connected peers indicate a healthy topology.`,
+ score: 2,
+ },
+ {
+ minimumValue: 1,
+ explanation: `Your Bee node is connected to peers, but this number should ideally be above ${OPTIMAL_CONNECTED_PEERS}. If you have only started your Bee node, this number may increase quickly.`,
+ score: 1,
+ },
+ {
+ minimumValue: 0,
+ explanation: 'Your Bee node has not connected to any peers. ' + GENERIC_ERROR,
+ score: 0,
+ },
+ ],
+ population: [
+ {
+ minimumValue: OPTIMAL_POPULATION,
+ explanation:
+ 'Perfect! Your Bee node seems to have a realistic value for the network size, which means everything is working well on your end.',
+ score: 2,
+ },
+ {
+ minimumValue: 1,
+ explanation: `Population is usually above ${OPTIMAL_POPULATION.toLocaleString()}. If the number does not increase within a few hours, there may be issues with your Bee node.`,
+ score: 1,
+ },
+ {
+ minimumValue: 0,
+ explanation: 'Your Bee node has no information on the network population. ' + GENERIC_ERROR,
+ score: 0,
+ },
+ ],
+ depth: [
+ {
+ minimumValue: OPTIMAL_DEPTH,
+ explanation: 'Perfect! Your Bee node has the highest available depth.',
+ score: 2,
+ },
+ {
+ minimumValue: 1,
+ explanation: `Your Bee node is supposed to reach a depth of ${OPTIMAL_DEPTH} eventually. Stagnation or decrease in this number may indicate problems with your Bee node.`,
+ score: 1,
+ },
+ {
+ minimumValue: 0,
+ explanation: 'Your Bee node has not started building its topology yet. ' + GENERIC_ERROR,
+ score: 0,
+ },
+ ],
+}
+
+export function pickThreshold(key: keyof Thresholds, value: number): ThresholdValue {
+ const thresholds = THRESHOLDS[key]
+ const maximumScore = thresholds[0].score
+ for (const item of thresholds) {
+ if (value >= item.minimumValue) {
+ return {
+ score: item.score,
+ maximumScore,
+ explanation: item.explanation,
+ }
+ }
+ }
+ const last = thresholds[thresholds.length - 1]
+
+ return {
+ score: last.score,
+ maximumScore,
+ explanation: last.explanation,
+ }
+}