"use client"; import { invoke } from "@tauri-apps/api/core"; import { emit } from "@tauri-apps/api/event"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { LoadingButton } from "@/components/loading-button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RippleButton } from "@/components/ui/ripple"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import type { VpnConfig, VpnType } from "@/types"; interface VpnFormDialogProps { isOpen: boolean; onClose: () => void; editingVpn?: VpnConfig | null; } interface WireGuardFormData { name: string; privateKey: string; address: string; dns: string; mtu: string; peerPublicKey: string; peerEndpoint: string; allowedIps: string; persistentKeepalive: string; presharedKey: string; } interface OpenVpnFormData { name: string; rawConfig: string; } const defaultWireGuardForm: WireGuardFormData = { name: "", privateKey: "", address: "", dns: "", mtu: "", peerPublicKey: "", peerEndpoint: "", allowedIps: "0.0.0.0/0, ::/0", persistentKeepalive: "", presharedKey: "", }; const defaultOpenVpnForm: OpenVpnFormData = { name: "", rawConfig: "", }; function buildWireGuardConfig(form: WireGuardFormData): string { const lines: string[] = ["[Interface]"]; lines.push(`PrivateKey = ${form.privateKey.trim()}`); lines.push(`Address = ${form.address.trim()}`); if (form.dns.trim()) lines.push(`DNS = ${form.dns.trim()}`); if (form.mtu.trim()) lines.push(`MTU = ${form.mtu.trim()}`); lines.push(""); lines.push("[Peer]"); lines.push(`PublicKey = ${form.peerPublicKey.trim()}`); lines.push(`Endpoint = ${form.peerEndpoint.trim()}`); lines.push(`AllowedIPs = ${form.allowedIps.trim()}`); if (form.persistentKeepalive.trim()) lines.push(`PersistentKeepalive = ${form.persistentKeepalive.trim()}`); if (form.presharedKey.trim()) lines.push(`PresharedKey = ${form.presharedKey.trim()}`); return lines.join("\n"); } export function VpnFormDialog({ isOpen, onClose, editingVpn, }: VpnFormDialogProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [vpnType, setVpnType] = useState("WireGuard"); const [wireGuardForm, setWireGuardForm] = useState(defaultWireGuardForm); const [openVpnForm, setOpenVpnForm] = useState(defaultOpenVpnForm); const resetForms = useCallback(() => { setVpnType("WireGuard"); setWireGuardForm(defaultWireGuardForm); setOpenVpnForm(defaultOpenVpnForm); }, []); useEffect(() => { if (isOpen) { if (editingVpn) { setVpnType(editingVpn.vpn_type); if (editingVpn.vpn_type === "WireGuard") { setWireGuardForm({ ...defaultWireGuardForm, name: editingVpn.name }); } else { setOpenVpnForm({ name: editingVpn.name, rawConfig: "" }); } } else { resetForms(); } } }, [isOpen, editingVpn, resetForms]); const handleClose = useCallback(() => { if (!isSubmitting) { onClose(); } }, [isSubmitting, onClose]); const handleSubmit = useCallback(async () => { if (editingVpn) { const name = vpnType === "WireGuard" ? wireGuardForm.name.trim() : openVpnForm.name.trim(); if (!name) { toast.error("VPN name is required"); return; } setIsSubmitting(true); try { await invoke("update_vpn_config", { vpnId: editingVpn.id, name, }); await emit("vpn-configs-changed"); toast.success("VPN updated successfully"); onClose(); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); toast.error(`Failed to update VPN: ${errorMessage}`); } finally { setIsSubmitting(false); } return; } if (vpnType === "WireGuard") { const { name, privateKey, address, peerPublicKey, peerEndpoint } = wireGuardForm; if (!name.trim()) { toast.error("VPN name is required"); return; } if (!privateKey.trim()) { toast.error("Private key is required"); return; } if (!address.trim()) { toast.error("Address is required"); return; } if (!peerPublicKey.trim()) { toast.error("Peer public key is required"); return; } if (!peerEndpoint.trim()) { toast.error("Peer endpoint is required"); return; } setIsSubmitting(true); try { const configData = buildWireGuardConfig(wireGuardForm); await invoke("create_vpn_config_manual", { name: name.trim(), vpnType: "WireGuard", configData, }); await emit("vpn-configs-changed"); toast.success("WireGuard VPN created successfully"); onClose(); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); toast.error(`Failed to create VPN: ${errorMessage}`); } finally { setIsSubmitting(false); } } else { const { name, rawConfig } = openVpnForm; if (!name.trim()) { toast.error("VPN name is required"); return; } if (!rawConfig.trim()) { toast.error("OpenVPN config content is required"); return; } setIsSubmitting(true); try { await invoke("create_vpn_config_manual", { name: name.trim(), vpnType: "OpenVPN", configData: rawConfig, }); await emit("vpn-configs-changed"); toast.success("OpenVPN configuration created successfully"); onClose(); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); toast.error(`Failed to create VPN: ${errorMessage}`); } finally { setIsSubmitting(false); } } }, [editingVpn, vpnType, wireGuardForm, openVpnForm, onClose]); const updateWireGuard = useCallback( (field: keyof WireGuardFormData, value: string) => { setWireGuardForm((prev) => ({ ...prev, [field]: value })); }, [], ); const updateOpenVpn = useCallback( (field: keyof OpenVpnFormData, value: string) => { setOpenVpnForm((prev) => ({ ...prev, [field]: value })); }, [], ); const dialogTitle = editingVpn ? "Edit VPN" : vpnType === "WireGuard" ? "Create WireGuard VPN" : "Create OpenVPN Configuration"; const dialogDescription = editingVpn ? "Update the name of your VPN configuration." : vpnType === "WireGuard" ? "Enter your WireGuard interface and peer details." : "Paste your .ovpn configuration file content."; return ( {dialogTitle} {dialogDescription}
{!editingVpn && (
)} {vpnType === "WireGuard" && ( <>
updateWireGuard("name", e.target.value)} placeholder="e.g. Home WireGuard" disabled={isSubmitting} />
{!editingVpn && ( <>
updateWireGuard("privateKey", e.target.value) } placeholder="Base64-encoded private key" disabled={isSubmitting} />
updateWireGuard("address", e.target.value) } placeholder="e.g. 10.0.0.2/24" disabled={isSubmitting} />
updateWireGuard("dns", e.target.value) } placeholder="e.g. 1.1.1.1" disabled={isSubmitting} />
updateWireGuard("mtu", e.target.value) } placeholder="e.g. 1420" disabled={isSubmitting} />
updateWireGuard("peerPublicKey", e.target.value) } placeholder="Base64-encoded peer public key" disabled={isSubmitting} />
updateWireGuard("peerEndpoint", e.target.value) } placeholder="e.g. vpn.example.com:51820" disabled={isSubmitting} />
updateWireGuard("allowedIps", e.target.value) } placeholder="e.g. 0.0.0.0/0, ::/0" disabled={isSubmitting} />
updateWireGuard( "persistentKeepalive", e.target.value, ) } placeholder="e.g. 25" disabled={isSubmitting} />
updateWireGuard("presharedKey", e.target.value) } placeholder="Base64-encoded preshared key" disabled={isSubmitting} />
)} )} {vpnType === "OpenVPN" && ( <>
updateOpenVpn("name", e.target.value)} placeholder="e.g. Work OpenVPN" disabled={isSubmitting} />
{!editingVpn && (