-
+ {/* 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. */}
+
+ {isAdvanced && (
+
+
+
+ )}
);
};
diff --git a/client/ui/frontend/src/lib/viewMode.tsx b/client/ui/frontend/src/lib/viewMode.tsx
new file mode 100644
index 000000000..988f8f116
--- /dev/null
+++ b/client/ui/frontend/src/lib/viewMode.tsx
@@ -0,0 +1,45 @@
+import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
+import { Window } from "@wailsio/runtime";
+
+export type ViewMode = "default" | "advanced";
+
+// Window dimensions per view. Height matches the Settings window (640) so
+// the chrome height is identical across surfaces; width grows from the
+// compact 380 default to 900 in advanced.
+export const VIEW_SIZE: Record
= {
+ default: { width: 380, height: 640 },
+ advanced: { width: 900, height: 640 },
+};
+
+type ViewModeContextValue = {
+ viewMode: ViewMode;
+ setViewMode: (mode: ViewMode) => void;
+};
+
+const ViewModeContext = createContext(null);
+
+export const ViewModeProvider = ({ children }: { children: ReactNode }) => {
+ const [viewMode, setMode] = useState("default");
+ const setViewMode = useCallback(
+ (mode: ViewMode) => {
+ setMode((prev) => {
+ if (prev === mode) return prev;
+ const { width, height } = VIEW_SIZE[mode];
+ void Window.SetSize(width, height).catch(() => {});
+ return mode;
+ });
+ },
+ [],
+ );
+ return (
+
+ {children}
+
+ );
+};
+
+export const useViewMode = () => {
+ const ctx = useContext(ViewModeContext);
+ if (!ctx) throw new Error("useViewMode must be used inside ViewModeProvider");
+ return ctx;
+};
diff --git a/client/ui/frontend/src/modules/peers/Peers.tsx b/client/ui/frontend/src/modules/peers/Peers.tsx
index 9f2dd98b3..f71d7ac3b 100644
--- a/client/ui/frontend/src/modules/peers/Peers.tsx
+++ b/client/ui/frontend/src/modules/peers/Peers.tsx
@@ -1,44 +1,56 @@
-import { useMemo, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/cn";
import { SearchInput } from "@/components/SearchInput";
-import { mockPeers } from "./mockPeers";
+import { useStatus } from "@/modules/daemon-status/StatusContext";
import { PeerFilters, StatusFilter } from "./PeerFilters";
import { PeersList } from "./PeersList";
-const isOnline = (status: string) => status === "connected";
+const isOnline = (connStatus: string) => connStatus === "Connected";
export const Peers = () => {
const { t } = useTranslation();
+ const { status } = useStatus();
const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState("all");
+ const searchRef = useRef(null);
+
+ // Peers is only mounted in advanced view (see layouts/Main.tsx), so a
+ // mount-time focus is equivalent to "focus when the user toggles into
+ // advanced view".
+ useEffect(() => {
+ searchRef.current?.focus();
+ }, []);
+
+ const peers = status?.peers ?? [];
const counts = useMemo>(() => {
- const online = mockPeers.filter((p) => isOnline(p.status)).length;
+ const online = peers.filter((p) => isOnline(p.connStatus)).length;
return {
- all: mockPeers.length,
+ all: peers.length,
online,
- offline: mockPeers.length - online,
+ offline: peers.length - online,
};
- }, []);
+ }, [peers]);
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;
+ return peers.filter((p) => {
+ if (statusFilter === "online" && !isOnline(p.connStatus)) return false;
+ if (statusFilter === "offline" && isOnline(p.connStatus)) return false;
if (q && !p.fqdn.toLowerCase().includes(q) && !p.ip.includes(q)) {
return false;
}
return true;
});
- }, [search, statusFilter]);
+ }, [peers, search, statusFilter]);
return (
setSearch(e.target.value)}
diff --git a/client/ui/frontend/src/modules/peers/PeersList.tsx b/client/ui/frontend/src/modules/peers/PeersList.tsx
index 7ea6d7cd3..cf5dc306d 100644
--- a/client/ui/frontend/src/modules/peers/PeersList.tsx
+++ b/client/ui/frontend/src/modules/peers/PeersList.tsx
@@ -1,14 +1,19 @@
import { useTranslation } from "react-i18next";
+import type { PeerStatus } from "@bindings/services/models.js";
import { cn } from "@/lib/cn";
-import { Peer, PeerStatus } from "./types";
-const DOT: Record = {
- connected: "bg-green-400",
- connecting: "bg-yellow-300 animate-pulse-slow",
- disconnected: "bg-nb-gray-500",
+const dotClass = (connStatus: string): string => {
+ switch (connStatus) {
+ case "Connected":
+ return "bg-green-400";
+ case "Connecting":
+ return "bg-yellow-300 animate-pulse-slow";
+ default:
+ return "bg-nb-gray-500";
+ }
};
-export const PeersList = ({ data }: { data: Peer[] }) => {
+export const PeersList = ({ data }: { data: PeerStatus[] }) => {
const { t } = useTranslation();
if (data.length === 0) {
return (
@@ -22,15 +27,15 @@ export const PeersList = ({ data }: { data: Peer[] }) => {
{data.map((peer) => (
-
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: "—",
- },
-];
diff --git a/client/ui/frontend/src/modules/peers/types.ts b/client/ui/frontend/src/modules/peers/types.ts
deleted file mode 100644
index 226b77536..000000000
--- a/client/ui/frontend/src/modules/peers/types.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-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;
-};