Files
netbird/client/ui
Zoltan Papp 505fcc7f7a [client/ui] Move profile-switch suppression from tray to Peers service
The optimistic Connecting paint and the Idle/stale-Connected
suppression lived in the tray's applyStatus, so only the tray got the
smoothed-out transition during a profile switch — the React Status
page (useStatus hook in frontend) subscribes to the same
netbird:status event and was seeing the raw daemon stream, complete
with the Disconnected blink.

Move the policy one layer up into the Peers service, between
SubscribeStatus and the Wails event bus, so every consumer downstream
sees the same filtered stream:

  * Peers gains BeginProfileSwitch / CancelProfileSwitch / shouldSuppress.
    BeginProfileSwitch sets the in-progress flag and emits a synthetic
    Connecting status so both the tray and React paint Connecting
    immediately. shouldSuppress swallows the daemon's stale Connected
    (peer-count teardown) and transient Idle (Down between flows)
    until Connecting / NeedsLogin / LoginFailed / SessionExpired /
    DaemonUnavailable indicates the new profile's flow has started,
    or a 30s safety timeout fires.

  * ProfileSwitcher.SwitchActive calls peers.BeginProfileSwitch when
    wasActive (prevStatus was Connected or Connecting) — the only
    cases where the daemon emits the blink-inducing sequence. Other
    prevStatuses already terminate cleanly on Idle.

  * Tray loses its switchInProgress fields, applyOptimisticConnecting
    helper, applyStatus suppression switch, and switchProfile's
    optimistic-paint call. handleDisconnect now calls
    Peers.CancelProfileSwitch alongside cancelling switchCancel, so
    the abort path bypasses the suppression filter and the daemon's
    Idle paints through immediately.

The full prevStatus -> action / optimistic label / suppressed events
matrix now lives in the ProfileSwitcher struct godoc, with the
suppression-rule-per-incoming-status table on the Peers struct
godoc — together they describe the click-time policy and the
stream-filter behaviour without duplication.

Wails bindings need regenerating to pick up Peers.BeginProfileSwitch
and Peers.CancelProfileSwitch.
2026-05-15 10:01:26 +02:00
..

NetBird desktop UI (Wails3 + React)

Replaces client/ui (Fyne). One binary on Windows / macOS / Linux, talks to the NetBird daemon over gRPC, renders a React frontend in a WebView.

Prerequisites

  • Go ≥ 1.25, Node ≥ 20, pnpm (corepack enable && corepack prepare pnpm@latest --activate)
  • wails3 CLI: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
  • task: go install github.com/go-task/task/v3/cmd/task@latest
  • A running NetBird daemon (default: unix:///var/run/netbird.sock, Windows tcp://127.0.0.1:41731)
  • Linux only: libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev

Develop without rebuilding

cd client/ui
task dev

task dev runs Vite (port 9245) + the Go binary + a *.go watcher. Frontend edits hot-reload instantly. Go edits trigger a rebuild and relaunch. Pass daemon flags after --:

task dev -- --daemon-addr=tcp://127.0.0.1:41731

For pure UI work (no native window, fastest loop):

cd frontend && pnpm dev

Production build

task build

Output in bin/. Frontend assets are embedded into the binary.

Cross-compile Windows from Linux

Install the mingw-w64 toolchain once:

sudo apt install gcc-mingw-w64-x86-64           # Debian/Ubuntu
sudo dnf install mingw64-gcc                    # Fedora
sudo pacman -S mingw-w64-gcc                    # Arch

Then:

CGO_ENABLED=1 task windows:build

Produces bin/netbird-ui.exe. macOS cross-compile from Linux is not supported (signing and notarization need a real Mac).

Windows console build (logs in the terminal)

Default windows:build links the binary as a Windows GUI app, which detaches from the launching console — logrus output, fmt.Println, and panics go nowhere visible. To debug tray/event/daemon issues:

CGO_ENABLED=1 task windows:build:console

Produces bin/netbird-ui-console.exe. Run it from cmd.exe / PowerShell / Windows Terminal and stdout/stderr land in that terminal. Same flag works on a native Windows build (drop the CGO_ENABLED=1 if your toolchain already has it set).

Regenerating bindings

When a Go service signature changes:

wails3 generate bindings

task dev does this automatically on *.go save.

Tray icons

Source SVGs live in assets/svg/ (state.svg + state-macos.svg). After editing any SVG, rasterize to the PNGs the Go side embeds:

task common:generate:tray:icons

Requires Inkscape. Commit the resulting assets/*.png files alongside the SVG change so CI doesn't need Inkscape installed.