Commit Graph

324 Commits

Author SHA1 Message Date
Zoltán Papp
db371a0263 [client/ui] Fix tray submenu not updating on KDE/Plasma
The Profiles and Exit Node submenus (and the About version/Update rows)
stopped reflecting changes on KDE/Plasma: after the first profile switch
the menu froze on its initial snapshot, and "Manage Profiles" — plus the
profile rows themselves — stopped responding to clicks entirely.

Root cause (confirmed via dbus-monitor): Plasma's StatusNotifierItem host
caches a submenu's layout the first time it is opened (GetLayout for that
submenu id) and never re-fetches it on a LayoutUpdated(parent=0) signal.
The old submenu.Clear()+Add() repaint allocated fresh monotonic item ids
each time but reused the same submenu container id, so Plasma kept showing
the stale snapshot and, on click, sent the stale ids back — which the
rebuilt itemMap no longer knew, silently no-op'ing the click.

Fix: route every dynamic tray-menu change through a new relayoutMenu that
rebuilds the whole tree (buildMenu + repaint cached state + a single
SetMenu), allocating brand-new submenu container ids. Plasma treats those
as unseen and re-queries them on next open, fixing both the stale paint
and the dead clicks. loadProfiles/refreshExitNodes now cache their rows
and drive relayoutMenu; the update row goes through a new onMenuChange
hook; the daemon-version row relayouts too. relayoutMenu is serialised by
menuMu and the fill*Submenu helpers are pure UI (no fetch, no SetMenu) so
it never recurses. The whole-tree SetMenu also subsumes the prior darwin
detached-NSMenu workaround.
2026-06-04 18:28:30 +02:00
Zoltán Papp
ca6f6d88cb Merge branch 'main' into ui-refactor 2026-06-04 17:06:11 +02:00
Zoltán Papp
e298747203 [client/ui] Refresh profile list in tray + UI on CLI profile add/remove
The daemon emits no dedicated profile-changed RPC event, and a profile
add/remove doesn't move the connection status, so the UI's SubscribeStatus
path never fired for CLI-driven `netbird profile add|remove` (and the tray's
iconChanged guard would swallow it anyway). The tray menu and the React
profile list stayed stale until the next status-string transition.

AddProfile/RemoveProfile now publish a marked INFO/SYSTEM event over
SubscribeEvents (metadata kind=profile-list-changed, empty userMessage so it
stays silent). The UI's dispatchSystemEvent recognises the marker and
re-emits the existing EventProfileChanged, which the tray's loadProfiles and
React's ProfileContext.refresh already subscribe to — so both surfaces
refresh from a single signal that originates in the shared daemon handler
(covering both CLI and UI-initiated removals). No proto change.

Also drop a stray, build-breaking `app.Updater` line in main.go.
2026-06-04 16:03:22 +02:00
Theodor Midtlien
5993ec6e43 [client] Allow wireguard port to be zero in UI and show port in status command (#6158)
* Allow wireguard port to be set to 0 in UI

* Add wireguard port to cmd status

* Correct protoc version
2026-06-04 15:04:11 +02:00
Zoltán Papp
cba4a8a63b [client/ui] Fix login wedging until restart after a failed SSO attempt
startLogin held both guards (the module-level loginInFlight and the
caller's React-level loginGuard) across the post-failure errorDialog
await. The native Windows MessageBox disables its parent for its whole
lifetime while the main window's WindowClosing hook hides instead of
closing, so the dialog promise can outlive the click — and even a clean
dismissal kept the guards held until the promise settled. Until then
every later Connect click and tray trigger-login was silently dropped at
the guard check, so the only way back was a client restart.

Release both guards the instant the flow itself settles, before the
dialog: startLogin now takes an onSettled callback fired in its finally
(driveLogin releases loginGuard through it), and the errorDialog await
moved out of the try/finally so no guard is ever gated on the dialog.
2026-06-04 13:42:29 +02:00
Zoltán Papp
93e068f753 [client/ui] Honour DEV=true in windows:build:console for DevTools
windows:build:console fixed -tags production, which disables the
WebKit/WebView2 DevTools inspector — so there was no way to get a
console-attached Windows binary with the frontend JS console reachable.
Mirror build:native's DEV handling: DEV=true drops the production tag
while keeping the console subsystem (no -H windowsgui).
2026-06-04 13:21:41 +02:00
Zoltán Papp
94065a8058 [client/ui] Enable tray status row on Linux to avoid greyed-out indicator
The informational status row at the top of the tray menu was disabled on
Linux, which painted the connection-status indicator greyed-out. Enable
it on Linux so the row renders at full opacity; it has no OnClick handler
so clicking it remains a no-op.
2026-06-04 11:42:22 +02:00
Eduard Gert
64199209cf add onboarding 2026-06-03 17:33:13 +02:00
Eduard Gert
1710868a09 add exit node switcher 2026-06-02 17:23:56 +02:00
Zoltán Papp
7798b7cf14 [client/ui] Disable WebKit compositing on Linux to avoid Intel/Mesa SIGSEGV
WebKitGTK's accelerated GL compositor crashes with a SIGSEGV inside
g_application_run on some Intel setups, hitting Mesa anv/i965 code paths
for DRM format modifiers that aren't implemented (FINISHME: YUV
colorspace / multi-planar formats). Disabling the DMA-BUF renderer alone
doesn't cover the GL compositor, so the crash survived that workaround.

Set WEBKIT_DISABLE_COMPOSITING_MODE=1 in init() (skipped if the user
already set it) to force CPU rendering, which is fine for a UI this
small and sidesteps the broken modifier path.
2026-06-02 15:10:45 +02:00
Zoltán Papp
179966b000 [client/ui] Detect KDE panel dark mode from kdeglobals Complementary colour
Split Linux panel-theme detection into two files and fix the KDE case
where the tray icon picked the wrong mono variant.

The freedesktop Settings portal's color-scheme reports the *global*
light/dark preference, but the KDE panel is painted from the
Complementary colour group, which can be dark even when the global
scheme is Light. The tray sits on the panel, so keying its black/white
mono icon off the portal value alone gave the wrong contrast on KDE.

Changes:
  - tray_theme_linux.go keeps the dark/light decision; on KDE it now
    reads the user's kdeglobals [Colors:Complementary] BackgroundNormal
    to determine the actual panel luminance, falling back to the portal
    color-scheme / GTK_THEME chain elsewhere.
  - tray_theme_watcher_linux.go (new) owns the live half: a private
    session-bus connection for the portal SettingChanged signal plus an
    fsnotify watch on kdeglobals, repainting the tray on a panel-theme
    flip.
  - tray_theme_linux_test.go (new) covers the kdeglobals Complementary
    parse against the KDE test-VM's real file layout.
2026-06-02 14:29:45 +02:00
Eduard Gert
48265a0143 add profile switch into settings, scroll to top after profile switch, add netbird icon to linux windows 2026-06-02 14:20:36 +02:00
Zoltán Papp
9a76507b14 [client/ui] Fix Linux package metadata and KDE Wayland icon association
Two Linux packaging issues, both surfaced by the netbird-ui deb/rpm
built from .goreleaser_ui.yaml.

1) License / vendor metadata
----------------------------
The nfpm entries for the UI package set only maintainer/description/
homepage, leaving the License and Vendor RPM/DEB tags empty. KDE
Discover (and GNOME Software) then render the package as
"Licenses: Unknown" / "Unknown author", with a scary license-warning
popup on install. The daemon's main .goreleaser.yaml already set
license (commit #5659) but never vendor, and the UI config was skipped
entirely.

Fix: add `license: BSD-3-Clause` + `vendor: NetBird` to both UI nfpm
entries (deb + rpm), and `vendor: NetBird` to the daemon's deb + rpm
entries for consistency. BSD-3-Clause is correct for client/ui — the
repo is BSD-3-Clause except management/, signal/, relay/, combined/
(AGPLv3), none of which the UI touches.

2) KDE Wayland window/taskbar icon
----------------------------------
On KDE Plasma 6 under Wayland the app launched with the generic Wails
icon in the window titlebar and the taskbar / Alt-Tab switcher, even
though /usr/share/pixmaps/netbird.png (the launcher icon, resolved from
the desktop entry's `Icon=netbird`) was correct.

Root cause is how a Wayland compositor decides a window's icon. Unlike
X11 there is no per-window _NET_WM_ICON the app can push at runtime —
GTK4 even removed gtk_window_set_icon, so the embedded assets/netbird.png
the binary carries is simply ignored. Instead the compositor matches the
window's Wayland **app_id** to an installed .desktop file and uses that
entry's `Icon=` key.

The app_id is not "netbird": Wails hardcodes it as `org.wails.<name>`
(pkg/application/linux_cgo.go: `fmt.Sprintf("org.wails.%s", name)`, name
= sanitized Options.Name), yielding **org.wails.netbird**. There is no
Wails option to override the prefix. Verified the live value on the
Fedora-40 / KDE 6.3 / Wayland test VM by dumping workspace.windowList()
via a KWin script:

    js: ZZZWIN org.wails.netbird ## NetBird

KDE needs two things to associate the running window with our desktop
entry and thus paint our icon:
  - the desktop entry's basename should equal the app_id, so the
    titlebar decoration (which looks the entry up by app_id ->
    <app_id>.desktop) finds it, and
  - a `StartupWMClass=<app_id>` line, which the taskbar / task switcher
    use to map the surface to the entry.

Fix (no Wails fork needed — app_id stays org.wails.netbird, which the
user never sees; only Name=NetBird and the icon are visible):
  - install the desktop file as `org.wails.netbird.desktop` instead of
    `netbird.desktop` (both deb and rpm contents in .goreleaser_ui.yaml)
  - add `StartupWMClass=org.wails.netbird` to
    client/ui/build/linux/netbird.desktop

`Icon=netbird` and the pixmaps/netbird.png payload are unchanged — they
were already correct. Confirmed on the test VM that both the titlebar
and taskbar/Alt-Tab now show the NetBird icon.
2026-06-02 12:52:24 +02:00
Eduard Gert
c3a0c1beeb update language switcher, remove flags 2026-06-02 12:36:29 +02:00
Eduard Gert
45095818bc update paddings and icons 2026-06-02 12:04:55 +02:00
braginini
1f74ee9c78 Merge remote-tracking branch 'origin/ui-refactor' into ui-refactor 2026-06-02 11:45:39 +02:00
braginini
562a538e91 fix(tray): round session-remaining to nearest hour/day 2026-06-02 11:45:29 +02:00
Eduard Gert
50b26a21fd Merge remote-tracking branch 'origin/ui-refactor' into ui-refactor 2026-06-02 11:38:03 +02:00
Eduard Gert
fefd0da7bf Adjust peers and resources tabs 2026-06-02 11:37:56 +02:00
braginini
d1f3d88f0d Rephrase session expiration 2026-06-02 11:37:48 +02:00
Zoltán Papp
a71ef1bab0 [client/ui] Guard session expiry updates to log only on change 2026-06-02 11:09:20 +02:00
Zoltán Papp
072d789463 client/ui: retry XEmbed-tray probe before claiming SNI watcher
The XEmbed tray (panel) can come up after the autostarted UI on minimal
WMs, so the single startup probe added in #6320 could miss a tray that
appears a second or two later, leaving the icon silently absent. Re-probe
for a ~10s grace period in a goroutine, claiming the watcher as soon as a
tray shows up; back off cleanly if none ever appears (headless/Wayland).
2026-06-01 21:55:04 +02:00
Pascal Fischer
8d05fe07bf Fix watcher registration on wayland (#6320)
* Fix hover label on linux

* Fix watcher registration on wayland
2026-06-01 21:48:39 +02:00
Zoltán Papp
4cee07bef5 client/ui: use monochrome tray icons on Linux
Linux now shows monochrome (black/white silhouette) tray icons instead
of the colored orange PNGs, matching the macOS template look. Since
Wails' Linux SNI backend ignores SetDarkModeIcon (its setDarkModeIcon
just calls setIcon, last-write-wins) and the SNI spec carries no panel
light/dark hint, the panel color scheme is detected in-process and the
black-vs-white silhouette is chosen in iconForState, pushed via a single
SetIcon.

Detection order (tray_theme_linux.go): freedesktop Settings portal
(org.freedesktop.appearance/color-scheme) -> GTK_THEME env (:dark
suffix) -> default dark. A SettingChanged subscription repaints live on
theme flips. macOS (template) and Windows (colored) paths are unchanged.

Icons are 48x48 mono PNGs (3% margin) generated from the macOS
silhouettes.
2026-06-01 20:23:46 +02:00
Zoltán Papp
5bebecc427 ui: disable WebKit sandbox when unprivileged userns are blocked
WebKitGTK crashes at startup when its bubblewrap sandbox can't create an
unprivileged user namespace (bwrap: setting up uid map: Permission denied
-> Failed to fully launch dbus-proxy -> panic in webkit_web_view_load_uri).
This happens in containers/VMs and on Ubuntu 24.04+ where AppArmor
restricts unprivileged user namespaces. Detect that the kernel blocks
userns via procfs and set WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS so the
UI stays usable; honor an explicit user override either way.
2026-06-01 20:06:44 +02:00
Zoltán Papp
3dbd96b172 Add Version service exposing GUI version to frontend 2026-06-01 19:23:41 +02:00
Eduard Gert
6fe35cae83 Merge remote-tracking branch 'origin/ui-refactor' into ui-refactor 2026-06-01 17:50:38 +02:00
Eduard Gert
88bd1f91a8 update session expire dialog to account for hours, days etc. 2026-06-01 17:50:21 +02:00
Eduard Gert
acfd680560 remove icons from profile dropdown 2026-06-01 17:37:05 +02:00
Pascal Fischer
0b8aae4566 Fix hover label on linux (#6318) 2026-06-01 17:36:39 +02:00
Eduard Gert
daf9a74d8f update dialog error for connection switch 2026-06-01 17:18:31 +02:00
Eduard Gert
8af90e40d5 Merge remote-tracking branch 'origin/ui-refactor' into ui-refactor
# Conflicts:
#	client/ui/frontend/src/contexts/SettingsContext.tsx
2026-06-01 17:15:38 +02:00
Eduard Gert
3f989f69cb update about gui version, add mock data, truncate some strings 2026-06-01 17:14:30 +02:00
Zoltan Papp
53d43980ad fix(ui): keep main window closable after a native dialog on Windows (#6319)
A native Windows MessageBox attached to a parent window disables that
window (WS_DISABLED) for its lifetime and re-enables it on dismissal.
When the parent is the main window — whose WindowClosing hook hides
instead of closes — the enable/hide sequence races and leaves the window
unable to process its close (X) button afterwards, so e.g. a rejected
login error dialog left the main window stuck open.

Route all native dialogs through src/lib/dialogs.ts, which forces
Detached: true on Windows (NULL owner, no window ever disabled) and is a
no-op on macOS/Linux (keeps the attached sheet-style presentation).
2026-06-01 17:03:53 +02:00
Zoltán Papp
49df24b18c escape ampersand in tray menu labels on Windows
Win32 swallows a lone & in an MFT_STRING menu item as the mnemonic
prefix, so "Help & Support" rendered as "Help  Support". Add a
build-tagged menuLabel() helper that doubles & to && on Windows and is
the identity on macOS/Linux (which render & literally), and apply it to
the About submenu label.
2026-06-01 16:44:18 +02:00
Zoltán Papp
c5611dd766 open main window on tray double-click on Windows
Wire the Windows systray's double-click to ShowWindow(), matching the
Windows-native convention for tray apps. The Wails v3 systray dispatches
WM_LBUTTONDBLCLK to the doubleClickHandler, so OnDoubleClick fires; left-
and right-click continue to open the menu. macOS/Linux are unchanged.
2026-06-01 16:41:44 +02:00
Eduard Gert
a4ad93008b add skeleton to launch netbird ui at login and own context 2026-06-01 14:57:31 +02:00
Eduard Gert
710d5c6182 apply different window width on windows 2026-06-01 11:07:33 +02:00
Eduard Gert
7538a9a133 update window width and dialogs top padding on macos 2026-06-01 09:51:59 +02:00
Zoltan Papp
27873866c2 Revert needs login icon 2026-05-31 04:34:17 +02:00
Zoltan Papp
18348e1491 client+ui: remove SSO handoff flicker and clean up abandoned login via context
Two follow-ups to the "hold NeedsLogin during the SSO browser wait" change.
Both target the visible state churn the tray showed during the auto-login
handoff (Connect / profile-switch lands on NeedsLogin -> the UI's startLogin
kicks off the SSO flow) and the broken recovery after the user dismisses the
browser-login popup with the window's X.

Background
----------
When a connect attempt lands on NeedsLogin, the UI's startLogin() drives the
SSO flow: Connection.Login() -> (NeedsSSOLogin) open the browser-login popup
-> Connection.WaitSSOLogin() blocks until the browser leg completes. The tray
and the React status page both paint the raw daemon status, so any transient
state the daemon publishes during this handoff is visible as a flicker.

Previously the handoff churned the daemon status through
  NeedsLogin -> Idle -> Connecting -> NeedsLogin
which read as a flicker on the tray icon and the status dot. Two distinct
sources produced the two intermediate states:

  * Idle       came from the UI's defensive cli.Down() at the top of
                Connection.Login (services/connection.go): it tore the engine
                down before every login to dislodge a possibly-parked
                WaitSSOLogin, emitting a StatusIdle on the way.
  * Connecting  came from server.go Login() unconditionally setting
                StatusConnecting before deciding whether the request is an
                SSO flow (which immediately returns NeedsLogin) or a
                setup-key flow (which actually dials Management).

Changes
-------
1. server.go Login(): only set StatusConnecting on the setup-key path, where
   we are about to dial Management with the key and the Connecting paint is
   meaningful. The SSO path returns NeedsLogin and parks on the browser leg,
   so it no longer flashes Connecting first. Removes the Connecting blip.

2. services/connection.go Login(): drop the pre-Login cli.Down(). The daemon
   already dislodges a pending WaitSSOLogin at Login entry (actCancel), and an
   abandoned browser leg is now torn down by cancelling the WaitSSOLogin RPC
   (see 3/4). Removing the Down removes the Idle blip on every login.

3. MainConnectionStatusSwitch.tsx startLogin(): on cancel (the browser-login
   popup's Cancel button or its window X, both routed through
   EventBrowserLoginCancel), cancel the in-flight WaitSSOLogin gRPC call via
   waitPromise.cancel() instead of issuing a heavy Connection.Down(). The
   daemon ties the wait to this call's context, so cancelling the call ends
   the wait cleanly with no engine teardown and no Idle paint.

4. server.go WaitSSOLogin(): when the wait unblocks with context.Canceled and
   the cancellation came from our caller (callerCtx.Err() != nil — the client
   cancelled the RPC or went away), clear the cached oauthAuthFlow so a fresh
   Login starts a new device code instead of reusing the abandoned one. The
   entry NeedsLogin stays in place, so a reattaching client still shows the
   login affordance. An internal abort (actCancel fired by a newer
   Login/WaitSSOLogin while our callerCtx is still live) is left untouched so
   the new owner's flow is not clobbered.

Effect
------
The auto-login handoff now goes Connected -> Connecting -> NeedsLogin and
holds, with no Idle/Connecting flicker in between. Dismissing the browser-login
popup with X now recovers the same way as the Cancel button: the WaitSSOLogin
RPC is cancelled, the stale OAuth flow is cleared, and the next connect opens a
fresh browser-login window instead of getting stuck.
2026-05-31 04:26:15 +02:00
Zoltan Papp
5df570feb8 ui: localize tray status labels for connect/login states
StatusLabel only mapped Idle and DaemonUnavailable, so Connected,
Connecting, NeedsLogin, LoginFailed and SessionExpired leaked the raw
daemon enum into the tray menu — untranslated in de/hu. Map all five to
tray.status.* keys (added in en/de/hu); keep the raw-enum default as a
fallback for any future status.
2026-05-31 03:15:34 +02:00
Zoltan Papp
ed4d823755 ui: auto-trigger browser login when profile switch lands on NeedsLogin
Add a second, longer-lived switchLoginWatch flag alongside switchInProgress
in DaemonFeed. Suppression still clears on the first Connecting push from the
new Up, but the login watcher survives past it to catch the eventual
NeedsLogin / LoginFailed / SessionExpired terminal and emit EventTriggerLogin,
so the React orchestrator opens the browser-login flow without a second
Connect click. shouldSuppress becomes consumeForSwitch, returning both the
suppress and triggerLogin signals. CancelProfileSwitch disarms the watch so
an aborted switch does not pop a login window.
2026-05-31 03:15:25 +02:00
Zoltan Papp
8b03c96851 ui: add launch-at-login (autostart) toggle for the UI
Add an Autostart Wails service wrapping app.Autostart and a toggle in
the General settings tab. The OS login-item registration is the single
source of truth (nothing mirrored to the preferences file). Affects the
graphical UI only, not the daemon. The toggle hides itself on platforms
where autostart is unsupported.
2026-05-31 02:01:02 +02:00
Zoltan Papp
b830a45333 ui: request macOS notification authorization on startup
Without an explicit authorization request the macOS notification center
keeps the app at .notDetermined and silently drops every toast. Request
it from the ApplicationStarted hook (after the notifier's Startup has
initialised the delegate), off the main goroutine since the call blocks
until the user responds. Linux/Windows notifier stubs report authorized,
so this is a no-op there.
2026-05-31 01:34:56 +02:00
Eduard Gert
b0d8ac6489 fix auto size detection 2026-05-29 17:21:45 +02:00
Eduard Gert
558769e671 localize window titles, fix size for windows (and other platforms?) 2026-05-29 16:58:08 +02:00
Zoltán Papp
fb6138a3ba tray: open menu on left-click on Windows
Wails v3 does not auto-show the tray menu on left-click on Windows — its
default left-click handler only logs and does nothing visible, so only
right-click opened the menu. macOS (NSStatusItem) and Linux
(StatusNotifierItem host) give us click→menu natively.

Add a build-tag-split bindTrayClick: the Windows variant wires
OnClick→OpenMenu (the same menu.ShowAt path right-click uses), while the
macOS/Linux variant stays a no-op — binding OnClick→OpenMenu on macOS
freezes the tray via NSStatusItem's blocking mouseDown on the main GCD
queue (the reason commit c77e5cef8 reverted the earlier wiring).
2026-05-29 16:02:41 +02:00
Eduard Gert
122d172f33 Merge remote-tracking branch 'origin/ui-refactor' into ui-refactor 2026-05-29 15:40:41 +02:00
Eduard Gert
0b19a99693 update ui for win11 2026-05-29 15:40:34 +02:00