This commit is contained in:
Yana
2024-11-26 21:44:37 +07:00
parent fbd8cc5b4d
commit a9c9381cb6
5 changed files with 345 additions and 30 deletions
+191
View File
@@ -0,0 +1,191 @@
import type { NextApiRequest, NextApiResponse } from "next";
import {
EXPLORER_API,
COSMOS_API,
VALIDATOR_API_EPOCH,
VALIDATOR_API_SUPPLY,
HARBOURMASTER_API_SUMMARY,
HARBOURMASTER_API_MIXNODES_STATS,
HARBOURMASTER_API_BASE,
CURRENT_EPOCH,
CURRENT_EPOCH_REWARDS,
CIRCULATING_NYM_SUPPLY,
} from "../urls";
export interface ExplorerData {
circulatingNymSupplyData: any;
nymNodesData: any;
packetsAndStakingData: any;
currentEpochData: any;
currentEpochRewardsData: any;
}
export interface ExplorerCache {
data?: ExplorerData;
lastUpdated?: Date;
}
declare global {
// Extend the global object with our custom property
var explorerCache: ExplorerCache | undefined;
}
const CACHE_TIME_SECONDS = 60 * 5; // 5 minutes
const getExplorerData = async () => {
// FETCH NYMNODES
const fetchNymNodes = await fetch(HARBOURMASTER_API_SUMMARY, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
// refresh event list cache at given interval
next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) },
});
// FETCH CURRENT EPOCH
const fetchCurrentEpoch = await fetch(CURRENT_EPOCH, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
// refresh event list cache at given interval
next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) },
});
// FETCH CURRENT EPOCH REWARDS
const fetchCurrentEpochRewards = await fetch(CURRENT_EPOCH_REWARDS, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
// refresh event list cache at given interval
next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) },
});
// FETCH CIRCULATING NYM SUPPLY
const fetchCirculatingNymSupply = await fetch(CIRCULATING_NYM_SUPPLY, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
// refresh event list cache at given interval
next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) },
});
// FETCH PACKETS AND STAKING
const fetchPacketsAndStaking = await fetch(HARBOURMASTER_API_MIXNODES_STATS, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
// refresh event list cache at given interval
next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) },
});
const [
circulatingNymSupplyRes,
nymNodesRes,
packetsAndStakingRes,
currentEpochRes,
currentEpochRewardsRes,
] = await Promise.all([
fetchCirculatingNymSupply,
fetchNymNodes,
fetchPacketsAndStaking,
fetchCurrentEpoch,
fetchCurrentEpochRewards,
]);
const [
circulatingNymSupplyData,
nymNodesData,
packetsAndStakingData,
currentEpochData,
currentEpochRewardsData,
] = await Promise.all([
circulatingNymSupplyRes.json(),
nymNodesRes.json(),
packetsAndStakingRes.json(),
currentEpochRes.json(),
currentEpochRewardsRes.json(),
]);
return [
circulatingNymSupplyData,
nymNodesData,
packetsAndStakingData,
currentEpochData,
currentEpochRewardsData,
];
};
export async function ensureCacheExists() {
// makes sure the cache exists in global memory
let doUpdate = false;
const now = new Date();
if (!global.explorerCache) {
global.explorerCache = {};
doUpdate = true;
}
if (
global.explorerCache.lastUpdated &&
now.getDate() - global.explorerCache.lastUpdated.getDate() >
CACHE_TIME_SECONDS
) {
doUpdate = true;
}
// if the cache has expired or never existed, get it from API's
if (doUpdate) {
const [
circulatingNymSupplyData,
nymNodesData,
packetsAndStakingData,
currentEpochData,
currentEpochRewardsData,
] = await getExplorerData();
packetsAndStakingData.pop();
global.explorerCache.data = {
circulatingNymSupplyData,
nymNodesData,
packetsAndStakingData,
currentEpochData,
currentEpochRewardsData,
};
global.explorerCache.lastUpdated = now;
}
}
export async function getCacheExplorerData() {
await ensureCacheExists();
if (!global.explorerCache?.data) {
return null;
}
return global.explorerCache.data || null;
}
/**
* This is a custom API route that returns metadata from Strapi about images: height, width, strapi download url.
*
* The response from Strapi is cached in memory for CACHE_TIME_SECONDS.
*/
// export default async function handler(
// req: NextApiRequest,
// res: NextApiResponse
// ) {
// // return cached data
// const data = await getCacheExplorerData();
// if (data) {
// res.status(200).json(data);
// res.end();
// }
// // catch-all
// res.status(404).end();
// }
+18
View File
@@ -0,0 +1,18 @@
export const HARBOURMASTER_API_SUMMARY =
"https://harbourmaster.nymtech.net/v2/summary";
export const EXPLORER_API = "https://explorer.nymtech.net/api/v1/countries";
export const VALIDATOR_API_SUPPLY =
"https://validator.nymtech.net/api/v1/circulating-supply";
export const COSMOS_API =
"https://api.nymtech.net/cosmos/bank/v1beta1/balances/n1a53udazy8ayufvy0s434pfwjcedzqv34yg485t";
export const VALIDATOR_API_EPOCH =
"https://validator.nymtech.net/api/v1/epoch/reward_params";
export const HARBOURMASTER_API_MIXNODES_STATS =
"https://harbourmaster.nymtech.net/v2/mixnodes/stats";
export const HARBOURMASTER_API_BASE = "https://harbourmaster.nymtech.net";
export const CURRENT_EPOCH =
" https://validator.nymtech.net/api/v1/epoch/current";
export const CURRENT_EPOCH_REWARDS =
"https://validator.nymtech.net/api/v1/epoch/reward_params";
export const CIRCULATING_NYM_SUPPLY =
"https://validator.nymtech.net/api/v1/circulating-supply";
@@ -8,15 +8,17 @@ import {
interface ICardUpDownPriceLineProps {
percentage: number;
priceWentUp: boolean;
numberWentUp: boolean;
}
const CardUpDownPriceLine = (
props: ICardUpDownPriceLineProps
): ReactElement => {
const { percentage, priceWentUp } = props;
const { percentage, numberWentUp } = props;
return (
<Box mb={3}>
<Typography sx={{ color: "#00CA33" }}>{percentage}% (24H)</Typography>
<Typography sx={{ color: numberWentUp ? "#00CA33" : "#DF1400" }}>
{percentage}% (24H)
</Typography>
</Box>
);
};
@@ -66,7 +68,7 @@ const CardDataRows = (props: ICardDataRowsProps): React.ReactNode => {
type ContentCardProps = {
overTitle?: string;
title?: string;
title?: string | number;
upDownLine?: ICardUpDownPriceLineProps;
titlePrice?: ICardTitlePriceProps;
dataRows?: ICardDataRowsProps;
@@ -90,7 +92,7 @@ export const ExplorerCard: FC<ContentCardProps> = ({
<Card onClick={onClick} sx={{ height: "100%" }}>
<CardContent>
{overTitle && (
<Typography fontSize={14} mb={3}>
<Typography fontSize={14} mb={3} textTransform={"uppercase"}>
{overTitle}
</Typography>
)}
@@ -6,22 +6,21 @@ import { Typography } from "@mui/material";
export interface IExplorerProgressBarProps {
title?: string;
start: string; // Start timestamp as ISO 8601 string
end: string; // End timestamp as ISO 8601 string
showEpoch: boolean;
}
export const ExplorerProgressBar = (props: IExplorerProgressBarProps) => {
const { start, end, showEpoch, title } = props;
const { start, showEpoch, title } = props;
const [progress, setProgress] = React.useState(0);
React.useEffect(() => {
// Parse the timestamps
// Parse the start timestamp
const startTime = new Date(start).getTime();
const endTime = new Date(end).getTime();
const endTime = startTime + 60 * 60 * 1000; // Add 1 hour to the start time
// Validate timestamps
if (isNaN(startTime) || isNaN(endTime)) {
console.error("Invalid start or end timestamp:", { start, end });
// Validate start timestamp
if (isNaN(startTime)) {
console.error("Invalid start timestamp:", { start });
return;
}
@@ -49,17 +48,50 @@ export const ExplorerProgressBar = (props: IExplorerProgressBarProps) => {
return () => {
clearInterval(timer);
};
}, [start, end]);
}, [start]);
// Helper function to format date
const formatDate = (timestamp: number) => {
const date = new Date(timestamp);
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are 0-based
const year = date.getFullYear();
return `${hours}:${minutes}, ${day}/${month}/${year}`;
};
const startTime = new Date(start).getTime();
const endTime = startTime + 60 * 60 * 1000;
return (
<Box sx={{ width: "100%" }}>
{title && <Typography mb={2}>{title}</Typography>}
{title && (
<Typography mb={2} textTransform={"uppercase"}>
{title}
</Typography>
)}
<LinearProgress variant="determinate" value={progress} />
<LinearProgress
variant="determinate"
value={progress}
sx={{
backgroundColor: "#CAD6D7",
"& .MuiLinearProgress-bar": {
backgroundColor: "#14E76F",
},
}}
/>
{showEpoch && (
<Box mt={2}>
<Typography>Start: {new Date(start).toLocaleString()}</Typography>
<Typography>End: {new Date(end).toLocaleString()}</Typography>
<Box display={"flex"} justifyContent={"space-between"}>
<Typography textTransform={"uppercase"}>START:</Typography>
<Typography> {formatDate(startTime)}</Typography>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Typography textTransform={"uppercase"}>END:</Typography>
<Typography> {formatDate(endTime)}</Typography>
</Box>
</Box>
)}
</Box>
+85 -13
View File
@@ -1,6 +1,6 @@
"use client";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { Box, Grid, Link, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
@@ -18,6 +18,8 @@ import { formatNumber } from "@/app/utils";
import { useMainContext } from "./context/main";
import { useRouter } from "next/navigation";
import { ExplorerCard } from "./components/ExplorerCard";
import type { GetStaticProps, InferGetStaticPropsType } from "next";
import { ExplorerData, getCacheExplorerData } from "./api/explorer";
// type ContentCardProps = {
// overTitle?: string;
@@ -36,13 +38,13 @@ const explorerCard = {
title: "SINGLE",
upDownLine: {
percentage: 10,
priceWentUp: true,
numberWentUp: true,
},
titlePrice: {
price: 1.15,
upDownLine: {
percentage: 10,
priceWentUp: true,
numberWentUp: true,
},
},
dataRows: {
@@ -73,19 +75,85 @@ const explorerCard = {
purpleLineNumericData: 4,
},
],
progressBar: {
title: "Current NGM epoch",
start: "2024-11-24T13:26:19Z",
end: "2024-11-24T14:26:19Z",
showEpoch: true,
},
paragraph: "Additional line",
};
export const DATA_REVALIDATE = 60;
const PageOverview = () => {
export default function PageOverview() {
const [explorerData, setExplorerData] = useState<ExplorerData | null>(null);
useEffect(() => {
async function fetchData() {
const data = await getCacheExplorerData();
setExplorerData(data);
}
fetchData();
}, []);
const theme = useTheme();
const router = useRouter();
console.log("explorerData :>> ", explorerData);
const currentEpochStart =
explorerData?.currentEpochData.current_epoch_start || "";
const progressBar = {
title: "Current NGM epoch",
start: currentEpochStart,
showEpoch: true,
};
const formatBigNum = (num: number) => {
if (typeof num === "number") {
if (num >= 1000000000) {
return (num / 1000000000).toFixed(1).replace(/\.0$/, "") + "B";
}
if (num >= 1000000) {
return (num / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
}
if (num >= 1000) {
return (num / 1000).toFixed(1).replace(/\.0$/, "") + "K";
}
return num;
}
};
const packetsSentLast24H =
explorerData?.packetsAndStakingData[
explorerData.packetsAndStakingData.length - 1
].total_packets_sent;
const packetsSentPrevious24H =
explorerData?.packetsAndStakingData[
explorerData.packetsAndStakingData.length - 2
].total_packets_sent;
const calculatePercentageChange = (last24H: number, previous24H: number) => {
if (previous24H === 0) {
throw new Error(
"Cannot calculate percentage change when yesterday's value is zero."
);
}
const change = ((last24H - previous24H) / previous24H) * 100;
return parseFloat(change.toFixed(2));
};
const percentage = calculatePercentageChange(
packetsSentLast24H,
packetsSentPrevious24H
);
const noiseCard = {
overTitle: "Noise generated last 24h",
title: formatBigNum(packetsSentLast24H) || "",
upDownLine: {
percentage: Math.abs(percentage),
numberWentUp: percentage > 0,
},
};
const {
summaryOverview,
gateways,
@@ -107,6 +175,12 @@ const PageOverview = () => {
<Grid item xs={12} md={4}>
<ExplorerCard {...explorerCard} />
</Grid>
<Grid item xs={12} md={4}>
<ExplorerCard progressBar={progressBar} />
</Grid>
<Grid item xs={12} md={4}>
<ExplorerCard {...noiseCard} />
</Grid>
<Grid item xs={12} md={4}>
<StatsCard
onClick={() => router.push("/network-components/mixnodes")}
@@ -218,6 +292,4 @@ const PageOverview = () => {
</Grid>
</Box>
);
};
export default PageOverview;
}