Commit Graph

177 Commits

Author SHA1 Message Date
Zoltan Papp
9d8eb76746 [client/ui] Replace update event fan-out with typed UpdateState API
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.
2026-05-15 13:31:17 +02:00
Eduard Gert
1ebb507cbb remove bindings from git 2026-05-15 13:01:19 +02:00
Eduard Gert
5411fa4350 remove old code, add german lang 2026-05-15 12:56:09 +02:00
Zoltan Papp
17cae1a75c [client/ui] Introduce localisation (i18n + preferences) feature packages
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.
2026-05-15 11:19:00 +02:00
Eduard Gert
c0b0eeb6ab update claude.md and rename windowmanager 2026-05-15 10:49:44 +02:00
Eduard Gert
d32721d7fc merge ui stuff 2026-05-15 10:20:51 +02:00
Eduard Gert
288f8dec08 Merge branch 'ui-refactor' into ui-refactor-ui 2026-05-15 10:16:30 +02:00
Eduard Gert
db8c9a0e30 add window manager 2026-05-15 10:14:01 +02:00
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
Zoltan Papp
0fe8764707 [client/ui] Optimistic Connecting on profile switch, status row disabled
Three UX fixes for the tray's profile-switch flow:

* Optimistic Connecting paint when switching from Connected/Connecting.
  Previously the daemon's Down step emitted Idle before the new
  profile's Up emitted Connecting, leaving the tray flashing
  "Disconnected" for the duration of the Down. switchProfile now sets a
  flag and synthesizes a Connecting paint at click time; applyStatus
  suppresses the transient Idle and the stale Connected updates that
  arrive during the old profile's teardown, clearing the flag only when
  the new profile's flow surfaces (Connecting, NeedsLogin, LoginFailed,
  SessionExpired, DaemonUnavailable, or a 30s safety timeout).

* Disconnect during an in-flight switch now actually disconnects. The
  switchCancel context cancels the ProfileSwitcher.SwitchActive
  goroutine so its queued Up RPC never fires, and the
  switchInProgress flag is cleared so the daemon's Idle push paints
  through immediately. Without this, the user's Disconnect click was
  followed seconds later by the switcher's Up bringing the new
  profile back online.

* The first menu row is informational only. SetEnabled(false) is
  re-applied to t.statusItem (initial build, applyStatus, and the
  optimistic paint) and the openRoute("/login") OnClick handler is
  dropped — every actionable transition flows through the
  Connect/Disconnect entries below.

The switchProfile and applyStatus godocs carry the full
prevStatus → suppressed-events / final-state transition tables so
future readers don't have to rebuild the policy from the code.
2026-05-14 15:44:30 +02:00
Zoltan Papp
e4eedbe18f [client/ui] Mirror tray profile switch to user-side ProfileManager
The Fyne UI used to write the active profile to both fronts on every
switch (profile.go:264-273): the daemon SwitchProfile RPC for
/var/lib/netbird/active_profile.json, then profileManager.SwitchProfile
for the user-side ~/Library/Application Support/netbird/active_profile.
The Wails ProfileSwitcher only kept the first.

Without the user-side mirror, a UI tray switch updates the daemon's
state but the CLI ProfileManager.GetActiveProfile() still returns the
stale "default". The next "netbird up" then sends ProfileName="default"
in the Login/Up request, and the daemon silently switches back to
default, reverting whatever the user just picked in the tray.

Mirror the daemon switch with profilemanager.NewProfileManager().
SwitchProfile after the daemon RPC succeeds. The daemon stays the
authority — a user-side write failure is logged as a warning, not a
hard error.
2026-05-14 14:52:14 +02:00
Zoltan Papp
fc1db63fc3 [client/ui] Fix profile-submenu race, restore Connect re-auth flow
Three tray fixes after the SubscribeStatus stream refactor:

* loadProfiles now serializes via a dedicated profileLoadMu and runs
  AFTER the SetHidden/SetEnabled writes inside applyStatus's iconChanged
  block. Previously the status-driven refresh fired before the menu-item
  writes finished, so processMenu's clearMenu/re-add NSMenu rebuild
  raced against SetHidden on darwin — the Disconnect entry could end
  up visible-but-disabled even when applyStatus had requested it hidden.

* The status row is no longer a hidden "Login" entry. It now renders
  as a plain enabled label (so the text isn't greyed out) but has no
  OnClick handler — clicks are no-ops, matching the legacy Fyne UI.
  All actionable transitions flow through Connect/Disconnect.

* handleConnect routes NeedsLogin/SessionExpired/LoginFailed to the
  frontend's /login route (which already runs Login → WaitSSOLogin →
  Up) instead of calling Up directly. The plain Up RPC errors with
  "up already in progress: current status NeedsLogin" in those
  states; the legacy Fyne UI drove the SSO dance from the Connect
  button as well.
2026-05-14 14:52:03 +02:00
Eduard Gert
258e7ec038 Merge branch 'refs/heads/ui-refactor' into ui-refactor-ui
# Conflicts:
#	client/ui/frontend/src/screens/Profiles.tsx
#	client/ui/main.go
2026-05-13 16:51:57 +02:00
Eduard Gert
1932b76f5b update stuff 2026-05-13 16:28:51 +02:00
Zoltan Papp
d33b841a33 [client/ui] Use type conversion for ProfileRef to UpParams (staticcheck) 2026-05-13 16:07:21 +02:00
Zoltan Papp
df1935da6d [client/ui] Regenerate Wails bindings after UpParams and ProfileSwitcher changes 2026-05-13 16:05:46 +02:00
Zoltan Papp
eb6be5a2f3 [client/ui] Always use async Up in the UI service layer
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.
2026-05-13 16:02:24 +02:00
Zoltan Papp
209f14fc2f [client/ui] Cancel in-flight profile switch on rapid profile changes
Store a switchCancel in Tray. Each switchProfile call cancels the
previous in-flight goroutine before starting a new one. Because gRPC
respects context cancellation, the previous Down/Up RPCs are aborted
and rapid clicks always converge to the last selected profile.
2026-05-13 16:00:31 +02:00
Zoltan Papp
2bd56ecf67 [client/ui] Remove goroutine from ProfileSwitcher.SwitchActive
Down and Up(async=true) are both fast RPCs; no background goroutine
is needed. SwitchActive is now fully synchronous — the tray wraps the
call in its own goroutine, and Wails handles React calls similarly.
2026-05-13 15:55:59 +02:00
Zoltan Papp
67988c2407 [client/ui] Make profile Switch sync, Down+Up async in ProfileSwitcher
Switch RPC errors are now returned synchronously to the caller so the
tray can show a toast immediately on invalid-profile or other early
failures. Down and Up run in a background goroutine so the caller
returns fast; Up still uses async=true so the goroutine is short-lived.
2026-05-13 15:54:33 +02:00
Zoltan Papp
53b2fb8dc1 [client/ui] Add async Up mode to avoid blocking profile switches
The daemon's Up RPC previously always blocked in waitForUp (up to 50s)
until the engine connected. The UI does not need this — status updates
already flow through the SubscribeStatus stream.

Add bool async = 4 to UpRequest. When true the daemon starts
connectWithRetryRuns and returns immediately; the CLI path (async=false,
the default) is unchanged.

ProfileSwitcher.SwitchActive now sets Async:true so all three RPCs
(Status, Switch, Down, Up) return quickly. The background goroutine and
its associated race condition are removed entirely.
2026-05-13 15:51:36 +02:00
Zoltan Papp
803144e569 [client/ui] Unify profile-switching logic in ProfileSwitcher service
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.
2026-05-13 15:46:00 +02:00
Zoltan Papp
c0cd88a3d0 [client/ui] Fix stale LoginFailed/NeedsLogin state after profile switch
When the active profile was in LoginFailed, NeedsLogin, or SessionExpired,
switching to another profile left the daemon holding stale management/signal
errors. The new profile inherited the error state from the previous one.

Two fixes:
1. server.go Down(): reset statusRecorder management/signal errors so the
   next Up() starts with a clean status snapshot instead of the previous
   profile's error state.
2. tray.go switchProfile(): add NeedsLogin/LoginFailed/SessionExpired to
   the needsDown set. Down() is called to flush stale daemon state, but
   Up() is not — the user initiates login on the new profile manually.
2026-05-13 15:13:20 +02:00
Zoltan Papp
6c9b821bf0 [client/ui] Show active profile name and account email in tray menu
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.
2026-05-13 14:13:50 +02:00
Eduard Gert
83030dbbd6 Merge branch 'ui-refactor' into ui-refactor-ui 2026-05-13 10:12:26 +02:00
Eduard Gert
1c8a6e3798 wip 2026-05-13 10:11:38 +02:00
Zoltan Papp
74ea03da9b [ci] Fix Windows installer icon/banner paths missed in ui-wails rename
The ui-wails -> ui rename deleted the fyne installer assets but left the
NSIS and WiX scripts pointing at client/ui/assets/netbird.ico, which broke
the Windows Installer CI job. Point both scripts at the Wails-side icon
(client/ui/build/windows/icon.ico) and restore banner.bmp into the new
build directory so the NSIS welcome/finish sidebar keeps rendering.
2026-05-13 02:28:43 +02:00
Zoltan Papp
e1bf362675 [client/ui] Refresh tray menu after status-indicator bitmap change
Wails v3 alpha's setMenuItemBitmap on darwin calls NSMenuItem.setImage
from whichever thread invokes SetBitmap — unlike the sibling setters
for label/disabled/hidden/checked, which dispatch_sync onto the main
queue. The off-thread AppKit call doesn't redraw, so the coloured
status dot stayed stale until the user closed and reopened the menu.

Force a tray.SetMenu rebuild after updating the bitmap; the rebuild
runs processMenu inside InvokeSync, which applies the bitmap to a fresh
NSMenuItem on the main thread and macOS picks it up immediately.
2026-05-12 21:46:05 +02:00
Zoltan Papp
af40ee52f8 [client/ui] Auto-reconnect tray profile switch when daemon was active
Picking a profile from the tray submenu only ran SwitchProfile on the
daemon, so the in-flight retry loop kept dialing the previous profile's
management server. The fix is to follow up Switch with Down+Up, but only
when the daemon was actively trying to be online — Connected or
Connecting. Idle / NeedsLogin / LoginFailed / SessionExpired stay as
deliberate waiting points so a profile pick doesn't surprise the user
with an SSO redirect or flip an intentionally offline daemon online.

The decision table lives in the switchProfile godoc.
2026-05-12 21:40:29 +02:00
Zoltan Papp
4988f2aa68 [client/ui] Refresh Profiles submenu by rebuilding the tray menu
Wails v3 alpha's submenu.Update() builds a fresh, detached NSMenu on
darwin instead of mutating the one attached to the parent menu item at
initial setup, so the visible Profiles entries stayed frozen on the
empty snapshot captured when the tray was registered: clicks reached
the new Go MenuItem objects (and the daemon SwitchProfile RPC ran), but
the checkmark never moved and reopening the menu still showed the old
selection.

Cache the top-level menu and call tray.SetMenu(t.menu) after each
loadProfiles refresh; macosSystemTray.setMenu clears and rebuilds the
entire NSMenu tree against the cached pointer, which propagates submenu
content changes to the visible menu.

Also adds INFO logs around profile click / SwitchProfile RPC / list
refresh so the active-profile flow is observable end-to-end.
2026-05-12 21:24:52 +02:00
Zoltan Papp
e3efaa5e59 [client] Fix tray flicker and stuck Connecting during management retry
The status snapshot tore down on every management retry because
state.Status() blanks the status when an error is wrapped, and the
SubscribeStatus stream propagated that as FailedPrecondition. The UI
treated any stream error as "daemon not running" and flickered the tray
to Not running between retries.

Disconnect was also unresponsive: Down set Idle before the retry
goroutine exited, which then overwrote it with Set(Connecting) on the
next attempt; the backoff sleep (up to 15s) wasn't context-aware, so the
goroutine kept running long after actCancel.

- buildStatusResponse falls back to the underlying status (via new
  state.CurrentStatus) instead of breaking the stream on wrapped errors.
- UI only flips to DaemonUnavailable on codes.Unavailable / non-status
  errors, so a live daemon returning FailedPrecondition is not reported
  as down.
- connect retry uses backoff.WithContext so actCancel interrupts the
  inter-attempt sleep, and skips Wrap(err) when the dial fails due to
  ctx cancellation.
- Down sets Idle after waiting for giveUpChan, so the retry goroutine
  can no longer race the disconnect.
- Tray hides Connect during Connecting and keeps Disconnect enabled so
  the user can abort an in-flight connection attempt.
2026-05-12 20:38:30 +02:00
Zoltan Papp
100d25a062 [client/ui] Add Profiles submenu to the tray
Mirror the main branch's profile list: a Profiles submenu populated
from the daemon's ListProfiles RPC, with the active profile shown as
a checked entry and a click on any other entry switching to it via
SwitchProfile.

The initial fill is deferred to the Common.ApplicationStarted hook
because Menu.Update() short-circuits while app.running is false and
the Wails3 macOS impl would nil-deref on early-startup InvokeSync.
2026-05-12 20:11:08 +02:00
Zoltan Papp
04b4330393 [client/ui] Add coloured status dot to tray menu
Show a small dot next to the first menu entry that reflects the
daemon state: green for Connected, yellow for Connecting, blue for
NeedsLogin/SessionExpired, red for LoginFailed/Error, grey for
Idle/Disconnected and dark grey for DaemonUnavailable. PNGs are 24x24
with a pHYs chunk declaring 144 DPI so NSImage renders them at 12 pt
while keeping retina-sharp pixel data; circles are supersampled 8x for
smooth edges.

Idle now surfaces as "Disconnected" in the menu label, daemon-status
literals moved to status* constants, and Exit Node / Resources are
gated on the Connected state instead of just daemon availability.
2026-05-12 20:05:50 +02:00
Eduard Gert
c8e18585c6 add update context 2026-05-11 17:21:38 +02:00
Eduard Gert
1931a2c8a8 add update available icon 2026-05-11 17:11:25 +02:00
Eduard Gert
108d43e702 add flags, update peers list 2026-05-11 16:17:54 +02:00
Eduard Gert
842ef0d657 update macos icon 2026-05-11 15:40:04 +02:00
Eduard Gert
439f44c6b4 merge 2026-05-11 15:16:41 +02:00
Eduard Gert
b5a970155b Merge branch 'ui-refactor' into ui-refactor-ui 2026-05-11 15:15:11 +02:00
Eduard Gert
0c287b6f4d fix vite dev server 2026-05-11 14:48:37 +02:00
Zoltan Papp
7a9f5a734f Merge branch 'main' into ui-refactor
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
2026-05-11 14:10:12 +02:00
Zoltan Papp
28a7eba756 [client/ui] Remove unused xembed_host_other.go stub 2026-05-11 13:54:17 +02:00
Zoltan Papp
b43a09a1c7 [client/ui] Add tray icon for needs-login/login-failed states
The tray now switches to a dedicated lock icon when the daemon reports
NeedsLogin, SessionExpired or LoginFailed — the latter mirrors the CLI,
which groups these three statuses together as "needs authentication"
and prints the same "Run netbird up" prompt. The macOS template variant
reuses the existing error-macos PNG because the project's macOS tray
PNGs use a 2-color (black + transparent) convention that rsvg-convert
of the badge-style SVG sources can't reproduce. The earlier badge-style
SVG sketches in assets/svg/ are removed (they were marked as reference
only and never matched the shipping PNG design).
2026-05-11 13:22:30 +02:00
Zoltan Papp
595dfbb6f1 [client/ui] Distinguish "daemon not running" tray state
The status stream emits a synthetic StatusDaemonUnavailable when the
gRPC client or stream cannot be established, fired once per outage and
cleared on the next real snapshot. The tray maps it to a "Not running"
status label, switches the icon to the error variant, hides
Connect/Disconnect (neither would work without the daemon), and
disables Settings, Networks and Create Debug Bundle so the user is not
routed to pages that would just fail to load.
2026-05-11 12:22:47 +02:00
Zoltan Papp
7f560df9be [client/ui] Tray menu opens on click; hide window at startup
Left-click on the tray icon now opens the menu on every platform — the
window is reached through a new "Open NetBird" entry. Only the action
that matches the current daemon state is shown: Connect when
disconnected, Disconnect when connected. The main window starts hidden
and is only surfaced via the tray, single-instance launch, or daemon
events.
2026-05-11 12:01:46 +02:00
Zoltán Papp
09052949a2 [client/ui] Finish ui-wails rename (import paths, fyne deps)
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.
2026-05-11 11:33:35 +02:00
Zoltán Papp
9aef31ff53 [client/ui] Replace fyne UI with Wails (rename ui-wails to ui)
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.
2026-05-11 11:20:22 +02:00
Viktor Liu
205ebcfda2 [management, client] Add IPv6 overlay support (#5631) 2026-05-07 11:33:37 +02:00
Viktor Liu
057d651d2e [client, proxy] Add packet capture to debug bundle and CLI (#5891) 2026-05-04 11:28:56 +02:00
Viktor Liu
407e9d304b [client] Move macOS sleep detection into the daemon (purego) (#5926) 2026-04-29 08:09:55 +02:00