mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-16 13:49:58 +00:00
Merge branch 'refs/heads/ui-refactor' into ui-refactor-ui
# Conflicts: # client/ui/frontend/src/screens/Profiles.tsx # client/ui/main.go
This commit is contained in:
@@ -147,7 +147,8 @@ func (s *Connection) Up(ctx context.Context, p UpParams) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := &proto.UpRequest{}
|
||||
// The UI always uses async mode: status updates flow via SubscribeStatus.
|
||||
req := &proto.UpRequest{Async: true}
|
||||
if p.ProfileName != "" {
|
||||
req.ProfileName = ptrStr(p.ProfileName)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,15 @@ const (
|
||||
// permission, etc.). Real daemon statuses come straight from
|
||||
// internal.Status* — none of those collide with this label.
|
||||
StatusDaemonUnavailable = "DaemonUnavailable"
|
||||
|
||||
// Daemon connection status strings — mirror internal.Status* in
|
||||
// client/internal/state.go.
|
||||
StatusConnected = "Connected"
|
||||
StatusConnecting = "Connecting"
|
||||
StatusIdle = "Idle"
|
||||
StatusNeedsLogin = "NeedsLogin"
|
||||
StatusLoginFailed = "LoginFailed"
|
||||
StatusSessionExpired = "SessionExpired"
|
||||
)
|
||||
|
||||
// Emitter is what peers.Watch needs from the host application: a simple
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"os/user"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
@@ -13,6 +14,15 @@ import (
|
||||
type Profile struct {
|
||||
Name string `json:"name"`
|
||||
IsActive bool `json:"isActive"`
|
||||
// Email is the account address associated with this profile, sourced from
|
||||
// the per-profile state file written by the CLI after a successful SSO
|
||||
// login (e.g. ~/Library/Application Support/netbird/default.state.json on
|
||||
// macOS). The daemon always runs as root, so its getConfigDir() resolves to
|
||||
// the root home directory and cannot reach the user-owned state file. The
|
||||
// UI process runs as the logged-in user and can read it directly via
|
||||
// profilemanager.ProfileManager, which is why the email is fetched here
|
||||
// instead of being returned by the ListProfiles RPC.
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// ProfileRef identifies a profile by name+username.
|
||||
@@ -55,9 +65,14 @@ func (s *Profiles) List(ctx context.Context, username string) ([]Profile, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pm := profilemanager.NewProfileManager()
|
||||
out := make([]Profile, 0, len(resp.GetProfiles()))
|
||||
for _, p := range resp.GetProfiles() {
|
||||
out = append(out, Profile{Name: p.GetName(), IsActive: p.GetIsActive()})
|
||||
prof := Profile{Name: p.GetName(), IsActive: p.GetIsActive()}
|
||||
if state, err := pm.GetProfileState(p.GetName()); err == nil {
|
||||
prof.Email = state.Email
|
||||
}
|
||||
out = append(out, prof)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
78
client/ui/services/profileswitcher.go
Normal file
78
client/ui/services/profileswitcher.go
Normal file
@@ -0,0 +1,78 @@
|
||||
//go:build !android && !ios && !freebsd && !js
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ProfileSwitcher encapsulates the full profile-switching reconnect policy so
|
||||
// both the tray and the React frontend use identical logic.
|
||||
//
|
||||
// Reconnect policy:
|
||||
//
|
||||
// ┌─────────────────┬──────────────────────┬────────────────────────────────────┐
|
||||
// │ Previous status │ Action │ Rationale │
|
||||
// ├─────────────────┼──────────────────────┼────────────────────────────────────┤
|
||||
// │ Connected │ Switch + Down + Up │ Reconnect with the new profile. │
|
||||
// │ Connecting │ Switch + Down + Up │ Stop old retry loop, restart. │
|
||||
// │ NeedsLogin │ Switch + Down │ Clear stale error; user logs in. │
|
||||
// │ LoginFailed │ Switch + Down │ Clear stale error; user logs in. │
|
||||
// │ SessionExpired │ Switch + Down │ Clear stale error; user logs in. │
|
||||
// │ Idle │ Switch only │ User chose offline; don't connect. │
|
||||
// └─────────────────┴──────────────────────┴────────────────────────────────────┘
|
||||
type ProfileSwitcher struct {
|
||||
profiles *Profiles
|
||||
connection *Connection
|
||||
peers *Peers
|
||||
}
|
||||
|
||||
// NewProfileSwitcher creates a ProfileSwitcher backed by the given services.
|
||||
func NewProfileSwitcher(profiles *Profiles, connection *Connection, peers *Peers) *ProfileSwitcher {
|
||||
return &ProfileSwitcher{profiles: profiles, connection: connection, peers: peers}
|
||||
}
|
||||
|
||||
// SwitchActive switches to the named profile applying the reconnect policy.
|
||||
// All RPCs complete quickly: Up uses async mode so the daemon starts the
|
||||
// connection attempt and returns immediately; status updates flow via the
|
||||
// SubscribeStatus stream.
|
||||
func (s *ProfileSwitcher) SwitchActive(ctx context.Context, p ProfileRef) error {
|
||||
prevStatus := ""
|
||||
if st, err := s.peers.Get(ctx); err == nil {
|
||||
prevStatus = st.Status
|
||||
} else {
|
||||
log.Warnf("profileswitcher: get status: %v", err)
|
||||
}
|
||||
|
||||
wasActive := strings.EqualFold(prevStatus, StatusConnected) ||
|
||||
strings.EqualFold(prevStatus, StatusConnecting)
|
||||
needsDown := wasActive ||
|
||||
strings.EqualFold(prevStatus, StatusNeedsLogin) ||
|
||||
strings.EqualFold(prevStatus, StatusLoginFailed) ||
|
||||
strings.EqualFold(prevStatus, StatusSessionExpired)
|
||||
|
||||
log.Infof("profileswitcher: switch profile=%q prevStatus=%q wasActive=%v needsDown=%v",
|
||||
p.ProfileName, prevStatus, wasActive, needsDown)
|
||||
|
||||
if err := s.profiles.Switch(ctx, p); err != nil {
|
||||
return fmt.Errorf("switch profile %q: %w", p.ProfileName, err)
|
||||
}
|
||||
|
||||
if needsDown {
|
||||
if err := s.connection.Down(ctx); err != nil {
|
||||
log.Errorf("profileswitcher: Down: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if wasActive {
|
||||
if err := s.connection.Up(ctx, UpParams(p)); err != nil {
|
||||
return fmt.Errorf("reconnect %q: %w", p.ProfileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user