Files
Mark Sinclair 15c3012199 explorer-api: add nym node endpoints + UI to show nym-nodes and account balances (#5183)
* explorer-api: add nym node endpoints + UI to show nym-nodes and account balances

* explorer-api: add endpoints to get operator rewards
explorer-ui: show delegations on nym-nodes, show operator rewards, bug fixes

* explorer-ui: change summary screen to only show nym-node stats

* explorer-api: add unstable routes to get legacy mixnodes and gateways from the contract instead of the Nym API
explorer-ui: adapt front-end to show less information in legacy nodes with plain bond types

* explorer-ui: fix up source of legacy mixnode data

* explorer-ui: add more account page null and undefined checks

* explorer-ui: filter out null gateway versions

* explorer-ui: sanitise gateway versions

* explorer-ui: add more guards on the balance parts to check that greater than 0

* explorer-api: make /tmp/unstable/gateways endpoint compatible with the current Harbour Master API

* explorer-ui: fix typo

* cargo fmt

* Add node-id, total stake and links to nodes list

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2024-12-05 08:17:30 +00:00

257 lines
7.4 KiB
TypeScript

'use client'
import React, { useMemo } from 'react'
import { Box, Card, Grid, Stack } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import {
MRT_ColumnDef,
MaterialReactTable,
useMaterialReactTable,
} from 'material-react-table'
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'
import { Tooltip as InfoTooltip } from '@nymproject/react/tooltip/Tooltip'
import { diff, gte, rcompare } from 'semver'
import { useMainContext } from '@/app/context/main'
import { TableToolbar } from '@/app/components/TableToolbar'
import { CustomColumnHeading } from '@/app/components/CustomColumnHeading'
import { Title } from '@/app/components/Title'
import { unymToNym } from '@/app/utils/currency'
import { Tooltip } from '@/app/components/Tooltip'
import { EXPLORER_FOR_ACCOUNTS } from '@/app/api/constants'
import { splice } from '@/app/utils'
import {
VersionDisplaySelector,
VersionSelectOptions,
} from '@/app/components/Gateways/VersionDisplaySelector'
import StyledLink from '@/app/components/StyledLink'
import {
GatewayRowType,
gatewayToGridRow,
} from '@/app/components/Gateways/Gateways'
import {LocatedGateway} from "@/app/typeDefs/explorer-api";
const gatewaySanitize = (g?: LocatedGateway): boolean => {
if(!g) {
return false;
}
if(!g.gateway.version || !g.gateway.version.trim().length) {
return false;
}
if(g.gateway.version === "null") {
return false;
}
return true;
}
const PageGateways = () => {
const { gateways } = useMainContext()
const [versionFilter, setVersionFilter] =
React.useState<VersionSelectOptions>(VersionSelectOptions.all)
const theme = useTheme()
const highestVersion = React.useMemo(() => {
if (gateways?.data) {
const versions = gateways.data.filter(gatewaySanitize).reduce(
(a: string[], b) => [...a, b.gateway.version],
[]
)
const [lastestVersion] = versions.sort(rcompare)
return lastestVersion
}
// fallback value
return '2.0.0'
}, [gateways])
const filterByLatestVersions = React.useMemo(() => {
const filtered = gateways?.data?.filter(gatewaySanitize).filter((gw) => {
const versionDiff = diff(highestVersion, gw.gateway.version)
return versionDiff === 'patch' || versionDiff === null
})
if (filtered) return filtered
return []
}, [gateways])
const filterByOlderVersions = React.useMemo(() => {
const filtered = gateways?.data?.filter(gatewaySanitize).filter((gw) => {
const versionDiff = diff(highestVersion, gw.gateway.version)
return versionDiff === 'major' || versionDiff === 'minor'
})
if (filtered) return filtered
return []
}, [gateways])
const filteredByVersion = React.useMemo(() => {
switch (versionFilter) {
case VersionSelectOptions.latestVersion:
return filterByLatestVersions
case VersionSelectOptions.olderVersions:
return filterByOlderVersions
case VersionSelectOptions.all:
return gateways?.data || []
default:
return []
}
}, [versionFilter, gateways])
const data = useMemo(() => {
return gatewayToGridRow(filteredByVersion || [])
}, [filteredByVersion])
const columns = useMemo<MRT_ColumnDef<GatewayRowType>[]>(() => {
return [
{
id: 'gateway-data',
header: 'Gateways Data',
columns: [
{
id: 'identity_key',
header: 'Identity Key',
accessorKey: 'identity_key',
size: 250,
Cell: ({ row }) => {
return (
<Stack direction="row" alignItems="center" gap={1}>
<CopyToClipboard
sx={{ mr: 0.5, color: 'grey.400' }}
smallIcons
value={row.original.identity_key}
tooltip={`Copy identity key ${row.original.identity_key} to clipboard`}
/>
<StyledLink
to={`/network-components/gateways/${row.original.identity_key}`}
dataTestId="identity-link"
color="text.primary"
>
{splice(7, 29, row.original.identity_key)}
</StyledLink>
</Stack>
)
},
},
{
id: 'version',
header: 'Version',
accessorKey: 'version',
size: 150,
Cell: ({ row }) => {
return (
<StyledLink
to={`/network-components/gateways/${row.original.identity_key}`}
data-testid="version"
color="text.primary"
>
{row.original.version}
</StyledLink>
)
},
},
{
id: 'location',
header: 'Location',
accessorKey: 'location',
size: 150,
Cell: ({ row }) => {
return (
<Box
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
data-testid="location-button"
>
<Tooltip
text={row.original.location}
id="gateway-location-text"
>
<Box
sx={{
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{row.original.location}
</Box>
</Tooltip>
</Box>
)
},
},
{
id: 'host',
header: 'IP:Port',
accessorKey: 'host',
size: 150,
Cell: ({ row }) => {
return (
<StyledLink
to={`/network-components/gateways/${row.original.identity_key}`}
data-testid="host"
color="text.primary"
>
{row.original.host}
</StyledLink>
)
},
},
{
id: 'owner',
header: 'Owner',
accessorKey: 'owner',
size: 150,
Cell: ({ row }) => {
return (
<StyledLink
to={`${EXPLORER_FOR_ACCOUNTS}/account/${row.original.owner}`}
target="_blank"
data-testid="owner"
color="text.primary"
>
{splice(7, 29, row.original.owner)}
</StyledLink>
)
},
},
],
},
]
}, [])
const table = useMaterialReactTable({
columns,
data,
})
return (
<>
<Box mb={2}>
<Title text="Legacy Gateways" />
</Box>
<Grid container>
<Grid item xs={12}>
<Card
sx={{
padding: 2,
height: '100%',
}}
>
<TableToolbar
childrenBefore={
<VersionDisplaySelector
handleChange={(option) => setVersionFilter(option)}
selected={versionFilter}
/>
}
/>
<MaterialReactTable table={table} />
</Card>
</Grid>
</Grid>
</>
)
}
export default PageGateways