mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-20 07:21:27 +02:00
Refactor UI --------- Co-authored-by: Eduard Gert <kontakt@eduardgert.de> Co-authored-by: braginini <bangvalo@gmail.com> Co-authored-by: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: riccardom <riccardomanfrin@gmail.com>
127 lines
4.2 KiB
Go
127 lines
4.2 KiB
Go
//go:build !android && !ios && !freebsd && !js
|
|
|
|
package main
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
|
|
"github.com/netbirdio/netbird/client/ui/services"
|
|
)
|
|
|
|
func (t *Tray) onStatusEvent(ev *application.CustomEvent) {
|
|
st, ok := ev.Data.(services.Status)
|
|
if !ok {
|
|
return
|
|
}
|
|
t.applyStatus(st)
|
|
}
|
|
|
|
// applyStatus repaints the tray from a daemon snapshot. Icon refresh is skipped
|
|
// when no icon-relevant input changed: the daemon emits rapid SubscribeStatus
|
|
// bursts during health probes that would otherwise spam Shell_NotifyIcon.
|
|
func (t *Tray) applyStatus(st services.Status) {
|
|
t.statusMu.Lock()
|
|
connected := strings.EqualFold(st.Status, services.StatusConnected)
|
|
iconChanged := connected != t.connected || st.Status != t.lastStatus
|
|
// The daemon re-emits SessionExpired on every snapshot while expired; act
|
|
// only on the transition into it so the notification fires once.
|
|
sessionExpiredEnter := strings.EqualFold(st.Status, services.StatusSessionExpired) &&
|
|
!strings.EqualFold(t.lastStatus, services.StatusSessionExpired)
|
|
|
|
triggerLogin := t.consumePendingConnectLogin(st.Status)
|
|
|
|
daemonVersionChanged := st.DaemonVersion != "" && st.DaemonVersion != t.lastDaemonVersion
|
|
t.connected = connected
|
|
t.lastStatus = st.Status
|
|
if daemonVersionChanged {
|
|
t.lastDaemonVersion = st.DaemonVersion
|
|
}
|
|
|
|
revisionChanged := st.NetworksRevision != t.lastNetworksRevision
|
|
t.lastNetworksRevision = st.NetworksRevision
|
|
t.statusMu.Unlock()
|
|
|
|
if triggerLogin {
|
|
t.app.Event.Emit(services.EventTriggerLogin)
|
|
}
|
|
|
|
// Cache-only; the row is painted by the relayout below.
|
|
sessionChanged := t.applySessionExpiry(st.SessionExpiresAt, connected)
|
|
|
|
if iconChanged {
|
|
t.applyIcon()
|
|
}
|
|
// All repainting goes through relayoutMenu (menuMu-serialised): applyStatus
|
|
// runs concurrently with itself and with relayouts, so in-place item
|
|
// mutation would race the buildMenu pointer swap.
|
|
if iconChanged || daemonVersionChanged || sessionChanged {
|
|
t.relayoutMenu()
|
|
}
|
|
// The revision is the only reliable signal: candidate routes never appear
|
|
// in the peer-status snapshot, so a removed exit node would go unnoticed.
|
|
if iconChanged || revisionChanged {
|
|
go t.refreshExitNodes()
|
|
}
|
|
// The daemon emits no active-profile event, so profile flips driven
|
|
// elsewhere (CLI, autoconnect) surface via status transitions.
|
|
if iconChanged {
|
|
go t.loadProfiles()
|
|
}
|
|
if sessionExpiredEnter {
|
|
t.handleSessionExpired()
|
|
}
|
|
}
|
|
|
|
// consumePendingConnectLogin acts on the SSO auto-handoff flag armed by
|
|
// handleConnect. Returns true on NeedsLogin so the browser-login flow starts
|
|
// without a second Connect click; clears the flag on any terminal state so a
|
|
// stale flag can't fire on a later daemon flip. Must hold statusMu.
|
|
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
|
|
}
|
|
|
|
// applyStatusIndicator sets the status dot bitmap. Call only from relayoutMenu
|
|
// (menuMu held): on macOS the bitmap repaints via the relayout's trailing
|
|
// SetMenu, not here — the tree is half-built.
|
|
func (t *Tray) applyStatusIndicator(status string) {
|
|
if t.statusItem == nil {
|
|
return
|
|
}
|
|
t.statusItem.SetBitmap(statusIndicatorBitmap(status))
|
|
}
|
|
|
|
func statusIndicatorBitmap(status string) []byte {
|
|
switch {
|
|
case strings.EqualFold(status, services.StatusConnected):
|
|
return iconMenuDotConnected
|
|
case strings.EqualFold(status, services.StatusConnecting):
|
|
return iconMenuDotConnecting
|
|
case strings.EqualFold(status, services.StatusNeedsLogin),
|
|
strings.EqualFold(status, services.StatusSessionExpired):
|
|
return iconMenuDotConnecting
|
|
case strings.EqualFold(status, services.StatusLoginFailed),
|
|
strings.EqualFold(status, statusError):
|
|
return iconMenuDotError
|
|
case strings.EqualFold(status, services.StatusDaemonUnavailable):
|
|
return iconMenuDotOffline
|
|
default:
|
|
return iconMenuDotIdle
|
|
}
|
|
}
|