mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-22 22:59:09 +02:00
Wails UI
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// ConnectionService exposes connect/disconnect/status operations to the Wails frontend.
|
||||
type ConnectionService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewConnectionService creates a new ConnectionService.
|
||||
func NewConnectionService(g GRPCClientIface) *ConnectionService {
|
||||
return &ConnectionService{grpcClient: g}
|
||||
}
|
||||
|
||||
// GetStatus returns the current daemon status.
|
||||
func (s *ConnectionService) GetStatus() (*StatusInfo, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
log.Debugf("GetStatus: failed to get gRPC client: %v", err)
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||
if err != nil {
|
||||
log.Warnf("GetStatus: status RPC failed: %v", err)
|
||||
return nil, fmt.Errorf("status rpc: %w", err)
|
||||
}
|
||||
|
||||
log.Debugf("GetStatus: daemon responded status=%q daemonVersion=%q fullStatus=%v",
|
||||
resp.Status, resp.DaemonVersion, resp.FullStatus != nil)
|
||||
|
||||
info := &StatusInfo{
|
||||
Status: resp.Status,
|
||||
}
|
||||
|
||||
if resp.FullStatus != nil && resp.FullStatus.LocalPeerState != nil {
|
||||
lp := resp.FullStatus.LocalPeerState
|
||||
info.IP = lp.GetIP()
|
||||
info.PublicKey = lp.GetPubKey()
|
||||
info.Fqdn = lp.GetFqdn()
|
||||
log.Debugf("GetStatus: localPeer ip=%q fqdn=%q pubKey=%q", info.IP, info.Fqdn, info.PublicKey)
|
||||
} else if resp.FullStatus == nil {
|
||||
log.Warnf("GetStatus: fullStatus is nil — daemon may not support full status or request flag was not set")
|
||||
} else {
|
||||
log.Debugf("GetStatus: fullStatus present but LocalPeerState is nil")
|
||||
}
|
||||
|
||||
if resp.FullStatus != nil {
|
||||
info.ConnectedPeers = len(resp.FullStatus.GetPeers())
|
||||
log.Debugf("GetStatus: connectedPeers=%d", info.ConnectedPeers)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Connect sends an Up request to the daemon.
|
||||
func (s *ConnectionService) Connect() error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := conn.Up(ctx, &proto.UpRequest{}); err != nil {
|
||||
log.Errorf("Up rpc failed: %v", err)
|
||||
return fmt.Errorf("connect: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect sends a Down request to the daemon.
|
||||
func (s *ConnectionService) Disconnect() error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := conn.Down(ctx, &proto.DownRequest{}); err != nil {
|
||||
log.Errorf("Down rpc failed: %v", err)
|
||||
return fmt.Errorf("disconnect: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatusInfo holds simplified status information for the frontend.
|
||||
type StatusInfo struct {
|
||||
Status string `json:"status"`
|
||||
IP string `json:"ip"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
Fqdn string `json:"fqdn"`
|
||||
ConnectedPeers int `json:"connectedPeers"`
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// DebugService exposes debug bundle creation and log-level control to the Wails frontend.
|
||||
type DebugService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewDebugService creates a new DebugService.
|
||||
func NewDebugService(g GRPCClientIface) *DebugService {
|
||||
return &DebugService{grpcClient: g}
|
||||
}
|
||||
|
||||
// DebugBundleParams holds the parameters for creating a debug bundle.
|
||||
type DebugBundleParams struct {
|
||||
Anonymize bool `json:"anonymize"`
|
||||
SystemInfo bool `json:"systemInfo"`
|
||||
Upload bool `json:"upload"`
|
||||
UploadURL string `json:"uploadUrl"`
|
||||
RunDurationMins int `json:"runDurationMins"`
|
||||
EnablePersistence bool `json:"enablePersistence"`
|
||||
}
|
||||
|
||||
// DebugBundleResult holds the result of creating a debug bundle.
|
||||
type DebugBundleResult struct {
|
||||
LocalPath string `json:"localPath"`
|
||||
UploadedKey string `json:"uploadedKey"`
|
||||
UploadFailureReason string `json:"uploadFailureReason"`
|
||||
}
|
||||
|
||||
// CreateDebugBundle creates a debug bundle via the daemon.
|
||||
func (s *DebugService) CreateDebugBundle(params DebugBundleParams) (*DebugBundleResult, error) {
|
||||
conn, err := s.grpcClient.GetClient(time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
if params.RunDurationMins > 0 {
|
||||
if err := s.configureForDebug(ctx, conn, params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req := &proto.DebugBundleRequest{
|
||||
Anonymize: params.Anonymize,
|
||||
SystemInfo: params.SystemInfo,
|
||||
}
|
||||
if params.Upload && params.UploadURL != "" {
|
||||
req.UploadURL = params.UploadURL
|
||||
}
|
||||
|
||||
resp, err := conn.DebugBundle(ctx, req)
|
||||
if err != nil {
|
||||
log.Errorf("DebugBundle rpc failed: %v", err)
|
||||
return nil, fmt.Errorf("create debug bundle: %w", err)
|
||||
}
|
||||
|
||||
return &DebugBundleResult{
|
||||
LocalPath: resp.GetPath(),
|
||||
UploadedKey: resp.GetUploadedKey(),
|
||||
UploadFailureReason: resp.GetUploadFailureReason(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *DebugService) configureForDebug(ctx context.Context, conn proto.DaemonServiceClient, params DebugBundleParams) error {
|
||||
statusResp, err := conn.Status(ctx, &proto.StatusRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get status: %w", err)
|
||||
}
|
||||
|
||||
wasConnected := statusResp.Status == "Connected" || statusResp.Status == "Connecting"
|
||||
|
||||
logLevelResp, err := conn.GetLogLevel(ctx, &proto.GetLogLevelRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get log level: %w", err)
|
||||
}
|
||||
originalLogLevel := logLevelResp.GetLevel()
|
||||
|
||||
// Set trace log level
|
||||
if _, err := conn.SetLogLevel(ctx, &proto.SetLogLevelRequest{Level: proto.LogLevel_TRACE}); err != nil {
|
||||
return fmt.Errorf("set log level: %w", err)
|
||||
}
|
||||
|
||||
// Bring service down then up to capture full connection logs
|
||||
if _, err := conn.Down(ctx, &proto.DownRequest{}); err != nil {
|
||||
log.Warnf("bring down for debug: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
if params.EnablePersistence {
|
||||
if _, err := conn.SetSyncResponsePersistence(ctx, &proto.SetSyncResponsePersistenceRequest{Enabled: true}); err != nil {
|
||||
log.Warnf("enable sync persistence: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := conn.Up(ctx, &proto.UpRequest{}); err != nil {
|
||||
return fmt.Errorf("bring service up: %w", err)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
if _, err := conn.StartCPUProfile(ctx, &proto.StartCPUProfileRequest{}); err != nil {
|
||||
log.Warnf("start CPU profiling: %v", err)
|
||||
}
|
||||
|
||||
// Wait for the collection duration
|
||||
collectionDur := time.Duration(params.RunDurationMins) * time.Minute
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(collectionDur):
|
||||
}
|
||||
|
||||
if _, err := conn.StopCPUProfile(ctx, &proto.StopCPUProfileRequest{}); err != nil {
|
||||
log.Warnf("stop CPU profiling: %v", err)
|
||||
}
|
||||
|
||||
// Restore original state
|
||||
if !wasConnected {
|
||||
if _, err := conn.Down(ctx, &proto.DownRequest{}); err != nil {
|
||||
log.Warnf("restore down state: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if originalLogLevel < proto.LogLevel_TRACE {
|
||||
if _, err := conn.SetLogLevel(ctx, &proto.SetLogLevelRequest{Level: originalLogLevel}); err != nil {
|
||||
log.Warnf("restore log level: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLogLevel returns the current daemon log level.
|
||||
func (s *DebugService) GetLogLevel() (string, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.GetLogLevel(ctx, &proto.GetLogLevelRequest{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get log level rpc: %w", err)
|
||||
}
|
||||
|
||||
return resp.GetLevel().String(), nil
|
||||
}
|
||||
|
||||
// SetLogLevel sets the daemon log level.
|
||||
func (s *DebugService) SetLogLevel(level string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var protoLevel proto.LogLevel
|
||||
switch level {
|
||||
case "TRACE":
|
||||
protoLevel = proto.LogLevel_TRACE
|
||||
case "DEBUG":
|
||||
protoLevel = proto.LogLevel_DEBUG
|
||||
case "INFO":
|
||||
protoLevel = proto.LogLevel_INFO
|
||||
case "WARN", "WARNING":
|
||||
protoLevel = proto.LogLevel_WARN
|
||||
case "ERROR":
|
||||
protoLevel = proto.LogLevel_ERROR
|
||||
default:
|
||||
protoLevel = proto.LogLevel_INFO
|
||||
}
|
||||
|
||||
if _, err := conn.SetLogLevel(ctx, &proto.SetLogLevelRequest{Level: protoLevel}); err != nil {
|
||||
return fmt.Errorf("set log level rpc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// GRPCClientIface is the interface services use to obtain a daemon client.
|
||||
type GRPCClientIface interface {
|
||||
GetClient(timeout time.Duration) (proto.DaemonServiceClient, error)
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// NetworkService exposes network/route management to the Wails frontend.
|
||||
type NetworkService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewNetworkService creates a new NetworkService.
|
||||
func NewNetworkService(g GRPCClientIface) *NetworkService {
|
||||
return &NetworkService{grpcClient: g}
|
||||
}
|
||||
|
||||
// NetworkInfo is a serializable view of a single network/route.
|
||||
type NetworkInfo struct {
|
||||
ID string `json:"id"`
|
||||
Range string `json:"range"`
|
||||
Domains []string `json:"domains"`
|
||||
Selected bool `json:"selected"`
|
||||
ResolvedIPs map[string][]string `json:"resolvedIPs"`
|
||||
}
|
||||
|
||||
// ListNetworks returns all networks from the daemon.
|
||||
func (s *NetworkService) ListNetworks() ([]NetworkInfo, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.ListNetworks(ctx, &proto.ListNetworksRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list networks rpc: %w", err)
|
||||
}
|
||||
|
||||
routes := make([]NetworkInfo, 0, len(resp.Routes))
|
||||
for _, r := range resp.Routes {
|
||||
info := NetworkInfo{
|
||||
ID: r.GetID(),
|
||||
Range: r.GetRange(),
|
||||
Domains: r.GetDomains(),
|
||||
Selected: r.GetSelected(),
|
||||
}
|
||||
if resolvedMap := r.GetResolvedIPs(); resolvedMap != nil {
|
||||
info.ResolvedIPs = make(map[string][]string)
|
||||
for domain, ipList := range resolvedMap {
|
||||
info.ResolvedIPs[domain] = ipList.GetIps()
|
||||
}
|
||||
}
|
||||
routes = append(routes, info)
|
||||
}
|
||||
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
return strings.ToLower(routes[i].ID) < strings.ToLower(routes[j].ID)
|
||||
})
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// ListOverlappingNetworks returns only networks with overlapping ranges.
|
||||
func (s *NetworkService) ListOverlappingNetworks() ([]NetworkInfo, error) {
|
||||
all, err := s.ListNetworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingRange := make(map[string][]NetworkInfo)
|
||||
for _, r := range all {
|
||||
if len(r.Domains) > 0 {
|
||||
continue
|
||||
}
|
||||
existingRange[r.Range] = append(existingRange[r.Range], r)
|
||||
}
|
||||
|
||||
var result []NetworkInfo
|
||||
for _, group := range existingRange {
|
||||
if len(group) > 1 {
|
||||
result = append(result, group...)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListExitNodes returns networks with range 0.0.0.0/0 (exit nodes).
|
||||
func (s *NetworkService) ListExitNodes() ([]NetworkInfo, error) {
|
||||
all, err := s.ListNetworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []NetworkInfo
|
||||
for _, r := range all {
|
||||
if r.Range == "0.0.0.0/0" {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SelectNetwork selects a single network by ID.
|
||||
func (s *NetworkService) SelectNetwork(id string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &proto.SelectNetworksRequest{
|
||||
NetworkIDs: []string{id},
|
||||
Append: true,
|
||||
}
|
||||
if _, err := conn.SelectNetworks(ctx, req); err != nil {
|
||||
log.Errorf("SelectNetworks rpc failed: %v", err)
|
||||
return fmt.Errorf("select network: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeselectNetwork deselects a single network by ID.
|
||||
func (s *NetworkService) DeselectNetwork(id string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &proto.SelectNetworksRequest{
|
||||
NetworkIDs: []string{id},
|
||||
}
|
||||
if _, err := conn.DeselectNetworks(ctx, req); err != nil {
|
||||
log.Errorf("DeselectNetworks rpc failed: %v", err)
|
||||
return fmt.Errorf("deselect network: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectAllNetworks selects all networks.
|
||||
func (s *NetworkService) SelectAllNetworks() error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &proto.SelectNetworksRequest{All: true}
|
||||
if _, err := conn.SelectNetworks(ctx, req); err != nil {
|
||||
return fmt.Errorf("select all networks: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeselectAllNetworks deselects all networks.
|
||||
func (s *NetworkService) DeselectAllNetworks() error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &proto.SelectNetworksRequest{All: true}
|
||||
if _, err := conn.DeselectNetworks(ctx, req); err != nil {
|
||||
return fmt.Errorf("deselect all networks: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectNetworks selects a list of networks by ID.
|
||||
func (s *NetworkService) SelectNetworks(ids []string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &proto.SelectNetworksRequest{NetworkIDs: ids, Append: true}
|
||||
if _, err := conn.SelectNetworks(ctx, req); err != nil {
|
||||
return fmt.Errorf("select networks: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeselectNetworks deselects a list of networks by ID.
|
||||
func (s *NetworkService) DeselectNetworks(ids []string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &proto.SelectNetworksRequest{NetworkIDs: ids}
|
||||
if _, err := conn.DeselectNetworks(ctx, req); err != nil {
|
||||
return fmt.Errorf("deselect networks: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// PeersService exposes peer listing operations to the Wails frontend.
|
||||
type PeersService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewPeersService creates a new PeersService.
|
||||
func NewPeersService(g GRPCClientIface) *PeersService {
|
||||
return &PeersService{grpcClient: g}
|
||||
}
|
||||
|
||||
// GetPeers returns the list of all peers with their status information.
|
||||
func (s *PeersService) GetPeers() ([]PeerInfo, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
log.Debugf("GetPeers: failed to get gRPC client: %v", err)
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||
if err != nil {
|
||||
log.Warnf("GetPeers: status RPC failed: %v", err)
|
||||
return nil, fmt.Errorf("status rpc: %w", err)
|
||||
}
|
||||
|
||||
if resp.FullStatus == nil {
|
||||
log.Debugf("GetPeers: fullStatus is nil")
|
||||
return []PeerInfo{}, nil
|
||||
}
|
||||
|
||||
peers := resp.FullStatus.GetPeers()
|
||||
log.Debugf("GetPeers: got %d peers from daemon", len(peers))
|
||||
|
||||
result := make([]PeerInfo, 0, len(peers))
|
||||
for _, p := range peers {
|
||||
info := PeerInfo{
|
||||
IP: p.GetIP(),
|
||||
PubKey: p.GetPubKey(),
|
||||
Fqdn: p.GetFqdn(),
|
||||
ConnStatus: p.GetConnStatus(),
|
||||
Relayed: p.GetRelayed(),
|
||||
RelayAddress: p.GetRelayAddress(),
|
||||
BytesRx: p.GetBytesRx(),
|
||||
BytesTx: p.GetBytesTx(),
|
||||
RosenpassEnabled: p.GetRosenpassEnabled(),
|
||||
Networks: p.GetNetworks(),
|
||||
LocalIceType: p.GetLocalIceCandidateType(),
|
||||
RemoteIceType: p.GetRemoteIceCandidateType(),
|
||||
LocalEndpoint: p.GetLocalIceCandidateEndpoint(),
|
||||
RemoteEndpoint: p.GetRemoteIceCandidateEndpoint(),
|
||||
}
|
||||
|
||||
if lat := p.GetLatency(); lat != nil {
|
||||
info.LatencyMs = float64(lat.Seconds)*1000 + float64(lat.Nanos)/1e6
|
||||
}
|
||||
|
||||
if ts := p.GetLastWireguardHandshake(); ts != nil {
|
||||
info.LastHandshake = ts.AsTime().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
if ts := p.GetConnStatusUpdate(); ts != nil {
|
||||
info.ConnStatusUpdate = ts.AsTime().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// PeerInfo holds simplified peer information for the frontend.
|
||||
type PeerInfo struct {
|
||||
IP string `json:"ip"`
|
||||
PubKey string `json:"pubKey"`
|
||||
Fqdn string `json:"fqdn"`
|
||||
ConnStatus string `json:"connStatus"`
|
||||
ConnStatusUpdate string `json:"connStatusUpdate"`
|
||||
Relayed bool `json:"relayed"`
|
||||
RelayAddress string `json:"relayAddress"`
|
||||
LatencyMs float64 `json:"latencyMs"`
|
||||
BytesRx int64 `json:"bytesRx"`
|
||||
BytesTx int64 `json:"bytesTx"`
|
||||
RosenpassEnabled bool `json:"rosenpassEnabled"`
|
||||
Networks []string `json:"networks"`
|
||||
LastHandshake string `json:"lastHandshake"`
|
||||
LocalIceType string `json:"localIceType"`
|
||||
RemoteIceType string `json:"remoteIceType"`
|
||||
LocalEndpoint string `json:"localEndpoint"`
|
||||
RemoteEndpoint string `json:"remoteEndpoint"`
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/user"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// ProfileService exposes profile management to the Wails frontend.
|
||||
type ProfileService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewProfileService creates a new ProfileService.
|
||||
func NewProfileService(g GRPCClientIface) *ProfileService {
|
||||
return &ProfileService{grpcClient: g}
|
||||
}
|
||||
|
||||
// ProfileInfo is a serializable view of a profile.
|
||||
type ProfileInfo struct {
|
||||
Name string `json:"name"`
|
||||
IsActive bool `json:"isActive"`
|
||||
}
|
||||
|
||||
// ActiveProfileInfo holds information about the currently active profile.
|
||||
type ActiveProfileInfo struct {
|
||||
ProfileName string `json:"profileName"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// ListProfiles returns all profiles for the current OS user.
|
||||
func (s *ProfileService) ListProfiles() ([]ProfileInfo, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
currUser, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get current user: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.ListProfiles(ctx, &proto.ListProfilesRequest{
|
||||
Username: currUser.Username,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list profiles rpc: %w", err)
|
||||
}
|
||||
|
||||
profiles := make([]ProfileInfo, 0, len(resp.Profiles))
|
||||
for _, p := range resp.Profiles {
|
||||
profiles = append(profiles, ProfileInfo{
|
||||
Name: p.Name,
|
||||
IsActive: p.IsActive,
|
||||
})
|
||||
}
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
// GetActiveProfile returns the currently active profile.
|
||||
func (s *ProfileService) GetActiveProfile() (*ActiveProfileInfo, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.GetActiveProfile(ctx, &proto.GetActiveProfileRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get active profile rpc: %w", err)
|
||||
}
|
||||
|
||||
return &ActiveProfileInfo{
|
||||
ProfileName: resp.ProfileName,
|
||||
Username: resp.Username,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SwitchProfile switches to the named profile.
|
||||
func (s *ProfileService) SwitchProfile(profileName string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
currUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current user: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := conn.SwitchProfile(ctx, &proto.SwitchProfileRequest{
|
||||
ProfileName: &profileName,
|
||||
Username: &currUser.Username,
|
||||
}); err != nil {
|
||||
log.Errorf("SwitchProfile rpc failed: %v", err)
|
||||
return fmt.Errorf("switch profile: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddProfile creates a new profile with the given name.
|
||||
func (s *ProfileService) AddProfile(profileName string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
currUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current user: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := conn.AddProfile(ctx, &proto.AddProfileRequest{
|
||||
ProfileName: profileName,
|
||||
Username: currUser.Username,
|
||||
}); err != nil {
|
||||
log.Errorf("AddProfile rpc failed: %v", err)
|
||||
return fmt.Errorf("add profile: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveProfile removes the named profile.
|
||||
func (s *ProfileService) RemoveProfile(profileName string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
currUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current user: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := conn.RemoveProfile(ctx, &proto.RemoveProfileRequest{
|
||||
ProfileName: profileName,
|
||||
Username: currUser.Username,
|
||||
}); err != nil {
|
||||
log.Errorf("RemoveProfile rpc failed: %v", err)
|
||||
return fmt.Errorf("remove profile: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout deregisters the named profile.
|
||||
func (s *ProfileService) Logout(profileName string) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
currUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current user: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
username := currUser.Username
|
||||
if _, err := conn.Logout(ctx, &proto.LogoutRequest{
|
||||
ProfileName: &profileName,
|
||||
Username: &username,
|
||||
}); err != nil {
|
||||
log.Errorf("Logout rpc failed: %v", err)
|
||||
return fmt.Errorf("logout: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// SettingsService exposes config get/set operations to the Wails frontend.
|
||||
type SettingsService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewSettingsService creates a new SettingsService.
|
||||
func NewSettingsService(g GRPCClientIface) *SettingsService {
|
||||
return &SettingsService{grpcClient: g}
|
||||
}
|
||||
|
||||
// ConfigInfo is a serializable view of the daemon configuration.
|
||||
type ConfigInfo struct {
|
||||
ManagementURL string `json:"managementUrl"`
|
||||
AdminURL string `json:"adminUrl"`
|
||||
PreSharedKey string `json:"preSharedKey"`
|
||||
InterfaceName string `json:"interfaceName"`
|
||||
WireguardPort int64 `json:"wireguardPort"`
|
||||
DisableAutoConnect bool `json:"disableAutoConnect"`
|
||||
ServerSSHAllowed bool `json:"serverSshAllowed"`
|
||||
RosenpassEnabled bool `json:"rosenpassEnabled"`
|
||||
RosenpassPermissive bool `json:"rosenpassPermissive"`
|
||||
LazyConnectionEnabled bool `json:"lazyConnectionEnabled"`
|
||||
BlockInbound bool `json:"blockInbound"`
|
||||
DisableNotifications bool `json:"disableNotifications"`
|
||||
}
|
||||
|
||||
// GetConfig retrieves the daemon configuration.
|
||||
func (s *SettingsService) GetConfig() (*ConfigInfo, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.GetConfig(ctx, &proto.GetConfigRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get config rpc: %w", err)
|
||||
}
|
||||
|
||||
cfg := &ConfigInfo{
|
||||
ManagementURL: resp.ManagementUrl,
|
||||
AdminURL: resp.AdminURL,
|
||||
PreSharedKey: resp.PreSharedKey,
|
||||
InterfaceName: resp.InterfaceName,
|
||||
WireguardPort: resp.WireguardPort,
|
||||
DisableAutoConnect: resp.DisableAutoConnect,
|
||||
ServerSSHAllowed: resp.ServerSSHAllowed,
|
||||
RosenpassEnabled: resp.RosenpassEnabled,
|
||||
LazyConnectionEnabled: resp.LazyConnectionEnabled,
|
||||
BlockInbound: resp.BlockInbound,
|
||||
DisableNotifications: resp.DisableNotifications,
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// SetConfig pushes configuration changes to the daemon.
|
||||
func (s *SettingsService) SetConfig(cfg ConfigInfo) error {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// The SetConfigRequest uses optional pointer fields for most settings.
|
||||
req := &proto.SetConfigRequest{
|
||||
ManagementUrl: cfg.ManagementURL,
|
||||
AdminURL: cfg.AdminURL,
|
||||
RosenpassEnabled: &cfg.RosenpassEnabled,
|
||||
InterfaceName: &cfg.InterfaceName,
|
||||
WireguardPort: &cfg.WireguardPort,
|
||||
OptionalPreSharedKey: &cfg.PreSharedKey,
|
||||
DisableAutoConnect: &cfg.DisableAutoConnect,
|
||||
ServerSSHAllowed: &cfg.ServerSSHAllowed,
|
||||
RosenpassPermissive: &cfg.RosenpassPermissive,
|
||||
DisableNotifications: &cfg.DisableNotifications,
|
||||
LazyConnectionEnabled: &cfg.LazyConnectionEnabled,
|
||||
BlockInbound: &cfg.BlockInbound,
|
||||
}
|
||||
|
||||
if _, err := conn.SetConfig(ctx, req); err != nil {
|
||||
log.Errorf("SetConfig rpc failed: %v", err)
|
||||
return fmt.Errorf("set config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToggleSSH toggles the SSH server allowed setting.
|
||||
func (s *SettingsService) ToggleSSH(enabled bool) error {
|
||||
cfg, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.ServerSSHAllowed = enabled
|
||||
return s.SetConfig(*cfg)
|
||||
}
|
||||
|
||||
// ToggleAutoConnect toggles the auto-connect setting.
|
||||
func (s *SettingsService) ToggleAutoConnect(enabled bool) error {
|
||||
cfg, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.DisableAutoConnect = !enabled
|
||||
return s.SetConfig(*cfg)
|
||||
}
|
||||
|
||||
// ToggleRosenpass toggles the Rosenpass quantum resistance setting.
|
||||
func (s *SettingsService) ToggleRosenpass(enabled bool) error {
|
||||
cfg, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.RosenpassEnabled = enabled
|
||||
return s.SetConfig(*cfg)
|
||||
}
|
||||
|
||||
// ToggleLazyConn toggles the lazy connections setting.
|
||||
func (s *SettingsService) ToggleLazyConn(enabled bool) error {
|
||||
cfg, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.LazyConnectionEnabled = enabled
|
||||
return s.SetConfig(*cfg)
|
||||
}
|
||||
|
||||
// ToggleBlockInbound toggles the block inbound setting.
|
||||
func (s *SettingsService) ToggleBlockInbound(enabled bool) error {
|
||||
cfg, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.BlockInbound = enabled
|
||||
return s.SetConfig(*cfg)
|
||||
}
|
||||
|
||||
// ToggleNotifications toggles the notifications setting.
|
||||
func (s *SettingsService) ToggleNotifications(enabled bool) error {
|
||||
cfg, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.DisableNotifications = !enabled
|
||||
return s.SetConfig(*cfg)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//go:build !(linux && 386)
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// UpdateService exposes update triggering and result polling to the Wails frontend.
|
||||
type UpdateService struct {
|
||||
grpcClient GRPCClientIface
|
||||
}
|
||||
|
||||
// NewUpdateService creates a new UpdateService.
|
||||
func NewUpdateService(g GRPCClientIface) *UpdateService {
|
||||
return &UpdateService{grpcClient: g}
|
||||
}
|
||||
|
||||
// InstallerResult holds the result of an installer run.
|
||||
type InstallerResult struct {
|
||||
Success bool `json:"success"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
// TriggerUpdate requests the daemon to perform an auto-update.
|
||||
func (s *UpdateService) TriggerUpdate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInstallerResult polls for the installer result (blocking until complete or timeout).
|
||||
func (s *UpdateService) GetInstallerResult() (*InstallerResult, error) {
|
||||
conn, err := s.grpcClient.GetClient(3 * time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
resp, err := conn.GetInstallerResult(ctx, &proto.InstallerResultRequest{})
|
||||
if err != nil {
|
||||
log.Infof("GetInstallerResult ended (daemon may have restarted): %v", err)
|
||||
return &InstallerResult{Success: true}, nil
|
||||
}
|
||||
|
||||
return &InstallerResult{
|
||||
Success: resp.Success,
|
||||
ErrorMsg: resp.ErrorMsg,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user