Files
nym/explorer/src/pages/MixnodeDetail/index.tsx
T
Pierre Dommerc fdbe3a1f6a fix(explorer,explorer-api): mixnode location (#2763)
* chore(explorer-api): remove useless route (/terms)

* feat(explorer-api-geoip): add coordinates lat&lon

* fix(explorer): mixnode location

* fix: typo

* fix: clippy
2023-01-05 15:53:15 +01:00

231 lines
7.2 KiB
TypeScript

import * as React from 'react';
import { Alert, AlertTitle, Box, CircularProgress, Grid, Typography } from '@mui/material';
import { useParams } from 'react-router-dom';
import { ColumnsType, DetailTable } from '../../components/DetailTable';
import { BondBreakdownTable } from '../../components/MixNodes/BondBreakdown';
import { DelegatorsInfoTable, EconomicsInfoColumns, EconomicsInfoRows } from '../../components/MixNodes/Economics';
import { ComponentError } from '../../components/ComponentError';
import { ContentCard } from '../../components/ContentCard';
import { TwoColSmallTable } from '../../components/TwoColSmallTable';
import { UptimeChart } from '../../components/UptimeChart';
import { WorldMap } from '../../components/WorldMap';
import { MixNodeDetailSection } from '../../components/MixNodes/DetailSection';
import { MixnodeContextProvider, useMixnodeContext } from '../../context/mixnode';
import { Title } from '../../components/Title';
const columns: ColumnsType[] = [
{
field: 'owner',
title: 'Owner',
headerAlign: 'left',
width: 230,
},
{
field: 'identity_key',
title: 'Identity Key',
headerAlign: 'left',
width: 230,
},
{
field: 'bond',
title: 'Stake',
flex: 1,
headerAlign: 'left',
},
{
field: 'self_percentage',
title: 'Bond %',
headerAlign: 'left',
width: 99,
},
{
field: 'host',
title: 'Host',
headerAlign: 'left',
flex: 1,
},
{
field: 'location',
title: 'Location',
headerAlign: 'left',
flex: 1,
},
{
field: 'layer',
title: 'Layer',
headerAlign: 'left',
flex: 1,
},
];
/**
* Shows mix node details
*/
const PageMixnodeDetailWithState: React.FC = () => {
const { mixNode, mixNodeRow, description, stats, status, uptimeStory, uniqDelegations } = useMixnodeContext();
return (
<Box component="main">
<Title text="Mixnode Detail" />
<Grid container spacing={2} mt={1} mb={6}>
<Grid item xs={12}>
{mixNodeRow && description?.data && (
<MixNodeDetailSection mixNodeRow={mixNodeRow} mixnodeDescription={description.data} />
)}
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<DetailTable columnsData={columns} tableName="Mixnode detail table" rows={mixNodeRow ? [mixNodeRow] : []} />
</Grid>
</Grid>
<Grid container spacing={2} mt={0}>
<Grid item xs={12}>
<DelegatorsInfoTable
columnsData={EconomicsInfoColumns}
tableName="Delegators info table"
rows={[EconomicsInfoRows()]}
/>
</Grid>
</Grid>
<Grid container spacing={2} mt={0}>
<Grid item xs={12}>
<ContentCard title={`Stake Breakdown (${uniqDelegations?.data?.length} delegators)`}>
<BondBreakdownTable />
</ContentCard>
</Grid>
</Grid>
<Grid container spacing={2} mt={0}>
<Grid item xs={12} md={4}>
<ContentCard title="Mixnode Stats">
{stats && (
<>
{stats.error && <ComponentError text="There was a problem retrieving this nodes stats." />}
<TwoColSmallTable
loading={stats.isLoading}
error={stats?.error?.message}
title="Since startup"
keys={['Received', 'Sent', 'Explicitly dropped']}
values={[
stats?.data?.packets_received_since_startup || 0,
stats?.data?.packets_sent_since_startup || 0,
stats?.data?.packets_explicitly_dropped_since_startup || 0,
]}
/>
<TwoColSmallTable
loading={stats.isLoading}
error={stats?.error?.message}
title="Since last update"
keys={['Received', 'Sent', 'Explicitly dropped']}
values={[
stats?.data?.packets_received_since_last_update || 0,
stats?.data?.packets_sent_since_last_update || 0,
stats?.data?.packets_explicitly_dropped_since_last_update || 0,
]}
marginBottom
/>
</>
)}
{!stats && <Typography>No stats information</Typography>}
</ContentCard>
</Grid>
<Grid item xs={12} md={8}>
{uptimeStory && (
<ContentCard title="Routing Score">
{uptimeStory.error && <ComponentError text="There was a problem retrieving routing score." />}
<UptimeChart loading={uptimeStory.isLoading} xLabel="date" uptimeStory={uptimeStory} />
</ContentCard>
)}
</Grid>
</Grid>
<Grid container spacing={2} mt={0}>
<Grid item xs={12} md={4}>
{status && (
<ContentCard title="Mixnode Status">
{status.error && <ComponentError text="There was a problem retrieving port information" />}
<TwoColSmallTable
loading={status.isLoading}
error={status?.error?.message}
keys={['Mix port', 'Verloc port', 'HTTP port']}
values={[1789, 1790, 8000].map((each) => each)}
icons={(status?.data?.ports && Object.values(status.data.ports)) || [false, false, false]}
/>
</ContentCard>
)}
</Grid>
<Grid item xs={12} md={8}>
{mixNode && (
<ContentCard title="Location">
{mixNode?.error && <ComponentError text="There was a problem retrieving this mixnode location" />}
{mixNode?.data?.location?.latitude && mixNode?.data?.location?.longitude && (
<WorldMap
loading={mixNode.isLoading}
userLocation={[mixNode.data.location.longitude, mixNode.data.location.latitude]}
/>
)}
</ContentCard>
)}
</Grid>
</Grid>
</Box>
);
};
/**
* Guard component to handle loading and not found states
*/
const PageMixnodeDetailGuard: React.FC = () => {
const { mixNode } = useMixnodeContext();
const { id } = useParams<{ id: string | undefined }>();
if (mixNode?.isLoading) {
return <CircularProgress />;
}
if (mixNode?.error) {
// eslint-disable-next-line no-console
console.error(mixNode?.error);
return (
<Alert severity="error">
Oh no! Could not load mixnode <code>{id || ''}</code>
</Alert>
);
}
// loaded, but not found
if (mixNode && !mixNode.isLoading && !mixNode.data) {
return (
<Alert severity="warning">
<AlertTitle>Mixnode not found</AlertTitle>
Sorry, we could not find a mixnode with id <code>{id || ''}</code>
</Alert>
);
}
return <PageMixnodeDetailWithState />;
};
/**
* Wrapper component that adds the mixnode content based on the `id` in the address URL
*/
export const PageMixnodeDetail: React.FC = () => {
const { id } = useParams<{ id: string | undefined }>();
if (!id) {
return <Alert severity="error">Oh no! No mixnode identity key specified</Alert>;
}
return (
<MixnodeContextProvider mixId={id}>
<PageMixnodeDetailGuard />
</MixnodeContextProvider>
);
};