import { useCallback, useEffect, useMemo, useState } from "react"; import { RefreshCw } from "lucide-react"; import { Networks as NetworksSvc } from "@bindings/services"; import type { Network } from "@bindings/services/models.js"; import { Button } from "../components/Button"; import { Tabs } from "../components/Tabs"; export default function Networks() { const [routes, setRoutes] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const refresh = useCallback(async () => { setLoading(true); try { const list = await NetworksSvc.List(); setRoutes(list); setError(null); } catch (e) { setError(String(e)); } finally { setLoading(false); } }, []); useEffect(() => { refresh(); }, [refresh]); const toggle = async (id: string, selected: boolean) => { try { if (selected) { await NetworksSvc.Deselect({ networkIds: [id], append: false, all: false }); } else { await NetworksSvc.Select({ networkIds: [id], append: true, all: false }); } await refresh(); } catch (e) { setError(String(e)); } }; const setAll = async (ids: string[], on: boolean) => { try { if (on) { await NetworksSvc.Select({ networkIds: ids, append: false, all: true }); } else { await NetworksSvc.Deselect({ networkIds: ids, append: false, all: true }); } await refresh(); } catch (e) { setError(String(e)); } }; const overlapping = useMemo(() => filterOverlapping(routes), [routes]); const exitNodes = useMemo( () => routes.filter((r) => isDefaultRoute(r.range)), [routes], ); return (

Networks

{error && (

{error}

)}
, }, { value: "overlap", label: `Overlapping (${overlapping.length})`, content: , }, { value: "exit", label: `Exit-node (${exitNodes.length})`, content: , }, ]} />
); } function NetworkList({ routes, onToggle, onSetAll, }: { routes: Network[]; onToggle: (id: string, selected: boolean) => void; onSetAll: (ids: string[], on: boolean) => void; }) { if (routes.length === 0) { return

No networks.

; } const ids = routes.map((r) => r.id); return (
    {routes.map((r) => (
  • onToggle(r.id, r.selected)} className="mt-1 h-4 w-4 accent-netbird" />

    {r.id}

    {r.range}

    {r.domains.length > 0 && (

    {r.domains.join(", ")}

    )}
  • ))}
); } // range is the merged display string from the daemon, e.g. "0.0.0.0/0", // "::/0", or "0.0.0.0/0, ::/0" when a v4 exit node has a paired v6 entry. function isDefaultRoute(range: string): boolean { return range.split(",").some((part) => { const trimmed = part.trim(); return trimmed === "0.0.0.0/0" || trimmed === "::/0"; }); } function filterOverlapping(routes: Network[]): Network[] { const byRange = new Map(); for (const r of routes) { if (r.domains.length > 0) continue; const arr = byRange.get(r.range) ?? []; arr.push(r); byRange.set(r.range, arr); } const out: Network[] = []; for (const arr of byRange.values()) { if (arr.length > 1) out.push(...arr); } return out; }