add peers, networks and exit node list (wip)

This commit is contained in:
Eduard Gert
2026-05-26 12:09:08 +02:00
parent b6d20edfeb
commit 67a1f3c4fe
14 changed files with 630 additions and 420 deletions
+34 -15
View File
@@ -1,28 +1,47 @@
import { useState } from "react";
import { ConnectionStatusSwitch } from "@/layouts/ConnectionStatusSwitch.tsx";
import { MainRightSide } from "@/layouts/MainRightSide.tsx";
import { Navigation, NavSection } from "@/layouts/Navigation.tsx";
import { useViewMode } from "@/lib/viewMode";
import { Peers } from "@/modules/peers/Peers";
import { Networks } from "@/modules/networks/Networks";
import { ExitNodes } from "@/modules/exit-nodes/ExitNodes";
import { NetworksProvider } from "@/modules/networks/NetworksContext";
export const Main = () => {
const { viewMode } = useViewMode();
const isAdvanced = viewMode === "advanced";
const [section, setSection] = useState<NavSection>("peers");
return (
<div className={"wails-draggable flex flex-1 min-h-0 p-4 gap-4"}>
{/* Fixed-width column for the connection switch — same in both
default and advanced view so the NetBird logo doesn't shift
horizontally when the window grows. In default view the
inner row is 348px so the column fills it; in advanced view
the column sits on the left with Peers in the remaining
space. */}
<div className={"flex flex-col items-center shrink-0 w-[348px]"}>
<ConnectionStatusSwitch />
<NetworksProvider>
<div className={"wails-draggable flex flex-1 min-h-0 p-4 gap-4"}>
{/* Fixed-width column for the connection switch. Navigation
is rendered absolutely at the bottom in advanced view so
it doesn't reshape the column and shift the switch up. */}
<div
className={
"relative flex flex-col items-center shrink-0 w-[348px]"
}
>
<ConnectionStatusSwitch />
{isAdvanced && (
<div className={"absolute inset-x-0 bottom-0 px-1"}>
<Navigation
active={section}
onSelect={setSection}
/>
</div>
)}
</div>
{isAdvanced && (
<MainRightSide>
{section === "peers" && <Peers />}
{section === "networks" && <Networks />}
{section === "exitNode" && <ExitNodes />}
</MainRightSide>
)}
</div>
{isAdvanced && (
<MainRightSide>
<Peers />
</MainRightSide>
)}
</div>
</NetworksProvider>
);
};
+49 -17
View File
@@ -1,42 +1,74 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Layers3Icon, MonitorSmartphoneIcon, SquareArrowUpRight } from "lucide-react";
import { CardNavItem } from "@/components/CardNavItem.tsx";
import { Layers3Icon, MonitorSmartphoneIcon } from "lucide-react";
import deFlag from "@/assets/flags/1x1/de.svg";
import { cn } from "@/lib/cn";
import { useStatus } from "@/modules/daemon-status/StatusContext";
import { useNetworks } from "@/modules/networks/NetworksContext";
export type NavSection = "peers" | "networks" | "exitNode";
type Props = {
peersActive?: boolean;
onPeersClick?: () => void;
active: NavSection;
onSelect: (section: NavSection) => void;
};
export const Navigation = ({ peersActive = false, onPeersClick }: Props) => {
export const Navigation = ({ active, onSelect }: Props) => {
const { t } = useTranslation();
const { status } = useStatus();
const { networkRoutes, exitNodes, activeExitNode } = useNetworks();
const peerCounts = useMemo(() => {
const peers = status?.peers ?? [];
const online = peers.filter((p) => p.connStatus === "Connected").length;
return { online, total: peers.length };
}, [status?.peers]);
const networkCounts = useMemo(
() => ({
active: networkRoutes.filter((r) => r.selected).length,
total: networkRoutes.length,
}),
[networkRoutes],
);
const exitNodeDescription = activeExitNode
? activeExitNode.id
: t("nav.exitNode.none", { total: exitNodes.length });
return (
<nav className={"w-full flex flex-col gap-1 mt-auto"}>
<CardNavItem
icon={MonitorSmartphoneIcon}
title={t("nav.peers.title")}
description={t("nav.peers.description", { online: 17, total: 25 })}
active={peersActive}
onClick={onPeersClick}
description={t("nav.peers.description", peerCounts)}
active={active === "peers"}
onClick={() => onSelect("peers")}
/>
<CardNavItem
icon={Layers3Icon}
title={t("nav.resources.title")}
description={t("nav.resources.description", { active: 13, total: 16 })}
description={t("nav.resources.description", networkCounts)}
iconSize={14}
active={active === "networks"}
onClick={() => onSelect("networks")}
/>
<CardNavItem
iconNode={
<img
src={deFlag}
alt={t("nav.exitNode.flagAlt", { country: "Germany" })}
className={
"h-6 w-6 rounded-full border-[3px] border-nb-gray-850 shrink-0"
}
<SquareArrowUpRight
size={14}
className={cn(
"transition-colors duration-150 rotate-45",
active === "exitNode"
? "text-nb-gray-200"
: "text-nb-gray-400",
)}
/>
}
title={t("nav.exitNode.title", { location: "Berlin" })}
description={"100.92.14.37"}
title={t("nav.exitNode.title")}
description={exitNodeDescription}
active={active === "exitNode"}
onClick={() => onSelect("exitNode")}
/>
</nav>
);