The auto-update feature was driven by two narrow Wails events
(netbird:update:available and :progress) plus a SystemEvent-metadata
iteration on the React side. Both surfaces had to know the daemon
metadata schema (new_version_available, enforced, progress_window),
and the frontend had no pull endpoint to seed its state on mount.
Extract the state machine into a new client/ui/updater package, mirroring
how i18n and preferences are split between domain logic and a thin
services facade. The package owns the State type, the metadata-key
parsing, the mutex-guarded Holder, and the single netbird:update:state
event. services.Update keeps the daemon RPCs (Trigger, GetInstallerResult,
Quit) and gains GetState as a Wails pull endpoint.
Tray-side update behaviour moves out of tray.go into a dedicated
trayUpdater (tray_update.go): owns its menu item, OS notification,
click handler, and the /update window opener triggered by the
daemon's progress_window:show. tray.go drops three callbacks and four
fields, and reads hasUpdate through the updater.
Frontend ClientVersionContext now seeds from Update.GetState() and
subscribes to netbird:update:state; the status.events iteration and
metadata-key string literals are gone. UpdateAvailableBanner renders
only for the enforced && !installing branch and labels its action
"Install now"; UpdateVersionCard splits the install vs. download
branches by Enforced so the disabled flow routes to GitHub.
Adds a tray + React translation pipeline driven by a single JSON locale
tree (frontend/src/i18n/locales) embedded into the Go binary. The tray
re-renders on language switch via a Localizer that subscribes to the
preferences store.
Layout:
- client/ui/i18n: Bundle, LanguageCode, Language, errors, embedded-FS
loader. Pure domain, no Wails/daemon deps.
- client/ui/preferences: Store + UIPreferences for user-scope UI state,
persisted under os.UserConfigDir()/netbird/ui-preferences.json with
atomic writes and a subscribe/broadcast channel.
- client/ui/services: thin Wails-binding facades (services.I18n,
services.Preferences) so React sees ctx-first signatures.
- client/ui/localizer.go: tray bridge that owns the active language,
exposes T()/StatusLabel() and re-paints the menu on prefs change.
- tray.go: every user-facing const replaced by translation keys via
t.loc.T(...); menu rebuild + state replay on language switch.
- main.go: //go:embed all:frontend/src/i18n/locales, wires Bundle ->
Store -> Localizer -> Wails facades in order.
Frontend API exposed via Wails bindings: I18n.Languages, I18n.Bundle,
Preferences.Get, Preferences.SetLanguage, plus the
netbird:preferences:changed event.
Includes regenerated Wails TS bindings (peers/profileswitcher/etc.
re-emitted as part of the build) and en/hu seed bundles.
The UI never needs to block on Up — status updates flow via the
SubscribeStatus stream. Hardcode Async:true in Connection.Up and remove
the Async field from UpParams so frontend callers are unaffected.
Both the tray and the React Profiles page previously had separate
switching logic: the tray applied a status-aware reconnect policy
(Down for error states, Up only when previously Connected/Connecting),
while the React page always called Switch + Up unconditionally with no
Down for LoginFailed/NeedsLogin/SessionExpired.
Introduce a single ProfileSwitcher service that encapsulates the full
reconnect policy. SwitchActive queries the current daemon status, calls
Switch, and launches Down/Up in a background goroutine so the caller
returns immediately after the Switch RPC completes. Both the tray and
the React Profiles page now delegate to this service.
Export the daemon status string constants (StatusConnected, etc.) from
the services package so tray.go no longer duplicates them as private
constants.
The Profiles submenu label now reflects the active profile name instead
of the static "Profiles" text. A disabled email item appears directly
below it in the main menu, matching the legacy Fyne/systray behaviour.
Email is read from the per-profile state file via profilemanager in the
UI process — not through the daemon RPC — because the daemon runs as
root and its getConfigDir() resolves to the root home directory, making
the user-owned state file inaccessible from the daemon side.
Port IPv6 overlay support (#5631) into the Wails UI:
- Add DisableIPv6 config toggle to Settings (NetworkTab + services)
- Filter ::/0 alongside 0.0.0.0/0 as an exit-node route
- Suppress duplicate v6 default-route notifications in tray
Follow-up to the rename commit: the previous commit moved the files but
the post-mv string substitutions (Go imports, frontend bindings, CI
config paths) were not re-staged so they slipped through. This commit
applies those edits and removes the fyne dependencies from go.mod/go.sum
now that the legacy fyne UI is gone.
Removes the legacy fyne-based client/ui implementation and renames the
Wails replacement (client/ui-wails) to take its place at client/ui. Go
imports, frontend bindings, CI workflows, goreleaser configs and the
windows .syso icon path are updated to follow the rename.