Merge branch 'main' into ui-refactor

Port IPv6 overlay support (#5631) into the Wails UI:
- Add DisableIPv6 config toggle to Settings (NetworkTab + services)
- Filter ::/0 alongside 0.0.0.0/0 as an exit-node route
- Suppress duplicate v6 default-route notifications in tray
This commit is contained in:
Zoltan Papp
2026-05-11 14:10:12 +02:00
286 changed files with 14639 additions and 3674 deletions

View File

@@ -58,6 +58,7 @@ export class Config {
"disableClientRoutes": boolean;
"disableServerRoutes": boolean;
"disableDns": boolean;
"disableIpv6": boolean;
"blockLanAccess": boolean;
"enableSshRoot": boolean;
"enableSshSftp": boolean;
@@ -125,6 +126,9 @@ export class Config {
if (!("disableDns" in $$source)) {
this["disableDns"] = false;
}
if (!("disableIpv6" in $$source)) {
this["disableIpv6"] = false;
}
if (!("blockLanAccess" in $$source)) {
this["blockLanAccess"] = false;
}
@@ -862,6 +866,7 @@ export class SetConfigParams {
"disableClientRoutes"?: boolean | null;
"disableServerRoutes"?: boolean | null;
"disableDns"?: boolean | null;
"disableIpv6"?: boolean | null;
"disableFirewall"?: boolean | null;
"blockLanAccess"?: boolean | null;
"enableSshRoot"?: boolean | null;

View File

@@ -54,7 +54,10 @@ export default function Networks() {
};
const overlapping = useMemo(() => filterOverlapping(routes), [routes]);
const exitNodes = useMemo(() => routes.filter((r) => r.range === "0.0.0.0/0"), [routes]);
const exitNodes = useMemo(
() => routes.filter((r) => r.range === "0.0.0.0/0" || r.range === "::/0"),
[routes],
);
return (
<div className="flex h-full flex-col p-6">

View File

@@ -68,6 +68,7 @@ export default function Settings() {
disableClientRoutes: cfg.disableClientRoutes,
disableServerRoutes: cfg.disableServerRoutes,
disableDns: cfg.disableDns,
disableIpv6: cfg.disableIpv6,
blockLanAccess: cfg.blockLanAccess,
enableSshRoot: cfg.enableSshRoot,
enableSshSftp: cfg.enableSshSftp,
@@ -182,6 +183,11 @@ function NetworkTab({ cfg, setField }: Ctx) {
onChange={(v) => setField("disableServerRoutes", v)}
label="Disable server routes"
/>
<Switch
checked={cfg.disableIpv6}
onChange={(v) => setField("disableIpv6", v)}
label="Disable IPv6 overlay addressing"
/>
<Switch
checked={cfg.blockLanAccess}
onChange={(v) => setField("blockLanAccess", v)}

View File

@@ -37,6 +37,7 @@ type Config struct {
DisableClientRoutes bool `json:"disableClientRoutes"`
DisableServerRoutes bool `json:"disableServerRoutes"`
DisableDNS bool `json:"disableDns"`
DisableIPv6 bool `json:"disableIpv6"`
BlockLANAccess bool `json:"blockLanAccess"`
EnableSSHRoot bool `json:"enableSshRoot"`
EnableSSHSFTP bool `json:"enableSshSftp"`
@@ -68,6 +69,7 @@ type SetConfigParams struct {
DisableClientRoutes *bool `json:"disableClientRoutes,omitempty"`
DisableServerRoutes *bool `json:"disableServerRoutes,omitempty"`
DisableDNS *bool `json:"disableDns,omitempty"`
DisableIPv6 *bool `json:"disableIpv6,omitempty"`
DisableFirewall *bool `json:"disableFirewall,omitempty"`
BlockLANAccess *bool `json:"blockLanAccess,omitempty"`
EnableSSHRoot *bool `json:"enableSshRoot,omitempty"`
@@ -127,6 +129,7 @@ func (s *Settings) GetConfig(ctx context.Context, p ConfigParams) (Config, error
DisableClientRoutes: resp.GetDisableClientRoutes(),
DisableServerRoutes: resp.GetDisableServerRoutes(),
DisableDNS: resp.GetDisableDns(),
DisableIPv6: resp.GetDisableIpv6(),
BlockLANAccess: resp.GetBlockLanAccess(),
EnableSSHRoot: resp.GetEnableSSHRoot(),
EnableSSHSFTP: resp.GetEnableSSHSFTP(),
@@ -162,6 +165,7 @@ func (s *Settings) SetConfig(ctx context.Context, p SetConfigParams) error {
DisableClientRoutes: p.DisableClientRoutes,
DisableServerRoutes: p.DisableServerRoutes,
DisableDns: p.DisableDNS,
DisableIpv6: p.DisableIPv6,
DisableFirewall: p.DisableFirewall,
BlockLanAccess: p.BlockLANAccess,
EnableSSHRoot: p.EnableSSHRoot,

View File

@@ -168,11 +168,16 @@ func NewTray(app *application.App, window *application.WebviewWindow, svc TraySe
}
// ShowWindow brings the main window forward — used by SIGUSR1 / Windows event.
// Show() alone is not enough on macOS: makeKeyAndOrderFront skips app
// activation, so a tray-style app's window pops up behind the currently
// active app. Focus() additionally calls activateIgnoringOtherApps:YES on
// macOS and SetForegroundWindow on Windows.
func (t *Tray) ShowWindow() {
if t.window == nil {
return
}
t.window.Show()
t.window.Focus()
}
func (t *Tray) buildMenu() *application.Menu {
@@ -243,6 +248,7 @@ func (t *Tray) openRoute(route string) {
return
}
t.window.Show()
t.window.Focus()
t.window.SetURL("/#" + route)
}
@@ -293,6 +299,12 @@ func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
if _, isUpdate := se.Metadata["new_version_available"]; isUpdate {
return
}
// Management pairs ::/0 with 0.0.0.0/0 for exit-node default routes;
// the v4 partner already drives the user-facing toast, so the v6 one
// is suppressed to avoid a duplicate notification.
if se.Category == "network" && se.Metadata["network"] == "::/0" {
return
}
critical := se.Severity == "critical"
t.mu.Lock()
@@ -377,6 +389,7 @@ func (t *Tray) handleUpdate() {
}
t.window.SetURL(url)
t.window.Show()
t.window.Focus()
}
go func() {
@@ -406,6 +419,7 @@ func (t *Tray) onUpdateProgress(ev *application.CustomEvent) {
}
t.window.SetURL(url)
t.window.Show()
t.window.Focus()
}
// applyStatus updates the tray icon, status label, exit-node submenu, and
@@ -500,6 +514,7 @@ func (t *Tray) handleSessionExpired() {
if t.window != nil {
t.window.SetURL("/#/login")
t.window.Show()
t.window.Focus()
}
}