mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-23 00:41:29 +02:00
Selecting a profile from the header dropdown or the tray submenu now always brings the connection up after the switch, regardless of the previous daemon state. Switching from the manage-profiles screen (including profile creation) never connects, leaving a chance to adjust the management URL first. ## Describe your changes ## Issue ticket number and link ## Stack <!-- branch-stack --> ### Checklist - [ ] Is it a bug fix - [ ] Is a typo/documentation fix - [x] Is a feature enhancement - [ ] It is a refactor - [ ] Created tests that fail without the change (if possible) - [ ] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) ### Docs PR URL (required if "docs added" is checked) Paste the PR link from https://github.com/netbirdio/docs here: https://github.com/netbirdio/docs/pull/__ <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/netbirdio/codesmith/netbird/pr/6838"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with Codesmith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787157550&installation_model_id=427504&pr_number=6838&repository=netbirdio%2Fnetbird&return_to=https%3A%2F%2Fgithub.com%2Fnetbirdio%2Fnetbird%2Fpull%2F6838&signature=8fe9c5f0779df46c76b4135a373591762eb1498d890ff6996626fb75d6433e0a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with Codesmith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>/codesmith</code> with what you need. Autofix is disabled.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the ability to switch profiles without automatically establishing a connection. * Existing profile switching continues to connect when appropriate, while safely handling active or pending connections during the switch. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
106 lines
3.6 KiB
Go
106 lines
3.6 KiB
Go
//go:build !android && !ios && !freebsd && !js
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
|
)
|
|
|
|
// ProfileSwitcher holds the switch policy shared by the tray and React
|
|
// frontend so both flip profiles identically. SwitchActive (plain selection:
|
|
// header dropdown, tray submenu) always connects after the switch;
|
|
// SwitchActiveNoConnect (manage-profiles screen) never does, so the user can
|
|
// still adjust the management URL before connecting. prevStatus from
|
|
// DaemonFeed.Get at entry only decides the teardown:
|
|
//
|
|
// Connected/Connecting/NeedsLogin/LoginFailed/SessionExpired → Down first.
|
|
// Idle → no Down.
|
|
type ProfileSwitcher struct {
|
|
profiles *Profiles
|
|
connection *Connection
|
|
feed *DaemonFeed
|
|
}
|
|
|
|
func NewProfileSwitcher(profiles *Profiles, connection *Connection, feed *DaemonFeed) *ProfileSwitcher {
|
|
return &ProfileSwitcher{profiles: profiles, connection: connection, feed: feed}
|
|
}
|
|
|
|
// SwitchActive switches to the named profile and always connects afterwards.
|
|
func (s *ProfileSwitcher) SwitchActive(ctx context.Context, p ProfileRef) error {
|
|
return s.switchActive(ctx, p, true)
|
|
}
|
|
|
|
// SwitchActiveNoConnect switches to the named profile without connecting,
|
|
// tearing down any existing connection first.
|
|
func (s *ProfileSwitcher) SwitchActiveNoConnect(ctx context.Context, p ProfileRef) error {
|
|
return s.switchActive(ctx, p, false)
|
|
}
|
|
|
|
func (s *ProfileSwitcher) switchActive(ctx context.Context, p ProfileRef, connect bool) error {
|
|
prevStatus := ""
|
|
if s.feed != nil {
|
|
if st, err := s.feed.Get(ctx); err == nil {
|
|
prevStatus = st.Status
|
|
} else {
|
|
log.Warnf("profileswitcher: get status: %v", err)
|
|
}
|
|
}
|
|
|
|
needsDown := strings.EqualFold(prevStatus, StatusConnected) ||
|
|
strings.EqualFold(prevStatus, StatusConnecting) ||
|
|
strings.EqualFold(prevStatus, StatusNeedsLogin) ||
|
|
strings.EqualFold(prevStatus, StatusLoginFailed) ||
|
|
strings.EqualFold(prevStatus, StatusSessionExpired)
|
|
|
|
log.Infof("profileswitcher: switch profile=%q prevStatus=%q connect=%v needsDown=%v",
|
|
p.ProfileName, prevStatus, connect, needsDown)
|
|
|
|
// Optimistic Connecting paint plus stale-push suppression during Down (see
|
|
// DaemonFeed suppression table); also arms the login-watch that pops
|
|
// browser-login when the new profile turns out to need SSO.
|
|
if connect && s.feed != nil {
|
|
s.feed.BeginProfileSwitch()
|
|
}
|
|
|
|
resolvedID, err := s.profiles.Switch(ctx, p)
|
|
if err != nil {
|
|
return fmt.Errorf("switch profile %q: %w", p.ProfileName, err)
|
|
}
|
|
|
|
// Mirror into the user-side ProfileManager state: the CLI's `netbird up`
|
|
// reads this file and sends the ID back in the Up RPC, so if it diverges
|
|
// the daemon reverts the UI switch on the next CLI `up`. Best-effort — the
|
|
// daemon is authoritative; a failure only leaves the CLI's view stale.
|
|
// Use the daemon-resolved ID rather than the handle we sent, since the
|
|
// on-disk state is keyed by ID, not display name.
|
|
if err := profilemanager.NewProfileManager().SwitchProfile(profilemanager.ID(resolvedID)); err != nil {
|
|
log.Warnf("profileswitcher: mirror to user-side ProfileManager failed: %v", err)
|
|
}
|
|
|
|
if needsDown {
|
|
if err := s.connection.Down(ctx); err != nil {
|
|
log.Errorf("profileswitcher: Down: %v", err)
|
|
}
|
|
}
|
|
|
|
if connect {
|
|
if err := s.connection.Up(ctx, UpParams(p)); err != nil {
|
|
return fmt.Errorf("connect %q: %w", p.ProfileName, err)
|
|
}
|
|
}
|
|
|
|
// The daemon emits no profile event, so fan out ourselves or the React
|
|
// ProfileContext stays on the old profile after a tray-initiated switch.
|
|
if s.feed != nil && s.feed.emitter != nil {
|
|
s.feed.emitter.Emit(EventProfileChanged, p)
|
|
}
|
|
|
|
return nil
|
|
}
|