Add peers page

This commit is contained in:
Arnaud 2024-10-10 10:34:59 +02:00
parent 6c96cbe295
commit 29ceaea8c5
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
7 changed files with 1510 additions and 0 deletions

View File

@ -0,0 +1,50 @@
import { Cell } from "@codex-storage/marketplace-ui-components";
import { useEffect, useState } from "react";
import { PeerPin } from "./types";
import { countriesCoordinates } from "./countries";
export type Props = {
address: string;
onPinAdd: (pin: PeerPin) => void;
};
const getFlagEmoji = (countryCode: string) => {
const codePoints = countryCode
.toUpperCase()
.split("")
.map((char) => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
};
export function PeerCountryCell({ address, onPinAdd }: Props) {
const [country, setCountry] = useState("");
useEffect(() => {
const [ip] = address.split(":");
console.info(ip);
fetch("https://api.country.is/" + ip)
.then((res) => res.json())
.then((json) => {
setCountry(json.country);
const coordinate = countriesCoordinates.find(
(c) => c.iso === json.country
);
if (coordinate) {
onPinAdd({
lat: parseFloat(coordinate.lat),
lng: parseFloat(coordinate.lng),
});
}
});
}, [address]);
return (
<Cell>
{getFlagEmoji(country)} {address}
</Cell>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
export type PeerPin = {
lat: number;
lng: number;
};

View File

@ -17,6 +17,7 @@ import { Route as DashboardIndexImport } from './routes/dashboard/index'
import { Route as DashboardSettingsImport } from './routes/dashboard/settings'
import { Route as DashboardRequestsImport } from './routes/dashboard/requests'
import { Route as DashboardPurchasesImport } from './routes/dashboard/purchases'
import { Route as DashboardPeersImport } from './routes/dashboard/peers'
import { Route as DashboardHelpImport } from './routes/dashboard/help'
import { Route as DashboardFavoritesImport } from './routes/dashboard/favorites'
import { Route as DashboardDisclaimerImport } from './routes/dashboard/disclaimer'
@ -55,6 +56,11 @@ const DashboardPurchasesRoute = DashboardPurchasesImport.update({
getParentRoute: () => DashboardRoute,
} as any)
const DashboardPeersRoute = DashboardPeersImport.update({
path: '/peers',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardHelpRoute = DashboardHelpImport.update({
path: '/help',
getParentRoute: () => DashboardRoute,
@ -133,6 +139,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardHelpImport
parentRoute: typeof DashboardImport
}
'/dashboard/peers': {
id: '/dashboard/peers'
path: '/peers'
fullPath: '/dashboard/peers'
preLoaderRoute: typeof DashboardPeersImport
parentRoute: typeof DashboardImport
}
'/dashboard/purchases': {
id: '/dashboard/purchases'
path: '/purchases'
@ -172,6 +185,7 @@ interface DashboardRouteChildren {
DashboardDisclaimerRoute: typeof DashboardDisclaimerRoute
DashboardFavoritesRoute: typeof DashboardFavoritesRoute
DashboardHelpRoute: typeof DashboardHelpRoute
DashboardPeersRoute: typeof DashboardPeersRoute
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
DashboardRequestsRoute: typeof DashboardRequestsRoute
DashboardSettingsRoute: typeof DashboardSettingsRoute
@ -184,6 +198,7 @@ const DashboardRouteChildren: DashboardRouteChildren = {
DashboardDisclaimerRoute: DashboardDisclaimerRoute,
DashboardFavoritesRoute: DashboardFavoritesRoute,
DashboardHelpRoute: DashboardHelpRoute,
DashboardPeersRoute: DashboardPeersRoute,
DashboardPurchasesRoute: DashboardPurchasesRoute,
DashboardRequestsRoute: DashboardRequestsRoute,
DashboardSettingsRoute: DashboardSettingsRoute,
@ -202,6 +217,7 @@ export interface FileRoutesByFullPath {
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/peers': typeof DashboardPeersRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
@ -215,6 +231,7 @@ export interface FileRoutesByTo {
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/peers': typeof DashboardPeersRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
@ -230,6 +247,7 @@ export interface FileRoutesById {
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/peers': typeof DashboardPeersRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
@ -246,6 +264,7 @@ export interface FileRouteTypes {
| '/dashboard/disclaimer'
| '/dashboard/favorites'
| '/dashboard/help'
| '/dashboard/peers'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
@ -258,6 +277,7 @@ export interface FileRouteTypes {
| '/dashboard/disclaimer'
| '/dashboard/favorites'
| '/dashboard/help'
| '/dashboard/peers'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
@ -271,6 +291,7 @@ export interface FileRouteTypes {
| '/dashboard/disclaimer'
| '/dashboard/favorites'
| '/dashboard/help'
| '/dashboard/peers'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
@ -315,6 +336,7 @@ export const routeTree = rootRoute
"/dashboard/disclaimer",
"/dashboard/favorites",
"/dashboard/help",
"/dashboard/peers",
"/dashboard/purchases",
"/dashboard/requests",
"/dashboard/settings",
@ -341,6 +363,10 @@ export const routeTree = rootRoute
"filePath": "dashboard/help.tsx",
"parent": "/dashboard"
},
"/dashboard/peers": {
"filePath": "dashboard/peers.tsx",
"parent": "/dashboard"
},
"/dashboard/purchases": {
"filePath": "dashboard/purchases.tsx",
"parent": "/dashboard"

View File

@ -12,6 +12,7 @@ import {
Settings,
HelpCircle,
TriangleAlert,
Earth,
} from "lucide-react";
import { ICON_SIZE } from "../utils/constants";
import { NodeIndicator } from "../components/NodeIndicator/NodeIndicator";
@ -87,6 +88,15 @@ const Layout = () => {
</Link>
),
},
{
type: "menu-item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/peers" {...p}>
<Earth size={ICON_SIZE} />
Peers
</Link>
),
},
{
type: "menu-item",
Component: (p: MenuItemComponentProps) => (

View File

@ -0,0 +1,10 @@
.peers-map,
.peers-table {
max-width: 1000px;
margin: auto;
}
.peers-table {
margin-top: 1rem;
width: 100%;
}

View File

@ -0,0 +1,98 @@
import { Cell, Row, Table } from "@codex-storage/marketplace-ui-components";
import { createFileRoute } from "@tanstack/react-router";
import { getMapJSON } from "dotted-map";
import DottedMap from "dotted-map/without-countries";
import { Promises } from "../../utils/promises";
import { useQuery } from "@tanstack/react-query";
import { PeerCountryCell } from "../../components/Peers/PeerCountryCell";
import { useCallback, useState } from "react";
import { PeerPin } from "../../components/Peers/types";
import "./peers.css";
import { CodexSdk } from "../../sdk/codex";
// This function accepts the same arguments as DottedMap in the example above.
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
export const Route = createFileRoute("/dashboard/peers")({
component: () => {
const [pins, setPins] = useState<[PeerPin, number][]>([]);
const { data } = useQuery({
queryFn: () =>
CodexSdk.debug.info().then((s) => Promises.rejectOnError(s)),
queryKey: ["debug"],
// No need to retry because if the connection to the node
// is back again, all the queries will be invalidated.
retry: false,
// The client node should be local, so display the cache value while
// making a background request looks good.
staleTime: 0,
// Refreshing when focus returns can be useful if a user comes back
// to the UI after performing an operation in the terminal.
refetchOnWindowFocus: true,
// Throw the error to the error boundary
throwOnError: true,
});
const onPinAdd = useCallback((pin: PeerPin) => {
setPins((val) => {
const [, quantity = 0] =
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
return [...val, [pin, quantity + 1]];
});
}, []);
// Its safe to re-create the map at each render, because of the
// pre-computation its super fast ⚡️
const map = new DottedMap({ map: JSON.parse(mapJsonString) });
pins.map(([pin, quantity]) =>
map.addPin({
lat: pin.lat,
lng: pin.lng,
svgOptions: { color: "#d6ff79", radius: 0.4 * quantity },
})
);
const svgMap = map.getSVG({
radius: 0.42,
color: "#423B38",
shape: "circle",
backgroundColor: "#020300",
});
const headers = ["Country", "PeerId", "Active"];
const rows =
((data as any)?.table?.nodes || []).map((node: any) => (
<Row
cells={[
<PeerCountryCell
onPinAdd={onPinAdd}
address={node.address}></PeerCountryCell>,
<Cell>{node.peerId}</Cell>,
<Cell>
{node.seen ? (
<div className="networkIndicator-point networkIndicator-point--online"></div>
) : (
<div className="networkIndicator-point networkIndicator-point--offline"></div>
)}
</Cell>,
]}></Row>
)) || [];
return (
<>
<img
src={`data:image/svg+xml;utf8,${encodeURIComponent(svgMap)}`}
className="peers-map"
/>
<Table headers={headers} rows={rows} className="peers-table" />
</>
);
},
});