This commit is contained in:
Eduard Gert
2026-05-06 10:47:40 +02:00
parent bfe19fa542
commit c3f9514182
32 changed files with 2815 additions and 175 deletions

View File

@@ -0,0 +1,53 @@
import { cn } from "@/lib/cn";
export type StatusFilter = "all" | "online" | "offline";
const FILTERS: { value: StatusFilter; label: string }[] = [
{ value: "all", label: "All" },
{ value: "online", label: "Online" },
{ value: "offline", label: "Offline" },
];
type Props = {
value: StatusFilter;
onChange: (value: StatusFilter) => void;
counts: Record<StatusFilter, number>;
};
export const PeerFilters = ({ value, onChange, counts }: Props) => {
return (
<div
className={
"flex w-full rounded-md border border-nb-gray-900 bg-nb-gray-940 p-0.5"
}
>
{FILTERS.map((f) => {
const active = value === f.value;
return (
<button
key={f.value}
type={"button"}
onClick={() => onChange(f.value)}
className={cn(
"flex-1 inline-flex items-center justify-center gap-1.5 rounded px-2.5 py-2 text-xs font-medium",
"transition-colors duration-150 cursor-default outline-none",
active
? "bg-nb-gray-800 text-nb-gray-100"
: "text-nb-gray-400 hover:text-nb-gray-200",
)}
>
{f.label}
<span
className={cn(
"text-[0.65rem] font-mono",
active ? "text-nb-gray-300" : "text-nb-gray-500",
)}
>
{counts[f.value]}
</span>
</button>
);
})}
</div>
);
};

View File

@@ -0,0 +1,51 @@
import { cn } from "@/lib/cn";
import { Peer, PeerStatus } from "./types";
const DOT: Record<PeerStatus, string> = {
connected: "bg-green-400",
connecting: "bg-yellow-300 animate-pulse-slow",
disconnected: "bg-nb-gray-500",
};
export const PeersList = ({ data }: { data: Peer[] }) => {
if (data.length === 0) {
return (
<div className={"py-12 text-center text-sm text-nb-gray-400"}>
No peers match the current filters.
</div>
);
}
return (
<ul className={"flex flex-col"}>
{data.map((peer) => (
<li
key={peer.id}
className={"flex items-center gap-3 px-4 py-3 min-w-0"}
>
<span
className={cn(
"h-2 w-2 rounded-full shrink-0",
DOT[peer.status],
)}
title={peer.status}
/>
<span
className={
"text-[0.81rem] font-medium text-nb-gray-100 truncate"
}
>
{peer.fqdn}
</span>
<span
className={
"ml-auto text-xs font-mono text-nb-gray-400 shrink-0"
}
>
{peer.ip}
</span>
</li>
))}
</ul>
);
};

View File

@@ -0,0 +1,73 @@
import { useMemo, useState } from "react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/cn";
import { SearchInput } from "@/components/SearchInput";
import { mockPeers } from "./mockPeers";
import { PeerFilters, StatusFilter } from "./PeerFilters";
import { PeersList } from "./PeersList";
const isOnline = (status: string) => status === "connected";
export const PeersModule = () => {
const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
const counts = useMemo<Record<StatusFilter, number>>(() => {
const online = mockPeers.filter((p) => isOnline(p.status)).length;
return {
all: mockPeers.length,
online,
offline: mockPeers.length - online,
};
}, []);
const filtered = useMemo(() => {
const q = search.trim().toLowerCase();
return mockPeers.filter((p) => {
if (statusFilter === "online" && !isOnline(p.status)) return false;
if (statusFilter === "offline" && isOnline(p.status)) return false;
if (q && !p.fqdn.toLowerCase().includes(q) && !p.ip.includes(q)) {
return false;
}
return true;
});
}, [search, statusFilter]);
return (
<div className={"flex flex-col w-full h-full min-h-0 pt-4"}>
<div className={"flex flex-col gap-3 px-4"}>
<SearchInput
placeholder={"Search by FQDN or IP…"}
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<PeerFilters
value={statusFilter}
onChange={setStatusFilter}
counts={counts}
/>
</div>
<ScrollArea.Root
type={"auto"}
className={"flex-1 min-h-0 overflow-hidden mt-3"}
>
<ScrollArea.Viewport className={"h-full w-full"}>
<PeersList data={filtered} />
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
orientation={"vertical"}
className={cn(
"flex select-none touch-none transition-colors",
"w-1.5 bg-transparent py-1",
)}
>
<ScrollArea.Thumb
className={
"flex-1 rounded-full bg-nb-gray-800 hover:bg-nb-gray-700 relative"
}
/>
</ScrollArea.Scrollbar>
</ScrollArea.Root>
</div>
);
};

View File

@@ -0,0 +1,385 @@
import { Peer } from "./types";
const minutesAgo = (m: number) => new Date(Date.now() - m * 60 * 1000);
export const mockPeers: Peer[] = [
{
id: "p-001",
fqdn: "alice-laptop.netbird.cloud",
ip: "100.64.0.12",
status: "connected",
lastHandshake: minutesAgo(1),
latencyMs: 18,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 84,
bytesTx: 1024 * 1024 * 12,
endpointLocal: "192.168.1.24:51820",
endpointRemote: "203.0.113.45:51820",
},
{
id: "p-002",
fqdn: "bob-desktop.netbird.cloud",
ip: "100.64.0.21",
status: "connected",
lastHandshake: minutesAgo(3),
latencyMs: 42,
relayed: true,
relayAddress: "rel.eu-central.netbird.io:443",
iceLocalCandidate: "relay",
iceRemoteCandidate: "relay",
bytesRx: 1024 * 380,
bytesTx: 1024 * 940,
endpointLocal: "10.0.0.8:51820",
endpointRemote: "198.51.100.7:51820",
},
{
id: "p-003",
fqdn: "build-runner-01.netbird.cloud",
ip: "100.64.0.34",
status: "connecting",
lastHandshake: minutesAgo(15),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 0,
bytesTx: 0,
endpointLocal: "192.168.1.45:51820",
endpointRemote: "—",
},
{
id: "p-004",
fqdn: "carol-phone.netbird.cloud",
ip: "100.64.0.55",
status: "disconnected",
lastHandshake: minutesAgo(620),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 5,
bytesTx: 1024 * 1024 * 2,
endpointLocal: "—",
endpointRemote: "—",
},
{
id: "p-005",
fqdn: "exit-berlin.netbird.cloud",
ip: "100.64.0.2",
status: "connected",
lastHandshake: minutesAgo(0.2),
latencyMs: 9,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 1024 * 2,
bytesTx: 1024 * 1024 * 512,
endpointLocal: "10.10.0.4:51820",
endpointRemote: "203.0.113.99:51820",
},
{
id: "p-006",
fqdn: "db-replica-eu.netbird.cloud",
ip: "100.64.0.78",
status: "connected",
lastHandshake: minutesAgo(7),
latencyMs: 64,
relayed: true,
relayAddress: "rel.us-east.netbird.io:443",
iceLocalCandidate: "relay",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 240,
bytesTx: 1024 * 1024 * 90,
endpointLocal: "172.16.0.10:51820",
endpointRemote: "198.51.100.42:51820",
},
{
id: "p-007",
fqdn: "dev-vm-mac.netbird.cloud",
ip: "100.64.0.91",
status: "disconnected",
lastHandshake: minutesAgo(2880),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 0,
bytesTx: 0,
endpointLocal: "—",
endpointRemote: "—",
},
{
id: "p-008",
fqdn: "ci-worker-03.netbird.cloud",
ip: "100.64.0.103",
status: "connected",
lastHandshake: minutesAgo(0.5),
latencyMs: 27,
relayed: false,
iceLocalCandidate: "prflx",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 14,
bytesTx: 1024 * 1024 * 3,
endpointLocal: "192.168.50.7:51820",
endpointRemote: "203.0.113.61:51820",
},
{
id: "p-009",
fqdn: "k8s-control-plane.netbird.cloud",
ip: "100.64.0.110",
status: "connected",
lastHandshake: minutesAgo(2),
latencyMs: 12,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 410,
bytesTx: 1024 * 1024 * 380,
endpointLocal: "10.0.1.10:51820",
endpointRemote: "10.0.1.11:51820",
},
{
id: "p-010",
fqdn: "k8s-worker-01.netbird.cloud",
ip: "100.64.0.111",
status: "connected",
lastHandshake: minutesAgo(2),
latencyMs: 14,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 220,
bytesTx: 1024 * 1024 * 190,
endpointLocal: "10.0.1.20:51820",
endpointRemote: "10.0.1.21:51820",
},
{
id: "p-011",
fqdn: "k8s-worker-02.netbird.cloud",
ip: "100.64.0.112",
status: "connecting",
lastHandshake: minutesAgo(8),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "srflx",
bytesRx: 0,
bytesTx: 0,
endpointLocal: "10.0.1.22:51820",
endpointRemote: "—",
},
{
id: "p-012",
fqdn: "monitoring-prom.netbird.cloud",
ip: "100.64.0.130",
status: "connected",
lastHandshake: minutesAgo(0.3),
latencyMs: 22,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 56,
bytesTx: 1024 * 1024 * 18,
endpointLocal: "10.20.0.5:51820",
endpointRemote: "203.0.113.122:51820",
},
{
id: "p-013",
fqdn: "grafana.netbird.cloud",
ip: "100.64.0.131",
status: "connected",
lastHandshake: minutesAgo(0.4),
latencyMs: 19,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 32,
bytesTx: 1024 * 1024 * 8,
endpointLocal: "10.20.0.6:51820",
endpointRemote: "203.0.113.123:51820",
},
{
id: "p-014",
fqdn: "loki-log-aggregator.netbird.cloud",
ip: "100.64.0.132",
status: "disconnected",
lastHandshake: minutesAgo(45),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 12,
bytesTx: 1024 * 1024 * 4,
endpointLocal: "—",
endpointRemote: "—",
},
{
id: "p-015",
fqdn: "dave-laptop.netbird.cloud",
ip: "100.64.0.140",
status: "connected",
lastHandshake: minutesAgo(1),
latencyMs: 38,
relayed: true,
relayAddress: "rel.eu-west.netbird.io:443",
iceLocalCandidate: "relay",
iceRemoteCandidate: "relay",
bytesRx: 1024 * 720,
bytesTx: 1024 * 410,
endpointLocal: "192.168.43.21:51820",
endpointRemote: "198.51.100.88:51820",
},
{
id: "p-016",
fqdn: "eve-iphone.netbird.cloud",
ip: "100.64.0.150",
status: "connecting",
lastHandshake: minutesAgo(20),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "host",
bytesRx: 0,
bytesTx: 0,
endpointLocal: "—",
endpointRemote: "—",
},
{
id: "p-017",
fqdn: "frank-windows.netbird.cloud",
ip: "100.64.0.155",
status: "connected",
lastHandshake: minutesAgo(4),
latencyMs: 76,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 6,
bytesTx: 1024 * 1024 * 2,
endpointLocal: "192.168.1.55:51820",
endpointRemote: "203.0.113.200:51820",
},
{
id: "p-018",
fqdn: "exit-frankfurt.netbird.cloud",
ip: "100.64.0.3",
status: "connected",
lastHandshake: minutesAgo(0.1),
latencyMs: 6,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 1024 * 5,
bytesTx: 1024 * 1024 * 1024 * 1,
endpointLocal: "10.10.0.5:51820",
endpointRemote: "203.0.113.150:51820",
},
{
id: "p-019",
fqdn: "exit-singapore.netbird.cloud",
ip: "100.64.0.4",
status: "disconnected",
lastHandshake: minutesAgo(180),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 880,
bytesTx: 1024 * 1024 * 220,
endpointLocal: "—",
endpointRemote: "—",
},
{
id: "p-020",
fqdn: "nas-home.netbird.cloud",
ip: "100.64.0.180",
status: "connected",
lastHandshake: minutesAgo(0.7),
latencyMs: 31,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 1024 * 3,
bytesTx: 1024 * 1024 * 480,
endpointLocal: "192.168.0.50:51820",
endpointRemote: "203.0.113.45:51820",
},
{
id: "p-021",
fqdn: "raspberrypi-iot.netbird.cloud",
ip: "100.64.0.181",
status: "connected",
lastHandshake: minutesAgo(5),
latencyMs: 54,
relayed: true,
relayAddress: "rel.eu-central.netbird.io:443",
iceLocalCandidate: "relay",
iceRemoteCandidate: "host",
bytesRx: 1024 * 240,
bytesTx: 1024 * 110,
endpointLocal: "192.168.0.121:51820",
endpointRemote: "198.51.100.42:51820",
},
{
id: "p-022",
fqdn: "staging-api.netbird.cloud",
ip: "100.64.0.200",
status: "connected",
lastHandshake: minutesAgo(0.2),
latencyMs: 16,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 92,
bytesTx: 1024 * 1024 * 140,
endpointLocal: "10.30.0.10:51820",
endpointRemote: "10.30.0.11:51820",
},
{
id: "p-023",
fqdn: "prod-api-eu.netbird.cloud",
ip: "100.64.0.201",
status: "connected",
lastHandshake: minutesAgo(0.1),
latencyMs: 8,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 1024 * 1024 * 1024 * 12,
bytesTx: 1024 * 1024 * 1024 * 3,
endpointLocal: "10.40.0.10:51820",
endpointRemote: "10.40.0.11:51820",
},
{
id: "p-024",
fqdn: "prod-api-us.netbird.cloud",
ip: "100.64.0.202",
status: "connected",
lastHandshake: minutesAgo(0.1),
latencyMs: 92,
relayed: false,
iceLocalCandidate: "srflx",
iceRemoteCandidate: "srflx",
bytesRx: 1024 * 1024 * 1024 * 8,
bytesTx: 1024 * 1024 * 1024 * 2,
endpointLocal: "10.50.0.10:51820",
endpointRemote: "203.0.113.210:51820",
},
{
id: "p-025",
fqdn: "old-jenkins.netbird.cloud",
ip: "100.64.0.220",
status: "disconnected",
lastHandshake: minutesAgo(8640),
latencyMs: 0,
relayed: false,
iceLocalCandidate: "host",
iceRemoteCandidate: "host",
bytesRx: 0,
bytesTx: 0,
endpointLocal: "—",
endpointRemote: "—",
},
];

View File

@@ -0,0 +1,20 @@
export type PeerStatus = "connected" | "connecting" | "disconnected";
export type IceCandidateType = "host" | "srflx" | "relay" | "prflx";
export type Peer = {
id: string;
fqdn: string;
ip: string;
status: PeerStatus;
lastHandshake: Date;
latencyMs: number;
relayed: boolean;
relayAddress?: string;
iceLocalCandidate: IceCandidateType;
iceRemoteCandidate: IceCandidateType;
bytesRx: number;
bytesTx: number;
endpointLocal: string;
endpointRemote: string;
};