From 4a4a5a01b35c2678999949dd35edf942083455e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 21:44:27 +0000 Subject: [PATCH] Add Mesh section to Settings for configuring/testing the mesh gate Admins can now toggle mesh.required, run verify/override, and see current mesh status entirely from the app, without hitting the API directly. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_019hu9pZvJY4BgmcQeAw2ugk --- src/pages/Settings.tsx | 174 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 1 deletion(-) diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index dfd0445..5a4130a 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { useSearchParams } from 'react-router-dom' -import { api, ApiError, type Integration, type AuthSession, type LoginEvent, type ManagedUser } from '../lib/api' +import { api, ApiError, type Integration, type AuthSession, type LoginEvent, type ManagedUser, type MeshStatus } from '../lib/api' import { useAuth } from '../lib/AuthContext' import { User, @@ -23,6 +23,7 @@ import { LogOut, Users, UserPlus, + Network, } from 'lucide-react' const navSections = [ @@ -30,6 +31,7 @@ const navSections = [ { id: 'appearance', label: 'Appearance', icon: Palette }, { id: 'security', label: 'Security', icon: Shield }, { id: 'users', label: 'Users', icon: Users, adminOnly: true }, + { id: 'mesh', label: 'Mesh', icon: Network, adminOnly: true }, { id: 'integrations', label: 'Integrations', icon: Plug }, { id: 'notifications', label: 'Notifications', icon: Bell }, { id: 'data', label: 'Data & Backup', icon: Database }, @@ -1804,11 +1806,181 @@ function UsersSection() { ) } +function MeshSection() { + const [status, setStatus] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + const [toggling, setToggling] = useState(false) + + const [cidr, setCidr] = useState('') + const [testIp, setTestIp] = useState('') + const [verifying, setVerifying] = useState(false) + const [verifyResult, setVerifyResult] = useState<{ ok: boolean; message: string; hostMeshIp: string | null } | null>(null) + const [overriding, setOverriding] = useState(false) + + async function load() { + try { + const s = await api.getMeshStatus() + setStatus(s) + setCidr(s.cidr ?? '') + setError('') + } catch (err) { + setError(err instanceof ApiError ? err.message : 'Failed to load mesh status') + } finally { + setLoading(false) + } + } + + useEffect(() => { + load() + }, []) + + async function handleToggle() { + if (!status) return + setToggling(true) + try { + const s = await api.setMeshRequired(!status.required) + setStatus(s) + } catch (err) { + setError(err instanceof ApiError ? err.message : 'Failed to update mesh requirement') + } finally { + setToggling(false) + } + } + + async function handleVerify() { + setError('') + setVerifyResult(null) + setVerifying(true) + try { + const result = await api.verifyMesh(cidr, testIp || undefined) + setVerifyResult(result) + load() + } catch (err) { + setError(err instanceof ApiError ? err.message : 'Failed to verify mesh') + } finally { + setVerifying(false) + } + } + + async function handleOverride() { + setOverriding(true) + try { + const s = await api.overrideMesh() + setStatus(s) + } catch (err) { + setError(err instanceof ApiError ? err.message : 'Failed to skip mesh setup') + } finally { + setOverriding(false) + } + } + + if (loading) { + return ( +
+
+

Loading…

+
+
+ ) + } + + return ( +
+
+
+

Mesh Network Gate

+
+ {status?.required ? 'Required' : 'Not required'} + +
+
+

+ When enabled, admins must verify this host is on a private mesh network (NetBird, WireGuard, + ZeroTier, Tailscale, etc.) before accessing the rest of the app. Members are never blocked — they + just see a notice banner until an admin finishes verification. +

+ {toggling &&

Updating…

} + + {status && ( +
+

+ Verified: {status.verified ? 'Yes' : 'No'} + {status.verifiedVia && ` (${status.verifiedVia})`} +

+ {status.cidr &&

CIDR: {status.cidr}

} + {status.hostMeshIp &&

Host mesh IP: {status.hostMeshIp}

} +

+ Override: {status.overridden ? 'Active (gate skipped)' : 'None'} +

+
+ )} +
+ +
+

Configure & Verify

+ + setCidr(e.target.value)} + placeholder="e.g. 100.64.0.0/10 (NetBird/Tailscale CGNAT range)" + /> + + setTestIp(e.target.value)} + placeholder="e.g. 100.64.0.1 — only needed if this host's own IP isn't in the mesh range" + /> +

+ If this host reaches the mesh through routing instead of holding a local mesh IP (e.g. a VPC + peered into the mesh), give us an address on the mesh we can ping to confirm reachability. +

+ + {error &&

{error}

} + {verifyResult && ( +

+ {verifyResult.message} + {verifyResult.hostMeshIp && ` — host mesh IP: ${verifyResult.hostMeshIp}`} +

+ )} + +
+ + {verifying ? 'Verifying…' : 'Verify Connection'} + + +
+
+
+ ) +} + const sectionComponents: Record React.ReactElement> = { profile: ProfileSection, appearance: AppearanceSection, security: SecuritySection, users: UsersSection, + mesh: MeshSection, integrations: IntegrationsSection, notifications: NotificationsSection, data: DataBackupSection,