Commit Graph

1637 Commits

Author SHA1 Message Date
Eduard Gert
62afff6d52 fix active profile name 2026-06-18 18:17:49 +02:00
Eduard Gert
731e87be72 switch lang tag on language switch, lint 2026-06-18 16:16:45 +02:00
Eduard Gert
461ec1c8f5 fix visible focus ring 2026-06-18 15:25:30 +02:00
Eduard Gert
de12fe7851 fix slide animation 2026-06-18 14:46:32 +02:00
Eduard Gert
2e9ae5e8e1 allow to edit profiles 2026-06-18 14:39:27 +02:00
Eduard Gert
0b5fa75549 lint and add frontend-ui workflow 2026-06-18 13:56:42 +02:00
Eduard Gert
9cbaf62eda make management switch accessible 2026-06-18 13:56:42 +02:00
Eduard Gert
cc842f17d5 add eslint 2026-06-18 13:56:41 +02:00
Eduard Gert
3175b880e4 add better keyboard nav and fix sonar lint 2026-06-18 13:56:41 +02:00
Eduard Gert
79e7dce47e make settings accessible
 Conflicts:
	client/ui/frontend/src/modules/profiles/ProfilesTab.tsx
2026-06-18 13:56:41 +02:00
Eduard Gert
77fe022351 make main screen accessible 2026-06-18 13:56:41 +02:00
Eduard Gert
9cdc6e3013 add a11y keys 2026-06-18 13:56:41 +02:00
Zoltán Papp
f6e50995b6 [client] Classify session extend errors for the UI
Route services.Session errors through the same classifier Connection
uses so RequestExtend/WaitExtend return a structured ClientError with a
clean localized short message instead of the raw daemon error. Extract
the shared errorClassifier into errors.go, and fall back to the gRPC
status code when no message substring matches, since the daemon now
forwards a clean desc without the English marker text.
2026-06-18 13:37:58 +02:00
Zoltán Papp
e9a7d62229 [client] Return innermost gRPC status from WaitExtendAuthSession
The daemon wrapped the management ExtendAuthSession error through two
%v/%w layers and remapped it to codes.Internal, so the UI surfaced the
full wrapped chain instead of the root cause. Forward only the innermost
gRPC status (original code + clean desc) to the client and log the full
chain. gstatus.FromError does not unwrap, so add innermostStatus to walk
the %w chain.
2026-06-18 13:37:47 +02:00
Zoltan Papp
a6d1194b5d Merge branch 'main' into ui-refactor
Resolve conflicts and complete the profile display-name -> ID migration
across the daemon, CLI, UI services, tray, and React frontend.

- Regenerate client/proto daemon.pb.go from the merged proto so it carries
  both branches' RPCs (ui-refactor: SubscribeStatus/RegisterUILog/ExtendAuthSession,
  main: RenameProfile + id fields); keep the v6.33.1 generator header
- server.go: combine ui-refactor's profile-list-changed events with main's
  id-bearing responses; publish profile-list-changed on rename
- Drop the deleted Fyne client_ui.go/profile.go and port main's profile-ID
  changes into the refactored services/tray
- UI services, tray and React: send the profile ID as the daemon handle and
  keep the display name for rendering only (activeProfileId vs activeProfile)
- Relax the profile-name input to match the daemon's sanitizeDisplayName
  (spaces, emoji, any valid UTF-8); cap at 128
- Expose RenameProfile via a Profiles.Rename services wrapper (+ regenerated
  bindings) for the frontend to wire up
- cmd/login.go: use the profile ID for GetProfileState
2026-06-18 11:40:51 +02:00
Theodor Midtlien
ee360963f9 [client] Migrate profile identity from display name to ID and allow renaming of profiles (#6367)
* Migrate to profile ids

* Migrate android profile manager

* Clean up

* Fix review

* Add ID type

* Fix test and runes in ShortID()

* Fix profile switch on up and android comments

* Revert android profile to string id

* Fix feedback

* Fix UI feedback

* Fix id assignment

* Add renaming of profiles

* Fix review

* Remove ui binary
* Fix getProfileConfigPath not validating id

* Change resolve handle order and fix server merge problems

* Fix mdm test
2026-06-18 08:49:19 +02:00
Maycon Santos
8d9580e491 [misc] improve goreleaser with RC handling and update docker builds (#6438)
- introduce variables to avoid publishing latest docker tags and installers
- Refactor .goreleaser.yaml to simplify docker configurations and add environment-driven flags
- removed management debug containers (it was doing only log var)
- Stopped building arm v6 32bits in favor of v7 32 bits for services (not client)
- Add target argument to docker files
2026-06-17 20:13:13 +02:00
Viktor Liu
5bd7c6c7ea [client] Detect and recover from a stalled signal receive stream (#6459) 2026-06-17 18:48:09 +02:00
Zoltan Papp
8ae2cd0a08 [client] Fix ios route notify ordering (#6454)
* [client] fix iOS route-update reordering that black-holed IPv6 on exit-node disable

On iOS the route notifier delivered each prefix update from its own
fire-and-forget goroutine (notify -> `go func`), so Go provided no ordering
guarantee between consecutive updates. It also read currentPrefixes inside
that goroutine without holding the lock, racing the next OnNewPrefixes write.

On exit-node disable the core removes the default routes as two separate
prefix updates (0.0.0.0/0, then the synthesized ::/0). When the two
goroutines were reordered, the stale snapshot still containing ::/0 was
delivered last and clobbered the correct default-free one. iOS then kept the
::/0 default route on the tunnel with no exit node to carry it, black-holing
all IPv6 traffic while IPv4 recovered correctly.

Fix: deliver updates through a single worker goroutine fed by a buffered
channel, preserving production order, and snapshot the joined prefix string
under the mutex so it can't race a concurrent update. Buffered so producers
(which run under the route manager lock) don't block on the listener callback.

* [client] close iOS notifier delivery goroutine on Stop, unbounded queue

The delivery goroutine was never stopped, leaking on every engine
restart. Add Notifier.Close, called from the route manager Stop after
routing cleanup.

Replace the buffered update channel with a cond-driven linked-list
queue so route-update producers (running under the route manager lock)
never block when the listener callback is slow.
2026-06-17 18:29:33 +02:00
Zoltán Papp
f9acc6a2e9 [client] Exclude UI fallback stubs from FreeBSD build
The _other.go fallback stubs in client/ui and client/ui/services matched
FreeBSD, dragging the Wails-importing services package into the FreeBSD
build and failing it (Wails v3 has no FreeBSD port). Add !freebsd (plus
!android/!ios/!js) to align them with the rest of these packages so the
whole UI is excluded on FreeBSD.
2026-06-17 17:46:54 +02:00
Eduard Gert
262372ce6c fix copy icon alignment 2026-06-17 17:29:35 +02:00
Eduard Gert
f5e5a12a90 add virtual scroll and fix empty results overlay 2026-06-17 17:26:38 +02:00
Zoltán Papp
6881c0f985 [client] Apply lazy connection toggle to running engine
The daemon SetConfig only persisted the lazy connection flag to the
profile config; the running engine was untouched, so a UI/CLI change
took effect only after a down/up or daemon restart.

Wire SetConfig to push the change into the running engine via a new
ConnMgr.SetLocalLazyConn, which sets enabledLocally like an env/CLI
flag so a later management sync cannot override it, and starts or
stops the lazy manager in place.
2026-06-17 16:51:01 +02:00
Zoltán Papp
e264eca8e7 Merge branch 'main' into ui-refactor 2026-06-17 16:02:57 +02:00
Viktor Liu
6fbc90b4d3 [client, relay] Expose relay transport and connection errors in status and metrics (#6342) 2026-06-17 15:41:48 +02:00
Eduard Gert
622e5e140f add overlay if daemon not compatible 2026-06-17 14:06:38 +02:00
Eduard Gert
6e696719c8 update tray images 2026-06-17 13:46:31 +02:00
Zoltán Papp
fedef7588f ui: detect outdated daemon via WailsUIReady probe
Add a no-op WailsUIReady RPC the UI probes once at startup. A reachable
daemon that returns Unimplemented predates this UI and is too old to
drive it; the probe distinguishes that from an unreachable daemon. On an
outdated daemon the UI fires a localized OS notification, since the main
window may not open to show an in-app error.

Proto regenerated with protoc 33.1 to keep the v6.33.1 header.
2026-06-17 13:00:13 +02:00
Zoltán Papp
3715fe9b72 ui: fix FreeBSD build by matching tray_theme_other build tags to tray.go
tray_theme_other.go references Tray but its build tag selected it on
FreeBSD, where tray.go (which defines Tray) is excluded via !freebsd.
Add the same !android && !ios && !freebsd && !js exclusions to the stub.
2026-06-17 12:37:50 +02:00
Eduard Gert
db2ea7554f remove mdm test scripts and other markdown files 2026-06-17 11:43:07 +02:00
Zoltán Papp
3a283f813f ui: extract applyMDMRestrictions to reduce cognitive complexity
Splits the MDM field-mapping block out of GetRestrictions into its own
helper to bring the function's cognitive complexity below the S3776 limit.
2026-06-17 11:32:36 +02:00
Zoltán Papp
bd924431ec ui: move dock_other no-op comment inside function body 2026-06-17 11:31:22 +02:00
Eduard Gert
855ef2465f refresh restrictions also on status changes 2026-06-17 10:25:20 +02:00
Eduard Gert
3e1dc1f023 microsoft windows only: open application on left click and tray menu on right click 2026-06-17 09:55:00 +02:00
Pascal Fischer
0c21aefc7f Left-click support for linux (#6410) 2026-06-16 17:46:48 +02:00
Zoltan Papp
6df0175607 [client] Add IsLoginRequiredCached for iOS mobile client (#6447)
Expose a network-free login-required check backed by the in-memory status
recorder. Unlike IsLoginRequired(), which creates a fresh auth client and
performs a blocking network call, IsLoginRequiredCached() reports whether the
LAST observed management error was an auth failure (PermissionDenied/
InvalidArgument).

This lets the iOS connection listener detect a mid-session token expiry from
within onDisconnected during teardown without blocking on a slow or
unavailable network.
2026-06-16 16:15:19 +02:00
Zoltan Papp
3c23700e56 [client] Add iOS debug bundle support in Go (#6270)
* Add iOS debug bundle support in Go

Thread cacheDir through NewClient -> RunOniOS -> MobileDependency.TempDir
so the iOS client can pass its sandbox-writable cache directory for
debug bundle zip file creation instead of os.TempDir().

Move log collection into platform-dispatched addPlatformLog():
- iOS: adds the file-based Go client log (with rotation, stderr/stdout
  companions and anonymization handled by addLogfile) plus the Swift app
  log (swift-log.log) written by the iOS app into the same log directory
- Other non-Android platforms: existing file-based log + systemd fallback

Narrow the debug_nonandroid.go build tag to !android && !ios so iOS no
longer attempts the systemd journal fallback.

Add a DebugBundle() entry point to the iOS Go client that generates a
bundle, uploads it and returns the upload key. It works with or without
a running engine: when the engine is up it reuses the live config, sync
response and client metrics; otherwise it loads the config from disk (or
the preloaded tvOS config). Guard the live config/ConnectClient behind a
state mutex since DebugBundle may run on a different thread.

* Include the iOS state file in the debug bundle

addStateFile() resolved the state path via ServiceManager.GetStatePath(),
which on iOS points at a hard-coded default that does not exist in the app
sandbox, so the state file was silently skipped.

Add an optional StatePath to GeneratorDependencies and use it when set,
falling back to the ServiceManager default otherwise. The iOS DebugBundle
passes the client's actual state file path (the App Group profile state),
matching the Android bundle which includes the state file.

* ios: enable sync response persistence for debug bundle

Turn on sync response persistence before starting the engine so
DebugBundle can include the network map. On iOS the store is disk-backed
(see syncstore) to keep the map out of the constrained process memory.

* ios: pass log file path through NewClient constructor (#6393)

Add logFilePath field to Client struct and expose it as a parameter
in NewClient so callers provide the Go log path at construction time.
Wire it into DebugBundle via GeneratorDependencies.LogPath so the
debug bundle includes client.log and swift-log.log regardless of
whether the bundle is triggered by the app or the management server.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* ios: pass log file path to engine for remote debug bundles

RunOniOS started the engine with an empty LogPath, so EngineConfig.LogPath
was never set. Management-triggered (jobs) debug bundles read the log path
from the engine config, so they collected no client logs (client.log,
rotated logs, swift-log.log). The GUI path was unaffected because it passes
c.logFilePath directly to the bundle generator.

Thread c.logFilePath through RunOniOS into the engine config so remote
bundles include the client logs too.

---------

Co-authored-by: evgeniyChepelev <68751844+evgeniyChepelev@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 15:54:46 +02:00
Zoltán Papp
1f69cd189e ui: elevate Windows debug-bundle reveal to bypass SystemTemp ACL
The daemon runs as SYSTEM and writes the bundle into C:\Windows\SystemTemp,
whose ACL denies the logged-in user, so a plain 'explorer /select' could not
open it. The Windows reveal now elevates via ShellExecuteW with the runas verb,
falling back to an unelevated reveal of the parent dir on decline.
2026-06-16 15:34:09 +02:00
Eduard Gert
44709983f7 fix focus on windows windows 2026-06-16 15:03:51 +02:00
Eduard Gert
e4918e0277 update main toggle switch 2026-06-16 14:03:13 +02:00
Eduard Gert
192c3109c0 add more ui logs and fix sonarqube lint 2026-06-16 13:45:54 +02:00
Eduard Gert
b16bdf5e8f refetch restrictions on window visibility change 2026-06-16 13:30:17 +02:00
Zoltan Papp
08a2b63675 [client] propagate exit-node deselect to synthesized v6 (::/0) route (#6296)
* [client] propagate exit-node deselect to synthesized v6 (::/0) route

When a client deselects an IPv4 exit node, the auto-generated IPv6 default
route (::/0) was still selected and pushed onto the tunnel interface, even
though the user disabled the exit node. On an exit node without a real IPv6
egress this blackholes IPv6 traffic, and because clients prefer IPv6 (happy
eyeballs) it can break general connectivity.

Root cause: the synthesized v6 route gets a different NetID than its v4 base
(base + "-v6"). The route selector keys deselects by NetID and defaults
unknown NetIDs to selected, so the "-v6" entry was never matched by the v4
deselect. The effectiveNetID() mirror that solves exactly this is used by
HasUserSelectionForRoute and FilterSelectedExitNodes, but categorizeUserSelection
called the raw IsSelected(), bypassing it and mis-categorizing the v6 pair as
user-selected.

Add RouteSelector.IsSelectedForExitNode(), which applies effectiveNetID before
the selection check, and use it in categorizeUserSelection. IsSelected() is left
untouched so non-exit code paths don't make unrelated "*-v6" routes inherit v4
state. Adds regression tests for the v4/v6 deselect mirror and explicit-v6
override.

* [client] add DIAG logging to trace exit-node v6 (::/0) route filtering

Temporary diagnostics to find why a deselected v4 exit node's synthesized
::/0 route still reaches the tunnel. Logs the full install path: incoming
client networks, route-selector state before/after the management-driven
update, what updateExitNodeSelections deselects/selects, and per-route
KEEP/SKIP/DROP decisions in FilterSelectedExitNodes and applyExitNodeFilter.
To be reverted once the real root cause is confirmed from a client log.

* [client] clear orphaned v6 exit selection when v4 pair is toggled

Root cause of the leaking ::/0 route, confirmed from client logs: the
synthesized "-v6" exit route could stay explicitly selected in the persisted
route-selector state while its v4 base was deselected (selected=[...-v6],
deselected=[...v4base]). Because the v6 entry then has its own explicit state,
effectiveNetID stops mirroring the v4 base, so FilterSelectedExitNodes keeps
::/0 and it is installed on the tunnel even though the user disabled the exit
node. This happened because the iOS SDK's deselect only pairs the "-v6" sibling
via ExpandV6ExitPairs when the v6 route is present in the current routesMap; a
deselect at a moment it wasn't expanded left the v6 selection orphaned.

Fix at the selector write path so it is independent of routesMap timing: when a
v4 exit NetID is selected or deselected, clear any orphaned explicit state on
its "-v6" sibling (clearPairedV6Locked), unless the sibling is part of the same
batch (the deliberate ExpandV6ExitPairs case). The v6 then falls back to
inheriting the v4 base via effectiveNetID, so a v4 deselect also drops ::/0 and
a v4 select brings both back.

Adds regression tests: a stale explicit v6 selection is cleared by a later v4
deselect, and an explicit v6 select made in the same batch is preserved.

* [ios] compute route connection status in the bridge

The iOS bridge exposed a route's Network as a possibly comma-joined string
("0.0.0.0/0, ::/0" for a merged exit node) but no connection status, forcing
the UI to infer status by string-matching that joined value against peer
routes — which never matched for the merged exit node, leaving it stuck as
not-connected. Android already computes status in the core (findBestRoutePeer).

Mirror that here: add a Status field to RoutesSelectionInfo and compute it from
the connected peers' route tables, matching the route's primary prefix, a merged
exit node's extra v6 prefix, or a dynamic route's domain pattern (the key the
route manager records). The UI can now read the status directly.

* [client] remove exit-node v6 DIAG logging and tidy routeselector

Drop the temporary DIAG diagnostics added to trace the leaking ::/0 route
(the root cause is fixed and confirmed). Also reorganize routeselector.go so
the exit-node helpers (clearPairedV6Locked, isExitNode) sit next to the
exit-node code paths and MarshalJSON/UnmarshalJSON are grouped together.

* [client] mirror v4 exit selection onto v6 pair at write time

The synthesized "-v6" exit route shares its v4 base's NetID plus a "-v6"
suffix. Selection state was reconciled at read time via effectiveNetID, a
mirror that could only be applied on exit-node code paths, which forced a
parallel IsSelectedForExitNode() alongside IsSelected() and a clearPairedV6Locked()
orphan cleanup on every toggle. That machinery still missed the case observed
in the field: a persisted state with the v4 base deselected but its "-v6"
sibling explicitly selected (orphaned). Because effectiveNetID returns the v6
entry itself once it carries explicit state, and clearPairedV6Locked only fires
on a live toggle, the loaded orphan survived and the ::/0 route leaked onto the
tunnel despite the exit node being disabled, breaking IPv6 (happy eyeballs).

Treat the v4/v6 exit pair as a single toggle and keep state consistent at write
time instead. RouteSelector.SyncPairedSelection forces the "-v6" entry to match
its v4 base unconditionally, resetting any orphaned explicit state. The route
manager, which knows the route prefixes, computes the pairs (V6ExitMergeSet) and
calls it from updateRouteSelectorFromManagement before selection is read, so both
collectExitNodeInfo and FilterSelectedExitNodes see consistent state, including
pairs loaded from persisted selector state.

This removes effectiveNetID, IsSelectedForExitNode and clearPairedV6Locked; the
selector is literal again and no longer needs the "exit-node paths only" caveat.
HasUserSelectionForRoute and applyExitNodeFilter use the raw NetID.

Adds a selector test for SyncPairedSelection (including the orphaned-v6 case) and
a route-manager test reproducing the persisted-orphan scenario from the field log.

* [client] add DIAG logging to trace v6 exit-pair mirror

The write-time mirror did not eliminate the leak in field testing. Re-add the
DIAG diagnostics around the exit-node selection flow to capture a fresh trace:

- UpdateRoutes: incoming client networks, selector state before/after the
  management update, and the networks remaining after FilterSelectedExitNodes.
- mirrorV6ExitPairSelections: the NetIDs present in this update and the v6 pairs
  V6ExitMergeSet derives from them (reveals whether the v4 base and its ::/0 pair
  are present in the same update so the pair can be matched).
- SyncPairedSelection: the base/paired state before and after the sync.
- FilterSelectedExitNodes / applyExitNodeFilter: per-route SKIP/KEEP/DROP and the
  selection lookups behind each decision.
- updateExitNodeSelections / logExitNodeUpdate: categorization and deselect set.

Temporary; to be removed once the root cause is confirmed.

* [client] remove v6 exit-pair mirror DIAG logging

Drop the temporary DIAG diagnostics added to trace the v4/v6 exit-pair mirror.
The field log confirmed the write-time mirror keeps the pair consistent (the
::/0 route is only ever applied alongside its v4 base and is dropped on deselect),
so the diagnostics are no longer needed.
2026-06-16 12:27:58 +02:00
Eduard Gert
4ae37919c0 keep general settings tab visible when update settings are locked 2026-06-16 12:24:06 +02:00
Eduard Gert
951e08e35c add mdm helper (windows) (can be deleted later) 2026-06-16 12:11:47 +02:00
Eduard Gert
64c9551a4d remove "Loading..." from profile dropdown and show skeleton 2026-06-16 12:09:49 +02:00
Eduard Gert
44156b6256 mdm test script (can be deleted later) 2026-06-16 11:33:10 +02:00
Eduard Gert
ebaaddc14b show macos dock icon when any window is visible 2026-06-16 11:32:04 +02:00
Pascal Fischer
967e2d6864 [management] network map for affected peers (#6105) 2026-06-15 17:43:22 +02:00
Viktor Liu
a44198fd77 [client] Add dialWebSocket method to WASM client (#5980) 2026-06-15 16:43:24 +02:00