add setting

This commit is contained in:
Eduard Gert
2026-05-06 14:21:01 +02:00
parent c3f9514182
commit 553be144b4
12 changed files with 173 additions and 25 deletions

View File

@@ -0,0 +1,69 @@
1. General
The "old tray" toggles + notifications. This is what 90% of users come to Settings for.
- Connect on startup — disableAutoConnect (inverted)
- Allow SSH — serverSshAllowed (master switch; the SSH tab is the detail)
- Quantum-resistance — rosenpassEnabled
- Nested when on: Permissive mode — rosenpassPermissive
- Lazy connections — lazyConnectionEnabled
- Block inbound — blockInbound
- Show notifications — disableNotifications (inverted)
▎ Note: blockInbound is technically a firewall behavior, but Stage 1 explicitly groups it with the tray-replacement toggles. Keep it here.
2. Connection
Identity + how the wire is established. The "what server am I talking to and how" tab.
- Management URL — managementUrl
- Pre-shared key — preSharedKey (password input, toggle reveal)
- Advanced (collapsed by default)
- Admin URL — adminUrl
- Interface name — interfaceName
- WireGuard port — wireguardPort
- MTU — mtu
3. Network
Routing / DNS / LAN behavior — i.e. what the daemon does to the host network.
- Network monitor — networkMonitor
- Disable DNS — disableDns
- Disable client routes — disableClientRoutes
- Disable server routes — disableServerRoutes
- Block LAN access — blockLanAccess
4. SSH
Detailed SSH server config. Greyed out with an inline notice ("Enable Allow SSH in General to configure") when serverSshAllowed is off.
- SSH root login — enableSshRoot
- SFTP — enableSshSftp
- Local port forwarding — enableSshLocalPortForwarding
- Remote port forwarding — enableSshRemotePortForwarding
- Advanced (collapsed)
- Disable SSH auth — disableSshAuth
- JWT cache TTL — sshJwtCacheTtl
5. Diagnostics
Everything you reach for when something is wrong. Mixes config (log level) with actions (bundle creation) deliberately — they're used together.
- Log level — Debug / Info / Warn / Error (dropdown)
- Log file path — read-only, with Copy + Reveal in Finder/Explorer buttons (configFile / logFile from daemon)
- Config file path — same pattern
- Debug bundle (own section)
- Anonymize toggle
- Include system info toggle
- Upload on create toggle → reveals URL field when on
- Create Bundle button → progress indicator → resulting path or upload URL displayed below
6. About
Version + update flow + identity reference.
- App version, daemon version
- Check for Updates button → drives the auto-update flow (15-min timeout, success/error states)
- Local peer info quick-reference (FQDN, IP) — same data the connection-state view shows
- Links: docs, GitHub repo, license

View File

@@ -1,8 +1,7 @@
export default function PlaceholderHeader() {
return (
<div
className="h-[36px] shrink-0 cursor-default"
style={{ "--wails-draggable": "drag" } as React.CSSProperties}
className="h-[36px] shrink-0 cursor-default wails-draggable"
/>
);
}

View File

@@ -120,7 +120,7 @@ export const ProfileSelector = ({ email = "" }: Props) => {
<button
type="button"
className={
"h-11 rounded-md text-nb-gray-300 flex items-center gap-1 text-xs hover:bg-nb-gray-930 data-[state=open]:bg-nb-gray-930 px-2 -mx-2 outline-none cursor-default transition-colors duration-150"
"h-11 rounded-md text-nb-gray-300 flex items-center gap-1 text-xs hover:bg-nb-gray-930 data-[state=open]:bg-nb-gray-930 px-2 -mx-1 outline-none cursor-default transition-colors duration-150"
}
>
<div

View File

@@ -19,3 +19,11 @@ body,
body {
@apply bg-nb-gray font-sans text-nb-gray-200 antialiased;
}
.wails-draggable {
--wails-draggable: drag;
}
.wails-no-draggable {
--wails-draggable: no-drag;
}

View File

@@ -1,12 +1,25 @@
import { ProfileSelector } from "@/components/ProfileSelector.tsx";
import { IconButton } from "@/components/IconButton.tsx";
import { SettingsIcon } from "lucide-react";
import { cn } from "@/lib/cn";
export const Header = () => {
type Props = {
settingsActive?: boolean;
onSettingsClick?: () => void;
};
export const Header = ({ settingsActive = false, onSettingsClick }: Props) => {
return (
<div className={"w-full justify-between flex mb-12"}>
<ProfileSelector />
<IconButton icon={SettingsIcon} />
<ProfileSelector email={"eduard@netbird.io"} />
<IconButton
icon={SettingsIcon}
onClick={onSettingsClick}
className={cn(
settingsActive &&
"bg-nb-gray-930 text-nb-gray-200 hover:text-nb-gray-200",
)}
/>
</div>
);
};

View File

@@ -0,0 +1,32 @@
import { ConnectionStatus } from "@/layouts/ConnectionStatus.tsx";
import { Header } from "@/layouts/Header.tsx";
import { Navigation } from "@/layouts/Navigation.tsx";
export type MainModule = "peers" | "settings";
type Props = {
active: MainModule;
onChange: (module: MainModule) => void;
};
export const MainLeftSide = ({ active, onChange }: Props) => {
return (
<div
className={"flex flex-col max-w-xs w-full shrink-0 items-center"}
>
<Header
settingsActive={active === "settings"}
onSettingsClick={() =>
onChange(active === "settings" ? "peers" : "settings")
}
/>
<ConnectionStatus />
<Navigation
peersActive={active === "peers"}
onPeersClick={() => {
if (active !== "peers") onChange("peers");
}}
/>
</div>
);
};

View File

@@ -0,0 +1,15 @@
import { ReactNode } from "react";
type Props = {
children: ReactNode;
};
export const MainRightSide = ({ children }: Props) => {
return (
<div
className={"wails-no-draggable flex-1 min-h-0 min-w-0 flex flex-col bg-nb-gray-935 rounded-xl rounded-br-2xl border border-nb-gray-900"}
>
{children}
</div>
);
};

View File

@@ -5,14 +5,20 @@ import {
SquareArrowUpRight,
} from "lucide-react";
export const Navigation = () => {
type Props = {
peersActive?: boolean;
onPeersClick?: () => void;
};
export const Navigation = ({ peersActive = false, onPeersClick }: Props) => {
return (
<nav className={"w-full flex flex-col gap-1 mt-8"}>
<NavItem
icon={MonitorSmartphoneIcon}
title={"Peers"}
description={"13 of 16 Online"}
active
active={peersActive}
onClick={onPeersClick}
/>
<NavItem
icon={Layers3Icon}

View File

@@ -8,7 +8,7 @@ import { PeersList } from "./PeersList";
const isOnline = (status: string) => status === "connected";
export const PeersModule = () => {
export const Peers = () => {
const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");

View File

@@ -0,0 +1,7 @@
export const Settings = () => {
return (
<div className={"flex flex-col w-full h-full min-h-0 pt-4 px-4"}>
<h2 className={"text-sm font-medium text-nb-gray-200"}>Settings</h2>
</div>
);
};

View File

@@ -1,23 +1,22 @@
import { ConnectionStatus } from "@/layouts/ConnectionStatus.tsx";
import { Header } from "@/layouts/Header.tsx";
import { Navigation } from "@/layouts/Navigation.tsx";
import { PeersModule } from "@/modules/peers/PeersModule.tsx";
import { useState } from "react";
import { MainLeftSide, MainModule } from "@/layouts/MainLeftSide.tsx";
import { MainRightSide } from "@/layouts/MainRightSide.tsx";
import { Peers } from "@/modules/peers/Peers.tsx";
import { Settings } from "@/modules/settings/Settings.tsx";
type Props = {
};
export const Main = ({}: Props) => {
return (
<div className={"flex h-full p-4 gap-4 min-h-0"}>
<div className={"flex flex-col max-w-xs w-full shrink-0 items-center"}>
<Header />
<ConnectionStatus />
<Navigation />
</div>
const [active, setActive] = useState<MainModule>("peers");
<div className={"flex-1 min-h-0 min-w-0 flex flex-col bg-nb-gray-935 rounded-xl border border-nb-gray-900"}>
<PeersModule />
</div>
return (
<div className={"wails-draggable flex h-full p-4 gap-4 min-h-0"}>
<MainLeftSide active={active} onChange={setActive} />
<MainRightSide>
{active === "peers" ? <Peers /> : <Settings />}
</MainRightSide>
</div>
);

View File

@@ -103,9 +103,9 @@ func main() {
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "NetBird",
Width: 880,
Height: 620,
Height: 520,
MinWidth: 880,
MinHeight: 620,
MinHeight: 520,
Hidden: false,
BackgroundColour: application.NewRGB(24, 26, 29),
URL: "/",