refactor: more robust vpn handling

This commit is contained in:
zhom
2026-04-04 03:16:04 +04:00
parent ac5d975e5b
commit 48883ddd03
19 changed files with 1936 additions and 203 deletions
+80
View File
@@ -3,8 +3,10 @@
import { invoke } from "@tauri-apps/api/core";
import { emit } from "@tauri-apps/api/event";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { LoadingButton } from "@/components/loading-button";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
Dialog,
DialogContent,
@@ -51,6 +53,14 @@ interface OpenVpnFormData {
rawConfig: string;
}
interface VpnDependencyStatus {
isAvailable: boolean;
requiresExternalInstall: boolean;
missingBinary: boolean;
missingWindowsAdapter: boolean;
dependencyCheckFailed: boolean;
}
const defaultWireGuardForm: WireGuardFormData = {
name: "",
privateKey: "",
@@ -92,12 +102,15 @@ export function VpnFormDialog({
onClose,
editingVpn,
}: VpnFormDialogProps) {
const { t } = useTranslation();
const [isSubmitting, setIsSubmitting] = useState(false);
const [vpnType, setVpnType] = useState<VpnType>("WireGuard");
const [wireGuardForm, setWireGuardForm] =
useState<WireGuardFormData>(defaultWireGuardForm);
const [openVpnForm, setOpenVpnForm] =
useState<OpenVpnFormData>(defaultOpenVpnForm);
const [vpnDependencyStatus, setVpnDependencyStatus] =
useState<VpnDependencyStatus | null>(null);
const resetForms = useCallback(() => {
setVpnType("WireGuard");
@@ -120,6 +133,32 @@ export function VpnFormDialog({
}
}, [isOpen, editingVpn, resetForms]);
useEffect(() => {
if (!isOpen) {
setVpnDependencyStatus(null);
return;
}
let cancelled = false;
void invoke<VpnDependencyStatus>("get_vpn_dependency_status", { vpnType })
.then((status) => {
if (!cancelled) {
setVpnDependencyStatus(status);
}
})
.catch((error) => {
console.error("Failed to load VPN dependency status:", error);
if (!cancelled) {
setVpnDependencyStatus(null);
}
});
return () => {
cancelled = true;
};
}, [isOpen, vpnType]);
const handleClose = useCallback(() => {
if (!isSubmitting) {
onClose();
@@ -258,6 +297,36 @@ export function VpnFormDialog({
? "Enter your WireGuard interface and peer details."
: "Paste your .ovpn configuration file content.";
let dependencyWarningTitle: string | null = null;
let dependencyWarningDescription: string | null = null;
if (
vpnType === "OpenVPN" &&
vpnDependencyStatus?.requiresExternalInstall &&
!vpnDependencyStatus.isAvailable
) {
if (vpnDependencyStatus.missingBinary) {
dependencyWarningTitle = t("vpnForm.dependencies.openVpnMissingTitle");
dependencyWarningDescription = t(
"vpnForm.dependencies.openVpnMissingDescription",
);
} else if (vpnDependencyStatus.missingWindowsAdapter) {
dependencyWarningTitle = t(
"vpnForm.dependencies.openVpnAdapterMissingTitle",
);
dependencyWarningDescription = t(
"vpnForm.dependencies.openVpnAdapterMissingDescription",
);
} else if (vpnDependencyStatus.dependencyCheckFailed) {
dependencyWarningTitle = t(
"vpnForm.dependencies.openVpnCheckFailedTitle",
);
dependencyWarningDescription = t(
"vpnForm.dependencies.openVpnCheckFailedDescription",
);
}
}
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-lg">
@@ -268,6 +337,17 @@ export function VpnFormDialog({
<ScrollArea className="max-h-[60vh] pr-4">
<div className="grid gap-4 py-2">
{dependencyWarningTitle && dependencyWarningDescription && (
<Alert className="border-warning/50 bg-warning/10">
<AlertTitle className="text-warning">
{dependencyWarningTitle}
</AlertTitle>
<AlertDescription className="text-warning">
{dependencyWarningDescription}
</AlertDescription>
</Alert>
)}
{!editingVpn && (
<div className="grid gap-2">
<Label>VPN Type</Label>
+10
View File
@@ -794,6 +794,16 @@
"button": "Clone"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "OpenVPN is not installed",
"openVpnMissingDescription": "You can save this configuration, but Donut Browser cannot connect it until OpenVPN is installed on this device.",
"openVpnAdapterMissingTitle": "OpenVPN adapter is missing",
"openVpnAdapterMissingDescription": "OpenVPN is installed, but no TAP/Wintun/ovpn-dco adapter was found. Repair or reinstall OpenVPN before connecting on Windows.",
"openVpnCheckFailedTitle": "OpenVPN install could not be verified",
"openVpnCheckFailedDescription": "Donut Browser could not inspect the local OpenVPN installation. Repair or reinstall OpenVPN before connecting on Windows."
}
},
"extensions": {
"title": "Extensions",
"description": "Manage browser extensions and extension groups for your profiles.",
+10
View File
@@ -794,6 +794,16 @@
"button": "Clonar"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "OpenVPN no está instalado",
"openVpnMissingDescription": "Puedes guardar esta configuración, pero Donut Browser no podrá conectarse hasta que OpenVPN esté instalado en este dispositivo.",
"openVpnAdapterMissingTitle": "Falta el adaptador de OpenVPN",
"openVpnAdapterMissingDescription": "OpenVPN está instalado, pero no se encontró ningún adaptador TAP/Wintun/ovpn-dco. Repara o reinstala OpenVPN antes de conectarte en Windows.",
"openVpnCheckFailedTitle": "No se pudo verificar la instalación de OpenVPN",
"openVpnCheckFailedDescription": "Donut Browser no pudo inspeccionar la instalación local de OpenVPN. Repara o reinstala OpenVPN antes de conectarte en Windows."
}
},
"extensions": {
"title": "Extensiones",
"description": "Administra extensiones de navegador y grupos de extensiones para tus perfiles.",
+10
View File
@@ -794,6 +794,16 @@
"button": "Cloner"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "OpenVPN n'est pas installé",
"openVpnMissingDescription": "Vous pouvez enregistrer cette configuration, mais Donut Browser ne pourra pas s'y connecter tant qu'OpenVPN n'est pas installé sur cet appareil.",
"openVpnAdapterMissingTitle": "L'adaptateur OpenVPN est manquant",
"openVpnAdapterMissingDescription": "OpenVPN est installé, mais aucun adaptateur TAP/Wintun/ovpn-dco n'a été trouvé. Réparez ou réinstallez OpenVPN avant de vous connecter sous Windows.",
"openVpnCheckFailedTitle": "L'installation d'OpenVPN n'a pas pu être vérifiée",
"openVpnCheckFailedDescription": "Donut Browser n'a pas pu inspecter l'installation locale d'OpenVPN. Réparez ou réinstallez OpenVPN avant de vous connecter sous Windows."
}
},
"extensions": {
"title": "Extensions",
"description": "Gérez les extensions de navigateur et les groupes d'extensions pour vos profils.",
+10
View File
@@ -794,6 +794,16 @@
"button": "複製"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "OpenVPN がインストールされていません",
"openVpnMissingDescription": "この設定は保存できますが、このデバイスに OpenVPN がインストールされるまで Donut Browser では接続できません。",
"openVpnAdapterMissingTitle": "OpenVPN アダプターが見つかりません",
"openVpnAdapterMissingDescription": "OpenVPN はインストールされていますが、TAP/Wintun/ovpn-dco アダプターが見つかりませんでした。Windows で接続する前に OpenVPN を修復または再インストールしてください。",
"openVpnCheckFailedTitle": "OpenVPN のインストールを確認できませんでした",
"openVpnCheckFailedDescription": "Donut Browser はローカルの OpenVPN インストールを確認できませんでした。Windows で接続する前に OpenVPN を修復または再インストールしてください。"
}
},
"extensions": {
"title": "拡張機能",
"description": "プロファイル用のブラウザ拡張機能と拡張機能グループを管理します。",
+10
View File
@@ -794,6 +794,16 @@
"button": "Clonar"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "OpenVPN não está instalado",
"openVpnMissingDescription": "Você pode salvar esta configuração, mas o Donut Browser não poderá se conectar até que o OpenVPN esteja instalado neste dispositivo.",
"openVpnAdapterMissingTitle": "O adaptador do OpenVPN está ausente",
"openVpnAdapterMissingDescription": "O OpenVPN está instalado, mas nenhum adaptador TAP/Wintun/ovpn-dco foi encontrado. Repare ou reinstale o OpenVPN antes de se conectar no Windows.",
"openVpnCheckFailedTitle": "Não foi possível verificar a instalação do OpenVPN",
"openVpnCheckFailedDescription": "O Donut Browser não conseguiu inspecionar a instalação local do OpenVPN. Repare ou reinstale o OpenVPN antes de se conectar no Windows."
}
},
"extensions": {
"title": "Extensões",
"description": "Gerencie extensões de navegador e grupos de extensões para seus perfis.",
+10
View File
@@ -794,6 +794,16 @@
"button": "Клонировать"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "OpenVPN не установлен",
"openVpnMissingDescription": "Вы можете сохранить эту конфигурацию, но Donut Browser не сможет подключиться, пока OpenVPN не будет установлен на этом устройстве.",
"openVpnAdapterMissingTitle": "Отсутствует адаптер OpenVPN",
"openVpnAdapterMissingDescription": "OpenVPN установлен, но адаптер TAP/Wintun/ovpn-dco не найден. Восстановите или переустановите OpenVPN перед подключением в Windows.",
"openVpnCheckFailedTitle": "Не удалось проверить установку OpenVPN",
"openVpnCheckFailedDescription": "Donut Browser не смог проверить локальную установку OpenVPN. Восстановите или переустановите OpenVPN перед подключением в Windows."
}
},
"extensions": {
"title": "Расширения",
"description": "Управляйте расширениями браузера и группами расширений для ваших профилей.",
+10
View File
@@ -794,6 +794,16 @@
"button": "克隆"
}
},
"vpnForm": {
"dependencies": {
"openVpnMissingTitle": "未安装 OpenVPN",
"openVpnMissingDescription": "你现在可以保存这个配置,但在此设备上安装 OpenVPN 之前,Donut Browser 无法连接它。",
"openVpnAdapterMissingTitle": "缺少 OpenVPN 适配器",
"openVpnAdapterMissingDescription": "已安装 OpenVPN,但未找到 TAP/Wintun/ovpn-dco 适配器。在 Windows 上连接前,请修复或重新安装 OpenVPN。",
"openVpnCheckFailedTitle": "无法验证 OpenVPN 安装",
"openVpnCheckFailedDescription": "Donut Browser 无法检查本机 OpenVPN 安装。在 Windows 上连接前,请修复或重新安装 OpenVPN。"
}
},
"extensions": {
"title": "扩展程序",
"description": "管理配置文件的浏览器扩展程序和扩展程序组。",