create gateway details page
This commit is contained in:
@@ -14,6 +14,7 @@ export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`;
|
||||
export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`;
|
||||
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
|
||||
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
|
||||
export const UPTIME_STORY_API_GATEWAY = `${VALIDATOR_API_BASE_URL}/api/v1/status/gateway`; // add ID then '/history' or '/report' to this.
|
||||
|
||||
// errors
|
||||
export const MIXNODE_API_ERROR = "We're having trouble finding that record, please try again or Contact Us.";
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
BLOCK_API,
|
||||
COUNTRY_DATA_API,
|
||||
GATEWAYS_API,
|
||||
UPTIME_STORY_API_GATEWAY,
|
||||
MIXNODE_API,
|
||||
MIXNODE_PING,
|
||||
MIXNODES_API,
|
||||
@@ -15,6 +16,8 @@ import {
|
||||
DelegationsResponse,
|
||||
UniqDelegationsResponse,
|
||||
GatewayResponse,
|
||||
GatewayReportResponse,
|
||||
UptimeStoryResponse,
|
||||
MixNodeDescriptionResponse,
|
||||
MixNodeResponse,
|
||||
MixNodeResponseItem,
|
||||
@@ -23,7 +26,6 @@ import {
|
||||
StatsResponse,
|
||||
StatusResponse,
|
||||
SummaryOverviewResponse,
|
||||
UptimeStoryResponse,
|
||||
ValidatorsResponse,
|
||||
} from '../typeDefs/explorer-api';
|
||||
|
||||
@@ -92,6 +94,23 @@ export class Api {
|
||||
return res.json();
|
||||
};
|
||||
|
||||
// static fetchGatewayByID = async (id: string): Promise<GatewayResponseItem | undefined> => {
|
||||
// const response = await fetch(`${GATEWAYS_API}/${id}`);
|
||||
|
||||
// // when the mixnode is not found, returned undefined
|
||||
// if (response.status === 404) {
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
// return response.json();
|
||||
// };
|
||||
|
||||
static fetchGatewayUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
|
||||
(await fetch(`${UPTIME_STORY_API_GATEWAY}/${id}/history`)).json();
|
||||
|
||||
static fetchGatewayReportById = async (id: string): Promise<GatewayReportResponse> =>
|
||||
(await fetch(`${UPTIME_STORY_API_GATEWAY}/${id}/report`)).json();
|
||||
|
||||
static fetchValidators = async (): Promise<ValidatorsResponse> => {
|
||||
const res = await fetch(VALIDATORS_API);
|
||||
const json = await res.json();
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
import { Box } from '@mui/system';
|
||||
import { cellStyles } from './Universal-DataGrid';
|
||||
import { currencyToString } from '../utils/currency';
|
||||
import { GatewayEnridedRowType } from './Gateways';
|
||||
import { MixnodeRowType } from './MixNodes';
|
||||
|
||||
export type ColumnsType = {
|
||||
@@ -43,42 +46,60 @@ function formatCellValues(val: string | number, field: string) {
|
||||
export const DetailTable: React.FC<{
|
||||
tableName: string;
|
||||
columnsData: ColumnsType[];
|
||||
rows: MixnodeRowType[];
|
||||
}> = ({ tableName, columnsData, rows }: UniversalTableProps) => (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label={tableName}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnsData?.map(({ field, title, flex }) => (
|
||||
<TableCell key={field} sx={{ fontSize: 14, fontWeight: 600, flex }}>
|
||||
{title}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((eachRow) => (
|
||||
<TableRow key={eachRow.id} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{columnsData?.map((_, index) => (
|
||||
<TableCell
|
||||
key={_.title}
|
||||
component="th"
|
||||
scope="row"
|
||||
variant="body"
|
||||
sx={{
|
||||
...cellStyles,
|
||||
padding: 2,
|
||||
width: 200,
|
||||
fontSize: 14,
|
||||
}}
|
||||
data-testid={`${_.title.replace(/ /g, '-')}-value`}
|
||||
>
|
||||
{formatCellValues(eachRow[columnsData[index].field], columnsData[index].field)}
|
||||
rows: MixnodeRowType[] | GatewayEnridedRowType[];
|
||||
}> = ({ tableName, columnsData, rows }: UniversalTableProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label={tableName}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnsData?.map(({ field, title, flex, tooltipInfo }) => (
|
||||
<TableCell key={field} sx={{ fontSize: 14, fontWeight: 600, flex }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
{tooltipInfo && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Tooltip
|
||||
title={tooltipInfo}
|
||||
id={field}
|
||||
placement="top-start"
|
||||
textColor={theme.palette.nym.networkExplorer.tooltip.color}
|
||||
bgColor={theme.palette.nym.networkExplorer.tooltip.background}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{title}
|
||||
</Box>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((eachRow) => (
|
||||
<TableRow key={eachRow.id} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{columnsData?.map((_, index) => (
|
||||
<TableCell
|
||||
key={_.title}
|
||||
component="th"
|
||||
scope="row"
|
||||
variant="body"
|
||||
sx={{
|
||||
...cellStyles,
|
||||
padding: 2,
|
||||
width: 200,
|
||||
fontSize: 14,
|
||||
}}
|
||||
data-testid={`${_.title.replace(/ /g, '-')}-value`}
|
||||
>
|
||||
{formatCellValues(eachRow[columnsData[index].field], columnsData[index].field)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GatewayResponse } from '../typeDefs/explorer-api';
|
||||
import { GatewayResponse, GatewayResponseItem, GatewayReportResponse } from '../typeDefs/explorer-api';
|
||||
|
||||
export type GatewayRowType = {
|
||||
id: string;
|
||||
@@ -9,15 +9,40 @@ export type GatewayRowType = {
|
||||
location: string;
|
||||
};
|
||||
|
||||
export type GatewayEnridedRowType = GatewayRowType & {
|
||||
routing_score: string;
|
||||
avg_uptime: string;
|
||||
clients_port: number;
|
||||
mix_port: number;
|
||||
};
|
||||
|
||||
export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowType[] {
|
||||
return !arrayOfGateways
|
||||
? []
|
||||
: arrayOfGateways.map((gw) => ({
|
||||
id: gw.owner,
|
||||
owner: gw.owner,
|
||||
identity_key: gw.gateway.identity_key || '',
|
||||
location: gw?.gateway?.location || '',
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
}));
|
||||
id: gw.owner,
|
||||
owner: gw.owner,
|
||||
identity_key: gw.gateway.identity_key || '',
|
||||
location: gw?.gateway?.location || '',
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
}));
|
||||
}
|
||||
|
||||
export function gatewayEnrichedToGridRow(
|
||||
gateway: GatewayResponseItem,
|
||||
report: GatewayReportResponse,
|
||||
): GatewayEnridedRowType {
|
||||
return {
|
||||
id: gateway.owner,
|
||||
owner: gateway.owner,
|
||||
identity_key: gateway.gateway.identity_key || '',
|
||||
location: gateway?.gateway?.location || '',
|
||||
bond: gateway.pledge_amount.amount || 0,
|
||||
host: gateway.gateway.host || '',
|
||||
clients_port: gateway.gateway.clients_port || 0,
|
||||
mix_port: gateway.gateway.mix_port || 0,
|
||||
routing_score: `${report.most_recent}%`,
|
||||
avg_uptime: `${report.last_day}%`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as React from 'react';
|
||||
import { ApiState, GatewayReportResponse, UptimeStoryResponse } from '../typeDefs/explorer-api';
|
||||
import { Api } from '../api';
|
||||
|
||||
/**
|
||||
* This context provides the state for a single gateway by identity key.
|
||||
*/
|
||||
|
||||
interface GatewayState {
|
||||
uptimeReport?: ApiState<GatewayReportResponse>;
|
||||
// uptimeStory?: ApiState<UptimeStoryResponse>;
|
||||
}
|
||||
|
||||
export const GatewayContext = React.createContext<GatewayState>({});
|
||||
|
||||
export const useGatewayContext = (): React.ContextType<typeof GatewayContext> =>
|
||||
React.useContext<GatewayState>(GatewayContext);
|
||||
|
||||
interface GatewayContextProviderProps {
|
||||
gatewayIdentityKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a state context for a gateway by identity
|
||||
* @param gatewayIdentityKey The identity key of the gateway
|
||||
*/
|
||||
export const GatewayContextProvider: React.FC<GatewayContextProviderProps> = ({ gatewayIdentityKey, children }) => {
|
||||
const [uptimeReport, fetchUptimeReportById, clearUptimeReportById] = useApiState<GatewayReportResponse>(
|
||||
gatewayIdentityKey,
|
||||
Api.fetchGatewayReportById,
|
||||
'Failed to fetch gateway uptime report by id',
|
||||
);
|
||||
|
||||
// const [uptimeStory, fetchUptimeHistory, clearUptimeHistory] = useApiState<UptimeStoryResponse>(
|
||||
// gatewayIdentityKey,
|
||||
// Api.fetchUptimeStoryById,
|
||||
// 'Failed to fetch gateway uptime history',
|
||||
// );
|
||||
|
||||
React.useEffect(() => {
|
||||
// when the identity key changes, remove all previous data
|
||||
clearUptimeReportById();
|
||||
Promise.all([fetchUptimeReportById()]);
|
||||
}, [gatewayIdentityKey]);
|
||||
|
||||
const state = React.useMemo<GatewayState>(
|
||||
() => ({
|
||||
uptimeReport,
|
||||
// uptimeStory,
|
||||
}),
|
||||
[
|
||||
{
|
||||
uptimeReport,
|
||||
// uptimeStory,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
return <GatewayContext.Provider value={state}>{children}</GatewayContext.Provider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook to get data from the API by passing an id to a delegate method that fetches the data asynchronously
|
||||
* @param id The id to fetch
|
||||
* @param fn Delegate the fetching to this method (must take `(id: string)` as a parameter)
|
||||
* @param errorMessage A static error message, to use when no dynamic error message is returned
|
||||
*/
|
||||
function useApiState<T>(
|
||||
id: string,
|
||||
fn: (id: string) => Promise<T>,
|
||||
errorMessage: string,
|
||||
): [ApiState<T> | undefined, () => Promise<ApiState<T>>, () => void] {
|
||||
// stores the state
|
||||
const [value, setValue] = React.useState<ApiState<T> | undefined>();
|
||||
|
||||
// clear the value
|
||||
const clearValueFn = () => setValue(undefined);
|
||||
|
||||
// this provides a method to trigger the delegate to fetch data
|
||||
const wrappedFetchFn = React.useCallback(async () => {
|
||||
try {
|
||||
// keep previous state and set to loading
|
||||
setValue((prevState) => ({ ...prevState, isLoading: true }));
|
||||
|
||||
// delegate to user function to get data and set if successful
|
||||
const data = await fn(id);
|
||||
const newValue: ApiState<T> = {
|
||||
isLoading: false,
|
||||
data,
|
||||
};
|
||||
setValue(newValue);
|
||||
return newValue;
|
||||
} catch (error) {
|
||||
// return the caught error or create a new error with the static error message
|
||||
const newValue: ApiState<T> = {
|
||||
error: error instanceof Error ? error : new Error(errorMessage),
|
||||
isLoading: false,
|
||||
};
|
||||
setValue(newValue);
|
||||
return newValue;
|
||||
}
|
||||
}, [setValue, fn]);
|
||||
return [value || { isLoading: true }, wrappedFetchFn, clearValueFn];
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, AlertTitle, Box, CircularProgress, Grid, Typography } from '@mui/material';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { GatewayResponseItem } from '../../typeDefs/explorer-api';
|
||||
import { ColumnsType, DetailTable } from '../../components/DetailTable';
|
||||
import { gatewayEnrichedToGridRow, GatewayEnridedRowType } from '../../components/Gateways';
|
||||
import { ComponentError } from '../../components/ComponentError';
|
||||
import { ContentCard } from '../../components/ContentCard';
|
||||
import { TwoColSmallTable } from '../../components/TwoColSmallTable';
|
||||
import { UptimeChart } from '../../components/UptimeChart';
|
||||
// import { GatewayDetailSection } from '../../components/Gateways/DetailSection';
|
||||
import { GatewayContextProvider, useGatewayContext } from '../../context/gateway';
|
||||
import { useMainContext } from '../../context/main';
|
||||
import { Title } from '../../components/Title';
|
||||
|
||||
const columns: ColumnsType[] = [
|
||||
{
|
||||
field: 'identity_key',
|
||||
title: 'Identity Key',
|
||||
headerAlign: 'left',
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
field: 'bond',
|
||||
title: 'Bond',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
},
|
||||
{
|
||||
field: 'routing_score',
|
||||
title: 'Routing Score',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
|
||||
},
|
||||
{
|
||||
field: 'avg_uptime',
|
||||
title: 'Avg. Score',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
title: 'IP',
|
||||
headerAlign: 'left',
|
||||
width: 99,
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
title: 'Location',
|
||||
headerAlign: 'left',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'owner',
|
||||
title: 'Owner',
|
||||
headerAlign: 'left',
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Shows gateway details
|
||||
*/
|
||||
const PageGatewayDetailsWithState: React.FC<{ selectedGateway: GatewayResponseItem | undefined }> = ({
|
||||
selectedGateway,
|
||||
}) => {
|
||||
const [enrichGateway, setEnrichGateway] = React.useState<GatewayEnridedRowType>();
|
||||
const [status, setStatus] = React.useState<number[] | undefined>();
|
||||
const { uptimeReport } = useGatewayContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (uptimeReport?.data && selectedGateway) {
|
||||
setEnrichGateway(gatewayEnrichedToGridRow(selectedGateway, uptimeReport.data));
|
||||
}
|
||||
}, [uptimeReport, selectedGateway]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (enrichGateway) {
|
||||
setStatus([enrichGateway?.mix_port, enrichGateway?.clients_port]);
|
||||
}
|
||||
}, [enrichGateway]);
|
||||
|
||||
return (
|
||||
<Box component="main">
|
||||
<Title text="Gateway Detail" />
|
||||
|
||||
<Grid container spacing={2} mt={1} mb={6}>
|
||||
<Grid item xs={12}>
|
||||
Gateway name & description
|
||||
{/* {gatewayRow && description?.data && (
|
||||
<GatewayDetailSection gatewayRow={gatewayRow} mixnodeDescription={description.data} />
|
||||
)} */}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<DetailTable
|
||||
columnsData={columns}
|
||||
tableName="Gateway detail table"
|
||||
rows={enrichGateway ? [enrichGateway] : []}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={2} mt={0}>
|
||||
<Grid item xs={12} md={4}>
|
||||
{status && (
|
||||
<ContentCard title="Gateway Status">
|
||||
<TwoColSmallTable
|
||||
loading={false}
|
||||
keys={['Mix port', 'Client WS API Port']}
|
||||
values={status.map((each) => each)}
|
||||
icons={status.map((elem) => !!elem) || [false, false]}
|
||||
/>
|
||||
</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>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Guard component to handle loading and not found states
|
||||
*/
|
||||
const PageGatewayDetailGuard: React.FC = () => {
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<GatewayResponseItem | undefined>();
|
||||
const { gateways } = useMainContext();
|
||||
const { id } = useParams<{ id: string | undefined }>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (gateways?.data) {
|
||||
setSelectedGateway(gateways.data.find((gateway) => gateway.gateway.identity_key === id));
|
||||
}
|
||||
}, [gateways]);
|
||||
|
||||
if (gateways?.isLoading) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
if (gateways?.error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(gateways?.error);
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Oh no! Could not load mixnode <code>{id || ''}</code>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
// loaded, but not found
|
||||
if (gateways && !gateways.isLoading && !gateways.data) {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Gateway not found</AlertTitle>
|
||||
Sorry, we could not find a mixnode with id <code>{id || ''}</code>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageGatewayDetailsWithState selectedGateway={selectedGateway} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper component that adds the mixnode content based on the `id` in the address URL
|
||||
*/
|
||||
export const PageGatewayDetail: React.FC = () => {
|
||||
const { id } = useParams<{ id: string | undefined }>();
|
||||
|
||||
if (!id) {
|
||||
return <Alert severity="error">Oh no! No mixnode identity key specified</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<GatewayContextProvider gatewayIdentityKey={id}>
|
||||
<PageGatewayDetailGuard />
|
||||
</GatewayContextProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Button, Card, Grid, Typography } from '@mui/material';
|
||||
import { Link as RRDLink, useParams, useNavigate } from 'react-router-dom';
|
||||
import { Button, Card, Grid, Typography, Link as MuiLink } from '@mui/material';
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
|
||||
import { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { useMainContext } from '../../context/main';
|
||||
@@ -22,6 +23,7 @@ export const PageGateways: React.FC = () => {
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log({ gateways });
|
||||
if (searchTerm === '' && gateways?.data) {
|
||||
setFilteredGateways(gateways?.data);
|
||||
} else {
|
||||
@@ -42,19 +44,19 @@ export const PageGateways: React.FC = () => {
|
||||
}, [searchTerm, gateways?.data]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: 'owner',
|
||||
headerName: 'Owner',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
width: 380,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="owner">
|
||||
{params.value}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// field: 'owner',
|
||||
// headerName: 'Owner',
|
||||
// renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
// width: 380,
|
||||
// headerAlign: 'left',
|
||||
// headerClassName: 'MuiDataGrid-header-override',
|
||||
// renderCell: (params: GridRenderCellParams) => (
|
||||
// <Typography sx={cellStyles} data-testid="owner">
|
||||
// {params.value}
|
||||
// </Typography>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
field: 'identity_key',
|
||||
headerName: 'Identity Key',
|
||||
@@ -63,9 +65,12 @@ export const PageGateways: React.FC = () => {
|
||||
width: 380,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="identity-key">
|
||||
// <Typography sx={cellStyles} data-testid="identity-key">
|
||||
// {params.value}
|
||||
// </Typography>
|
||||
<MuiLink component={RRDLink} to={`/network-components/gateway/${params.row.identity_key}`}>
|
||||
{params.value}
|
||||
</Typography>
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -81,6 +86,32 @@ export const PageGateways: React.FC = () => {
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'routing_score',
|
||||
headerName: 'Routing Score',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 200,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="routing-score">
|
||||
{params.value}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'avg_score',
|
||||
headerName: 'Avg. Score',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Avg. Score" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 200,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="avg-score">
|
||||
{params.value}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />,
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { Routes as ReactRouterRoutes, Route, useNavigate } from 'react-router-dom';
|
||||
import { BIG_DIPPER } from '../api/constants';
|
||||
import { PageGateways } from '../pages/Gateways';
|
||||
import { PageGatewayDetail } from '../pages/GateDetail';
|
||||
import { PageMixnodeDetail } from '../pages/MixnodeDetail';
|
||||
import { PageMixnodes } from '../pages/Mixnodes';
|
||||
|
||||
@@ -18,6 +19,7 @@ export const NetworkComponentsRoutes: React.FC = () => (
|
||||
<Route path="mixnodes" element={<PageMixnodes />} />
|
||||
<Route path="mixnode/:id" element={<PageMixnodeDetail />} />
|
||||
<Route path="gateways" element={<PageGateways />} />
|
||||
<Route path="gateway/:id" element={<PageGatewayDetail />} />
|
||||
<Route path="validators" element={<ValidatorRoute />} />
|
||||
<Route path="gateways/:id" element={<h1> Specific Gateways ID</h1>} />
|
||||
</ReactRouterRoutes>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import { UseButtonParameters } from "@mui/base";
|
||||
|
||||
export interface ClientConfig {
|
||||
url: string;
|
||||
version: string;
|
||||
@@ -126,12 +128,9 @@ export type GatewayResponse = GatewayResponseItem[];
|
||||
export interface GatewayReportResponse {
|
||||
identity: string;
|
||||
owner: string;
|
||||
most_recent_ipv4: boolean;
|
||||
most_recent_ipv6: boolean;
|
||||
last_hour_ipv4: number;
|
||||
last_hour_ipv6: number;
|
||||
last_day_ipv4: number;
|
||||
last_day_ipv6: number;
|
||||
most_recent: number;
|
||||
last_hour: number;
|
||||
last_day: number;
|
||||
}
|
||||
|
||||
export type GatewayHistoryResponse = StatsResponse;
|
||||
@@ -202,6 +201,14 @@ export type StatusResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
// export type UptimeReportResponse = {
|
||||
// identity: string;
|
||||
// owner: string;
|
||||
// most_recent: number;
|
||||
// last_hour: number;
|
||||
// last_day: UseButtonParameters;
|
||||
// };
|
||||
|
||||
export type UptimeTime = {
|
||||
date: string;
|
||||
uptime: number;
|
||||
|
||||
Reference in New Issue
Block a user