From 57f5a73f3a8d957bf967c51612dc09c802bb68dc Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Mon, 4 Oct 2021 12:26:13 +0200 Subject: [PATCH] feat: info page redesign (#207) * feat: info page redesign * style: headers and nesting for expandable lists * style: body typography * chore: bee version check * feat: key list item component * style: hover state for the key displaying component * style: left border on expanded items * fix: word break and splitting for hexstrings divisible by 6 * chore: moved the styles to classes * style: tooltips now have arrow * feat: indicate value has been copied * feat: removed the connectivity table in favor of info page * feat: added health tooltips for connectivity * refactor: simplified the topology into single component * fix: spacing between the bee version and the update button/chip * fix: spacing on devices not supporting rowGap --- src/components/ExpandableList.tsx | 65 +++++++++++++ src/components/ExpandableListItem.tsx | 54 +++++++++++ src/components/ExpandableListItemKey.tsx | 114 +++++++++++++++++++++++ src/components/SideBar.tsx | 7 +- src/components/TopologyStats.tsx | 64 +++++-------- src/pages/info/StatusCard.tsx | 113 ---------------------- src/pages/info/index.tsx | 67 +++++++------ src/pages/peers/PeerTable.tsx | 93 ------------------ src/pages/peers/index.tsx | 21 ----- src/routes.tsx | 3 - src/theme.tsx | 13 +++ 11 files changed, 309 insertions(+), 305 deletions(-) create mode 100644 src/components/ExpandableList.tsx create mode 100644 src/components/ExpandableListItem.tsx create mode 100644 src/components/ExpandableListItemKey.tsx delete mode 100644 src/pages/info/StatusCard.tsx delete mode 100644 src/pages/peers/PeerTable.tsx delete mode 100644 src/pages/peers/index.tsx diff --git a/src/components/ExpandableList.tsx b/src/components/ExpandableList.tsx new file mode 100644 index 0000000..ef1b398 --- /dev/null +++ b/src/components/ExpandableList.tsx @@ -0,0 +1,65 @@ +import { ReactElement, ReactNode, useState } from 'react' +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' +import { Collapse, ListItem, ListItemText, Typography } from '@material-ui/core' +import { ExpandLess, ExpandMore } from '@material-ui/icons' + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: '100%', + padding: 0, + margin: 0, + marginTop: theme.spacing(4), + '&:first-child': { + marginTop: 0, + }, + }, + rootLevel1: { marginTop: theme.spacing(1) }, + rootLevel2: { marginTop: theme.spacing(0.5) }, + header: { + backgroundColor: theme.palette.background.paper, + }, + content: { + marginTop: theme.spacing(1), + }, + }), +) + +interface Props { + children?: ReactNode + label: ReactNode + level?: 0 | 1 | 2 + defaultOpen?: boolean +} + +export default function ExpandableList({ children, label, level, defaultOpen }: Props): ReactElement | null { + const classes = useStyles() + const [open, setOpen] = useState(Boolean(defaultOpen)) + + const handleClick = () => { + setOpen(!open) + } + + let rootLevelClass = '' + let typographyVariant: 'h1' | 'h2' | 'h3' = 'h1' + + if (level === 1) { + rootLevelClass = classes.rootLevel1 + typographyVariant = 'h2' + } else if (level === 2) { + rootLevelClass = classes.rootLevel2 + typographyVariant = 'h3' + } + + return ( +
+ + {label}} /> + {open ? : } + + +
{children}
+
+
+ ) +} diff --git a/src/components/ExpandableListItem.tsx b/src/components/ExpandableListItem.tsx new file mode 100644 index 0000000..3ce97ec --- /dev/null +++ b/src/components/ExpandableListItem.tsx @@ -0,0 +1,54 @@ +import { ReactElement, ReactNode } from 'react' +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' +import { Typography, Grid, IconButton, Tooltip } from '@material-ui/core' +import { Info } from 'react-feather' +import ListItem from '@material-ui/core/ListItem' + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + header: { + backgroundColor: theme.palette.background.paper, + marginBottom: theme.spacing(0.25), + wordBreak: 'break-word', + }, + copyValue: { + cursor: 'pointer', + padding: theme.spacing(1), + borderRadius: 0, + '&:hover': { + backgroundColor: '#fcf2e8', + color: theme.palette.primary.main, + }, + }, + }), +) + +interface Props { + label?: string + value?: ReactNode + tooltip?: string +} + +export default function ExpandableListItem({ label, value, tooltip }: Props): ReactElement | null { + const classes = useStyles() + + return ( + + + {label && {label}} + {value && ( + + {value} + {tooltip && ( + + + + + + )} + + )} + + + ) +} diff --git a/src/components/ExpandableListItemKey.tsx b/src/components/ExpandableListItemKey.tsx new file mode 100644 index 0000000..17be527 --- /dev/null +++ b/src/components/ExpandableListItemKey.tsx @@ -0,0 +1,114 @@ +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, Tooltip } from '@material-ui/core' +import { Eye, Minus } from 'react-feather' +import { CopyToClipboard } from 'react-copy-to-clipboard' + +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(2), + marginBottom: theme.spacing(2), + }, + keyMargin: { + marginRight: theme.spacing(1), + }, + }), +) + +interface Props { + label: string + value: string +} + +const lengthWithoutPrefix = (s: string) => s.replace(/^0x/i, '').length + +function isPrefixedHexString(s: unknown): boolean { + return typeof s === 'string' && /^0x[0-9a-f]+$/i.test(s) +} + +const split = (s: string): string[] => { + const nonPrefixLength = lengthWithoutPrefix(s) + + if (nonPrefixLength % 6 === 0) return s.match(/(0x|.{6})/gi) || [] + + return s.match(/(0x|.{1,8})/gi) || [] +} + +export default function ExpandableListItemKey({ label, value }: Props): ReactElement | null { + const classes = useStyles() + const [open, setOpen] = useState(false) + const [copied, setCopied] = useState(false) + const toggleOpen = () => setOpen(!open) + + const tooltipClickHandler = () => setCopied(true) + const tooltipCloseHandler = () => setCopied(false) + + const splitValues = split(value) + const hasPrefix = isPrefixedHexString(value) + + return ( + + + + {label && {label}} + +
+ {!open && ( + + + + {`${ + hasPrefix ? `${splitValues[0]} ${splitValues[1]}` : splitValues[0] + }[…]${splitValues[splitValues.length - 1]}`} + + + + )} + + {open ? : } + +
+
+
+ +
+ + + {/* This has to be wrapped in two spans otherwise either the tooltip or the highlighting does not work*/} + + + {splitValues.map((s, i) => ( + + {s} + + ))} + + + + +
+
+
+
+ ) +} diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 38a805d..fc390d4 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom' import { createStyles, Theme, makeStyles } from '@material-ui/core/styles' import { OpenInNewSharp } from '@material-ui/icons' import { Divider, List, Drawer, Grid, Link as MUILink } from '@material-ui/core' -import { Home, FileText, DollarSign, Share2, Settings, Layers, BookOpen } from 'react-feather' +import { Home, FileText, DollarSign, Settings, Layers, BookOpen } from 'react-feather' import { ROUTES } from '../routes' import SideBarItem from './SideBarItem' import SideBarStatus from './SideBarStatus' @@ -32,11 +32,6 @@ const navBarItems = [ path: ROUTES.ACCOUNTING, icon: DollarSign, }, - { - label: 'Peers', - path: ROUTES.PEERS, - icon: Share2, - }, { label: 'Settings', path: ROUTES.SETTINGS, diff --git a/src/components/TopologyStats.tsx b/src/components/TopologyStats.tsx index 08baccf..65ec8ad 100644 --- a/src/components/TopologyStats.tsx +++ b/src/components/TopologyStats.tsx @@ -1,64 +1,44 @@ 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' +import ExpandableList from './ExpandableList' +import ExpandableListItem from './ExpandableListItem' -interface RootProps { +interface Props { topology: Topology | null } -interface Props extends RootProps { - thresholds: ThresholdValues -} - -const TopologyStats = (props: RootProps): ReactElement => { +const TopologyStats = (props: Props): 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 = ({ 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 = ({ topology, thresholds }: Props): ReactElement => ( - - - - - - - - - - - -) - export default TopologyStats diff --git a/src/pages/info/StatusCard.tsx b/src/pages/info/StatusCard.tsx deleted file mode 100644 index 690b38d..0000000 --- a/src/pages/info/StatusCard.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { ReactElement, useState } from 'react' -import { Link } from 'react-router-dom' - -import { createStyles, makeStyles } from '@material-ui/core/styles' -import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/' -import { ArrowRight, ArrowDropUp } from '@material-ui/icons/' -import { NodeAddresses, Topology } from '@ethersphere/bee-js' -import { ROUTES } from '../../routes' - -const useStyles = makeStyles(() => - createStyles({ - root: { - display: 'flex', - flex: '1 1 auto', - flexDirection: 'column', - }, - status: { - color: '#2145a0', - backgroundColor: '#e1effe', - }, - }), -) - -interface Props { - nodeAddresses: NodeAddresses | null - nodeTopology: Topology | null - userBeeVersion?: string - isLatestBeeVersion: boolean - isOk: boolean - latestUrl: string -} - -function StatusCard({ - userBeeVersion, - nodeAddresses, - nodeTopology, - isLatestBeeVersion, - latestUrl, -}: Props): ReactElement | null { - const classes = useStyles() - - const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState(false) - - return ( - - - <> -
- Discovered Nodes: {nodeTopology?.population} - - Connected Peers: - {nodeTopology?.connected} - -
-
- - AGENT: - - Bee - {' '} - {userBeeVersion || '-'} - {isLatestBeeVersion ? ( - - ) : ( - - )} - - - PUBLIC KEY: - {nodeAddresses?.publicKey ? nodeAddresses.publicKey : '-'} - - - PSS PUBLIC KEY: - {nodeAddresses?.pssPublicKey ? nodeAddresses.pssPublicKey : '-'} - - - OVERLAY ADDRESS (PEER ID): - {nodeAddresses?.overlay ? nodeAddresses.overlay : '-'} - - - setUnderlayAddresessVisible(!underlayAddressesVisible)}> - - - {underlayAddressesVisible && ( -
- {nodeAddresses?.underlay.map(item => ( -
  • {item}
  • - ))} -
    - )} -
    -
    - -
    -
    - ) -} - -export default StatusCard diff --git a/src/pages/info/index.tsx b/src/pages/info/index.tsx index 3e7d7ac..9643fea 100644 --- a/src/pages/info/index.tsx +++ b/src/pages/info/index.tsx @@ -1,24 +1,14 @@ import { ReactElement, useContext } from 'react' -import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' +import { Chip, Button } from '@material-ui/core' -import StatusCard from './StatusCard' -import EthereumAddressCard from '../../components/EthereumAddressCard' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import { Context as BeeContext } from '../../providers/Bee' - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - display: 'grid', - rowGap: theme.spacing(3), - }, - }), -) +import ExpandableList from '../../components/ExpandableList' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import TopologyStats from '../../components/TopologyStats' export default function Status(): ReactElement { - const classes = useStyles() - const { status, latestUserVersion, @@ -32,18 +22,41 @@ export default function Status(): ReactElement { if (!status.all) return return ( -
    - - {nodeAddresses && chequebookAddress && ( - - )} +
    + + + + Bee + {' '} + {latestUserVersion || '-'}{' '} + {isLatestBeeVersion ? ( + + ) : ( + + )} +
    + } + /> + + + + + + {nodeAddresses?.underlay.map(addr => ( + + ))} + + + + + + +
    ) } diff --git a/src/pages/peers/PeerTable.tsx b/src/pages/peers/PeerTable.tsx deleted file mode 100644 index 28ad060..0000000 --- a/src/pages/peers/PeerTable.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { ReactElement, useState, useContext } from 'react' -import { makeStyles } from '@material-ui/core/styles' -import { - Table, - TableBody, - TableCell, - TableContainer, - TableRow, - TableHead, - Button, - Paper, - Tooltip, - CircularProgress, -} from '@material-ui/core' -import { Autorenew } from '@material-ui/icons' - -import { Context as SettingsContext } from '../../providers/Settings' -import type { Peer } from '@ethersphere/bee-js' - -const useStyles = makeStyles({ - table: { - minWidth: 650, - }, -}) - -interface Props { - peers: Peer[] | null -} - -interface PeerLatency { - rtt: string - loading: boolean -} - -function getPingState(peerLatency: Record, peer: Peer): ReactElement { - if (peerLatency[peer.address]?.loading) return - - if (peerLatency[peer.address]?.rtt) return {peerLatency[peer.address]?.rtt} - - return -} - -function PeerTable(props: Props): ReactElement { - const classes = useStyles() - const { beeDebugApi } = useContext(SettingsContext) - - const [peerLatency, setPeerLatency] = useState>({}) - - const pingPeer = (peerId: string) => { - setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: '', loading: true } })) - beeDebugApi - ?.pingPeer(peerId) - .then(res => { - setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: res.rtt, loading: false } })) - }) - .catch(() => { - setPeerLatency(prevPeerLatency => ({ ...prevPeerLatency, [peerId]: { rtt: 'error', loading: false } })) - }) - } - - return ( - - - - - Index - Peer Id - Actions - - - - {props.peers?.map((peer: Peer, idx: number) => ( - - - {idx + 1} - - {peer.address} - - - - - - - ))} - -
    -
    - ) -} - -export default PeerTable diff --git a/src/pages/peers/index.tsx b/src/pages/peers/index.tsx deleted file mode 100644 index c1e1d51..0000000 --- a/src/pages/peers/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import PeerTable from './PeerTable' -import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' - -import { Context } from '../../providers/Bee' -import TopologyStats from '../../components/TopologyStats' -import { ReactElement, useContext } from 'react' - -export default function Peers(): ReactElement { - const { topology, peers, status } = useContext(Context) - - if (!status.all) { - return - } - - return ( - <> - - - - ) -} diff --git a/src/routes.tsx b/src/routes.tsx index 46ce76a..cf25ff6 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -6,7 +6,6 @@ import { Route } from 'react-router-dom' import Info from './pages/info' import Status from './pages/status' import Files from './pages/files' -import Peers from './pages/peers' import Accounting from './pages/accounting' import Settings from './pages/settings' import Stamps from './pages/stamps' @@ -14,7 +13,6 @@ import Stamps from './pages/stamps' export enum ROUTES { INFO = '/', FILES = '/files', - PEERS = '/peers', ACCOUNTING = '/accounting', SETTINGS = '/settings', STAMPS = '/stamps', @@ -25,7 +23,6 @@ const BaseRouter = (): ReactElement => ( - diff --git a/src/theme.tsx b/src/theme.tsx index af7ed18..13fdc12 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -130,6 +130,19 @@ export const theme = createMuiTheme({ fontSize: '1.3rem', fontWeight: 500, }, + h2: { + fontSize: '1rem', + fontWeight: 500, + }, + h3: { + fontSize: '0.8rem', + fontWeight: 500, + }, + body2: { + fontFamily: '"IBM Plex Mono", monospace', + fontWeight: 500, + fontSize: '1rem', + }, }, })