mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-18 04:29:54 +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>
138 lines
4.2 KiB
Go
138 lines
4.2 KiB
Go
//go:build !android && !ios && !freebsd && !js
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
|
|
"github.com/netbirdio/netbird/client/proto"
|
|
"github.com/netbirdio/netbird/client/ui/authsession"
|
|
"github.com/netbirdio/netbird/client/ui/services"
|
|
)
|
|
|
|
// onSystemEvent fires an OS notification for daemon SystemEvents that carry a
|
|
// user-facing message. Gated by the "Notifications" toggle; critical events bypass it.
|
|
func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
|
|
se, ok := ev.Data.(services.SystemEvent)
|
|
if !ok {
|
|
return
|
|
}
|
|
// config_changed carries no UserMessage, so handle it before the message gate below.
|
|
if se.Category == "system" && se.Metadata[proto.MetadataTypeKey] == proto.MetadataTypeConfigChanged {
|
|
log.Infof("config_changed event received (source=%s); refreshing tray restrictions", se.Metadata[proto.MetadataSourceKey])
|
|
go t.refreshRestrictions()
|
|
go t.loadConfig()
|
|
// MDM gets a localised toast here; the daemon's English "policy_applied"
|
|
// event is suppressed in shouldSkipSystemEvent. Other sources stay silent.
|
|
if se.Metadata[proto.MetadataSourceKey] == proto.MetadataSourceMDM {
|
|
t.profileMu.Lock()
|
|
enabled := t.notificationsEnabled
|
|
t.profileMu.Unlock()
|
|
if enabled {
|
|
t.notify(
|
|
t.loc.T("notify.mdm.policyApplied.title"),
|
|
t.loc.T("notify.mdm.policyApplied.body"),
|
|
notifyIDMDMPolicy,
|
|
)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
// Session-warning and deadline-rejected events build their body locally from
|
|
// metadata; every other event needs a UserMessage.
|
|
isSessionWarning := se.Metadata[authsession.MetaWarning] == "true"
|
|
isDeadlineRejected := se.Metadata[authsession.MetaDeadlineRejected] != ""
|
|
if !isSessionWarning && !isDeadlineRejected && se.UserMessage == "" {
|
|
return
|
|
}
|
|
if shouldSkipSystemEvent(se) {
|
|
return
|
|
}
|
|
|
|
critical := strings.EqualFold(se.Severity, services.SeverityCritical)
|
|
t.profileMu.Lock()
|
|
enabled := t.notificationsEnabled
|
|
t.profileMu.Unlock()
|
|
if !enabled && !critical {
|
|
return
|
|
}
|
|
|
|
// Session-warning events route via stable metadata flags rather than
|
|
// category/severity so a daemon-side reword still lands here. Final warning
|
|
// auto-opens the SessionExpiration dialog with no notification (the dialog is
|
|
// the last-chance reminder; doubling up would be noise).
|
|
if isDeadlineRejected {
|
|
t.notify(
|
|
t.loc.T("notify.sessionDeadlineRejected.title"),
|
|
t.loc.T("notify.sessionDeadlineRejected.body"),
|
|
notifyIDSessionExpired,
|
|
)
|
|
return
|
|
}
|
|
|
|
if se.Metadata != nil && se.Metadata[authsession.MetaWarning] == "true" {
|
|
if se.Metadata[authsession.MetaFinal] == "true" {
|
|
t.openSessionExpiration()
|
|
return
|
|
}
|
|
t.notifySessionWarning(
|
|
t.loc.T("notify.sessionWarning.title"),
|
|
t.buildSessionWarningBody(se.Metadata),
|
|
)
|
|
return
|
|
}
|
|
|
|
body := se.UserMessage
|
|
if id := se.Metadata["id"]; id != "" {
|
|
body += fmt.Sprintf(" ID: %s", id)
|
|
}
|
|
t.notify(eventTitle(se), body, notifyIDEvent+se.ID)
|
|
}
|
|
|
|
// eventTitle composes a notification title, e.g. "Critical: DNS", "Warning: Authentication".
|
|
func eventTitle(e services.SystemEvent) string {
|
|
prefix := titleCase(e.Severity)
|
|
if prefix == "" {
|
|
prefix = "Info"
|
|
}
|
|
category := titleCase(e.Category)
|
|
if category == "" {
|
|
category = "System"
|
|
}
|
|
return prefix + ": " + category
|
|
}
|
|
|
|
func titleCase(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
|
}
|
|
|
|
// shouldSkipSystemEvent reports whether a daemon SystemEvent must not surface as
|
|
// a tray notification:
|
|
// - update-available announcements (trayUpdater emits its own)
|
|
// - install-progress signals (consumed by the install-progress window)
|
|
// - the ::/0 partner of an exit-node default route (0.0.0.0/0 already toasted)
|
|
func shouldSkipSystemEvent(se services.SystemEvent) bool {
|
|
// "policy_applied" carries a hardcoded English message; the localised toast
|
|
// fires on the paired config_changed (source=mdm) event instead.
|
|
if se.Metadata[proto.MetadataTypeKey] == proto.MetadataTypePolicyApplied {
|
|
return true
|
|
}
|
|
if _, isUpdate := se.Metadata["new_version_available"]; isUpdate {
|
|
return true
|
|
}
|
|
if _, isProgress := se.Metadata["progress_window"]; isProgress {
|
|
return true
|
|
}
|
|
if se.Category == "network" && se.Metadata["network"] == "::/0" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|