-
Settings
+
Settings
- {/* Tabs */}
-
- {(['connection', 'network', 'security'] as Tab[]).map(t => (
-
- ))}
-
+
-
-
- {tab === 'connection' && (
- <>
-
-
+
+
+ update('managementUrl', e.target.value)}
placeholder="https://api.netbird.io:443"
+ style={{ width: 240 }}
/>
-
-
-
+
+ update('adminUrl', e.target.value)}
+ style={{ width: 240 }}
/>
-
-
-
+
+ update('preSharedKey', e.target.value)}
placeholder="Leave empty to clear"
+ style={{ width: 240 }}
/>
-
- update('disableAutoConnect', !v)}
- />
- update('disableNotifications', !v)}
- />
- >
- )}
+
+
- {tab === 'network' && (
- <>
-
-
+
+ update('disableAutoConnect', !v)} />
+
+
+ update('disableNotifications', !v)} />
+
+
+ >
+ )}
+
+ {tab === 'network' && (
+ <>
+
+
+ update('interfaceName', e.target.value)}
placeholder="netbird0"
+ style={{ width: 180 }}
/>
-
-
-
+
+ update('wireguardPort', parseInt(e.target.value) || 0)}
placeholder="51820"
+ style={{ width: 100 }}
/>
-
- update('lazyConnectionEnabled', v)}
- />
- update('blockInbound', v)}
- />
- >
- )}
+
+
- {tab === 'security' && (
- <>
- update('serverSshAllowed', v)}
- />
- update('rosenpassEnabled', v)}
- />
- update('rosenpassPermissive', v)}
- />
- >
- )}
-
+
+
+ update('lazyConnectionEnabled', v)} />
+
+
+ update('blockInbound', v)} />
+
+
+ >
+ )}
-
-
- {saved &&
Saved!}
- {error &&
{error}}
+ {tab === 'security' && (
+
+
+ update('serverSshAllowed', v)} />
+
+
+ update('rosenpassEnabled', v)} />
+
+
+ update('rosenpassPermissive', v)} />
+
+
+ )}
+
+
+
+ {saved && Saved!}
+ {error && {error}}
)
}
-
-function Field({ label, children }: { label: string; children: React.ReactNode }) {
- return (
-
-
- {children}
-
- )
-}
-
-function Toggle({ label, checked, onChange }: { label: string; checked: boolean; onChange: (v: boolean) => void }) {
- return (
-
- )
-}
diff --git a/client/uiwails/frontend/src/pages/Status.tsx b/client/uiwails/frontend/src/pages/Status.tsx
index e0554cb0d..22494f589 100644
--- a/client/uiwails/frontend/src/pages/Status.tsx
+++ b/client/uiwails/frontend/src/pages/Status.tsx
@@ -1,6 +1,9 @@
import { useState, useEffect, useCallback } from 'react'
import { Events, Call } from '@wailsio/runtime'
import type { StatusInfo } from '../bindings'
+import Card from '../components/ui/Card'
+import CardRow from '../components/ui/CardRow'
+import Button from '../components/ui/Button'
async function getStatus(): Promise
{
try {
@@ -24,21 +27,21 @@ async function disconnect(): Promise {
await Call.ByName('github.com/netbirdio/netbird/client/uiwails/services.ConnectionService.Disconnect')
}
-function statusColor(status: string): string {
+function statusDotColor(status: string): string {
switch (status) {
- case 'Connected': return 'text-green-400'
- case 'Connecting': return 'text-yellow-400'
- case 'Disconnected': return 'text-nb-gray-400'
- default: return 'text-red-400'
+ case 'Connected': return 'var(--color-status-green)'
+ case 'Connecting': return 'var(--color-status-yellow)'
+ case 'Disconnected': return 'var(--color-status-gray)'
+ default: return 'var(--color-status-red)'
}
}
-function statusDot(status: string): string {
+function statusTextColor(status: string): string {
switch (status) {
- case 'Connected': return 'bg-green-400'
- case 'Connecting': return 'bg-yellow-400 animate-pulse'
- case 'Disconnected': return 'bg-nb-gray-600'
- default: return 'bg-red-400'
+ case 'Connected': return 'var(--color-status-green)'
+ case 'Connecting': return 'var(--color-status-yellow)'
+ case 'Disconnected': return 'var(--color-text-secondary)'
+ default: return 'var(--color-status-red)'
}
}
@@ -54,9 +57,7 @@ export default function Status() {
useEffect(() => {
refresh()
- // Poll every 10 seconds as fallback (push events handle real-time updates)
const id = setInterval(refresh, 10000)
- // Also listen for push events from the tray
const unsub = Events.On('status-changed', (event: { data: StatusInfo[] }) => {
if (event.data[0]) setStatus(event.data[0])
})
@@ -97,65 +98,64 @@ export default function Status() {
return (
-
Status
+
Status
- {/* Status card */}
-
-
-
-
- {status?.status ?? 'Loading…'}
-
+ {/* Status hero */}
+
+
+
+
+
+ {status?.status ?? 'Loading\u2026'}
+
+
- {status && (
-
- {status.ip && (
- <>
- IP Address
- {status.ip}
- >
- )}
- {status.fqdn && (
- <>
- Hostname
- {status.fqdn}
- >
- )}
- {status.connectedPeers > 0 && (
- <>
- Connected Peers
- {status.connectedPeers}
- >
- )}
-
+ {status?.ip && (
+
+ {status.ip}
+
)}
-
+ {status?.fqdn && (
+
+ {status.fqdn}
+
+ )}
+ {status && status.connectedPeers > 0 && (
+
+ {status.connectedPeers}
+
+ )}
+
- {/* Action button */}
+ {/* Actions */}
{!isConnected && !isConnecting && (
-
+
)}
{(isConnected || isConnecting) && (
-
+
)}
{error && (
-
+
{error}
)}
diff --git a/client/uiwails/frontend/src/pages/Update.tsx b/client/uiwails/frontend/src/pages/Update.tsx
index 2f40b8414..4c18e3d00 100644
--- a/client/uiwails/frontend/src/pages/Update.tsx
+++ b/client/uiwails/frontend/src/pages/Update.tsx
@@ -1,6 +1,8 @@
import { useState, useEffect, useRef } from 'react'
import { Call } from '@wailsio/runtime'
import type { InstallerResult } from '../bindings'
+import Card from '../components/ui/Card'
+import Button from '../components/ui/Button'
type UpdateState = 'idle' | 'triggering' | 'polling' | 'success' | 'failed' | 'timeout'
@@ -10,7 +12,6 @@ export default function Update() {
const [errorMsg, setErrorMsg] = useState('')
const abortRef = useRef
(null)
- // Animate dots when polling
useEffect(() => {
if (state !== 'polling') return
let count = 0
@@ -40,7 +41,6 @@ export default function Update() {
setState('polling')
- // Poll for installer result (up to 15 minutes handled server-side)
try {
console.log('[Update] calling services.UpdateService.GetInstallerResult')
const result = await Call.ByName('github.com/netbirdio/netbird/client/uiwails/services.UpdateService.GetInstallerResult') as InstallerResult
@@ -51,63 +51,56 @@ export default function Update() {
setErrorMsg(result?.errorMsg ?? 'Update failed')
setState('failed')
}
- } catch (e) {
- // If the daemon restarts, the gRPC call may fail — treat as success
+ } catch {
setState('success')
}
}
return (
-
Update
-
+
Update
+
Trigger an automatic client update managed by the NetBird daemon.
-
- {state === 'idle' && (
- <>
-
Click below to trigger a daemon-managed update.
-
- >
- )}
+
+
+ {state === 'idle' && (
+ <>
+
Click below to trigger a daemon-managed update.
+
+ >
+ )}
- {state === 'triggering' && (
-
Triggering update…
- )}
+ {state === 'triggering' && (
+
Triggering update\u2026
+ )}
- {state === 'polling' && (
-
-
Updating{dots}
-
The daemon is installing the update. Please wait.
-
- )}
+ {state === 'polling' && (
+
+
Updating{dots}
+
The daemon is installing the update. Please wait.
+
+ )}
- {state === 'success' && (
-
-
Update Successful!
-
The client has been updated. You may need to restart.
-
- )}
+ {state === 'success' && (
+
+
Update Successful!
+
The client has been updated. You may need to restart.
+
+ )}
- {state === 'failed' && (
-
-
Update Failed
- {errorMsg &&
{errorMsg}
}
-
-
- )}
-
+ {state === 'failed' && (
+
+
Update Failed
+ {errorMsg &&
{errorMsg}
}
+
+
+ )}
+
+
)
}
diff --git a/client/uiwails/main.go b/client/uiwails/main.go
index 892849c2d..0f95d222a 100644
--- a/client/uiwails/main.go
+++ b/client/uiwails/main.go
@@ -8,6 +8,7 @@ import (
"flag"
"os"
"runtime"
+ "time"
log "github.com/sirupsen/logrus"
"github.com/wailsapp/wails/v3/pkg/application"
@@ -123,6 +124,14 @@ func main() {
evtManager := event.NewManager(*daemonAddr, notify)
go evtManager.Start(ctx)
+ // TEST: fire a desktop notification shortly after startup so we can
+ // verify that the notification pipeline works end-to-end.
+ go func() {
+ time.Sleep(3 * time.Second)
+ log.Infof("--- trigger notification ---")
+ notify("NetBird Test", "If you see this, notifications are working!")
+ }()
+
if err := app.Run(); err != nil {
log.Fatalf("app run: %v", err)
}
diff --git a/client/uiwails/uiwails b/client/uiwails/uiwails
deleted file mode 100755
index 4615444ea..000000000
Binary files a/client/uiwails/uiwails and /dev/null differ