mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-20 15:49:55 +00:00
report daemon-down as DaemonUnavailable on initial Peers.Get and gate UI
- Peers.Get returns Status{Status: DaemonUnavailable} on Unavailable
instead of an error so the React useStatus initial refresh picks up
the same string the live event stream emits — the overlay no longer
depends on receiving the synthetic event during boot.
- ProfileContext.refresh swallows Unavailable so the redundant
"Load Profiles Failed" popup does not overlap the overlay.
- Tray Profiles submenu is disabled while the daemon is unavailable,
matching the existing settings/debug/connect gating.
- gRPC client uses a 5s ConnectParams MaxDelay; the default 120s cap
was keeping the SubChannel in backoff for tens of seconds after the
daemon came back, masking the recovery.
This commit is contained in:
@@ -56,9 +56,17 @@ export const ProfileProvider = ({ children }: { children: ReactNode }) => {
|
||||
setActiveProfile(active.profileName || "default");
|
||||
setProfiles(list);
|
||||
} catch (e) {
|
||||
// Daemon-down is already surfaced globally by
|
||||
// DaemonUnavailableOverlay; a second popup on top of it is
|
||||
// pure noise. Every profile RPC routes through the same gRPC
|
||||
// conn, so the Unavailable code is the reliable marker.
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
if (msg.includes("code = Unavailable")) {
|
||||
return;
|
||||
}
|
||||
await Dialogs.Error({
|
||||
Title: i18next.t("profile.error.loadTitle"),
|
||||
Message: e instanceof Error ? e.message : String(e),
|
||||
Message: msg,
|
||||
});
|
||||
} finally {
|
||||
setLoaded(true);
|
||||
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
@@ -39,6 +41,19 @@ func (c *Conn) Client() (proto.DaemonServiceClient, error) {
|
||||
strings.TrimPrefix(c.addr, "tcp://"),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithUserAgent(desktop.GetUIUserAgent()),
|
||||
// Without ConnectParams the SubChannel uses gRPC's default 120s
|
||||
// MaxDelay, so after a couple of failed dials the UI waits 30-60s
|
||||
// before noticing a freshly-started daemon. The Wails UI is a
|
||||
// desktop client expecting prompt reconnects, not a high-fanout
|
||||
// backend, so a 5s cap is a better trade-off than the default.
|
||||
grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
BaseDelay: 1 * time.Second,
|
||||
Multiplier: 1.6,
|
||||
Jitter: 0.2,
|
||||
MaxDelay: 5 * time.Second,
|
||||
},
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial daemon: %w", err)
|
||||
|
||||
@@ -253,14 +253,25 @@ func (s *Peers) ServiceShutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the current daemon status snapshot.
|
||||
// Get returns the current daemon status snapshot. When the daemon socket
|
||||
// is unreachable (process down, socket missing, permission denied) it
|
||||
// returns Status{Status: StatusDaemonUnavailable} instead of an error so
|
||||
// the frontend's initial useStatus().refresh() picks up the same string
|
||||
// the live event stream emits — the React overlay and per-screen gating
|
||||
// then key off a single status enum without a parallel "error" path.
|
||||
func (s *Peers) Get(ctx context.Context) (Status, error) {
|
||||
cli, err := s.conn.Client()
|
||||
if err != nil {
|
||||
if isDaemonUnreachable(err) {
|
||||
return Status{Status: StatusDaemonUnavailable}, nil
|
||||
}
|
||||
return Status{}, err
|
||||
}
|
||||
resp, err := cli.Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||
if err != nil {
|
||||
if isDaemonUnreachable(err) {
|
||||
return Status{Status: StatusDaemonUnavailable}, nil
|
||||
}
|
||||
return Status{}, err
|
||||
}
|
||||
return statusFromProto(resp), nil
|
||||
|
||||
@@ -214,6 +214,9 @@ func (t *Tray) reapplyMenuState() {
|
||||
if t.debugItem != nil {
|
||||
t.debugItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
if t.profileSubmenuItem != nil {
|
||||
t.profileSubmenuItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
if daemonVersion != "" && t.daemonVersionItem != nil {
|
||||
t.daemonVersionItem.SetLabel(t.loc.T("tray.menu.daemonVersion", "version", daemonVersion))
|
||||
}
|
||||
@@ -511,6 +514,9 @@ func (t *Tray) applyStatus(st services.Status) {
|
||||
if t.debugItem != nil {
|
||||
t.debugItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
if t.profileSubmenuItem != nil {
|
||||
t.profileSubmenuItem.SetEnabled(!daemonUnavailable)
|
||||
}
|
||||
// Refresh the Profiles submenu on every status-text transition: the
|
||||
// daemon does not emit an active-profile event, so the startup race
|
||||
// (UI loads profiles before autoconnect picks the persisted profile)
|
||||
|
||||
Reference in New Issue
Block a user