fix(vpn): backport VPN fixes from production debugging
- Fix _commit_and_sync infinite recursion - Use admin session for subnet_index allocation (bypass RLS) - Auto-set VPN endpoint from CORS_ORIGINS hostname - Remove server address field from VPN setup UI - Add DELETE endpoint and button for VPN config removal - Add wg-reload watcher for reliable config hot-reload via wg syncconf - Add wg_status.json writer for live peer handshake status in UI - Per-tenant SNAT for poller-to-device routing through VPN - Restrict VPN→eth0 forwarding to Docker networks only (block exit node abuse) - Use 10.10.0.0/16 allowed-address in RouterOS commands - Fix structlog event= conflict (use audit=True) - Export backup_scheduler proxy for firmware/upgrade imports
This commit is contained in:
@@ -134,6 +134,16 @@ export function VpnPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: () => vpnApi.deleteConfig(tenantId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['vpn-config'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['vpn-peers'] })
|
||||
toast({ title: 'VPN configuration deleted' })
|
||||
},
|
||||
onError: (e: any) => toast({ title: e?.response?.data?.detail || 'Failed to delete VPN', variant: 'destructive' }),
|
||||
})
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
const connectedPeerIds = new Set(peers.map((p) => p.device_id))
|
||||
@@ -195,22 +205,6 @@ export function VpnPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 text-left">
|
||||
<Label htmlFor="endpoint" className="text-text-secondary">
|
||||
Server Address <span className="text-text-muted">(optional)</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="endpoint"
|
||||
placeholder="your-server.example.com:51820"
|
||||
value={endpoint}
|
||||
onChange={(e) => setEndpoint(e.target.value)}
|
||||
className="text-center"
|
||||
/>
|
||||
<p className="text-xs text-text-muted">
|
||||
The public hostname or IP where devices will connect. You can set this later.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{writable && (
|
||||
<Button
|
||||
onClick={() => setupMutation.mutate()}
|
||||
@@ -264,6 +258,19 @@ export function VpnPage() {
|
||||
<Button size="sm" onClick={() => setShowAddDevice(true)}>
|
||||
<Plus className="h-4 w-4 mr-1" /> Add Device
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-red-400 border-red-800 hover:bg-red-900/30"
|
||||
onClick={() => {
|
||||
if (confirm('Delete VPN configuration? All peers will be removed.')) {
|
||||
deleteMutation.mutate()
|
||||
}
|
||||
}}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
Delete VPN
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1136,6 +1136,9 @@ export const vpnApi = {
|
||||
updateConfig: (tenantId: string, data: { endpoint?: string; is_enabled?: boolean }) =>
|
||||
api.patch<VpnConfigResponse>(`/api/tenants/${tenantId}/vpn`, data).then((r) => r.data),
|
||||
|
||||
deleteConfig: (tenantId: string) =>
|
||||
api.delete(`/api/tenants/${tenantId}/vpn`),
|
||||
|
||||
listPeers: (tenantId: string) =>
|
||||
api.get<VpnPeerResponse[]>(`/api/tenants/${tenantId}/vpn/peers`).then((r) => r.data),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user