mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-01 12:31:42 +02:00
ui: add launch-at-login (autostart) toggle for the UI
Add an Autostart Wails service wrapping app.Autostart and a toggle in the General settings tab. The OS login-item registration is the single source of truth (nothing mirrored to the preferences file). Affects the graphical UI only, not the daemon. The toggle hides itself on platforms where autostart is unsupported.
This commit is contained in:
@@ -38,6 +38,7 @@ All services live in `services/` and assume a build tag `!android && !ios && !fr
|
||||
| `WindowManager` | `windowmanager.go` | `OpenSettings(tab)` / `OpenBrowserLogin(uri)` / `CloseBrowserLogin` / `OpenSessionExpired` / `OpenSessionAboutToExpire(seconds)` / `OpenInstallProgress(version)` / `CloseInstallProgress`. `OpenSettings("")` opens the General tab; pass a tab id (e.g. `"profiles"`) to deep-link, encoded as `?tab=…` in the start URL. `OpenInstallProgress` is `AlwaysOnTop` and hides every other visible window for the duration of the install (restored on close). Auxiliary windows are created on first open and **destroyed** on close (Wails-recommended singleton pattern; prevents the macOS dock-reopen from resurrecting hidden windows). |
|
||||
| `I18n` | `i18n.go` | Thin facade over `i18n.Bundle`. `Languages()` returns the shipped locales (`_index.json`); `Bundle(code)` returns the full key→text map for one language so the React layer can drive its own translation library. |
|
||||
| `Preferences` | `preferences.go` | Thin facade over `preferences.Store`. `Get()` returns `{language, viewMode}`; `SetLanguage(code)` validates against `i18n.Bundle.HasLanguage` and persists; `SetViewMode(mode)` validates against the known set (`default`/`advanced`) and persists. Both broadcast `netbird:preferences:changed`. `main.go` reads `viewMode` from the store to size the main window at startup. |
|
||||
| `Autostart` | `autostart.go` | Thin facade over Wails' `app.Autostart` (`*application.AutostartManager`). `Supported()` / `IsEnabled()` / `SetEnabled(bool)` — launch-the-UI-at-login toggle. The OS login-item registration (launchd/SMAppService on macOS, `HKCU\…\Run` on Windows, XDG `.desktop` on Linux) is the **single source of truth** — nothing is mirrored to the preferences file. `Enable` registers the running executable with no extra args (the app comes up hidden into the tray). Affects the **graphical UI only**, not the daemon/background service. `Supported()` is false on server/mobile builds (`ErrAutostartNotSupported`); the React toggle in `SettingsGeneral.tsx` hides itself when false. |
|
||||
|
||||
`DaemonConn` is defined in `services/conn.go`; `ptrStr` (string-to-*string helper for proto pointer fields) lives there too.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Dialogs } from "@wailsio/runtime";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import FancyToggleSwitch from "@/components/switches/FancyToggleSwitch";
|
||||
import { HelpText } from "@/components/typography/HelpText";
|
||||
@@ -10,12 +11,46 @@ import { useSettings } from "@/contexts/SettingsContext.tsx";
|
||||
import { ManagementServerSwitch } from "@/components/ManagementServerSwitch.tsx";
|
||||
import { ManagementMode, useManagementUrl } from "@/hooks/useManagementUrl.ts";
|
||||
import { LanguagePicker } from "@/components/LanguagePicker.tsx";
|
||||
import { Autostart } from "@bindings/services";
|
||||
import i18next from "@/lib/i18n";
|
||||
|
||||
export function SettingsGeneral() {
|
||||
const { t } = useTranslation();
|
||||
const { config, setField } = useSettings();
|
||||
const { mode, setMode, setUrl, displayUrl, showError, canSave, save } = useManagementUrl();
|
||||
|
||||
// Autostart lives in the OS login-item registry, not the daemon config, so
|
||||
// it has its own read-on-mount state. supported gates whether we render the
|
||||
// toggle at all (false on server/mobile builds).
|
||||
const [autostartSupported, setAutostartSupported] = useState(false);
|
||||
const [autostartEnabled, setAutostartEnabled] = useState(false);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
const supported = await Autostart.Supported();
|
||||
if (cancelled) return;
|
||||
setAutostartSupported(supported);
|
||||
if (!supported) return;
|
||||
setAutostartEnabled(await Autostart.IsEnabled());
|
||||
})().catch(() => {});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onAutostartChange = async (enabled: boolean) => {
|
||||
setAutostartEnabled(enabled);
|
||||
try {
|
||||
await Autostart.SetEnabled(enabled);
|
||||
} catch (e) {
|
||||
setAutostartEnabled(!enabled);
|
||||
await Dialogs.Error({
|
||||
Title: i18next.t("settings.general.autostart.errorTitle"),
|
||||
Message: String(e),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const prevMode = useRef(mode);
|
||||
useEffect(() => {
|
||||
@@ -44,6 +79,14 @@ export function SettingsGeneral() {
|
||||
label={t("settings.general.notifications.label")}
|
||||
helpText={t("settings.general.notifications.help")}
|
||||
/>
|
||||
{autostartSupported && (
|
||||
<FancyToggleSwitch
|
||||
value={autostartEnabled}
|
||||
onChange={onAutostartChange}
|
||||
label={t("settings.general.autostart.label")}
|
||||
helpText={t("settings.general.autostart.help")}
|
||||
/>
|
||||
)}
|
||||
</SectionGroup>
|
||||
|
||||
<SectionGroup title={t("settings.general.section.connection")}>
|
||||
|
||||
@@ -135,6 +135,9 @@
|
||||
"settings.general.connectOnStartup.help": "Beim Start des Dienstes automatisch eine Verbindung herstellen.",
|
||||
"settings.general.notifications.label": "Desktop-Benachrichtigungen",
|
||||
"settings.general.notifications.help": "Desktop-Benachrichtigungen für neue Updates und Verbindungsereignisse anzeigen.",
|
||||
"settings.general.autostart.label": "NetBird-UI beim Anmelden starten",
|
||||
"settings.general.autostart.help": "Die NetBird-Oberfläche beim Anmelden automatisch starten. Dies betrifft nur die grafische Oberfläche, nicht den Hintergrunddienst.",
|
||||
"settings.general.autostart.errorTitle": "Ändern des Autostarts fehlgeschlagen",
|
||||
"settings.general.language.label": "Anzeigesprache",
|
||||
"settings.general.language.help": "Wählen Sie die Sprache der NetBird-Oberfläche.",
|
||||
"settings.general.language.search": "Sprache suchen…",
|
||||
|
||||
@@ -157,6 +157,9 @@
|
||||
"settings.general.connectOnStartup.help": "Automatically establish a connection when the service starts.",
|
||||
"settings.general.notifications.label": "Desktop Notifications",
|
||||
"settings.general.notifications.help": "Show desktop notifications for new updates and connection events.",
|
||||
"settings.general.autostart.label": "Launch NetBird UI at Login",
|
||||
"settings.general.autostart.help": "Start the NetBird interface automatically when you log in. This affects the graphical interface only, not the background service.",
|
||||
"settings.general.autostart.errorTitle": "Autostart Change Failed",
|
||||
"settings.general.language.label": "Display Language",
|
||||
"settings.general.language.help": "Choose the language for the NetBird interface.",
|
||||
"settings.general.language.search": "Search language…",
|
||||
|
||||
@@ -135,6 +135,9 @@
|
||||
"settings.general.connectOnStartup.help": "A szolgáltatás indulásakor automatikusan kapcsolatot létesít.",
|
||||
"settings.general.notifications.label": "Asztali értesítések",
|
||||
"settings.general.notifications.help": "Asztali értesítések megjelenítése új frissítésekről és kapcsolati eseményekről.",
|
||||
"settings.general.autostart.label": "NetBird UI indítása bejelentkezéskor",
|
||||
"settings.general.autostart.help": "A NetBird felület automatikus indítása bejelentkezéskor. Ez csak a grafikus felületet érinti, a háttérszolgáltatást nem.",
|
||||
"settings.general.autostart.errorTitle": "Az automatikus indítás módosítása sikertelen",
|
||||
"settings.general.language.label": "Megjelenítési nyelv",
|
||||
"settings.general.language.help": "Válassza ki a NetBird felület nyelvét.",
|
||||
"settings.general.language.search": "Nyelv keresése…",
|
||||
|
||||
@@ -298,6 +298,7 @@ func registerServices(app *application.App, conn *Conn, s registeredServices) {
|
||||
app.RegisterService(application.NewService(s.profileSwitcher))
|
||||
app.RegisterService(application.NewService(services.NewI18n(s.bundle)))
|
||||
app.RegisterService(application.NewService(services.NewPreferences(s.prefStore)))
|
||||
app.RegisterService(application.NewService(services.NewAutostart(app.Autostart)))
|
||||
}
|
||||
|
||||
// newMainWindow creates the hidden main window, sized to the user's last view
|
||||
|
||||
62
client/ui/services/autostart.go
Normal file
62
client/ui/services/autostart.go
Normal file
@@ -0,0 +1,62 @@
|
||||
//go:build !android && !ios && !freebsd && !js
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// Autostart is the Wails-bound facade over Wails' AutostartManager. The OS
|
||||
// login-item registration (launchd/SMAppService on macOS, HKCU\…\Run on
|
||||
// Windows, an XDG .desktop on Linux) is the single source of truth — IsEnabled
|
||||
// reads it directly, so nothing is mirrored to the preferences file. Enable
|
||||
// registers the running executable to launch at login with no extra arguments;
|
||||
// the app comes up hidden into the tray, same as a normal launch.
|
||||
type Autostart struct {
|
||||
mgr *application.AutostartManager
|
||||
}
|
||||
|
||||
// NewAutostart wraps the application's AutostartManager (app.Autostart).
|
||||
func NewAutostart(mgr *application.AutostartManager) *Autostart {
|
||||
return &Autostart{mgr: mgr}
|
||||
}
|
||||
|
||||
// Supported reports whether autostart can be toggled on this platform. The
|
||||
// frontend hides the toggle entirely when this is false.
|
||||
func (a *Autostart) Supported(_ context.Context) bool {
|
||||
_, err := a.mgr.Status()
|
||||
return !errors.Is(err, application.ErrAutostartNotSupported)
|
||||
}
|
||||
|
||||
// IsEnabled reports whether the app is currently registered to launch at
|
||||
// login. On an unsupported platform it returns false without error so the
|
||||
// frontend can render the toggle off (gated by Supported).
|
||||
func (a *Autostart) IsEnabled(_ context.Context) (bool, error) {
|
||||
enabled, err := a.mgr.IsEnabled()
|
||||
if err != nil {
|
||||
if errors.Is(err, application.ErrAutostartNotSupported) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("read autostart state: %w", err)
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
// SetEnabled registers (enabled) or removes (disabled) the launch-at-login
|
||||
// entry. The change takes effect on the next login, not immediately.
|
||||
func (a *Autostart) SetEnabled(_ context.Context, enabled bool) error {
|
||||
if enabled {
|
||||
if err := a.mgr.Enable(); err != nil {
|
||||
return fmt.Errorf("enable autostart: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := a.mgr.Disable(); err != nil {
|
||||
return fmt.Errorf("disable autostart: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user