mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-04 12:39:06 +02:00
[client] UI refactor (#6069)
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>
This commit is contained in:
co-authored by
Eduard Gert
braginini
Pascal Fischer
Claude
riccardom
parent
679c7182a4
commit
8b7ce337d8
@@ -0,0 +1,126 @@
|
||||
//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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user