diff --git a/UPDATELOG.md b/UPDATELOG.md index ed808260..30e82255 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -77,6 +77,8 @@ - 异步化配置:优化端口查找和配置保存逻辑 - 重构事件通知机制到独立线程,避免前端卡死 - 优化端口设置,每个端口可随机设置端口号 + - 优化了随机端口和密钥机制,防止随机时卡死! + - 优化了保存机制,使用平滑函数,防止客户端卡死!优化了翻译问题! ## v2.2.3 diff --git a/src/components/setting/mods/controller-viewer.tsx b/src/components/setting/mods/controller-viewer.tsx index 98b83f66..988d81e8 100644 --- a/src/components/setting/mods/controller-viewer.tsx +++ b/src/components/setting/mods/controller-viewer.tsx @@ -3,11 +3,13 @@ import { useClashInfo } from "@/hooks/use-clash"; import { showNotice } from "@/services/noticeService"; import { ContentCopy, - RefreshRounded + RefreshRounded, } from "@mui/icons-material"; import { Alert, Box, + Button, + CircularProgress, FormControlLabel, IconButton, List, @@ -39,18 +41,6 @@ const generateRandomPassword = (length: number = 32): string => { return password; }; -// 初始化执行一次随机生成 -const useAppInitialization = (autoGenerate: boolean, onGenerate: () => void) => { - const [initialized, setInitialized] = useState(false); - - useEffect(() => { - if (!initialized && autoGenerate) { - onGenerate(); - setInitialized(true); - } - }, [initialized, autoGenerate, onGenerate]); -}; - export const ControllerViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); @@ -63,44 +53,121 @@ export const ControllerViewer = forwardRef((props, ref) => { const autoGenerate = autoGenerateState!; const [copySuccess, setCopySuccess] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [isRestarting, setIsRestarting] = useState(false); const { clashInfo, patchInfo } = useClashInfo(); const [controller, setController] = useState(clashInfo?.server || ""); const [secret, setSecret] = useState(clashInfo?.secret || ""); - // 初始化生成随机配置 - useAppInitialization(autoGenerate, () => { - const port = generateRandomPort(); - const password = generateRandomPassword(); + // 直接通过API重启内核 + const restartCoreDirectly = useLockFn(async () => { + try { + const controllerUrl = controller || clashInfo?.server || 'http://localhost:9090'; - const host = controller.split(':')[0] || '127.0.0.1'; - const newController = `${host}:${port}`; + const headers: Record = { + 'Content-Type': 'application/json', + }; - setController(newController); - setSecret(password); + if (secret) { + headers['Authorization'] = `Bearer ${secret}`; + } - patchInfo({ "external-controller": newController, secret: password }); + const response = await fetch(`${controllerUrl}/restart`, { + method: 'POST', + headers, + }); - showNotice('info', t("Auto generated new config on startup"), 1000); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(errorText || 'Failed to restart core'); + } + + const contentType = response.headers.get('content-type'); + if (contentType && contentType.includes('application/json')) { + return await response.json(); + } else { + const text = await response.text(); + console.log('Non-JSON response:', text); + return { message: 'Restart request sent successfully' }; + } + } catch (err: any) { + console.error('Error restarting core:', err); + throw err; + } }); + // 生成随机配置并重启内核(静默模式) + const generateAndRestart = useLockFn(async () => { + try { + setIsRestarting(true); + + const port = generateRandomPort(); + const password = generateRandomPassword(); + + const host = controller.split(':')[0] || '127.0.0.1'; + const newController = `${host}:${port}`; + + setController(newController); + setSecret(password); + + // 更新配置 + await patchInfo({ "external-controller": newController, secret: password }); + + // 直接重启内核 + await restartCoreDirectly(); + + // 静默执行,不显示通知 + } catch (err: any) { + showNotice('error', err.message || t("Failed to generate configuration or restart core"), 4000); + } finally { + setIsRestarting(false); + } + }); + + // 仅在对话框打开时生成配置 useImperativeHandle(ref, () => ({ - open: () => { + open: async () => { setOpen(true); - setController(clashInfo?.server || ""); - setSecret(clashInfo?.secret || ""); + + // 如果自动生成开启,则生成新配置 + if (autoGenerate) { + await generateAndRestart(); + } else { + // 否则加载现有配置 + setController(clashInfo?.server || ""); + setSecret(clashInfo?.secret || ""); + } }, close: () => setOpen(false), })); + // 当自动生成开关状态变化时触发 + useEffect(() => { + if (autoGenerate && open) { + generateAndRestart(); + } + }, [autoGenerate, open]); + + // 优化后的保存函数 const onSave = useLockFn(async () => { + if (!controller.trim()) { + showNotice('info', t("Controller address cannot be empty"), 3000); + return; + } + try { + setIsSaving(true); + await patchInfo({ "external-controller": controller, secret }); - showNotice('success', t("External Controller Address Modified"), 1000); + + showNotice('success', t("Configuration saved successfully"), 2000); setOpen(false); } catch (err: any) { - showNotice('error', err.message || err.toString(), 4000); + showNotice('error', err.message || t("Failed to save configuration"), 4000); + } finally { + setIsSaving(false); } }); @@ -141,7 +208,16 @@ export const ControllerViewer = forwardRef((props, ref) => { open={open} title={t("External Controller")} contentSx={{ width: 400 }} - okBtn={t("Save")} + okBtn={ + isSaving ? ( + + + {t("Saving...")} + + ) : ( + t("Save") + ) + } cancelBtn={t("Cancel")} onClose={() => setOpen(false)} onCancel={() => setOpen(false)} @@ -156,7 +232,7 @@ export const ControllerViewer = forwardRef((props, ref) => { size="small" onClick={handleGeneratePort} color="primary" - disabled={autoGenerate} + disabled={autoGenerate || isSaving || isRestarting} > @@ -170,15 +246,15 @@ export const ControllerViewer = forwardRef((props, ref) => { value={controller} placeholder="Required" onChange={(e) => setController(e.target.value)} - disabled={autoGenerate} + disabled={autoGenerate || isSaving || isRestarting} /> {autoGenerate && ( - handleCopyToClipboard(controller, "controller")} color="primary" + disabled={isSaving || isRestarting} > @@ -195,7 +271,7 @@ export const ControllerViewer = forwardRef((props, ref) => { size="small" onClick={handleGenerateSecret} color="primary" - disabled={autoGenerate} + disabled={autoGenerate || isSaving || isRestarting} > @@ -211,7 +287,7 @@ export const ControllerViewer = forwardRef((props, ref) => { onChange={(e) => setSecret(e.target.value?.replace(/[^\x00-\x7F]/g, "")) } - disabled={autoGenerate} + disabled={autoGenerate || isSaving || isRestarting} /> {autoGenerate && ( @@ -219,6 +295,7 @@ export const ControllerViewer = forwardRef((props, ref) => { size="small" onClick={() => handleCopyToClipboard(secret, "secret")} color="primary" + disabled={isSaving || isRestarting} > @@ -228,17 +305,21 @@ export const ControllerViewer = forwardRef((props, ref) => { - + setAutoGenerate(!autoGenerate)} color="primary" + disabled={isSaving || isRestarting} /> } label={autoGenerate ? t("On") : t("Off")} diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index 5eaeec7b..4e3b2b2d 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -11,6 +11,7 @@ import { LanRounded, SettingsRounded } from "@mui/icons-material"; +import ErrorOutlineRounded from '@mui/icons-material/ErrorOutlineRounded'; import { MenuItem, Select, TextField, Typography } from "@mui/material"; import { invoke } from "@tauri-apps/api/core"; import { useLockFn } from "ahooks"; @@ -219,10 +220,20 @@ const SettingClash = ({ onError }: Props) => { /> - ctrlRef.current?.open()} - label={t("External")} - /> + ctrlRef.current?.open()} + label={ + <> + {t("External")} + + + } + /> webRef.current?.open()} label={t("Web UI")} /> diff --git a/src/locales/en.json b/src/locales/en.json index f95737b2..92de208b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -617,12 +617,16 @@ "Unsupported Country/Region": "Unsupported Country/Region", "Failed (Network Connection)": "Failed (Network Connection)", "Auto Random Config": "Auto Random Config", - "Automatically generate new config on application startup": "Automatically generate new config on application startup", + "Generate new config and restart core when entering settings": "Generate new config and restart core when entering settings", "Manual configuration": "Manual configuration", "Controller address copied to clipboard": "Controller address copied to clipboard", "Secret copied to clipboard": "Secret copied to clipboard", "Copy to clipboard": "Copy to clipboard", "Generate Random Secret": "Generate Random Secret", "Generate Random Port": "Generate Random Port", - "Port Config": "Port Config" + "Port Config": "Port Config", + "Configuration saved successfully": "Configuration saved successfully", + "Last generated": "Last generated", + "External Controller Config": "External Controller Config", + "Enable one-click random API port and key. Click to randomize the port and key": "Enable one-click random API port and key. Click to randomize the port and key" } diff --git a/src/locales/zh.json b/src/locales/zh.json index 5fc117f8..16745680 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -616,13 +616,17 @@ "No (IP Banned By Disney+)": "不支持(IP被Disney+禁止)", "Unsupported Country/Region": "不支持的国家/地区", "Failed (Network Connection)": "测试失败(网络连接问题)", - "Auto Random Config": "一键随机端口和密码", - "Automatically generate new config on application startup": "自动随机API端口和密码,重新进入设置即可随机!", + "Auto Random Config": "启动时自动随机端口和密码", + "Generate new config and restart core when entering settings": "自动随机API端口和密码,重新进入设置即可", "Manual configuration": "手动配置", "Controller address copied to clipboard": "API端口已经复制到剪贴板", "Secret copied to clipboard": "API密钥已经复制到剪贴板", "Copy to clipboard": "点击我复制", "Generate Random Secret": "随机API密钥", "Generate Random Port": "随机API端口", - "Port Config": "端口设置" + "Port Config": "端口设置", + "Configuration saved successfully": "随机配置,保存完成", + "Last generated": "记录生成", + "External Controller Config": "API配置", + "Enable one-click random API port and key. Click to randomize the port and key": "开启一键随机API端口和密钥,点进去就可以随机端口和密钥" }