mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-01 19:19:07 +02:00
[client] Add UI client event notifications (#3207)
This commit is contained in:
+47
-1
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/client/ui/event"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
@@ -161,6 +162,7 @@ type serviceClient struct {
|
||||
mAllowSSH *systray.MenuItem
|
||||
mAutoConnect *systray.MenuItem
|
||||
mEnableRosenpass *systray.MenuItem
|
||||
mNotifications *systray.MenuItem
|
||||
mAdvancedSettings *systray.MenuItem
|
||||
|
||||
// application with main windows.
|
||||
@@ -196,6 +198,8 @@ type serviceClient struct {
|
||||
isUpdateIconActive bool
|
||||
showRoutes bool
|
||||
wRoutes fyne.Window
|
||||
|
||||
eventManager *event.Manager
|
||||
}
|
||||
|
||||
// newServiceClient instance constructor
|
||||
@@ -429,6 +433,7 @@ func (s *serviceClient) menuUpClick() error {
|
||||
log.Errorf("up service: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -570,6 +575,7 @@ func (s *serviceClient) onTrayReady() {
|
||||
s.mAllowSSH = s.mSettings.AddSubMenuItemCheckbox("Allow SSH", "Allow SSH connections", false)
|
||||
s.mAutoConnect = s.mSettings.AddSubMenuItemCheckbox("Connect on Startup", "Connect automatically when the service starts", false)
|
||||
s.mEnableRosenpass = s.mSettings.AddSubMenuItemCheckbox("Enable Quantum-Resistance", "Enable post-quantum security via Rosenpass", false)
|
||||
s.mNotifications = s.mSettings.AddSubMenuItemCheckbox("Notifications", "Enable notifications", true)
|
||||
s.mAdvancedSettings = s.mSettings.AddSubMenuItem("Advanced Settings", "Advanced settings of the application")
|
||||
s.loadSettings()
|
||||
|
||||
@@ -606,6 +612,10 @@ func (s *serviceClient) onTrayReady() {
|
||||
}
|
||||
}()
|
||||
|
||||
s.eventManager = event.NewManager(s.app, s.addr)
|
||||
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||
go s.eventManager.Start(s.ctx)
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
for {
|
||||
@@ -680,7 +690,20 @@ func (s *serviceClient) onTrayReady() {
|
||||
defer s.mRoutes.Enable()
|
||||
s.runSelfCommand("networks", "true")
|
||||
}()
|
||||
case <-s.mNotifications.ClickedCh:
|
||||
if s.mNotifications.Checked() {
|
||||
s.mNotifications.Uncheck()
|
||||
} else {
|
||||
s.mNotifications.Check()
|
||||
}
|
||||
if s.eventManager != nil {
|
||||
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||
}
|
||||
if err := s.updateConfig(); err != nil {
|
||||
log.Errorf("failed to update config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("process connection: %v", err)
|
||||
}
|
||||
@@ -780,8 +803,20 @@ func (s *serviceClient) getSrvConfig() {
|
||||
if !cfg.RosenpassEnabled {
|
||||
s.sRosenpassPermissive.Disable()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if s.mNotifications == nil {
|
||||
return
|
||||
}
|
||||
if cfg.DisableNotifications {
|
||||
s.mNotifications.Uncheck()
|
||||
} else {
|
||||
s.mNotifications.Check()
|
||||
}
|
||||
if s.eventManager != nil {
|
||||
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *serviceClient) onUpdateAvailable() {
|
||||
@@ -846,6 +881,15 @@ func (s *serviceClient) loadSettings() {
|
||||
} else {
|
||||
s.mEnableRosenpass.Uncheck()
|
||||
}
|
||||
|
||||
if cfg.DisableNotifications {
|
||||
s.mNotifications.Uncheck()
|
||||
} else {
|
||||
s.mNotifications.Check()
|
||||
}
|
||||
if s.eventManager != nil {
|
||||
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||
}
|
||||
}
|
||||
|
||||
// updateConfig updates the configuration parameters
|
||||
@@ -854,12 +898,14 @@ func (s *serviceClient) updateConfig() error {
|
||||
disableAutoStart := !s.mAutoConnect.Checked()
|
||||
sshAllowed := s.mAllowSSH.Checked()
|
||||
rosenpassEnabled := s.mEnableRosenpass.Checked()
|
||||
notificationsDisabled := !s.mNotifications.Checked()
|
||||
|
||||
loginRequest := proto.LoginRequest{
|
||||
IsLinuxDesktopClient: runtime.GOOS == "linux",
|
||||
ServerSSHAllowed: &sshAllowed,
|
||||
RosenpassEnabled: &rosenpassEnabled,
|
||||
DisableAutoConnect: &disableAutoStart,
|
||||
DisableNotifications: ¬ificationsDisabled,
|
||||
}
|
||||
|
||||
if err := s.restartClient(&loginRequest); err != nil {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
app fyne.App
|
||||
addr string
|
||||
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func NewManager(app fyne.App, addr string) *Manager {
|
||||
return &Manager{
|
||||
app: app,
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Manager) Start(ctx context.Context) {
|
||||
e.mu.Lock()
|
||||
e.ctx, e.cancel = context.WithCancel(ctx)
|
||||
e.mu.Unlock()
|
||||
|
||||
expBackOff := backoff.WithContext(&backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 0,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}, ctx)
|
||||
|
||||
if err := backoff.Retry(e.streamEvents, expBackOff); err != nil {
|
||||
log.Errorf("event stream ended: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Manager) streamEvents() error {
|
||||
e.mu.Lock()
|
||||
ctx := e.ctx
|
||||
e.mu.Unlock()
|
||||
|
||||
client, err := getClient(e.addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create client: %w", err)
|
||||
}
|
||||
|
||||
stream, err := client.SubscribeEvents(ctx, &proto.SubscribeRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe to events: %w", err)
|
||||
}
|
||||
|
||||
log.Info("subscribed to daemon events")
|
||||
defer func() {
|
||||
log.Info("unsubscribed from daemon events")
|
||||
}()
|
||||
|
||||
for {
|
||||
event, err := stream.Recv()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error receiving event: %w", err)
|
||||
}
|
||||
e.handleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Manager) Stop() {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if e.cancel != nil {
|
||||
e.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Manager) SetNotificationsEnabled(enabled bool) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.enabled = enabled
|
||||
}
|
||||
|
||||
func (e *Manager) handleEvent(event *proto.SystemEvent) {
|
||||
e.mu.Lock()
|
||||
enabled := e.enabled
|
||||
e.mu.Unlock()
|
||||
|
||||
if !enabled {
|
||||
return
|
||||
}
|
||||
|
||||
title := e.getEventTitle(event)
|
||||
e.app.SendNotification(fyne.NewNotification(title, event.UserMessage))
|
||||
}
|
||||
|
||||
func (e *Manager) getEventTitle(event *proto.SystemEvent) string {
|
||||
var prefix string
|
||||
switch event.Severity {
|
||||
case proto.SystemEvent_ERROR, proto.SystemEvent_CRITICAL:
|
||||
prefix = "Error"
|
||||
case proto.SystemEvent_WARNING:
|
||||
prefix = "Warning"
|
||||
default:
|
||||
prefix = "Info"
|
||||
}
|
||||
|
||||
var category string
|
||||
switch event.Category {
|
||||
case proto.SystemEvent_DNS:
|
||||
category = "DNS"
|
||||
case proto.SystemEvent_NETWORK:
|
||||
category = "Network"
|
||||
case proto.SystemEvent_AUTHENTICATION:
|
||||
category = "Authentication"
|
||||
case proto.SystemEvent_CONNECTIVITY:
|
||||
category = "Connectivity"
|
||||
default:
|
||||
category = "System"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: %s", prefix, category)
|
||||
}
|
||||
|
||||
func getClient(addr string) (proto.DaemonServiceClient, error) {
|
||||
conn, err := grpc.NewClient(
|
||||
strings.TrimPrefix(addr, "tcp://"),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithUserAgent(system.GetDesktopUIUserAgent()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto.NewDaemonServiceClient(conn), nil
|
||||
}
|
||||
Reference in New Issue
Block a user