From a2be41caf8847cdf47a75bf7f8bcbfac72e7100d Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 7 May 2026 11:24:11 +0200 Subject: [PATCH] add about setting --- client/ui-wails/frontend/settings.md | 88 ++- .../assets/logos/netbird-app-icon-light.svg | 14 + .../src/assets/logos/netbird-app-icon.svg | 14 + .../src/components/FancyToggleSwitch.tsx | 2 +- .../src/modules/settings/Settings.tsx | 591 +++++++++++++++++- .../modules/settings/SettingsNavigation.tsx | 6 + 6 files changed, 678 insertions(+), 37 deletions(-) create mode 100644 client/ui-wails/frontend/src/assets/logos/netbird-app-icon-light.svg create mode 100644 client/ui-wails/frontend/src/assets/logos/netbird-app-icon.svg diff --git a/client/ui-wails/frontend/settings.md b/client/ui-wails/frontend/settings.md index 6ee885d13..6cb80adf0 100644 --- a/client/ui-wails/frontend/settings.md +++ b/client/ui-wails/frontend/settings.md @@ -2,7 +2,7 @@ Each row has a title and short description. Booleans default to **toggle switch**; pick another control only when noted. -Tab order: **General · Network · SSH · Troubleshooting · About**. +Tab order: **General · Network · Security · SSH · Advanced · Troubleshooting · About**. --- @@ -10,7 +10,7 @@ Tab order: **General · Network · SSH · Troubleshooting · About**. App behavior + how the client connects. -### Startup +### General - **Connect on startup** — `disableAutoConnect` (inverted) · *toggle switch* - Automatically connect to NetBird when the app launches. @@ -19,34 +19,24 @@ App behavior + how the client connects. ### Connection -- **Management URL** — `managementUrl` · *text input* - - The NetBird management server this client connects to. -- **Admin URL** — `adminUrl` · *text input* - - Web dashboard URL used by "Open Admin Panel". -- **Pre-shared key** — `preSharedKey` · *password input with reveal toggle* - - Optional WireGuard pre-shared key for an extra layer of symmetric encryption. - -### Interface - -- **Interface name** — `interfaceName` · *text input* - - Name of the WireGuard network interface created on this host. -- **WireGuard port** — `wireguardPort` · *number input* - - Local UDP port the WireGuard interface listens on. -- **MTU** — `mtu` · *number input* - - Maximum transmission unit for the WireGuard interface. +- **Management Server** — `managementUrl` · *label + help text + (text input next to inline Save button)* + - Help text sits between the label and the input. The NetBird management server this client connects to; saving reconnects to apply the new server. Save button persists explicitly (in addition to the global debounced auto-save) since changing the server triggers a reconnect. --- ## 2. Network -Routing, DNS, firewall, and encryption — everything the daemon does on the wire and to the host network. +Routing and DNS — how the daemon reaches peers and resolves names. -### Routing & DNS +### Connectivity - **Lazy connections** — `lazyConnectionEnabled` · *toggle switch* - Only establish peer tunnels on first traffic instead of eagerly at startup. - **Network monitor** — `networkMonitor` · *toggle switch* - Reconnect automatically when the host network changes (Wi-Fi switch, VPN, sleep/wake). + +### Routing & DNS + - **Enable DNS** — `disableDns` (inverted) · *toggle switch* - Apply NetBird-managed DNS settings to the host resolver. - **Enable client routes** — `disableClientRoutes` (inverted) · *toggle switch* @@ -54,6 +44,12 @@ Routing, DNS, firewall, and encryption — everything the daemon does on the wir - **Enable server routes** — `disableServerRoutes` (inverted) · *toggle switch* - Advertise this host's local routes to other peers. +--- + +## 3. Security + +Firewall and on-the-wire encryption — what's blocked and how the tunnel is protected. + ### Firewall - **Block inbound traffic** — `blockInbound` · *toggle switch* @@ -70,14 +66,17 @@ Routing, DNS, firewall, and encryption — everything the daemon does on the wir --- -## 3. SSH +## 4. SSH -NetBird SSH server config. Master switch at the top; sub-toggles greyed out with an inline notice ("Enable Allow SSH to configure") when the master is off. +NetBird SSH server config. Master switch at the top; sub-toggles greyed out when the master is off. ### Server - **Allow SSH** — `serverSshAllowed` · *toggle switch* (master) - Run the NetBird SSH server on this host so other peers can connect to it. + +### Capabilities + - **Allow root login** — `enableSshRoot` · *toggle switch* - Permit incoming SSH sessions to authenticate as `root`. - **Enable SFTP** — `enableSshSftp` · *toggle switch* @@ -96,16 +95,29 @@ NetBird SSH server config. Master switch at the top; sub-toggles greyed out with --- -## 4. Troubleshooting +## 5. Advanced -Everything you reach for when something is wrong. Config + actions deliberately mixed — they're used together. +Power-user knobs: tunnel security, interface tuning, and log verbosity. -### Logging +### Security -- **Log level** — *dropdown: Debug / Info / Warn / Error* - - Verbosity of the daemon log. Raise to Debug when reproducing an issue. -- **Log file path** — *read-only text + Copy + Reveal in Finder/Explorer* -- **Config file path** — *read-only text + Copy + Reveal in Finder/Explorer* +- **Pre-shared key** — `preSharedKey` · *label + help text + password input with reveal toggle* + - Help text sits between the label and the input. Optional WireGuard pre-shared key for an extra layer of symmetric encryption; must match the value on every peer. + +### Interface + +- **Name** — `interfaceName` · *text input* + - Name of the WireGuard network interface created on this host. +- **WireGuard Port** — `wireguardPort` · *number input* + - Local UDP port the WireGuard interface listens on. +- **MTU** — `mtu` · *number input* + - Maximum transmission unit for the WireGuard interface. + +--- + +## 6. Troubleshooting + +Everything you reach for when something is wrong. ### Debug bundle @@ -119,11 +131,19 @@ Everything you reach for when something is wrong. Config + actions deliberately --- -## 5. About +## 7. About -Version, update flow, and identity reference. +Two-row layout. Top row pairs the app icon with the product name + versions; everything else stacks below full-width. -- App version, daemon version -- **Check for Updates** — *button* (drives auto-update flow; 15-min timeout, success/error states) -- Local peer info quick-reference (FQDN, IP) — same data shown in the connection-state view -- Links: docs, GitHub repo, license +**Top row** (icon left, info right): + +1. **App icon** — `netbird-app-icon.svg`, `w-24 h-24`, rounded corners, subtle border (`border-nb-gray-800`). +2. **NetBird** heading + version lines: + - **GUI v{x.y.z}** — from `frontend/package.json` at build time + - **Client v{x.y.z}** — from `Status.daemonVersion` + +**Below the top row**, in order: + +3. **Update banner** *(visible only when an event in `Status.events` carries `metadata["new_version_available"]`)* — "Version X.Y.Z is available." + a **What's new?** link → GitHub release page for that version, plus a **Restart now** primary button → `Update.Trigger()`. +4. **Copyright** — "© {current year} NetBird. All Rights Reserved." (year from `new Date().getFullYear()`). +5. **Legal links** — Imprint · Privacy · CLA · Terms of Service. Each opens via Wails `Browser.OpenURL` with `window.open` fallback. diff --git a/client/ui-wails/frontend/src/assets/logos/netbird-app-icon-light.svg b/client/ui-wails/frontend/src/assets/logos/netbird-app-icon-light.svg new file mode 100644 index 000000000..9d8a5f70b --- /dev/null +++ b/client/ui-wails/frontend/src/assets/logos/netbird-app-icon-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/client/ui-wails/frontend/src/assets/logos/netbird-app-icon.svg b/client/ui-wails/frontend/src/assets/logos/netbird-app-icon.svg new file mode 100644 index 000000000..db24a50f7 --- /dev/null +++ b/client/ui-wails/frontend/src/assets/logos/netbird-app-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/client/ui-wails/frontend/src/components/FancyToggleSwitch.tsx b/client/ui-wails/frontend/src/components/FancyToggleSwitch.tsx index a3440a06a..5d78a7353 100644 --- a/client/ui-wails/frontend/src/components/FancyToggleSwitch.tsx +++ b/client/ui-wails/frontend/src/components/FancyToggleSwitch.tsx @@ -27,7 +27,7 @@ export default function FancyToggleSwitch({ dataCy, className, labelClassName, - textWrapperClassName = "max-w-sm", + textWrapperClassName = "max-w-md", }: Readonly) { const handleToggle = () => { if (disabled) return; diff --git a/client/ui-wails/frontend/src/modules/settings/Settings.tsx b/client/ui-wails/frontend/src/modules/settings/Settings.tsx index f79626d30..a155667b3 100644 --- a/client/ui-wails/frontend/src/modules/settings/Settings.tsx +++ b/client/ui-wails/frontend/src/modules/settings/Settings.tsx @@ -1,19 +1,606 @@ -import { useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { Browser } from "@wailsio/runtime"; +import { + Settings as SettingsSvc, + Profiles as ProfilesSvc, + Update as UpdateSvc, +} from "../../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { Config } from "../../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import netbirdAppIcon from "@/assets/logos/netbird-app-icon.svg"; +import pkg from "../../../package.json"; +import { useStatus } from "@/hooks/useStatus"; +import { Button } from "@/components/Button"; +import FancyToggleSwitch from "@/components/FancyToggleSwitch"; +import { HelpText } from "@/components/HelpText"; +import { Input } from "@/components/Input"; +import { Label } from "@/components/Label"; +import { cn } from "@/lib/cn"; import { MainRightSide } from "@/layouts/MainRightSide.tsx"; import { SettingsNavigation, SettingsSection, } from "@/modules/settings/SettingsNavigation.tsx"; +type Ctx = { + cfg: Config; + setField: (k: K, v: Config[K]) => void; +}; + +const SAVE_DEBOUNCE_MS = 400; + +const buildPayload = ( + cfg: Config, + profileName: string, + username: string, +) => ({ + profileName, + username, + managementUrl: cfg.managementUrl, + adminUrl: cfg.adminUrl, + interfaceName: cfg.interfaceName, + wireguardPort: cfg.wireguardPort, + mtu: cfg.mtu, + preSharedKey: cfg.preSharedKey, + disableAutoConnect: cfg.disableAutoConnect, + serverSshAllowed: cfg.serverSshAllowed, + rosenpassEnabled: cfg.rosenpassEnabled, + rosenpassPermissive: cfg.rosenpassPermissive, + disableNotifications: cfg.disableNotifications, + lazyConnectionEnabled: cfg.lazyConnectionEnabled, + blockInbound: cfg.blockInbound, + networkMonitor: cfg.networkMonitor, + disableClientRoutes: cfg.disableClientRoutes, + disableServerRoutes: cfg.disableServerRoutes, + disableDns: cfg.disableDns, + blockLanAccess: cfg.blockLanAccess, + enableSshRoot: cfg.enableSshRoot, + enableSshSftp: cfg.enableSshSftp, + enableSshLocalPortForwarding: cfg.enableSshLocalPortForwarding, + enableSshRemotePortForwarding: cfg.enableSshRemotePortForwarding, + disableSshAuth: cfg.disableSshAuth, + sshJwtCacheTtl: cfg.sshJwtCacheTtl, +}); + export const Settings = () => { const [active, setActive] = useState("general"); + const [username, setUsername] = useState(""); + const [profile, setProfile] = useState(""); + const [cfg, setCfg] = useState(null); + const [error, setError] = useState(null); + const dirtyRef = useRef(false); + + const load = useCallback(async () => { + try { + const u = await ProfilesSvc.Username(); + const activeProfile = await ProfilesSvc.GetActive(); + const profileName = activeProfile.profileName || "default"; + setUsername(u); + setProfile(profileName); + const c = await SettingsSvc.GetConfig({ + profileName, + username: u, + }); + setCfg(c); + setError(null); + } catch (e) { + setError(String(e)); + } + }, []); + + useEffect(() => { + load(); + }, [load]); + + const setField: Ctx["setField"] = (k, v) => { + dirtyRef.current = true; + setCfg((c) => (c ? { ...c, [k]: v } : c)); + }; + + const saveNow = useCallback(async () => { + if (!cfg) return; + try { + await SettingsSvc.SetConfig(buildPayload(cfg, profile, username)); + setError(null); + } catch (e) { + setError(String(e)); + } + }, [cfg, profile, username]); + + useEffect(() => { + if (!cfg || !dirtyRef.current) return; + const t = setTimeout(saveNow, SAVE_DEBOUNCE_MS); + return () => clearTimeout(t); + }, [cfg, saveNow]); return (
- {null} + + {error && ( +

{error}

+ )} +
+ {!cfg ? ( +
+ Loading… +
+ ) : ( +
+ {active === "general" && ( + + )} + {active === "network" && ( + + )} + {active === "security" && ( + + )} + {active === "ssh" && ( + + )} + {active === "advanced" && ( + + )} + {active === "troubleshooting" && ( + + )} + {active === "about" && } +
+ )} +
+
); }; + +const SectionGroup = ({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) => ( +
+

+ {title} +

+
{children}
+
+); + +function GeneralSection({ + cfg, + setField, + onSaveServer, +}: Ctx & { onSaveServer: () => void | Promise }) { + return ( + <> + + setField("disableAutoConnect", !v)} + label={"Connect on startup"} + helpText={ + "Automatically connect to NetBird when the app launches." + } + /> + setField("disableNotifications", !v)} + label={"Show notifications"} + helpText={ + "Show desktop notifications for connection events and updates." + } + /> + + + +
+ + + The NetBird management server this client connects to. + Saving will reconnect to apply the new server. + +
+
+ + setField("managementUrl", e.target.value) + } + /> +
+ +
+
+
+ + ); +} + +function NetworkSection({ cfg, setField }: Ctx) { + return ( + <> + + setField("lazyConnectionEnabled", v)} + label={"Lazy connections"} + helpText={ + "Only establish peer tunnels on first traffic instead of eagerly at startup." + } + /> + setField("networkMonitor", v)} + label={"Network monitor"} + helpText={ + "Reconnect automatically when the host network changes (Wi-Fi switch, VPN, sleep/wake)." + } + /> + + + + setField("disableDns", !v)} + label={"Enable DNS"} + helpText={ + "Apply NetBird-managed DNS settings to the host resolver." + } + /> + setField("disableClientRoutes", !v)} + label={"Enable client routes"} + helpText={ + "Accept routes advertised by other peers so this client can reach their networks." + } + /> + setField("disableServerRoutes", !v)} + label={"Enable server routes"} + helpText={ + "Advertise this host's local routes to other peers." + } + /> + + + ); +} + +function SecuritySection({ cfg, setField }: Ctx) { + return ( + <> + + setField("blockInbound", v)} + label={"Block inbound traffic"} + helpText={ + "Drop all unsolicited inbound traffic on the NetBird interface." + } + /> + setField("blockLanAccess", v)} + label={"Block LAN access"} + helpText={ + "Prevent peers from reaching this host's local network." + } + /> + + + + setField("rosenpassEnabled", v)} + label={"Quantum-resistant encryption"} + helpText={ + "Add a post-quantum key exchange (Rosenpass) on top of WireGuard." + } + > + setField("rosenpassPermissive", v)} + label={"Permissive mode"} + helpText={ + "Allow connections to peers without quantum-resistance support." + } + /> + + + + ); +} + +function SshSection({ cfg, setField }: Ctx) { + const sshOff = !cfg.serverSshAllowed; + return ( + <> + + setField("serverSshAllowed", v)} + label={"Allow SSH"} + helpText={ + "Run the NetBird SSH server on this host so other peers can connect to it." + } + /> + + + + setField("enableSshRoot", v)} + label={"Allow root login"} + helpText={ + "Permit incoming SSH sessions to authenticate as root." + } + disabled={sshOff} + /> + setField("enableSshSftp", v)} + label={"Enable SFTP"} + helpText={"Allow file transfers over the NetBird SSH server."} + disabled={sshOff} + /> + setField("enableSshLocalPortForwarding", v)} + label={"Local port forwarding"} + helpText={ + "Allow clients to forward local ports through this host." + } + disabled={sshOff} + /> + + setField("enableSshRemotePortForwarding", v) + } + label={"Remote port forwarding"} + helpText={ + "Allow clients to expose remote ports back through this host." + } + disabled={sshOff} + /> + + + + setField("disableSshAuth", v)} + label={"Disable SSH auth"} + helpText={ + "Skip JWT authentication for incoming SSH sessions. Insecure — diagnostics only." + } + disabled={sshOff} + /> +
+
+ + + How long verified JWTs are cached before + re-validation. Shorter values increase load on the + management server; longer values delay revocation. + +
+
+ + setField( + "sshJwtCacheTtl", + Number(e.target.value), + ) + } + customSuffix={ + s + } + disabled={sshOff} + /> +
+
+
+ + ); +} + +function AdvancedSection({ cfg, setField }: Ctx) { + return ( + <> + +
+ + + Optional WireGuard pre-shared key for an extra layer of + symmetric encryption. Must match the value configured + on every peer in the network. + + + setField("preSharedKey", e.target.value) + } + /> +
+
+ + + setField("interfaceName", e.target.value)} + /> +
+ + setField("wireguardPort", Number(e.target.value)) + } + /> + + setField("mtu", Number(e.target.value)) + } + /> +
+
+ + ); +} + +function TroubleshootingSection() { + return ( + +

+ Debug bundle creation is not yet wired up in this view. +

+
+ ); +} + +const LEGAL_LINKS: { label: string; url: string }[] = [ + { label: "Imprint", url: "https://netbird.io/imprint" }, + { label: "Privacy", url: "https://netbird.io/privacy" }, + { label: "CLA", url: "https://netbird.io/cla" }, + { label: "Terms of Service", url: "https://netbird.io/terms" }, +]; + +function openUrl(url: string) { + void Browser.OpenURL(url).catch(() => window.open(url, "_blank")); +} + +function AboutSection() { + const { status } = useStatus(); + const guiVersion = pkg.version; + const daemonVersion = status?.daemonVersion ?? "—"; + + const updateVersion = (status?.events ?? []) + .map((e) => e.metadata?.["new_version_available"]) + .find((v): v is string => Boolean(v)); + + const triggerUpdate = () => { + UpdateSvc.Trigger().catch(() => {}); + }; + + return ( +
+
+ {"NetBird"} +
+

NetBird

+
+
GUI v{guiVersion}
+
Client v{daemonVersion}
+
+
+
+ + {updateVersion && ( +
+
+

+ Version {updateVersion} is available. +

+ +
+ +
+ )} + +

+ © {new Date().getFullYear()} NetBird. All Rights Reserved. +

+
+ {LEGAL_LINKS.map((link, i) => ( + + {i > 0 && ( + + · + + )} + + + ))} +
+
+ ); +} diff --git a/client/ui-wails/frontend/src/modules/settings/SettingsNavigation.tsx b/client/ui-wails/frontend/src/modules/settings/SettingsNavigation.tsx index f2079b715..0e6be77d2 100644 --- a/client/ui-wails/frontend/src/modules/settings/SettingsNavigation.tsx +++ b/client/ui-wails/frontend/src/modules/settings/SettingsNavigation.tsx @@ -3,14 +3,18 @@ import { InfoIcon, LifeBuoyIcon, NetworkIcon, + ShieldIcon, SlidersHorizontalIcon, TerminalIcon, + WrenchIcon, } from "lucide-react"; export type SettingsSection = | "general" | "network" + | "security" | "ssh" + | "advanced" | "troubleshooting" | "about"; @@ -26,7 +30,9 @@ const ITEMS: { }[] = [ { id: "general", icon: SlidersHorizontalIcon, title: "General" }, { id: "network", icon: NetworkIcon, title: "Network" }, + { id: "security", icon: ShieldIcon, title: "Security" }, { id: "ssh", icon: TerminalIcon, title: "SSH" }, + { id: "advanced", icon: WrenchIcon, title: "Advanced" }, { id: "troubleshooting", icon: LifeBuoyIcon, title: "Troubleshooting" }, { id: "about", icon: InfoIcon, title: "About" }, ];