Commit Graph

3130 Commits

Author SHA1 Message Date
Zoltán Papp
1416a2e160 ui: reduce cognitive complexity in tray/feed/xembed status handlers
Extract helpers to bring three methods under the 20 cognitive-complexity
limit without changing behavior:

- DaemonFeed.statusStreamLoop: split out handleStatusRecvErr and emitStatus
- Tray.applyStatus: split out consumePendingConnectLogin and
  refreshMenuItemsForStatus
- xembedHost.flattenMenu: split out menuItemFromLayout plus propString /
  propBool / propInt32 dbusmenu property accessors
2026-05-29 14:45:58 +02:00
Zoltán Papp
88db1724bf Merge branch 'main' into ui-refactor 2026-05-29 14:39:40 +02:00
Eduard Gert
d0d7252c24 update settings bottom bar height 2026-05-29 14:25:36 +02:00
Eduard Gert
9dc9e7184e add first run lang detection 2026-05-29 14:24:20 +02:00
Eduard Gert
1985caf993 add os detection 2026-05-29 14:04:45 +02:00
Eduard Gert
16570b3223 prevent content flash in settings 2026-05-29 13:43:48 +02:00
Eduard Gert
967235e964 update button size and weight 2026-05-29 13:10:38 +02:00
Eduard Gert
7d876571da update CLAUDE.md 2026-05-29 13:08:06 +02:00
Eduard Gert
e6a624dcee preload settings window, prevent opening hidden windows on macos 2026-05-29 13:07:34 +02:00
Zoltán Papp
bee92f5fcd ui/frontend: update StatusContext for Peers → DaemonFeed rename
Missed in the previous commit. The StatusContext is the only frontend
consumer of the renamed service (the modules/main/.../peers/Peers.tsx
React component is a different identifier — unchanged).
2026-05-28 21:43:44 +02:00
Zoltan Papp
f4914fdfcc build: replace Wails3 scaffolding placeholders with NetBird identity
The build/config.yml that wails3 init scaffolded shipped with 'My Company',
'My Product', 'com.mycompany.myproduct' and '(c) 2025, My Company' template
defaults. The per-platform assets generated from it (Info.plist,
Info.dev.plist, info.json, nsis/wails_tools.nsh) carried the same strings,
which were visible in macOS Finder Get Info, Windows .exe Properties and
the NSIS installer.

Updated to the NetBird identity used by the legacy Fyne UI on main:

- companyName / copyright   -> 'NetBird GmbH' (matches main release.yml's
                              COPYRIGHT env passed to goversioninfo)
- productName               -> 'NetBird'
- productIdentifier         -> 'io.netbird.client' (matches CFBundleIdentifier)
- description               -> 'NetBird desktop client'
- darwin NSHumanReadableCopyright   -> 'NetBird GmbH'
- windows LegalCopyright            -> 'NetBird GmbH'
- nsis INFO_COPYRIGHT               -> 'NetBird GmbH'

Version fields (0.0.1) are left in place: release builds get the real
version via goversioninfo (Windows) and sign-pipelines (macOS .app),
so the placeholder is only visible in local task package / task run
output and doesn't reach release artifacts.
2026-05-28 21:32:16 +02:00
Zoltán Papp
2cdc6ef1c6 ui: split tray.go into feature files, rename Peers service to DaemonFeed
The 1542-line tray.go grew into a 14-feature kitchen sink. Split it
into feature-coherent same-package siblings, give the daemon-stream
service a name that matches what it actually does, and trim the
cargo-cult context.WithCancel pattern from click handlers.

File layout (tray.go: 1542 → ~470 lines):
  - tray_status.go    onStatusEvent / applyStatus / status indicator
  - tray_icon.go      applyIcon / iconForState (tray icon painting)
  - tray_events.go    onSystemEvent + eventTitle / titleCase, plus a
                      shouldSkipSystemEvent helper that names the
                      three "daemon notification we don't surface"
                      filters
  - tray_session.go   session-expiry row + warning notification flow +
                      handleSessionExpired (moved from tray.go)
  - tray_profiles.go  loadConfig / loadProfiles / switchProfile
  - tray_exitnodes.go exit-node submenu (rebuild / refresh / toggle)

Mutex split: the kitchen-sink t.mu becomes four domain-scoped mutexes
so a long-running gRPC call in one domain can't block status-push
readers in another:
  - statusMu        connected / lastStatus / lastDaemonVersion /
                    lastNetworksRevision / pendingConnectLogin
  - sessionMu       sessionExpiresAt (read by the 30s ticker,
                    written by applySessionExpiry on every status push)
  - profileMu       activeProfile / activeUsername /
                    notificationsEnabled / switchCancel
  - exitNodesMu     row cache (read in reapplyMenuState's Repaint copy)
  - exitNodesRebuildMu  serialises ListNetworks + submenu rebuild +
                        SetMenu (already separate, kept)

Service rename: the "Peers" service handled the daemon's full
SubscribeStatus snapshot (peers, daemon version, management/signal
link state, networks revision, SSO deadline) plus the SubscribeEvents
notification stream and the profile-switch suppression filter. Peers
was a misleading name for a daemon-stream fan-out service. Rename to
DaemonFeed in services/, profileswitcher's stored reference, the
TrayServices struct, main.go wiring, and every doc comment that
referenced it. peers.go → daemon_feed.go. The Status.Peers field
itself (the peer list in the snapshot) is unchanged.

Event constant renames (wire strings unchanged so the frontend keeps
working without regenerating bindings beyond the rename):
  - EventStatus → EventStatusSnapshot
    Payload is a full Status struct (daemon-wide snapshot), not just
    a state-change ping — name the value-shape.
  - EventSystem → EventDaemonNotification
    Payload is a daemon SystemEvent meant to drive an OS toast or a
    Recent Events row. "System" was too generic; "Notification"
    matches what consumers do with it.

Concurrency fixes:
  - WaitExtendAuthSession now preempts a previous in-flight wait
    via the existing SetWaitCancel/CancelWait infrastructure on
    PendingFlow, the same pattern WaitSSOLogin uses. The previous
    waiter exits with codes.Canceled; the authsession service
    translates that to ExtendResult{Preempted: true} so the tray
    and the about-to-expire dialog stay silent on the losing flow
    instead of showing a false-failure toast. Without this, both
    a tray "Extend now" click and a dialog "Stay connected" click
    on the same deadline started two parallel IdP polls, and
    whichever lost the device-code check painted a bogus error.
  - mgmClient.ExtendAuthSession drops the dead backoff retry loop.
    The loop only retried on codes.Canceled, but the inner mgmCtx
    was derived from context.Background() and never cancelled, so
    every real error went straight to backoff.Permanent on the
    first attempt. Replace with a single
    context.WithTimeout(c.ctx, ConnectTimeout) call; daemon
    shutdown now interrupts the RPC and behaviour on real errors
    is unchanged.

Click-handler hygiene: six call sites used the cargo-cult
context.WithCancel(context.Background()) + defer cancel() pattern
without ever calling cancel() externally. Replace with
context.Background() directly (loadConfig, loadProfiles,
runExtendSession, dismissSessionWarning, handleConnect's Up,
handleDisconnect's Down). The one site that genuinely needs the
cancel — switchProfile, which stores it in t.switchCancel so
handleDisconnect can preempt the switch — keeps WithCancel.

Helper extraction: shouldSkipSystemEvent groups the three
"daemon notification we drop on the floor" checks
(new_version_available metadata, progress_window metadata, the
::/0 partner of an exit-node default-route event) behind a single
named predicate. Each had a comment explaining why; collecting
them moves the rationale into the helper docstring and shrinks
onSystemEvent to a router.
2026-05-28 21:26:57 +02:00
Zoltán Papp
3279b705fe session-extend: drop dead retry loop in mgmClient.ExtendAuthSession
The backoff loop only retried on codes.Canceled, but mgmCtx was derived
from context.Background() and never cancelled by anything — so every
real error path (Unavailable, DeadlineExceeded, etc.) went through
backoff.Permanent on the first attempt. The loop was a no-op wrapper
that just held the call open for the daemon's lifetime regardless of
shutdown.

Replace with a single context.WithTimeout(c.ctx, ConnectTimeout) call.
Daemon shutdown now interrupts the RPC; behaviour on real errors is
unchanged.
2026-05-28 19:28:15 +02:00
Zoltán Papp
e94a4cbce5 session-extend: preempt previous WaitExtendAuthSession on new wait
When the tray "Extend now" notification action and the about-to-expire
dialog both start a flow for the same deadline, the daemon was running
two independent IdP polls and the older one surfaced an InvalidArgument
toast as soon as the second RequestExtend overwrote the pending flow.

Follow the WaitSSOLogin pattern: at the top of WaitExtendAuthSession
cancel the previous wait (the SetWaitCancel/CancelWait pair on
PendingFlow already existed but was unused), then register the new
wait's cancel. Preempted callers exit with codes.Canceled; the
authsession service translates that into ExtendResult{Preempted: true}
so the tray and the React dialog can stay silent on the losing flow
instead of showing a false-failure toast / error dialog.
2026-05-28 19:17:46 +02:00
Zoltan Papp
174dc24867 [management] Add SSO session extend flow (management) (#6197)
* add SSO session extend flow (management)

Adds the management-server half of the SSO session-extension feature:

- New ExtendAuthSession gRPC RPC that refreshes a peer's session expiry
  using a fresh JWT, validated through the same pipeline as Login but
  without tearing down the tunnel or redoing the NetworkMap sync.
- Per-peer SessionExpiresAt timestamp on every LoginResponse and
  SyncResponse so connected clients learn the deadline on the existing
  long-lived stream, and admin-side changes (toggling expiration,
  changing the expiration window) reach every peer within seconds.
- SessionExpiresAt(...) helper on Peer that derives the absolute UTC
  deadline from LastLogin + the account-level PeerLoginExpiration
  setting, returning zero when the peer is not SSO-tracked or expiration
  is disabled.

The matching client-side consumer of these fields lands separately.

* encode SessionExpiresAt as 3-state on the wire

Previously the `sessionExpiresAt` field on LoginResponse, SyncResponse
and ExtendAuthSessionResponse was 2-state: a valid timestamp meant
"new deadline", and nil meant "clear". That conflated two distinct
meanings — "no info in this snapshot" vs "expiry is explicitly off /
peer is not SSO-tracked" — so a Sync push that legitimately couldn't
compute the deadline (settings lookup failed) would silently clear the
client's anchor and lose the warning window.

Three states now, encoded on the same field number (no .proto schema
churn — only comments and the server-side encoder change):

  - nil pointer (field absent) → "no info"; client preserves anchor
  - &Timestamp{} (seconds=0, nanos=0) → explicit "disabled / not SSO"
    sentinel; client clears
  - valid timestamp → new absolute UTC deadline

A new encodeSessionExpiresAt helper centralises the zero/non-zero
encoding and is shared by the Sync, Login and ExtendAuthSession
builders. The Sync builder still emits nil when settings are missing.
Login and ExtendAuthSession always carry an authoritative value.

The matching client-side decoder lands on feature/session-extend.

* add UserExtendedPeerSession activity event

ExtendAuthSession previously reused UserLoggedInPeer for its audit
record, which conflated two distinct user actions: a full interactive
SSO login (tunnel re-established, network map resync) versus an
in-place deadline refresh (tunnel untouched). Auditors reading the log
couldn't tell which one happened, and downstream dashboards/alerts on
"login" volume were polluted by routine extends.

Adds a dedicated UserExtendedPeerSession Activity (code 125,
"user.peer.session.extend") and switches ExtendPeerSession over to it.
The peer-extend audit trail is now distinguishable from interactive
logins.

* make ExtendAuthSession JWT-retry backoff cancellable

Skip the retry log and 200ms wait on the final attempt, and replace the
uncancellable time.Sleep with a select on time.After/ctx.Done so an
upstream cancellation aborts the wait instead of running it to
completion.
2026-05-28 19:14:14 +02:00
Eduard Gert
c1db8ab0ab add manage profiles to tray 2026-05-28 18:04:38 +02:00
Eduard Gert
2bf945e745 remove unused packages 2026-05-28 17:18:49 +02:00
Eduard Gert
4556d52a60 fix view mode toggle 2026-05-28 16:36:15 +02:00
Eduard Gert
51b243bdfa remove unused stuff, refactor frontend folder structure 2026-05-28 16:26:13 +02:00
Eduard Gert
e09bc8894d Merge remote-tracking branch 'origin/ui-refactor' into ui-refactor 2026-05-28 15:45:51 +02:00
Zoltan Papp
55c1f44fb0 build: drop -buildvcs=false so go embeds vcs.revision into the ui binary
All Wails3 Taskfiles passed -buildvcs=false to go build, which disables
the automatic VCS info embedding Go 1.18+ does by default. As a result
runtime/debug.ReadBuildInfo() returned an empty vcs.revision in our
netbird-ui binary, so the upcoming version.NetbirdCommit() helper from
PR #6263 could not display the git sha for dev builds.

Removed from build:native in all three platform Taskfiles plus the
Windows build:console and the Dockerfile.cross cross-compile script.
go version -m bin/netbird-ui now reports vcs.revision and vcs.modified.
2026-05-28 15:22:05 +02:00
Eduard Gert
ac8d417c12 update exit node tab 2026-05-28 14:43:28 +02:00
Eduard Gert
dccc0ebe4b update resources tab 2026-05-28 14:28:51 +02:00
Zoltán Papp
35498c572a ci: bump node to v22 in release workflow
pnpm 11 requires Node.js >= 22.13 (uses node:sqlite, added in 22.5),
but the release workflow still pinned Node 20. After bumping pnpm to
v11 in the previous commit, the frontend build hook now fails with
ERR_UNKNOWN_BUILTIN_MODULE 'node:sqlite' until Node also moves to 22.
2026-05-28 13:57:30 +02:00
Zoltán Papp
cda621bb27 ci: bump pnpm to v11 in release workflow
The frontend uses pnpm 11 (packageManager field, v11 lockfile, and the
allowBuilds key in pnpm-workspace.yaml is a pnpm 10+ feature), but the
release_ui job's pnpm/action-setup was pinned to v9. v9 rejects the
workspace file with 'packages field missing or empty' before the
frontend build hook can run.
2026-05-28 13:51:43 +02:00
Zoltán Papp
d57b30f8d5 Merge branch 'main' into ui-refactor 2026-05-28 13:43:19 +02:00
Zoltan Papp
d82b950718 frontend: approve esbuild postinstall via pnpm-workspace.yaml
pnpm 11 blocks dependency build scripts by default and exits non-zero
when any are skipped, which made task build fail at install:frontend:deps.
esbuild's postinstall is required to fetch the platform-specific binary.
2026-05-28 13:40:43 +02:00
braginini
3bd058d425 Use old version of the Dock icon 2026-05-28 12:45:36 +02:00
Zoltan Papp
0082f51830 i18n: pluralize exit node nav title 2026-05-28 11:46:19 +02:00
Zoltan Papp
e4420b1f96 tray: separator between troubleshoot and version info in about submenu 2026-05-28 11:44:13 +02:00
Zoltan Papp
a5635f8825 tray: use yellow connecting dot for needs-login state 2026-05-28 11:41:22 +02:00
Riccardo Manfrin
7ea5e37dd4 [client] Improve rosenpass support (#6136)
* Updates rosenpass version

go-rosenpass v0.4.0 → v0.5.42 bump — detailed findings

Change summary
cunicu.li/go-rosenpass  v0.4.0  → v0.5.42   (target)
cilium/ebpf             v0.15.0 → v0.19.0   (transitive)
gopacket/gopacket       v1.1.1  → v1.4.0    (transitive)
wireguard               2023-07 → 2023-12   (transitive)
wireguard/wgctrl        2023-04 → 2024-12   (transitive)

Wire interop

v0.4.0 (in v0.70.5) <-> v0.5.42 OK
v0.5.42 <-> v0.5.42 OK

Quantum resistance: true both ends

---
**Replay error eliminated.**

Before (on v0.4.0):

`ERROR Failed to handle message: failed to load biscuit (ICR1): detected replay`

Recurring every ~50ms for minutes at a time. Gone entirely after both ends upgraded to v0.5.42. Upstream fix in biscuit/replay handling between v0.4.x and v0.5.x series.

* Fixup [::]:port socket trying to send to v4

* Adds more tests on netbird<->rosenpass interactions

* Anticipates rp handler creation before generateConfig

* [client] Moves deterministic key gen into rosenpass

* go mod tidy

* Adds reminder to reason about rosenpass surface area

* Apply code rabbit suggestions
2026-05-28 09:01:18 +02:00
Riccardo Manfrin
9d7ef9b255 [client] Fix statemanager possible deadlock (#6228)
1. Stop() takes m.mu.Lock() and defers m.mu.Unlock()
2. <-m.done under lock
3. periodicStateSave defers close(m.done)
4. periodicStateSave calls PersistState() (line 256) which does m.mu.Lock()

Double Stop() remains idempotent: second cancel() on dead ctx
 (no-op) and reads done already closed (immediate return).
2026-05-28 08:54:15 +02:00
Zoltan Papp
966fbec119 routemanager: enforce a single selected exit node
Exit nodes are mutually exclusive, but the RouteSelector stores routes with
default-on semantics, so every available exit node reported as selected at once.

Reconcile exit-node selection on each network map (and on runtime selection):
keep at most one selected — the user's persisted pick, else whatever management
marks for auto-apply (SkipAutoApply=false), else none. Never auto-activate an
exit node the map doesn't request; it stays off until the user picks it.

The server deselects sibling exit nodes when the user activates one (leaving
non-exit routes untouched), and the tray/React exit-node toggle now appends so
activating an exit node no longer wipes network-route selections.
2026-05-27 20:48:16 +02:00
Zoltan Papp
f693d268b4 tray: selectable exit nodes + push-based network list refresh
Make the tray Exit Node submenu selectable (mutually exclusive, sourced from
ListNetworks by NetID) instead of read-only.

Add networksRevision to the status snapshot, bumped by the route manager on
network-map and selection changes, so the tray and the React NetworksContext
re-fetch ListNetworks via the push stream instead of polling. The peer-status
route list only carries chosen routes, so a candidate exit node appearing or
disappearing would otherwise never reach the UI.
2026-05-27 20:48:16 +02:00
Eduard Gert
09f4109b01 update peers ui 2026-05-27 18:01:06 +02:00
Eduard Gert
ad7d7fa881 change font weight 2026-05-27 17:03:31 +02:00
Pascal Fischer
944a258459 [management] extend nmap monitoring (#6271) 2026-05-27 16:56:02 +02:00
Eduard Gert
b84c7618e7 fix viewmode height, update other ui stuff 2026-05-27 16:40:57 +02:00
Eduard Gert
ec5da43d73 persist viewMode across restarts 2026-05-27 15:52:15 +02:00
Eduard Gert
a8ad73d2d9 add shortcuts in tray for quit and settings item 2026-05-27 15:37:05 +02:00
Eduard Gert
a241112a1d disable resize 2026-05-27 15:35:51 +02:00
Eduard Gert
e62dff0f66 add github, docs links etc. to settings about page 2026-05-27 15:35:40 +02:00
Eduard Gert
5cecca2c23 store viewmode in ui preferences 2026-05-27 15:21:51 +02:00
Eduard Gert
0e83d2ad94 add peer details 2026-05-27 14:52:19 +02:00
Pascal Fischer
1f9a829f2c [management] update log levels (#6266) 2026-05-27 11:43:49 +02:00
Zoltan Papp
004a305e46 tray: add 30s ticker to keep session-expiry countdown fresh 2026-05-27 00:33:09 +02:00
Zoltan Papp
c77e5cef85 tray: revert on-open click handler — OpenMenu freezes tray and React
Binding OnClick/OnRightClick to call OpenMenu() on macOS routes the menu
open through showMenu(), which runs the blocking [button mouseDown:] inside
a dispatched block on the serial main GCD queue. While the menu is open that
block never returns, starving every other main-queue task — both tray item
updates and the webview event delivery that drives React freeze until the
menu closes.

Revert to the pre-d9f0189 state: no click handlers bound, native NSStatusItem
auto-show for left-click, Wails default rightClickHandler for right-click.
refreshSessionExpiresLabel() is kept for the follow-up fix.
2026-05-27 00:24:46 +02:00
Zoltan Papp
13179081d2 Merge branch 'main' into ui-refactor 2026-05-26 23:41:18 +02:00
Zoltan Papp
2d3c8fc555 tray: drop dead iconMenuNetbird and openRoute after menu rework
The menu reorganisation removed the About brand-mark bitmap and rerouted
every openRoute caller to WindowManager auxiliary windows, leaving both
the iconMenuNetbird embed (all three platforms) and the openRoute helper
unreferenced. Remove them so the unused linter passes.
2026-05-26 23:25:45 +02:00