mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-27 06:49:07 +01:00
add peers, networks and exit node list (wip)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user