mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-17 03:59:56 +00:00
- **Wails v3 application** (`client/ui`) with a React + TypeScript + Tailwind frontend replacing the Fyne UI: main connection view, exit-node switcher, networks/peers browser with detail panels, profile management, settings (general, network, SSH, security, troubleshooting, appearance), debug-bundle creation, and a first-run welcome flow. - **Internationalization**: go-i18n bundle with 9 locales (en, de, es, fr, hu, it, pt, ru, zh-CN) shared between the tray and the frontend. - **New system tray** implementation with per-platform theme-aware icons, including a native XEmbed host for Linux (`xembed_tray_linux.c`) and a Linux theme watcher. - **Session handling**: auth session watcher (`client/internal/auth/sessionwatch`), pending login flow, session-expiration dialog and tray notifications, and `netbird login` improvements. - **Daemon API extensions** (`daemon.proto`): status stream subscription, event stream, networks/exit-node selection endpoints, and richer full status — with probe throttling on the daemon side to protect against UI-driven request storms. - **UI preferences store** persisted per profile, autostart management via the daemon (single source of truth in HKCU on Windows). - **Build system**: Taskfile-based builds per platform (macOS, Linux, Windows), Docker cross-compilation images, MSIX/NSIS/nfpm/AppImage packaging, and a new `frontend-ui` CI workflow. Co-authored-by: Zoltan Papp <zoltan.pmail@gmail.com> 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: 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
|
|
}
|
|
}
|