diff --git a/explorer-nextjs/src/actions/getNymNodes.ts b/explorer-nextjs/src/actions/getNymNodes.ts index d42234aa06..70d214c683 100644 --- a/explorer-nextjs/src/actions/getNymNodes.ts +++ b/explorer-nextjs/src/actions/getNymNodes.ts @@ -1,8 +1,8 @@ -import type NymNode from "@/app/api/types"; -import { NYM_NODES } from "@/app/api/urls"; +import type { IObservatoryNode } from "@/app/api/types"; +import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; -const getNymNodes = async (): Promise => { - const response = await fetch(`${NYM_NODES}`, { +const getNymNodes = async (): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_NODES_URL}`, { next: { revalidate: 900, }, diff --git a/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx b/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx index 399e2a0b72..0fbb46a999 100644 --- a/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx +++ b/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx @@ -3,11 +3,12 @@ import type NodeData from "@/app/api/types"; import { NYM_ACCOUNT_ADDRESS, NYM_NODES, NYM_PRICES_API } from "@/app/api/urls"; import { AccountBalancesCard } from "@/components/accountPageComponents/AccountBalancesCard"; import { AccountInfoCard } from "@/components/accountPageComponents/AccountInfoCard"; +import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; import { ContentLayout } from "@/components/contentLayout/ContentLayout"; import SectionHeading from "@/components/headings/SectionHeading"; import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; import { Box, Typography } from "@mui/material"; -import Grid2 from "@mui/material/Grid2"; +import Grid from "@mui/material/Grid2"; export default async function Account({ params, @@ -61,11 +62,11 @@ export default async function Account({ return ( - - + + - - + + - - + + - - + + - - + + + + + + + + ); } catch (error) { diff --git a/explorer-nextjs/src/app/(pages)/explorer/page.tsx b/explorer-nextjs/src/app/(pages)/explorer/page.tsx index ef74c4df7c..439dcc2e0d 100644 --- a/explorer-nextjs/src/app/(pages)/explorer/page.tsx +++ b/explorer-nextjs/src/app/(pages)/explorer/page.tsx @@ -1,8 +1,10 @@ +import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; import CardSkeleton from "@/components/cards/Skeleton"; import { ContentLayout } from "@/components/contentLayout/ContentLayout"; import SectionHeading from "@/components/headings/SectionHeading"; import NodeTableWithAction from "@/components/nodeTable/NodeTableWithAction"; import { Wrapper } from "@/components/wrapper"; +import Grid from "@mui/material/Grid2"; import { Suspense } from "react"; export default function ExplorerPage() { @@ -13,6 +15,12 @@ export default function ExplorerPage() { }> + + + + + + ); diff --git a/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx b/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx index eb671ff146..7f1c240d4d 100644 --- a/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx +++ b/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx @@ -1,15 +1,19 @@ -import type NodeData from "@/app/api/types"; -import { NYM_NODES } from "@/app/api/urls"; +import type { IObservatoryNode } from "@/app/api/types"; +import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; +import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +import ExplorerCard from "@/components/cards/ExplorerCard"; import { ContentLayout } from "@/components/contentLayout/ContentLayout"; import SectionHeading from "@/components/headings/SectionHeading"; +import DelegationsTable from "@/components/nodeTable/DelegationsTable"; import { BasicInfoCard } from "@/components/nymNodePageComponents/BasicInfoCard"; +import { NodeChatCard } from "@/components/nymNodePageComponents/ChatCard"; import { NodeMetricsCard } from "@/components/nymNodePageComponents/NodeMetricsCard"; import { NodeProfileCard } from "@/components/nymNodePageComponents/NodeProfileCard"; import { NodeRewardsCard } from "@/components/nymNodePageComponents/NodeRewardsCard"; import { QualityIndicatorsCard } from "@/components/nymNodePageComponents/QualityIndicatorsCard"; import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; import { Box } from "@mui/material"; -import Grid2 from "@mui/material/Grid2"; +import Grid from "@mui/material/Grid2"; export default async function NymNode({ params, @@ -19,7 +23,7 @@ export default async function NymNode({ try { const id = Number((await params).id); - const response = await fetch(NYM_NODES, { + const observatoryResponse = await fetch(DATA_OBSERVATORY_NODES_URL, { headers: { Accept: "application/json", "Content-Type": "application/json; charset=utf-8", @@ -28,87 +32,141 @@ export default async function NymNode({ // refresh event list cache at given interval }); - const nymNodes: NodeData[] = await response.json(); + const observatoryNymNodes: IObservatoryNode[] = + await observatoryResponse.json(); - if (!nymNodes) { + if (!observatoryNymNodes) { return null; } - const nymNode = nymNodes.find((node) => node.node_id === id); + const observatoryNymNode = observatoryNymNodes.find( + (node) => node.node_id === id, + ); - if (!nymNode) { + if (!observatoryNymNode) { return null; } + const nodeDelegationsResponse = await fetch( + `${DATA_OBSERVATORY_NODES_URL}/${id}/delegations`, + { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + next: { revalidate: 60 }, + // refresh event list cache at given interval + }, + ); + + const delegations = await nodeDelegationsResponse.json(); + return ( - - + + - + {observatoryNymNode.bonding_address && ( + + )} - - - - - - - - - - - - - - + {observatoryNymNode && ( + + + + )} + {observatoryNymNode.rewarding_details && ( + + + + )} + {observatoryNymNode && ( + + + + )} + {observatoryNymNode.rewarding_details && ( + + + + )} + {observatoryNymNode && ( + + + + )} + {delegations && ( + + + + + + )} + + - - - + + + + + + + + + ); } catch (error) { diff --git a/explorer-nextjs/src/app/api/types.ts b/explorer-nextjs/src/app/api/types.ts index 8951b0052d..644d8addb6 100644 --- a/explorer-nextjs/src/app/api/types.ts +++ b/explorer-nextjs/src/app/api/types.ts @@ -147,3 +147,221 @@ export interface IAccountBalancesInfo { total_value: IAmountDetails; vesting_account?: null | string; } + +export interface IObservatoryNode { + accepted_tnc: boolean; + bonded: boolean; + bonding_address: string; + description: { + authenticator: { + address: string; + }; + auxiliary_details: { + accepted_operator_terms_and_conditions: boolean; + announce_ports: { + mix_port: number | null; + verloc_port: number | null; + }; + location: string | null; + }; + build_information: { + binary_name: string; + build_timestamp: string; + build_version: string; + cargo_profile: string; + cargo_triple: string; + commit_branch: string; + commit_sha: string; + commit_timestamp: string; + rustc_channel: string; + rustc_version: string; + }; + declared_role: { + entry: boolean; + exit_ipr: boolean; + exit_nr: boolean; + mixnode: boolean; + }; + host_information: { + hostname: string | null; + ip_address: string[]; + }; + keys: { + ed25519: string; + x25519: string; + x25519_noise: string | null; + }; + ip_packet_router: { + address: string; + }; + last_polled: string; + mixnet_websockets: { + ws_port: number; + wss_port: number | null; + }; + network_requester: { + address: string; + uses_exit_policy: boolean; + }; + wireguard: string | null; + geoip: { + city: string; + country: string; + ip_address: string; + loc: string; + node_id: number; + org: string; + postal: string; + region: string; + }; + }; + identity_key: string; + ip_address: string; + node_id: number; + node_type: string; + original_pledge: number; + rewarding_details: { + cost_params: { + interval_operating_cost: { + amount: string; + denom: string; + }; + profit_margin_percent: string; + }; + delegates: string; + last_rewarded_epoch: number; + operator: string; + total_unit_reward: string; + unique_delegations: number; + unit_delegation: string; + }; + self_description: { + details: string; + moniker: string; + security_contact: string; + website: string; + }; + total_stake: number; + uptime: number; +} +export interface NodeRewardDetails { + amount: { + amount: string; + denom: string; + }; + cumulative_reward_ratio: string; + height: number; + node_id: number; + owner: string; +} + +export type LastProbeResult = { + gateway: string; + outcome: { + as_entry: { + can_connect: boolean; + can_route: boolean; + }; + as_exit: { + can_connect: boolean; + can_route_ip_external_v4: boolean; + can_route_ip_external_v6: boolean; + can_route_ip_v4: boolean; + can_route_ip_v6: boolean; + }; + wg: { + can_handshake_v4: boolean; + can_handshake_v6: boolean; + can_register: boolean; + can_resolve_dns_v4: boolean; + can_resolve_dns_v6: boolean; + ping_hosts_performance_v4: number; + ping_hosts_performance_v6: number; + ping_ips_performance_v4: number; + ping_ips_performance_v6: number; + }; + }; +}; + +export type GatewayStatus = { + blacklisted: boolean; + bonded: boolean; + config_score: number; + description: { + details: string; + moniker: string; + security_contact: string; + website: string; + }; + explorer_pretty_bond: { + identity_key: string; + location: { + latitude: number; + longitude: number; + two_letter_iso_country_code: string; + }; + owner: string; + pledge_amount: { + amount: string; + denom: string; + }; + }; + gateway_identity_key: string; + last_probe_log: string; + last_probe_result: LastProbeResult; // Reference to the separate type + last_testrun_utc: string; + last_updated_utc: string; + performance: number; + routing_score: number; + self_described: { + authenticator: { + address: string; + }; + auxiliary_details: { + accepted_operator_terms_and_conditions: boolean; + announce_ports: { + mix_port: number | null; + verloc_port: number | null; + }; + location: string; + }; + build_information: { + binary_name: string; + build_timestamp: string; + build_version: string; + cargo_profile: string; + cargo_triple: string; + }; + declared_role: { + entry: boolean; + exit_ipr: boolean; + exit_nr: boolean; + mixnode: boolean; + }; + host_information: { + hostname: string; + ip_address: string[]; + keys: { + ed25519: string; + x25519: string; + x25519_noise: string | null; + }; + }; + ip_packet_router: { + address: string; + }; + last_polled: string; + mixnet_websockets: { + ws_port: number; + wss_port: number | null; + }; + network_requester: { + address: string; + uses_exit_policy: boolean; + }; + wireguard: { + port: number; + public_key: string; + }; + }; +}; diff --git a/explorer-nextjs/src/app/api/urls.ts b/explorer-nextjs/src/app/api/urls.ts index d4e981d1c9..548863c24f 100644 --- a/explorer-nextjs/src/app/api/urls.ts +++ b/explorer-nextjs/src/app/api/urls.ts @@ -32,3 +32,5 @@ export const NYM_PRICES_API = "https://canary-nym-vpn-chain-payment-watcher.nymte.ch/v1/price/average"; export const VALIDATOR_BASE_URL = process.env.NEXT_PUBLIC_VALIDATOR_URL || "https://rpc.nymtech.net"; +export const DATA_OBSERVATORY_NODES_URL = + "https://api.nym.spectredao.net/api/v1/nodes"; diff --git a/explorer-nextjs/src/components/comments/index.tsx b/explorer-nextjs/src/components/comments/index.tsx index 51ae6430b1..6c060c8956 100644 --- a/explorer-nextjs/src/components/comments/index.tsx +++ b/explorer-nextjs/src/components/comments/index.tsx @@ -29,8 +29,8 @@ export const Remark42Comments = () => { if (typeof window !== "undefined") { // Set Remark42 configuration on the window object window.remark_config = { - host: "https://remark.blockfend.com", - site_id: "nym-explorer-test", + host: "https://remark42.nymte.ch", + site_id: "remark", components: ["embed", "last-comments"], max_shown_comments: 100, theme: "light", @@ -68,13 +68,14 @@ export const Remark42Comments = () => {