Files
netbird/client/ui/frontend/wails-go-api (1).md
2026-05-11 15:15:11 +02:00

9.7 KiB

Wails Go API surface for the React frontend

All bindings live under frontend/bindings/github.com/netbirdio/netbird/client/ui-wails/services/. Import them as:

import { Connection, Peers, Networks, Settings, Profiles, Debug, Update, Forwarding } from "./bindings/github.com/netbirdio/netbird/client/ui-wails/services";
import * as $models from "./bindings/github.com/netbirdio/netbird/client/ui-wails/services/models";

Every method returns $CancellablePromise<T> (Wails3 wrapper around a Promise — call .cancel() to abort the underlying gRPC stream / call).

Push events

Subscribe with the Wails event API: import { Events } from "@wailsio/runtime".

Event name Payload type Fires on
netbird:status Status Daemon connection-state change (Connected / Connecting / Disconnected / Idle), peer-list change, address change, management/signal flip. Replaces polling.
netbird:event SystemEvent One push per daemon-emitted event (DNS/network/auth/connectivity/system). Drives toasts and the event log.
netbird:update:available UpdateAvailable Daemon detected a new version. Show the update menu/banner.
netbird:update:progress UpdateProgress action:"show" → open the update progress page; action:"hide" → close.

Calling Peers.Watch() once at boot starts both backend stream loops; both self-restart with backoff on errors.

Connection lifecycle — Connection

Connection.Up(p: UpParams): Promise<void>
Connection.Down(): Promise<void>
Connection.Login(p: LoginParams): Promise<LoginResult>
Connection.WaitSSOLogin(p: WaitSSOParams): Promise<string>  // returns email/userInfo
Connection.Logout(p: LogoutParams): Promise<void>
  • Up flow: call Login first; if LoginResult.needsSsoLogin === true open verificationUriComplete in the browser, then call WaitSSOLogin with { userCode: LoginResult.userCode, hostname: ... }. Once that resolves call Up.
  • Down flow: just Down(). The daemon transitions to Idle.
class LoginParams { profileName, username, managementUrl, setupKey, preSharedKey, hostname, hint: string }
class LoginResult { needsSsoLogin: boolean; userCode, verificationUri, verificationUriComplete: string }
class WaitSSOParams { userCode, hostname: string }
class UpParams { profileName, username: string }
class LogoutParams { profileName, username: string }

Status / peer list — Peers

Peers.Get(): Promise<Status>     // one-shot snapshot
Peers.Watch(): Promise<void>     // call once at boot to enable push events
class Status {
  status: string                // "Idle" | "Connecting" | "Connected" | "SessionExpired" (see below)
  daemonVersion: string
  management: PeerLink
  signal: PeerLink
  local: LocalPeer
  peers: PeerStatus[]
  events: SystemEvent[]
}

class PeerLink {
  url: string
  connected: boolean
}

class LocalPeer {
  ip, pubKey, fqdn: string
  networks: string[]
}

class PeerStatus {
  ip, pubKey, fqdn: string
  connStatus: string                              // "Connected" | "Connecting" | "Idle"
  connStatusUpdateUnix: number                    // unix seconds
  relayed: boolean
  localIceCandidateType, remoteIceCandidateType: string
  localIceCandidateEndpoint, remoteIceCandidateEndpoint: string
  bytesRx, bytesTx: number
  latencyMs: number
  relayAddress: string                            // populated when relayed
  lastHandshakeUnix: number
  rosenpassEnabled: boolean
  networks: string[]
}

class SystemEvent {
  id: string
  severity: string     // "info" | "warning" | "error" | "critical"
  category: string     // "network" | "dns" | "authentication" | "connectivity" | "system"
  message: string      // technical / log message
  userMessage: string  // human-friendly message — render this
  timestamp: number    // unix seconds
  metadata: Record<string, string>
}

Connection-state values

The Status.status field uses these literal strings (from the daemon):

Value Meaning
"Idle" Disconnected — Up not invoked, or Down completed
"Connecting" Up in progress
"Connected" Tunnel up
"SessionExpired" SSO token expired — needs Login again

(The Fyne UI also reads a synthetic "Error" label for some failed states; check events for details.)

ICE candidate type values

localIceCandidateType / remoteIceCandidateType are pion/ICE strings: "host", "srflx", "prflx", "relay", or "" while connecting.

Networks — Networks

Networks.List(): Promise<Network[]>
Networks.Select(p: SelectNetworksParams): Promise<void>
Networks.Deselect(p: SelectNetworksParams): Promise<void>
class Network {
  id, range: string                             // range is a CIDR
  selected: boolean
  domains: string[]                             // empty unless this is a domain network
  resolvedIps: Record<string, string[]>         // domain -> IPs
}

class SelectNetworksParams {
  networkIds: string[]
  append: boolean   // false = replace selection, true = merge with existing
  all: boolean      // true = ignore networkIds and target every network (Select-All / Deselect-All)
}

The Fyne UI's All / Overlapping / Exit-node tabs are filters over the same List() result:

  • Exit-node: range === "0.0.0.0/0" || range === "::/0"
  • Overlapping: client-side detection of CIDR overlap among range values
  • All: everything

Forwarding / exposed services — Forwarding

Forwarding.List(): Promise<ForwardingRule[]>
class ForwardingRule {
  protocol: string                  // "tcp" | "udp"
  destinationPort: PortInfo
  translatedAddress, translatedHostname: string
  translatedPort: PortInfo
}

class PortInfo {                    // exactly one field is populated
  port?: number
  range?: PortRange
}

class PortRange { start, end: number }

Profiles — Profiles

Profiles.List(username: string): Promise<Profile[]>
Profiles.GetActive(): Promise<ActiveProfile>
Profiles.Switch(p: ProfileRef): Promise<void>
Profiles.Add(p: ProfileRef): Promise<void>
Profiles.Remove(p: ProfileRef): Promise<void>
Profiles.Username(): Promise<string>           // current OS username
class Profile { name: string; isActive: boolean }
class ProfileRef { profileName, username: string }
class ActiveProfile { profileName, username: string }

Settings / config — Settings

Settings.GetConfig(p: ConfigParams): Promise<Config>
Settings.SetConfig(p: SetConfigParams): Promise<void>
Settings.GetFeatures(): Promise<Features>
class ConfigParams { profileName, username: string }   // identifies which profile's config

class Config {
  managementUrl, adminUrl, configFile, logFile, preSharedKey: string
  interfaceName: string; wireguardPort, mtu: number
  disableAutoConnect, serverSshAllowed: boolean
  rosenpassEnabled, rosenpassPermissive: boolean
  disableNotifications, lazyConnectionEnabled, blockInbound: boolean
  networkMonitor, disableClientRoutes, disableServerRoutes: boolean
  disableDns, blockLanAccess: boolean
  enableSshRoot, enableSshSftp: boolean
  enableSshLocalPortForwarding, enableSshRemotePortForwarding: boolean
  disableSshAuth: boolean
  sshJwtCacheTtl: number
}

class SetConfigParams {
  // identity (always required)
  profileName, username: string
  // any field below is optional — only the ones you set are pushed to the daemon
  managementUrl?, adminUrl?, ...
  // ... same shape as Config
}

class Features {
  // feature flags from the daemon — hide UI sections when these are true
  disableProfiles, disableUpdateSettings, disableNetworks: boolean
}

SetConfig is partial — supply only the fields you want to change, plus profileName + username. Booleans use Go pointer-presence under the hood; on the TS side undefined / missing means "leave as-is".

Debug bundle / log level — Debug

Debug.GetLogLevel(): Promise<LogLevel>
Debug.SetLogLevel(lvl: LogLevel): Promise<void>
Debug.Bundle(p: DebugBundleParams): Promise<DebugBundleResult>
class LogLevel { level: string }   // "trace" | "debug" | "info" | "warning" | "error" | "panic"

class DebugBundleParams {
  anonymize: boolean
  systemInfo: boolean
  uploadUrl: string                   // empty string = no upload
  logFileCount: number                // 0 = default
}

class DebugBundleResult {
  path: string                        // local path of the generated bundle
  uploadedKey: string                 // populated when uploadUrl was set
  uploadFailureReason: string         // populated on upload error
}

Update flow — Update

Update.Trigger(): Promise<UpdateResult>            // start the install
Update.GetInstallerResult(): Promise<UpdateResult> // poll the install outcome (long-running)
class UpdateResult { success: boolean; errorMsg: string }

class UpdateAvailable {           // payload of "netbird:update:available"
  version: string
  enforced: boolean               // true = management server requires it
}

class UpdateProgress {            // payload of "netbird:update:progress"
  action: string                  // "show" | "hide"
  version: string
}

Typical flow:

  1. Listen for "netbird:update:available" → show the "Update X.Y.Z" affordance.
  2. User clicks → call Update.Trigger().
  3. The page that shows the install progress polls GetInstallerResult() (15-min timeout). On success: true the daemon will exit; the app should app.Quit() (or restart). On success: false show errorMsg.

Toast notifications

The tray sends OS notifications via application/services/notifications automatically for netbird:event events that have userMessage. The frontend doesn't need to do anything for that; the data is also delivered via netbird:event if you want to render an in-window log.