mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-26 22:39:07 +01:00
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
This commit is contained in:
+85
-71
@@ -41,26 +41,7 @@ func (t *Tray) applyStatus(st services.Status) {
|
||||
sessionExpiredEnter := strings.EqualFold(st.Status, services.StatusSessionExpired) &&
|
||||
!strings.EqualFold(t.lastStatus, services.StatusSessionExpired)
|
||||
|
||||
// Consume the SSO auto-handoff flag armed by handleConnect. Trigger
|
||||
// the browser-login flow on a Connect → NeedsLogin transition so the
|
||||
// user doesn't need to click Connect a second time. Clear it on any
|
||||
// other terminal state — including Connecting bursts that resolve to
|
||||
// Connected / Idle / LoginFailed / DaemonUnavailable — so a stale
|
||||
// flag can't fire weeks later when the daemon happens to flip.
|
||||
triggerLogin := false
|
||||
if t.pendingConnectLogin {
|
||||
switch {
|
||||
case strings.EqualFold(st.Status, services.StatusNeedsLogin):
|
||||
triggerLogin = true
|
||||
t.pendingConnectLogin = false
|
||||
case strings.EqualFold(st.Status, services.StatusConnected),
|
||||
strings.EqualFold(st.Status, services.StatusIdle),
|
||||
strings.EqualFold(st.Status, services.StatusLoginFailed),
|
||||
strings.EqualFold(st.Status, services.StatusSessionExpired),
|
||||
strings.EqualFold(st.Status, services.StatusDaemonUnavailable):
|
||||
t.pendingConnectLogin = false
|
||||
}
|
||||
}
|
||||
triggerLogin := t.consumePendingConnectLogin(st.Status)
|
||||
|
||||
daemonVersionChanged := st.DaemonVersion != "" && st.DaemonVersion != t.lastDaemonVersion
|
||||
t.connected = connected
|
||||
@@ -79,57 +60,7 @@ func (t *Tray) applyStatus(st services.Status) {
|
||||
|
||||
if iconChanged {
|
||||
t.applyIcon()
|
||||
daemonUnavailable := strings.EqualFold(st.Status, services.StatusDaemonUnavailable)
|
||||
connecting := strings.EqualFold(st.Status, services.StatusConnecting)
|
||||
if t.statusItem != nil {
|
||||
// Label-only: row is informational (no OnClick). Enablement
|
||||
// is platform-dependent via statusRowEnabled — Windows
|
||||
// keeps it enabled so the Win32 disabled-state mask does
|
||||
// not desaturate the coloured dot; macOS/Linux disable it.
|
||||
// Swap the displayed text so the user sees a familiar
|
||||
// phrase instead of the raw daemon enum.
|
||||
t.statusItem.SetLabel(t.loc.StatusLabel(st.Status))
|
||||
t.statusItem.SetEnabled(statusRowEnabled())
|
||||
t.applyStatusIndicator(st.Status)
|
||||
}
|
||||
if t.upItem != nil {
|
||||
// Connect stays visible/clickable in NeedsLogin/SessionExpired/
|
||||
// LoginFailed too — the daemon's Up RPC kicks off the SSO flow
|
||||
// when re-auth is required, mirroring the legacy Fyne client
|
||||
// where the same button drove the initial and the re-login
|
||||
// paths. Hidden only when the action would be a no-op (tunnel
|
||||
// up, daemon mid-connect — Disconnect takes the slot) or
|
||||
// would fail with no useful side effect (daemon unreachable).
|
||||
t.upItem.SetHidden(connected || connecting || daemonUnavailable)
|
||||
t.upItem.SetEnabled(!connected && !connecting && !daemonUnavailable)
|
||||
}
|
||||
if t.downItem != nil {
|
||||
// Disconnect is the abort path while the daemon is still
|
||||
// retrying the management dial — without it the user has no
|
||||
// way to stop the loop short of killing the daemon.
|
||||
t.downItem.SetHidden(!connected && !connecting)
|
||||
t.downItem.SetEnabled(connected || connecting)
|
||||
}
|
||||
// Exit Node parent-item enablement (greyed unless the tunnel is up
|
||||
// AND at least one candidate exists) is owned by refreshExitNodes,
|
||||
// triggered below on this same transition. Settings just needs the
|
||||
// daemon socket reachable.
|
||||
if t.settingsItem != nil {
|
||||
t.settingsItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
if t.profileSubmenuItem != nil {
|
||||
t.profileSubmenuItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
// Refresh the Profiles submenu on every status-text transition: the
|
||||
// daemon does not emit an active-profile event, so the startup race
|
||||
// (UI loads profiles before autoconnect picks the persisted profile)
|
||||
// and a CLI "profile select && up" both surface here. Fired AFTER
|
||||
// all SetHidden/SetEnabled writes on the static menu items above so
|
||||
// loadProfiles' SetMenu rebuild (which clearMenu+processMenu the
|
||||
// entire NSMenu and re-assigns item.impl) cannot race those
|
||||
// writes — the Wails 3 alpha menu API is not goroutine-safe and
|
||||
// reads item.disabled/item.hidden at NSMenuItem construction time.
|
||||
go t.loadProfiles()
|
||||
t.refreshMenuItemsForStatus(st, connected)
|
||||
}
|
||||
// Re-fetch the selectable exit-node list whenever the daemon's routed-
|
||||
// networks revision bumps (a route candidate added/removed, or a selection
|
||||
@@ -150,6 +81,89 @@ func (t *Tray) applyStatus(st services.Status) {
|
||||
t.applySessionExpiry(st.SessionExpiresAt, connected)
|
||||
}
|
||||
|
||||
// consumePendingConnectLogin acts on the SSO auto-handoff flag armed by
|
||||
// handleConnect. It returns true (and clears the flag) when the daemon
|
||||
// reached NeedsLogin, signalling the browser-login flow should start so the
|
||||
// user doesn't need to click Connect a second time. The flag is also cleared
|
||||
// on any other terminal state — including Connecting bursts that resolve to
|
||||
// Connected / Idle / LoginFailed / DaemonUnavailable — so a stale flag can't
|
||||
// fire weeks later when the daemon happens to flip. Must be called with
|
||||
// statusMu held.
|
||||
func (t *Tray) consumePendingConnectLogin(status string) bool {
|
||||
if !t.pendingConnectLogin {
|
||||
return false
|
||||
}
|
||||
switch {
|
||||
case strings.EqualFold(status, services.StatusNeedsLogin):
|
||||
t.pendingConnectLogin = false
|
||||
return true
|
||||
case strings.EqualFold(status, services.StatusConnected),
|
||||
strings.EqualFold(status, services.StatusIdle),
|
||||
strings.EqualFold(status, services.StatusLoginFailed),
|
||||
strings.EqualFold(status, services.StatusSessionExpired),
|
||||
strings.EqualFold(status, services.StatusDaemonUnavailable):
|
||||
t.pendingConnectLogin = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// refreshMenuItemsForStatus updates the status row, Connect/Disconnect
|
||||
// enablement, Settings/Profiles gating, and Profiles submenu on a status-text
|
||||
// transition (called from applyStatus only when iconChanged).
|
||||
func (t *Tray) refreshMenuItemsForStatus(st services.Status, connected bool) {
|
||||
daemonUnavailable := strings.EqualFold(st.Status, services.StatusDaemonUnavailable)
|
||||
connecting := strings.EqualFold(st.Status, services.StatusConnecting)
|
||||
if t.statusItem != nil {
|
||||
// Label-only: row is informational (no OnClick). Enablement
|
||||
// is platform-dependent via statusRowEnabled — Windows
|
||||
// keeps it enabled so the Win32 disabled-state mask does
|
||||
// not desaturate the coloured dot; macOS/Linux disable it.
|
||||
// Swap the displayed text so the user sees a familiar
|
||||
// phrase instead of the raw daemon enum.
|
||||
t.statusItem.SetLabel(t.loc.StatusLabel(st.Status))
|
||||
t.statusItem.SetEnabled(statusRowEnabled())
|
||||
t.applyStatusIndicator(st.Status)
|
||||
}
|
||||
if t.upItem != nil {
|
||||
// Connect stays visible/clickable in NeedsLogin/SessionExpired/
|
||||
// LoginFailed too — the daemon's Up RPC kicks off the SSO flow
|
||||
// when re-auth is required, mirroring the legacy Fyne client
|
||||
// where the same button drove the initial and the re-login
|
||||
// paths. Hidden only when the action would be a no-op (tunnel
|
||||
// up, daemon mid-connect — Disconnect takes the slot) or
|
||||
// would fail with no useful side effect (daemon unreachable).
|
||||
t.upItem.SetHidden(connected || connecting || daemonUnavailable)
|
||||
t.upItem.SetEnabled(!connected && !connecting && !daemonUnavailable)
|
||||
}
|
||||
if t.downItem != nil {
|
||||
// Disconnect is the abort path while the daemon is still
|
||||
// retrying the management dial — without it the user has no
|
||||
// way to stop the loop short of killing the daemon.
|
||||
t.downItem.SetHidden(!connected && !connecting)
|
||||
t.downItem.SetEnabled(connected || connecting)
|
||||
}
|
||||
// Exit Node parent-item enablement (greyed unless the tunnel is up
|
||||
// AND at least one candidate exists) is owned by refreshExitNodes,
|
||||
// triggered by applyStatus on this same transition. Settings just needs
|
||||
// the daemon socket reachable.
|
||||
if t.settingsItem != nil {
|
||||
t.settingsItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
if t.profileSubmenuItem != nil {
|
||||
t.profileSubmenuItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
// Refresh the Profiles submenu on every status-text transition: the
|
||||
// daemon does not emit an active-profile event, so the startup race
|
||||
// (UI loads profiles before autoconnect picks the persisted profile)
|
||||
// and a CLI "profile select && up" both surface here. Fired AFTER
|
||||
// all SetHidden/SetEnabled writes on the static menu items above so
|
||||
// loadProfiles' SetMenu rebuild (which clearMenu+processMenu the
|
||||
// entire NSMenu and re-assigns item.impl) cannot race those
|
||||
// writes — the Wails 3 alpha menu API is not goroutine-safe and
|
||||
// reads item.disabled/item.hidden at NSMenuItem construction time.
|
||||
go t.loadProfiles()
|
||||
}
|
||||
|
||||
// applyStatusIndicator sets the small coloured dot shown on the status
|
||||
// menu entry. The dot mirrors the tray icon's state through a fixed
|
||||
// palette: green for Connected, yellow for Connecting, blue for the
|
||||
|
||||
Reference in New Issue
Block a user