optimized the random port and key mechanism to prevent random freezing! optimized the save mechanism, using smooth functions to prevent the client from freezing! Optimized the translation problem!
This commit is contained in:
@@ -77,6 +77,8 @@
|
||||
- 异步化配置:优化端口查找和配置保存逻辑
|
||||
- 重构事件通知机制到独立线程,避免前端卡死
|
||||
- 优化端口设置,每个端口可随机设置端口号
|
||||
- 优化了随机端口和密钥机制,防止随机时卡死!
|
||||
- 优化了保存机制,使用平滑函数,防止客户端卡死!优化了翻译问题!
|
||||
|
||||
## v2.2.3
|
||||
|
||||
|
||||
@@ -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<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -63,44 +53,121 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const autoGenerate = autoGenerateState!;
|
||||
|
||||
const [copySuccess, setCopySuccess] = useState<null | string>(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<string, string> = {
|
||||
'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<DialogRef>((props, ref) => {
|
||||
open={open}
|
||||
title={t("External Controller")}
|
||||
contentSx={{ width: 400 }}
|
||||
okBtn={t("Save")}
|
||||
okBtn={
|
||||
isSaving ? (
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
{t("Saving...")}
|
||||
</Box>
|
||||
) : (
|
||||
t("Save")
|
||||
)
|
||||
}
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
@@ -156,7 +232,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
size="small"
|
||||
onClick={handleGeneratePort}
|
||||
color="primary"
|
||||
disabled={autoGenerate}
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
>
|
||||
<RefreshRounded fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -170,15 +246,15 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
value={controller}
|
||||
placeholder="Required"
|
||||
onChange={(e) => setController(e.target.value)}
|
||||
disabled={autoGenerate}
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
/>
|
||||
{autoGenerate && (
|
||||
|
||||
<Tooltip title={t("Copy to clipboard")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => handleCopyToClipboard(controller, "controller")}
|
||||
color="primary"
|
||||
disabled={isSaving || isRestarting}
|
||||
>
|
||||
<ContentCopy fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -195,7 +271,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
size="small"
|
||||
onClick={handleGenerateSecret}
|
||||
color="primary"
|
||||
disabled={autoGenerate}
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
>
|
||||
<RefreshRounded fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -211,7 +287,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onChange={(e) =>
|
||||
setSecret(e.target.value?.replace(/[^\x00-\x7F]/g, ""))
|
||||
}
|
||||
disabled={autoGenerate}
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
/>
|
||||
{autoGenerate && (
|
||||
<Tooltip title={t("Copy to clipboard")}>
|
||||
@@ -219,6 +295,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
size="small"
|
||||
onClick={() => handleCopyToClipboard(secret, "secret")}
|
||||
color="primary"
|
||||
disabled={isSaving || isRestarting}
|
||||
>
|
||||
<ContentCopy fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -228,17 +305,21 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
|
||||
<ListItemText primary={t("Auto Random Config")} secondary={
|
||||
autoGenerate
|
||||
? t("Automatically generate new config on application startup")
|
||||
: t("Manual configuration")
|
||||
} />
|
||||
<ListItemText
|
||||
primary={t("Auto Random Config")}
|
||||
secondary={
|
||||
autoGenerate
|
||||
? t("Generate new config and restart core when entering settings")
|
||||
: t("Manual configuration")
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={autoGenerate}
|
||||
onChange={() => setAutoGenerate(!autoGenerate)}
|
||||
color="primary"
|
||||
disabled={isSaving || isRestarting}
|
||||
/>
|
||||
}
|
||||
label={autoGenerate ? t("On") : t("Off")}
|
||||
|
||||
@@ -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) => {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
onClick={() => ctrlRef.current?.open()}
|
||||
label={t("External")}
|
||||
/>
|
||||
<SettingItem
|
||||
onClick={() => ctrlRef.current?.open()}
|
||||
label={
|
||||
<>
|
||||
{t("External")}
|
||||
<TooltipIcon
|
||||
title={t("Enable one-click random API port and key. Click to randomize the port and key")}
|
||||
color={"error"}
|
||||
icon={ErrorOutlineRounded}
|
||||
sx={{ fontSize: "0.875em", ml: 0.5 }}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingItem onClick={() => webRef.current?.open()} label={t("Web UI")} />
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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端口和密钥,点进去就可以随机端口和密钥"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user