mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 07:16:38 +00:00
[client] Add netbird ui improvements (#3222)
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -148,22 +148,24 @@ type serviceClient struct {
|
||||
icError []byte
|
||||
|
||||
// systray menu items
|
||||
mStatus *systray.MenuItem
|
||||
mUp *systray.MenuItem
|
||||
mDown *systray.MenuItem
|
||||
mAdminPanel *systray.MenuItem
|
||||
mSettings *systray.MenuItem
|
||||
mAbout *systray.MenuItem
|
||||
mVersionUI *systray.MenuItem
|
||||
mVersionDaemon *systray.MenuItem
|
||||
mUpdate *systray.MenuItem
|
||||
mQuit *systray.MenuItem
|
||||
mRoutes *systray.MenuItem
|
||||
mAllowSSH *systray.MenuItem
|
||||
mAutoConnect *systray.MenuItem
|
||||
mEnableRosenpass *systray.MenuItem
|
||||
mNotifications *systray.MenuItem
|
||||
mAdvancedSettings *systray.MenuItem
|
||||
mStatus *systray.MenuItem
|
||||
mUp *systray.MenuItem
|
||||
mDown *systray.MenuItem
|
||||
mAdminPanel *systray.MenuItem
|
||||
mSettings *systray.MenuItem
|
||||
mAbout *systray.MenuItem
|
||||
mVersionUI *systray.MenuItem
|
||||
mVersionDaemon *systray.MenuItem
|
||||
mUpdate *systray.MenuItem
|
||||
mQuit *systray.MenuItem
|
||||
mNetworks *systray.MenuItem
|
||||
mAllowSSH *systray.MenuItem
|
||||
mAutoConnect *systray.MenuItem
|
||||
mEnableRosenpass *systray.MenuItem
|
||||
mNotifications *systray.MenuItem
|
||||
mAdvancedSettings *systray.MenuItem
|
||||
mCreateDebugBundle *systray.MenuItem
|
||||
mExitNode *systray.MenuItem
|
||||
|
||||
// application with main windows.
|
||||
app fyne.App
|
||||
@@ -200,6 +202,14 @@ type serviceClient struct {
|
||||
wRoutes fyne.Window
|
||||
|
||||
eventManager *event.Manager
|
||||
|
||||
exitNodeMu sync.Mutex
|
||||
mExitNodeItems []menuHandler
|
||||
}
|
||||
|
||||
type menuHandler struct {
|
||||
*systray.MenuItem
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// newServiceClient instance constructor
|
||||
@@ -473,6 +483,9 @@ func (s *serviceClient) updateStatus() error {
|
||||
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
|
||||
if err != nil {
|
||||
log.Errorf("get service status: %v", err)
|
||||
if s.connected {
|
||||
s.app.SendNotification(fyne.NewNotification("Error", "Connection to service lost"))
|
||||
}
|
||||
s.setDisconnectedStatus()
|
||||
return err
|
||||
}
|
||||
@@ -498,7 +511,8 @@ func (s *serviceClient) updateStatus() error {
|
||||
s.mStatus.SetTitle("Connected")
|
||||
s.mUp.Disable()
|
||||
s.mDown.Enable()
|
||||
s.mRoutes.Enable()
|
||||
s.mNetworks.Enable()
|
||||
go s.updateExitNodes()
|
||||
systrayIconState = true
|
||||
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
||||
s.setDisconnectedStatus()
|
||||
@@ -554,7 +568,9 @@ func (s *serviceClient) setDisconnectedStatus() {
|
||||
s.mStatus.SetTitle("Disconnected")
|
||||
s.mDown.Disable()
|
||||
s.mUp.Enable()
|
||||
s.mRoutes.Disable()
|
||||
s.mNetworks.Disable()
|
||||
s.mExitNode.Disable()
|
||||
go s.updateExitNodes()
|
||||
}
|
||||
|
||||
func (s *serviceClient) onTrayReady() {
|
||||
@@ -577,10 +593,16 @@ func (s *serviceClient) onTrayReady() {
|
||||
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.mCreateDebugBundle = s.mSettings.AddSubMenuItem("Create Debug Bundle", "Create and open debug information bundle")
|
||||
s.loadSettings()
|
||||
|
||||
s.mRoutes = systray.AddMenuItem("Networks", "Open the networks management window")
|
||||
s.mRoutes.Disable()
|
||||
s.exitNodeMu.Lock()
|
||||
s.mExitNode = systray.AddMenuItem("Exit Node", "Select exit node for routing traffic")
|
||||
s.mExitNode.Disable()
|
||||
s.exitNodeMu.Unlock()
|
||||
|
||||
s.mNetworks = systray.AddMenuItem("Networks", "Open the networks management window")
|
||||
s.mNetworks.Disable()
|
||||
systray.AddSeparator()
|
||||
|
||||
s.mAbout = systray.AddMenuItem("About", "About")
|
||||
@@ -599,6 +621,9 @@ func (s *serviceClient) onTrayReady() {
|
||||
systray.AddSeparator()
|
||||
s.mQuit = systray.AddMenuItem("Quit", "Quit the client app")
|
||||
|
||||
// update exit node menu in case service is already connected
|
||||
go s.updateExitNodes()
|
||||
|
||||
s.update.SetOnUpdateListener(s.onUpdateAvailable)
|
||||
go func() {
|
||||
s.getSrvConfig()
|
||||
@@ -614,6 +639,12 @@ func (s *serviceClient) onTrayReady() {
|
||||
|
||||
s.eventManager = event.NewManager(s.app, s.addr)
|
||||
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||
s.eventManager.AddHandler(func(event *proto.SystemEvent) {
|
||||
if event.Category == proto.SystemEvent_SYSTEM {
|
||||
s.updateExitNodes()
|
||||
}
|
||||
})
|
||||
|
||||
go s.eventManager.Start(s.ctx)
|
||||
|
||||
go func() {
|
||||
@@ -628,7 +659,7 @@ func (s *serviceClient) onTrayReady() {
|
||||
defer s.mUp.Enable()
|
||||
err := s.menuUpClick()
|
||||
if err != nil {
|
||||
s.runSelfCommand("error-msg", err.Error())
|
||||
s.app.SendNotification(fyne.NewNotification("Error", "Failed to connect to NetBird service"))
|
||||
return
|
||||
}
|
||||
}()
|
||||
@@ -638,7 +669,7 @@ func (s *serviceClient) onTrayReady() {
|
||||
defer s.mDown.Enable()
|
||||
err := s.menuDownClick()
|
||||
if err != nil {
|
||||
s.runSelfCommand("error-msg", err.Error())
|
||||
s.app.SendNotification(fyne.NewNotification("Error", "Failed to connect to NetBird service"))
|
||||
return
|
||||
}
|
||||
}()
|
||||
@@ -676,6 +707,13 @@ func (s *serviceClient) onTrayReady() {
|
||||
defer s.getSrvConfig()
|
||||
s.runSelfCommand("settings", "true")
|
||||
}()
|
||||
case <-s.mCreateDebugBundle.ClickedCh:
|
||||
go func() {
|
||||
if err := s.createAndOpenDebugBundle(); err != nil {
|
||||
log.Errorf("Failed to create debug bundle: %v", err)
|
||||
s.app.SendNotification(fyne.NewNotification("Error", "Failed to create debug bundle"))
|
||||
}
|
||||
}()
|
||||
case <-s.mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
return
|
||||
@@ -684,10 +722,10 @@ func (s *serviceClient) onTrayReady() {
|
||||
if err != nil {
|
||||
log.Errorf("%s", err)
|
||||
}
|
||||
case <-s.mRoutes.ClickedCh:
|
||||
s.mRoutes.Disable()
|
||||
case <-s.mNetworks.ClickedCh:
|
||||
s.mNetworks.Disable()
|
||||
go func() {
|
||||
defer s.mRoutes.Enable()
|
||||
defer s.mNetworks.Enable()
|
||||
s.runSelfCommand("networks", "true")
|
||||
}()
|
||||
case <-s.mNotifications.ClickedCh:
|
||||
@@ -718,7 +756,11 @@ func (s *serviceClient) runSelfCommand(command, arg string) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(proc, fmt.Sprintf("--%s=%s", command, arg))
|
||||
cmd := exec.Command(proc,
|
||||
fmt.Sprintf("--%s=%s", command, arg),
|
||||
fmt.Sprintf("--daemon-addr=%s", s.addr),
|
||||
)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||
log.Errorf("start %s UI: %v, %s", command, err, string(out))
|
||||
@@ -737,7 +779,11 @@ func normalizedVersion(version string) string {
|
||||
return versionString
|
||||
}
|
||||
|
||||
func (s *serviceClient) onTrayExit() {}
|
||||
func (s *serviceClient) onTrayExit() {
|
||||
for _, item := range s.mExitNodeItems {
|
||||
item.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// getSrvClient connection to the service.
|
||||
func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonServiceClient, error) {
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// ClientConfig basic settings for the UI application.
|
||||
type ClientConfig struct {
|
||||
configPath string
|
||||
logFile string
|
||||
daemonAddr string
|
||||
}
|
||||
|
||||
// Config object with default settings.
|
||||
//
|
||||
// We are creating this package to extract utility functions from the cmd package
|
||||
// reading and parsing the configurations for the client should be done here
|
||||
func Config() *ClientConfig {
|
||||
defaultConfigPath := "/etc/wiretrustee/config.json"
|
||||
defaultLogFile := "/var/log/wiretrustee/client.log"
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
|
||||
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log"
|
||||
}
|
||||
|
||||
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||
}
|
||||
return &ClientConfig{
|
||||
configPath: defaultConfigPath,
|
||||
logFile: defaultLogFile,
|
||||
daemonAddr: defaultDaemonAddr,
|
||||
}
|
||||
}
|
||||
|
||||
// DaemonAddr of the gRPC API.
|
||||
func (c *ClientConfig) DaemonAddr() string {
|
||||
return c.daemonAddr
|
||||
}
|
||||
|
||||
// LogFile path.
|
||||
func (c *ClientConfig) LogFile() string {
|
||||
return c.logFile
|
||||
}
|
||||
50
client/ui/debug.go
Normal file
50
client/ui/debug.go
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||
)
|
||||
|
||||
func (s *serviceClient) createAndOpenDebugBundle() error {
|
||||
conn, err := s.getSrvClient(failFastTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %v", err)
|
||||
}
|
||||
|
||||
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get status: %v", err)
|
||||
}
|
||||
|
||||
overview := nbstatus.ConvertToStatusOutputOverview(statusResp, true, "", nil, nil, nil)
|
||||
statusOutput := nbstatus.ParseToFullDetailSummary(overview)
|
||||
|
||||
resp, err := conn.DebugBundle(s.ctx, &proto.DebugBundleRequest{
|
||||
Anonymize: true,
|
||||
Status: statusOutput,
|
||||
SystemInfo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create debug bundle: %v", err)
|
||||
}
|
||||
|
||||
bundleDir := filepath.Dir(resp.GetPath())
|
||||
if err := open.Start(bundleDir); err != nil {
|
||||
return fmt.Errorf("failed to open debug bundle directory: %v", err)
|
||||
}
|
||||
|
||||
s.app.SendNotification(fyne.NewNotification(
|
||||
"Debug Bundle",
|
||||
fmt.Sprintf("Debug bundle created at %s. Administrator privileges are required to access it.", resp.GetPath()),
|
||||
))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package event
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -17,14 +18,17 @@ import (
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
)
|
||||
|
||||
type Handler func(*proto.SystemEvent)
|
||||
|
||||
type Manager struct {
|
||||
app fyne.App
|
||||
addr string
|
||||
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
enabled bool
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
enabled bool
|
||||
handlers []Handler
|
||||
}
|
||||
|
||||
func NewManager(app fyne.App, addr string) *Manager {
|
||||
@@ -100,20 +104,41 @@ func (e *Manager) SetNotificationsEnabled(enabled bool) {
|
||||
func (e *Manager) handleEvent(event *proto.SystemEvent) {
|
||||
e.mu.Lock()
|
||||
enabled := e.enabled
|
||||
handlers := slices.Clone(e.handlers)
|
||||
e.mu.Unlock()
|
||||
|
||||
if !enabled {
|
||||
// critical events are always shown
|
||||
if !enabled && event.Severity != proto.SystemEvent_CRITICAL {
|
||||
return
|
||||
}
|
||||
|
||||
title := e.getEventTitle(event)
|
||||
e.app.SendNotification(fyne.NewNotification(title, event.UserMessage))
|
||||
if event.UserMessage != "" {
|
||||
title := e.getEventTitle(event)
|
||||
body := event.UserMessage
|
||||
id := event.Metadata["id"]
|
||||
if id != "" {
|
||||
body += fmt.Sprintf(" ID: %s", id)
|
||||
}
|
||||
e.app.SendNotification(fyne.NewNotification(title, body))
|
||||
}
|
||||
|
||||
for _, handler := range handlers {
|
||||
go handler(event)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Manager) AddHandler(handler Handler) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.handlers = append(e.handlers, handler)
|
||||
}
|
||||
|
||||
func (e *Manager) getEventTitle(event *proto.SystemEvent) string {
|
||||
var prefix string
|
||||
switch event.Severity {
|
||||
case proto.SystemEvent_ERROR, proto.SystemEvent_CRITICAL:
|
||||
case proto.SystemEvent_CRITICAL:
|
||||
prefix = "Critical"
|
||||
case proto.SystemEvent_ERROR:
|
||||
prefix = "Error"
|
||||
case proto.SystemEvent_WARNING:
|
||||
prefix = "Warning"
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"fyne.io/systray"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
@@ -237,14 +240,14 @@ func (s *serviceClient) selectNetwork(id string, checked bool) {
|
||||
s.showError(fmt.Errorf("failed to select network: %v", err))
|
||||
return
|
||||
}
|
||||
log.Infof("Route %s selected", id)
|
||||
log.Infof("Network '%s' selected", id)
|
||||
} else {
|
||||
if _, err := conn.DeselectNetworks(s.ctx, req); err != nil {
|
||||
log.Errorf("failed to deselect network: %v", err)
|
||||
s.showError(fmt.Errorf("failed to deselect network: %v", err))
|
||||
return
|
||||
}
|
||||
log.Infof("Network %s deselected", id)
|
||||
log.Infof("Network '%s' deselected", id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +327,201 @@ func (s *serviceClient) updateNetworksBasedOnDisplayTab(tabs *container.AppTabs,
|
||||
s.updateNetworks(grid, f)
|
||||
}
|
||||
|
||||
func (s *serviceClient) updateExitNodes() {
|
||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||
if err != nil {
|
||||
log.Errorf("get client: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
exitNodes, err := s.getExitNodes(conn)
|
||||
if err != nil {
|
||||
log.Errorf("get exit nodes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.exitNodeMu.Lock()
|
||||
defer s.exitNodeMu.Unlock()
|
||||
|
||||
s.recreateExitNodeMenu(exitNodes)
|
||||
|
||||
if len(s.mExitNodeItems) > 0 {
|
||||
s.mExitNode.Enable()
|
||||
} else {
|
||||
s.mExitNode.Disable()
|
||||
}
|
||||
|
||||
log.Debugf("Exit nodes updated: %d", len(s.mExitNodeItems))
|
||||
}
|
||||
|
||||
func (s *serviceClient) recreateExitNodeMenu(exitNodes []*proto.Network) {
|
||||
for _, node := range s.mExitNodeItems {
|
||||
node.cancel()
|
||||
node.Remove()
|
||||
}
|
||||
s.mExitNodeItems = nil
|
||||
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" {
|
||||
s.mExitNode.Remove()
|
||||
s.mExitNode = systray.AddMenuItem("Exit Node", "Select exit node for routing traffic")
|
||||
}
|
||||
|
||||
for _, node := range exitNodes {
|
||||
menuItem := s.mExitNode.AddSubMenuItemCheckbox(
|
||||
node.ID,
|
||||
fmt.Sprintf("Use exit node %s", node.ID),
|
||||
node.Selected,
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s.mExitNodeItems = append(s.mExitNodeItems, menuHandler{
|
||||
MenuItem: menuItem,
|
||||
cancel: cancel,
|
||||
})
|
||||
go s.handleChecked(ctx, node.ID, menuItem)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *serviceClient) getExitNodes(conn proto.DaemonServiceClient) ([]*proto.Network, error) {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, defaultFailTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.ListNetworks(ctx, &proto.ListNetworksRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list networks: %v", err)
|
||||
}
|
||||
|
||||
var exitNodes []*proto.Network
|
||||
for _, network := range resp.Routes {
|
||||
if network.Range == "0.0.0.0/0" {
|
||||
exitNodes = append(exitNodes, network)
|
||||
}
|
||||
}
|
||||
return exitNodes, nil
|
||||
}
|
||||
|
||||
func (s *serviceClient) handleChecked(ctx context.Context, id string, item *systray.MenuItem) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case _, ok := <-item.ClickedCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := s.toggleExitNode(id, item); err != nil {
|
||||
log.Errorf("failed to toggle exit node: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add function to toggle exit node selection
|
||||
func (s *serviceClient) toggleExitNode(nodeID string, item *systray.MenuItem) error {
|
||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Toggling exit node '%s'", nodeID)
|
||||
|
||||
s.exitNodeMu.Lock()
|
||||
defer s.exitNodeMu.Unlock()
|
||||
|
||||
exitNodes, err := s.getExitNodes(conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get exit nodes: %v", err)
|
||||
}
|
||||
|
||||
var exitNode *proto.Network
|
||||
// find other selected nodes and ours
|
||||
ids := make([]string, 0, len(exitNodes))
|
||||
for _, node := range exitNodes {
|
||||
if node.ID == nodeID {
|
||||
// preserve original state
|
||||
cp := *node //nolint:govet
|
||||
exitNode = &cp
|
||||
|
||||
// set desired state for recreation
|
||||
node.Selected = true
|
||||
continue
|
||||
}
|
||||
if node.Selected {
|
||||
ids = append(ids, node.ID)
|
||||
|
||||
// set desired state for recreation
|
||||
node.Selected = false
|
||||
}
|
||||
}
|
||||
|
||||
if item.Checked() && len(ids) == 0 {
|
||||
// exit node is the only selected node, deselect it
|
||||
ids = append(ids, nodeID)
|
||||
exitNode = nil
|
||||
}
|
||||
|
||||
// deselect all other selected exit nodes
|
||||
if err := s.deselectOtherExitNodes(conn, ids, item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.selectNewExitNode(conn, exitNode, nodeID, item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// linux/bsd doesn't handle Check/Uncheck well, so we recreate the menu
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" {
|
||||
s.recreateExitNodeMenu(exitNodes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceClient) deselectOtherExitNodes(conn proto.DaemonServiceClient, ids []string, currentItem *systray.MenuItem) error {
|
||||
// deselect all other selected exit nodes
|
||||
if len(ids) > 0 {
|
||||
deselectReq := &proto.SelectNetworksRequest{
|
||||
NetworkIDs: ids,
|
||||
}
|
||||
if _, err := conn.DeselectNetworks(s.ctx, deselectReq); err != nil {
|
||||
return fmt.Errorf("deselect networks: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Deselected exit nodes: %v", ids)
|
||||
}
|
||||
|
||||
// uncheck all other exit node menu items
|
||||
for _, i := range s.mExitNodeItems {
|
||||
if i.MenuItem == currentItem {
|
||||
continue
|
||||
}
|
||||
i.Uncheck()
|
||||
log.Infof("Unchecked exit node %v", i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceClient) selectNewExitNode(conn proto.DaemonServiceClient, exitNode *proto.Network, nodeID string, item *systray.MenuItem) error {
|
||||
if exitNode != nil && !exitNode.Selected {
|
||||
selectReq := &proto.SelectNetworksRequest{
|
||||
NetworkIDs: []string{exitNode.ID},
|
||||
Append: true,
|
||||
}
|
||||
if _, err := conn.SelectNetworks(s.ctx, selectReq); err != nil {
|
||||
return fmt.Errorf("select network: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Selected exit node '%s'", nodeID)
|
||||
}
|
||||
|
||||
item.Check()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGridAndFilterFromTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) (*fyne.Container, filter) {
|
||||
switch tabs.Selected().Text {
|
||||
case overlappingNetworksText:
|
||||
|
||||
Reference in New Issue
Block a user