feat: add rustfmt configuration and CI workflow for code formatting

refactor: streamline formatting workflow by removing unused taplo steps and clarifying directory change

refactor: remove unnecessary directory change step in formatting workflow
This commit is contained in:
Tunglies
2025-06-06 21:11:14 +08:00
parent 689042df60
commit 09969d95de
89 changed files with 2630 additions and 2008 deletions

View File

@@ -82,21 +82,21 @@ export const BackupConfigViewer = memo(
if (!url) {
urlRef.current?.focus();
showNotice('error', t("WebDAV URL Required"));
showNotice("error", t("WebDAV URL Required"));
throw new Error(t("WebDAV URL Required"));
} else if (!isValidUrl(url)) {
urlRef.current?.focus();
showNotice('error', t("Invalid WebDAV URL"));
showNotice("error", t("Invalid WebDAV URL"));
throw new Error(t("Invalid WebDAV URL"));
}
if (!username) {
usernameRef.current?.focus();
showNotice('error', t("WebDAV URL Required"));
showNotice("error", t("WebDAV URL Required"));
throw new Error(t("Username Required"));
}
if (!password) {
passwordRef.current?.focus();
showNotice('error', t("WebDAV URL Required"));
showNotice("error", t("WebDAV URL Required"));
throw new Error(t("Password Required"));
}
};
@@ -110,11 +110,11 @@ export const BackupConfigViewer = memo(
data.username.trim(),
data.password,
).then(() => {
showNotice('success', t("WebDAV Config Saved"));
showNotice("success", t("WebDAV Config Saved"));
onSaveSuccess();
});
} catch (error) {
showNotice('error', t("WebDAV Config Save Failed", { error }), 3000);
showNotice("error", t("WebDAV Config Save Failed", { error }), 3000);
} finally {
setLoading(false);
}
@@ -125,11 +125,11 @@ export const BackupConfigViewer = memo(
try {
setLoading(true);
await createWebdavBackup().then(async () => {
showNotice('success', t("Backup Created"));
showNotice("success", t("Backup Created"));
await onBackupSuccess();
});
} catch (error) {
showNotice('error', t("Backup Failed", { error }));
showNotice("error", t("Backup Failed", { error }));
} finally {
setLoading(false);
}

View File

@@ -61,7 +61,7 @@ export const BackupTableViewer = memo(
const handleRestore = useLockFn(async (filename: string) => {
await restoreWebDavBackup(filename).then(() => {
showNotice('success', t("Restore Success, App will restart in 1s"));
showNotice("success", t("Restore Success, App will restart in 1s"));
});
await restartApp();
});

View File

@@ -52,7 +52,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
const errorMsg = await changeClashCore(core);
if (errorMsg) {
showNotice('error', errorMsg);
showNotice("error", errorMsg);
setChangingCore(null);
return;
}
@@ -65,7 +65,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
}, 500);
} catch (err: any) {
setChangingCore(null);
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
});
@@ -73,11 +73,11 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
try {
setRestarting(true);
await restartCore();
showNotice('success', t(`Clash Core Restarted`));
showNotice("success", t(`Clash Core Restarted`));
setRestarting(false);
} catch (err: any) {
setRestarting(false);
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
});
@@ -86,14 +86,14 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
setUpgrading(true);
await upgradeCore();
setUpgrading(false);
showNotice('success', t(`Core Version Updated`));
showNotice("success", t(`Core Version Updated`));
} catch (err: any) {
setUpgrading(false);
const errMsg = err.response?.data?.message || err.toString();
const showMsg = errMsg.includes("already using latest version")
? "Already Using Latest Core Version"
: errMsg;
showNotice('error', t(showMsg));
showNotice("error", t(showMsg));
}
});

View File

@@ -11,7 +11,7 @@ import {
ListItem,
ListItemText,
Stack,
TextField
TextField,
} from "@mui/material";
import { useLockFn, useRequest } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react";
@@ -26,127 +26,136 @@ interface ClashPortViewerRef {
close: () => void;
}
const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
const generateRandomPort = () =>
Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
export const ClashPortViewer = forwardRef<ClashPortViewerRef, ClashPortViewerProps>(
(props, ref) => {
const { t } = useTranslation();
const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge();
const [open, setOpen] = useState(false);
export const ClashPortViewer = forwardRef<
ClashPortViewerRef,
ClashPortViewerProps
>((props, ref) => {
const { t } = useTranslation();
const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge();
const [open, setOpen] = useState(false);
// Mixed Port
const [mixedPort, setMixedPort] = useState(
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
);
// Mixed Port
const [mixedPort, setMixedPort] = useState(
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897,
);
// 其他端口状态
const [socksPort, setSocksPort] = useState(verge?.verge_socks_port ?? 7898);
const [socksEnabled, setSocksEnabled] = useState(verge?.verge_socks_enabled ?? false);
const [httpPort, setHttpPort] = useState(verge?.verge_port ?? 7899);
const [httpEnabled, setHttpEnabled] = useState(verge?.verge_http_enabled ?? false);
const [redirPort, setRedirPort] = useState(verge?.verge_redir_port ?? 7895);
const [redirEnabled, setRedirEnabled] = useState(verge?.verge_redir_enabled ?? false);
const [tproxyPort, setTproxyPort] = useState(verge?.verge_tproxy_port ?? 7896);
const [tproxyEnabled, setTproxyEnabled] = useState(verge?.verge_tproxy_enabled ?? false);
// 其他端口状态
const [socksPort, setSocksPort] = useState(verge?.verge_socks_port ?? 7898);
const [socksEnabled, setSocksEnabled] = useState(
verge?.verge_socks_enabled ?? false,
);
const [httpPort, setHttpPort] = useState(verge?.verge_port ?? 7899);
const [httpEnabled, setHttpEnabled] = useState(
verge?.verge_http_enabled ?? false,
);
const [redirPort, setRedirPort] = useState(verge?.verge_redir_port ?? 7895);
const [redirEnabled, setRedirEnabled] = useState(
verge?.verge_redir_enabled ?? false,
);
const [tproxyPort, setTproxyPort] = useState(
verge?.verge_tproxy_port ?? 7896,
);
const [tproxyEnabled, setTproxyEnabled] = useState(
verge?.verge_tproxy_enabled ?? false,
);
// 添加保存请求防止GUI卡死
const { loading, run: saveSettings } = useRequest(
async (params: {
clashConfig: any;
vergeConfig: any;
}) => {
const { clashConfig, vergeConfig } = params;
await Promise.all([
patchInfo(clashConfig),
patchVerge(vergeConfig)
]);
// 添加保存请求防止GUI卡死
const { loading, run: saveSettings } = useRequest(
async (params: { clashConfig: any; vergeConfig: any }) => {
const { clashConfig, vergeConfig } = params;
await Promise.all([patchInfo(clashConfig), patchVerge(vergeConfig)]);
},
{
manual: true,
onSuccess: () => {
setOpen(false);
showNotice("success", t("Port settings saved")); // 调用提示函数
},
{
manual: true,
onSuccess: () => {
setOpen(false);
showNotice("success", t("Port settings saved")); // 调用提示函数
},
onError: () => {
showNotice("error", t("Failed to save settings")); // 调用提示函数
}
}
);
useImperativeHandle(ref, () => ({
open: () => {
setMixedPort(verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897);
setSocksPort(verge?.verge_socks_port ?? 7898);
setSocksEnabled(verge?.verge_socks_enabled ?? false);
setHttpPort(verge?.verge_port ?? 7899);
setHttpEnabled(verge?.verge_http_enabled ?? false);
setRedirPort(verge?.verge_redir_port ?? 7895);
setRedirEnabled(verge?.verge_redir_enabled ?? false);
setTproxyPort(verge?.verge_tproxy_port ?? 7896);
setTproxyEnabled(verge?.verge_tproxy_enabled ?? false);
setOpen( true);
onError: () => {
showNotice("error", t("Failed to save settings")); // 调用提示函数
},
close: () => setOpen(false),
}));
},
);
const onSave = useLockFn(async () => {
// 端口冲突检测
const portList = [
mixedPort,
socksEnabled ? socksPort : -1,
httpEnabled ? httpPort : -1,
redirEnabled ? redirPort : -1,
tproxyEnabled ? tproxyPort : -1
].filter(p => p !== -1);
useImperativeHandle(ref, () => ({
open: () => {
setMixedPort(verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897);
setSocksPort(verge?.verge_socks_port ?? 7898);
setSocksEnabled(verge?.verge_socks_enabled ?? false);
setHttpPort(verge?.verge_port ?? 7899);
setHttpEnabled(verge?.verge_http_enabled ?? false);
setRedirPort(verge?.verge_redir_port ?? 7895);
setRedirEnabled(verge?.verge_redir_enabled ?? false);
setTproxyPort(verge?.verge_tproxy_port ?? 7896);
setTproxyEnabled(verge?.verge_tproxy_enabled ?? false);
setOpen(true);
},
close: () => setOpen(false),
}));
if (new Set(portList).size !== portList.length) {
return;
}
const onSave = useLockFn(async () => {
// 端口冲突检测
const portList = [
mixedPort,
socksEnabled ? socksPort : -1,
httpEnabled ? httpPort : -1,
redirEnabled ? redirPort : -1,
tproxyEnabled ? tproxyPort : -1,
].filter((p) => p !== -1);
// 验证端口范围
const isValidPort = (port: number) => port >= 1 && port <= 65535;
const allPortsValid = [
mixedPort,
socksEnabled ? socksPort : 0,
httpEnabled ? httpPort : 0,
redirEnabled ? redirPort : 0,
tproxyEnabled ? tproxyPort : 0
].every(port => port === 0 || isValidPort(port));
if (new Set(portList).size !== portList.length) {
return;
}
if (!allPortsValid) {
return;
}
// 验证端口范围
const isValidPort = (port: number) => port >= 1 && port <= 65535;
const allPortsValid = [
mixedPort,
socksEnabled ? socksPort : 0,
httpEnabled ? httpPort : 0,
redirEnabled ? redirPort : 0,
tproxyEnabled ? tproxyPort : 0,
].every((port) => port === 0 || isValidPort(port));
// 准备配置数据
const clashConfig = {
"mixed-port": mixedPort,
"socks-port": socksPort,
port: httpPort,
"redir-port": redirPort,
"tproxy-port": tproxyPort
};
if (!allPortsValid) {
return;
}
const vergeConfig = {
verge_mixed_port: mixedPort,
verge_socks_port: socksPort,
verge_socks_enabled: socksEnabled,
verge_port: httpPort,
verge_http_enabled: httpEnabled,
verge_redir_port: redirPort,
verge_redir_enabled: redirEnabled,
verge_tproxy_port: tproxyPort,
verge_tproxy_enabled: tproxyEnabled
};
// 准备配置数据
const clashConfig = {
"mixed-port": mixedPort,
"socks-port": socksPort,
port: httpPort,
"redir-port": redirPort,
"tproxy-port": tproxyPort,
};
// 提交保存请求
await saveSettings({ clashConfig, vergeConfig });
});
const vergeConfig = {
verge_mixed_port: mixedPort,
verge_socks_port: socksPort,
verge_socks_enabled: socksEnabled,
verge_port: httpPort,
verge_http_enabled: httpEnabled,
verge_redir_port: redirPort,
verge_redir_enabled: redirEnabled,
verge_tproxy_port: tproxyPort,
verge_tproxy_enabled: tproxyEnabled,
};
// 优化的数字输入处理
const handleNumericChange = (setter: (value: number) => void) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\D+/, '');
if (value === '') {
// 提交保存请求
await saveSettings({ clashConfig, vergeConfig });
});
// 优化的数字输入处理
const handleNumericChange =
(setter: (value: number) => void) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\D+/, "");
if (value === "") {
setter(0);
return;
}
@@ -157,190 +166,201 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef, ClashPortViewerPro
}
};
return (
<BaseDialog
open={open}
title={t("Port Configuration")}
contentSx={{
width: 400
}}
okBtn={
loading ? (
<Stack direction="row" alignItems="center" spacing={1}>
<CircularProgress size={20} />
{t("Saving...")}
</Stack>
) : t("Save")
}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List sx={{ width: "100%" }}>
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Mixed Port")}
primaryTypographyProps={{ fontSize: 12 }}
return (
<BaseDialog
open={open}
title={t("Port Configuration")}
contentSx={{
width: 400,
}}
okBtn={
loading ? (
<Stack direction="row" alignItems="center" spacing={1}>
<CircularProgress size={20} />
{t("Saving...")}
</Stack>
) : (
t("Save")
)
}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List sx={{ width: "100%" }}>
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Mixed Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={mixedPort}
onChange={(e) =>
setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
inputProps={{ style: { fontSize: 12 } }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={mixedPort}
onChange={(e) => setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setMixedPort(generateRandomPort())}
title={t("Random Port")}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={true}
disabled={true}
sx={{ ml: 0.5, opacity: 0.7 }}
/>
</div>
</ListItem>
<IconButton
size="small"
onClick={() => setMixedPort(generateRandomPort())}
title={t("Random Port")}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={true}
disabled={true}
sx={{ ml: 0.5, opacity: 0.7 }}
/>
</div>
</ListItem>
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Socks Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={socksPort}
onChange={(e) =>
setSocksPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
disabled={!socksEnabled}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setSocksPort(generateRandomPort())}
title={t("Random Port")}
disabled={!socksEnabled}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={socksEnabled}
onChange={(_, c) => setSocksEnabled(c)}
sx={{ ml: 0.5 }}
/>
</div>
</ListItem>
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("HTTP Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={httpPort}
onChange={(e) =>
setHttpPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
disabled={!httpEnabled}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setHttpPort(generateRandomPort())}
title={t("Random Port")}
disabled={!httpEnabled}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={httpEnabled}
onChange={(_, c) => setHttpEnabled(c)}
sx={{ ml: 0.5 }}
/>
</div>
</ListItem>
{OS !== "windows" && (
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Socks Port")}
primary={t("Redir Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={socksPort}
onChange={(e) => setSocksPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
disabled={!socksEnabled}
value={redirPort}
onChange={(e) =>
setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
disabled={!redirEnabled}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setSocksPort(generateRandomPort())}
onClick={() => setRedirPort(generateRandomPort())}
title={t("Random Port")}
disabled={!socksEnabled}
disabled={!redirEnabled}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={socksEnabled}
onChange={(_, c) => setSocksEnabled(c)}
checked={redirEnabled}
onChange={(_, c) => setRedirEnabled(c)}
sx={{ ml: 0.5 }}
/>
</div>
</ListItem>
)}
{OS === "linux" && (
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("HTTP Port")}
primary={t("Tproxy Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={httpPort}
onChange={(e) => setHttpPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
disabled={!httpEnabled}
value={tproxyPort}
onChange={(e) =>
setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
disabled={!tproxyEnabled}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setHttpPort(generateRandomPort())}
onClick={() => setTproxyPort(generateRandomPort())}
title={t("Random Port")}
disabled={!httpEnabled}
disabled={!tproxyEnabled}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={httpEnabled}
onChange={(_, c) => setHttpEnabled(c)}
checked={tproxyEnabled}
onChange={(_, c) => setTproxyEnabled(c)}
sx={{ ml: 0.5 }}
/>
</div>
</ListItem>
{OS !== "windows" && (
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Redir Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={redirPort}
onChange={(e) => setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
disabled={!redirEnabled}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setRedirPort(generateRandomPort())}
title={t("Random Port")}
disabled={!redirEnabled}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={redirEnabled}
onChange={(_, c) => setRedirEnabled(c)}
sx={{ ml: 0.5 }}
/>
</div>
</ListItem>
)}
{OS === "linux" && (
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Tproxy Port")}
primaryTypographyProps={{ fontSize: 12 }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
size="small"
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
value={tproxyPort}
onChange={(e) => setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
disabled={!tproxyEnabled}
inputProps={{ style: { fontSize: 12 } }}
/>
<IconButton
size="small"
onClick={() => setTproxyPort(generateRandomPort())}
title={t("Random Port")}
disabled={!tproxyEnabled}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
</IconButton>
<Switch
size="small"
checked={tproxyEnabled}
onChange={(_, c) => setTproxyEnabled(c)}
sx={{ ml: 0.5 }}
/>
</div>
</ListItem>
)}
</List>
</BaseDialog>
);
}
);
)}
</List>
</BaseDialog>
);
});

View File

@@ -13,7 +13,7 @@ import {
ListItemText,
Snackbar,
TextField,
Tooltip
Tooltip,
} from "@mui/material";
import { useLockFn } from "ahooks";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
@@ -42,58 +42,72 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
// 保存配置
const onSave = useLockFn(async () => {
if (!controller.trim()) {
showNotice('error', t("Controller address cannot be empty"), 3000);
showNotice("error", t("Controller address cannot be empty"), 3000);
return;
}
if (!secret.trim()) {
showNotice('error', t("Secret cannot be empty"), 3000);
showNotice("error", t("Secret cannot be empty"), 3000);
return;
}
try {
setIsSaving(true);
await patchInfo({ "external-controller": controller, secret });
showNotice('success', t("Configuration saved successfully"), 2000);
showNotice("success", t("Configuration saved successfully"), 2000);
setOpen(false);
} catch (err: any) {
showNotice('error', err.message || t("Failed to save configuration"), 4000);
showNotice(
"error",
err.message || t("Failed to save configuration"),
4000,
);
} finally {
setIsSaving(false);
}
});
// 复制到剪贴板
const handleCopyToClipboard = useLockFn(async (text: string, type: string) => {
try {
await navigator.clipboard.writeText(text);
setCopySuccess(type);
setTimeout(() => setCopySuccess(null), 2000);
} catch (err) {
showNotice('error', t("Failed to copy"), 2000);
}
});
const handleCopyToClipboard = useLockFn(
async (text: string, type: string) => {
try {
await navigator.clipboard.writeText(text);
setCopySuccess(type);
setTimeout(() => setCopySuccess(null), 2000);
} catch (err) {
showNotice("error", t("Failed to copy"), 2000);
}
},
);
return (
<BaseDialog
open={open}
title={t("External Controller")}
contentSx={{ width: 400 }}
okBtn={isSaving ? (
<Box display="flex" alignItems="center" gap={1}>
<CircularProgress size={16} color="inherit" />
{t("Saving...")}
</Box>
) : (
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)}
onOk={onSave}
>
<List>
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
<ListItem
sx={{
padding: "5px 2px",
display: "flex",
justifyContent: "space-between",
}}
>
<ListItemText primary={t("External Controller")} />
<Box display="flex" alignItems="center" gap={1}>
<TextField
@@ -101,11 +115,11 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
sx={{
width: 175,
opacity: 1,
pointerEvents: 'auto'
pointerEvents: "auto",
}}
value={controller}
placeholder="Required"
onChange={e => setController(e.target.value)}
onChange={(e) => setController(e.target.value)}
disabled={isSaving}
/>
<Tooltip title={t("Copy to clipboard")}>
@@ -121,7 +135,13 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
</Box>
</ListItem>
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
<ListItem
sx={{
padding: "5px 2px",
display: "flex",
justifyContent: "space-between",
}}
>
<ListItemText primary={t("Core Secret")} />
<Box display="flex" alignItems="center" gap={1}>
<TextField
@@ -129,11 +149,11 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
sx={{
width: 175,
opacity: 1,
pointerEvents: 'auto'
pointerEvents: "auto",
}}
value={secret}
placeholder={t("Recommended")}
onChange={e => setSecret(e.target.value)}
onChange={(e) => setSecret(e.target.value)}
disabled={isSaving}
/>
<Tooltip title={t("Copy to clipboard")}>
@@ -153,13 +173,12 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
<Snackbar
open={copySuccess !== null}
autoHideDuration={2000}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert severity="success">
{copySuccess === "controller"
? t("Controller address copied to clipboard")
: t("Secret copied to clipboard")
}
: t("Secret copied to clipboard")}
</Alert>
</Snackbar>
</BaseDialog>

View File

@@ -59,7 +59,13 @@ const DEFAULT_DNS_CONFIG = {
"*.msftncsi.com",
"www.msftconnecttest.com",
],
"default-nameserver": ["system", "223.6.6.6", "8.8.8.8", "2400:3200::1", "2001:4860:4860::8888"],
"default-nameserver": [
"system",
"223.6.6.6",
"8.8.8.8",
"2400:3200::1",
"2001:4860:4860::8888",
],
nameserver: [
"8.8.8.8",
"https://doh.pub/dns-query",
@@ -70,7 +76,7 @@ const DEFAULT_DNS_CONFIG = {
"proxy-server-nameserver": [
"https://doh.pub/dns-query",
"https://dns.alidns.com/dns-query",
"tls://223.5.5.5"
"tls://223.5.5.5",
],
"direct-nameserver": [],
"direct-nameserver-follow-policy": false,
@@ -219,8 +225,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
dnsConfig["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
dnsConfig["use-system-hosts"] ??
DEFAULT_DNS_CONFIG["use-system-hosts"],
dnsConfig["use-system-hosts"] ?? DEFAULT_DNS_CONFIG["use-system-hosts"],
ipv6: dnsConfig.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6,
fakeIpFilter:
dnsConfig["fake-ip-filter"]?.join(", ") ??
@@ -229,7 +234,8 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
dnsConfig.nameserver?.join(", ") ??
DEFAULT_DNS_CONFIG.nameserver.join(", "),
fallback:
dnsConfig.fallback?.join(", ") ?? DEFAULT_DNS_CONFIG.fallback.join(", "),
dnsConfig.fallback?.join(", ") ??
DEFAULT_DNS_CONFIG.fallback.join(", "),
defaultNameserver:
dnsConfig["default-nameserver"]?.join(", ") ??
DEFAULT_DNS_CONFIG["default-nameserver"].join(", "),
@@ -299,9 +305,8 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
// 从表单值更新YAML内容
const updateYamlFromValues = () => {
const config: Record<string, any> = {};
const dnsConfig = generateDnsConfig();
if (Object.keys(dnsConfig).length > 0) {
config.dns = dnsConfig;
@@ -311,7 +316,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
if (Object.keys(hosts).length > 0) {
config.hosts = hosts;
}
setYamlContent(yaml.dump(config, { forceQuotes: true }));
};
@@ -320,10 +325,10 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
try {
const parsedYaml = yaml.load(yamlContent) as any;
if (!parsedYaml) return;
updateValuesFromConfig(parsedYaml);
} catch (err: any) {
showNotice('error', t("Invalid YAML format"));
showNotice("error", t("Invalid YAML format"));
}
};
@@ -505,7 +510,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
if (Object.keys(dnsConfig).length > 0) {
config.dns = dnsConfig;
}
const hosts = parseHosts(values.hosts);
if (Object.keys(hosts).length > 0) {
config.hosts = hosts;
@@ -521,30 +526,41 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
// 保存配置
await invoke("save_dns_config", { dnsConfig: config });
// 验证配置
const [isValid, errorMsg] = await invoke<[boolean, string]>("validate_dns_config", {});
const [isValid, errorMsg] = await invoke<[boolean, string]>(
"validate_dns_config",
{},
);
if (!isValid) {
let cleanErrorMsg = errorMsg;
// 提取关键错误信息
if (errorMsg.includes("level=error")) {
const errorLines = errorMsg.split('\n').filter(line =>
line.includes("level=error") ||
line.includes("level=fatal") ||
line.includes("failed")
);
const errorLines = errorMsg
.split("\n")
.filter(
(line) =>
line.includes("level=error") ||
line.includes("level=fatal") ||
line.includes("failed"),
);
if (errorLines.length > 0) {
cleanErrorMsg = errorLines.map(line => {
const msgMatch = line.match(/msg="([^"]+)"/);
return msgMatch ? msgMatch[1] : line;
}).join(", ");
cleanErrorMsg = errorLines
.map((line) => {
const msgMatch = line.match(/msg="([^"]+)"/);
return msgMatch ? msgMatch[1] : line;
})
.join(", ");
}
}
showNotice('error', t("DNS configuration error") + ": " + cleanErrorMsg);
showNotice(
"error",
t("DNS configuration error") + ": " + cleanErrorMsg,
);
return;
}
@@ -555,9 +571,9 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
}
setOpen(false);
showNotice('success', t("DNS settings saved"));
showNotice("success", t("DNS settings saved"));
} catch (err: any) {
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
});

View File

@@ -80,7 +80,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
});
setOpen(false);
} catch (err: any) {
showNotice('error', err.toString());
showNotice("error", err.toString());
}
});

View File

@@ -88,7 +88,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
const onSwitchFormat = (_e: any, value: boolean) => value;
const onError = (err: any) => {
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
};
const onChangeData = (patch: Partial<IVergeConfig>) => {
mutateVerge({ ...verge, ...patch }, false);

View File

@@ -44,7 +44,7 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
});
setOpen(false);
} catch (err: any) {
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
});

View File

@@ -62,7 +62,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
});
setOpen(false);
} catch (err: any) {
showNotice('error', err.toString());
showNotice("error", err.toString());
}
});
@@ -246,7 +246,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
endAdornment: (
<InputAdornment position="end">{t("millis")}</InputAdornment>
),
}
},
}}
/>
</ListItem>

View File

@@ -129,7 +129,7 @@ const AddressDisplay = (props: { label: string; content: string }) => {
size="small"
onClick={async () => {
await writeText(props.content);
showNotice('success', t("Copy Success"));
showNotice("success", t("Copy Success"));
}}
>
<ContentCopyRounded sx={{ fontSize: "18px" }} />

View File

@@ -202,11 +202,14 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const onSave = useLockFn(async () => {
if (value.duration < 1) {
showNotice('error', t("Proxy Daemon Duration Cannot be Less than 1 Second"));
showNotice(
"error",
t("Proxy Daemon Duration Cannot be Less than 1 Second"),
);
return;
}
if (value.bypass && !validReg.test(value.bypass)) {
showNotice('error', t("Invalid Bypass Format"));
showNotice("error", t("Invalid Bypass Format"));
return;
}
@@ -223,7 +226,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
!ipv6Regex.test(value.proxy_host) &&
!hostnameRegex.test(value.proxy_host)
) {
showNotice('error', t("Invalid Proxy Host Format"));
showNotice("error", t("Invalid Proxy Host Format"));
return;
}
@@ -271,41 +274,44 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
}
// 判断是否需要重置系统代理
const needResetProxy =
value.pac !== proxy_auto_config ||
proxyHost !== proxy_host ||
pacContent !== pac_file_content ||
const needResetProxy =
value.pac !== proxy_auto_config ||
proxyHost !== proxy_host ||
pacContent !== pac_file_content ||
value.bypass !== system_proxy_bypass ||
value.use_default !== use_default_bypass;
await patchVerge(patch);
// 更新系统代理状态以便UI立即反映变化
await Promise.all([mutate("getSystemProxy"), mutate("getAutotemProxy")]);
// 只有在修改了影响系统代理的配置且系统代理当前启用时,才重置系统代理
if (needResetProxy) {
const currentSysProxy = await getSystemProxy();
const currentAutoProxy = await getAutotemProxy();
if (value.pac ? currentAutoProxy?.enable : currentSysProxy?.enable) {
// 临时关闭系统代理
await patchVergeConfig({ enable_system_proxy: false });
// 减少等待时间
await new Promise((resolve) => setTimeout(resolve, 200));
// 重新开启系统代理
await patchVergeConfig({ enable_system_proxy: true });
// 更新UI状态
await Promise.all([mutate("getSystemProxy"), mutate("getAutotemProxy")]);
await Promise.all([
mutate("getSystemProxy"),
mutate("getAutotemProxy"),
]);
}
}
setOpen(false);
} catch (err: any) {
showNotice('error', err.toString());
showNotice("error", err.toString());
} finally {
setSaving(false);
}
@@ -415,7 +421,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
slotProps={{
input: {
endAdornment: <InputAdornment position="end">s</InputAdornment>,
}
},
}}
onChange={(e) => {
setValue((v) => ({
@@ -432,12 +438,14 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
edge="end"
disabled={!enabled}
checked={value.use_default}
onChange={(_, e) => setValue((v) => ({
...v,
use_default: e,
// 当取消选择use_default且当前bypass为空时填充默认值
bypass: (!e && !v.bypass) ? defaultBypass() : v.bypass
}))}
onChange={(_, e) =>
setValue((v) => ({
...v,
use_default: e,
// 当取消选择use_default且当前bypass为空时填充默认值
bypass: !e && !v.bypass ? defaultBypass() : v.bypass,
}))
}
/>
</ListItem>
)}

View File

@@ -49,7 +49,7 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
await patchVerge({ theme_setting: theme });
setOpen(false);
} catch (err: any) {
showNotice('error', err.toString());
showNotice("error", err.toString());
}
});

View File

@@ -77,13 +77,13 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
);
try {
await enhanceProfiles();
showNotice('success', t("Settings Applied"));
showNotice("success", t("Settings Applied"));
} catch (err: any) {
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
setOpen(false);
} catch (err: any) {
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
});

View File

@@ -1,5 +1,11 @@
import useSWR from "swr";
import { forwardRef, useImperativeHandle, useState, useMemo, useEffect } from "react";
import {
forwardRef,
useImperativeHandle,
useState,
useMemo,
useEffect,
} from "react";
import { useLockFn } from "ahooks";
import { Box, LinearProgress, Button } from "@mui/material";
import { useTranslation } from "react-i18next";
@@ -18,7 +24,8 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [currentProgressListener, setCurrentProgressListener] = useState<UnlistenFn | null>(null);
const [currentProgressListener, setCurrentProgressListener] =
useState<UnlistenFn | null>(null);
const updateState = useUpdateState();
const setUpdateState = useSetUpdateState();
@@ -55,12 +62,12 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
const onUpdate = useLockFn(async () => {
if (portableFlag) {
showNotice('error', t("Portable Updater Error"));
showNotice("error", t("Portable Updater Error"));
return;
}
if (!updateInfo?.body) return;
if (breakChangeFlag) {
showNotice('error', t("Break Change Update Error"));
showNotice("error", t("Break Change Update Error"));
return;
}
if (updateState) return;
@@ -86,7 +93,7 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
await updateInfo.downloadAndInstall();
await relaunch();
} catch (err: any) {
showNotice('error', err?.message || err.toString());
showNotice("error", err?.message || err.toString());
} finally {
setUpdateState(false);
if (progressListener) {

View File

@@ -7,10 +7,7 @@ import { updateGeoData } from "@/services/api";
import { invoke_uwp_tool } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
import getSystem from "@/utils/get-system";
import {
LanRounded,
SettingsRounded
} from "@mui/icons-material";
import { LanRounded, SettingsRounded } from "@mui/icons-material";
import { MenuItem, Select, TextField, Typography } from "@mui/material";
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
@@ -71,9 +68,9 @@ const SettingClash = ({ onError }: Props) => {
const onUpdateGeo = async () => {
try {
await updateGeoData();
showNotice('success', t("GeoData Updated"));
showNotice("success", t("GeoData Updated"));
} catch (err: any) {
showNotice('error', err?.response.data.message || err.toString());
showNotice("error", err?.response.data.message || err.toString());
}
};
@@ -90,7 +87,7 @@ const SettingClash = ({ onError }: Props) => {
} catch (err: any) {
setDnsSettingsEnabled(!enable);
localStorage.setItem("dns_settings_enabled", String(!enable));
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
await patchVerge({ enable_dns_settings: !enable }).catch(() => {});
throw err;
}
@@ -203,9 +200,7 @@ const SettingClash = ({ onError }: Props) => {
</GuardState>
</SettingItem>
<SettingItem
label={t("Port Config")}
>
<SettingItem label={t("Port Config")}>
<TextField
autoComplete="new-password"
disabled={false}
@@ -225,7 +220,9 @@ const SettingClash = ({ onError }: Props) => {
<>
{t("External")}
<TooltipIcon
title={t("Enable one-click random API port and key. Click to randomize the port and key")}
title={t(
"Enable one-click random API port and key. Click to randomize the port and key",
)}
sx={{ opacity: "0.7" }}
/>
</>

View File

@@ -47,18 +47,18 @@ const SettingVergeAdvanced = ({ onError }: Props) => {
try {
const info = await checkUpdate();
if (!info?.available) {
showNotice('success', t("Currently on the Latest Version"));
showNotice("success", t("Currently on the Latest Version"));
} else {
updateRef.current?.open();
}
} catch (err: any) {
showNotice('error', err.message || err.toString());
showNotice("error", err.message || err.toString());
}
};
const onExportDiagnosticInfo = useCallback(async () => {
await exportDiagnosticInfo();
showNotice('success', t("Copy Success"), 1000);
showNotice("success", t("Copy Success"), 1000);
}, []);
return (
@@ -110,7 +110,10 @@ const SettingVergeAdvanced = ({ onError }: Props) => {
<SettingItem
label={t("LightWeight Mode Settings")}
extra={
<TooltipIcon title={t("LightWeight Mode Info")} sx={{ opacity: "0.7" }} />
<TooltipIcon
title={t("LightWeight Mode Info")}
sx={{ opacity: "0.7" }}
/>
}
onClick={() => liteModeRef.current?.open()}
/>

View File

@@ -69,7 +69,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
const onCopyClashEnv = useCallback(async () => {
await copyClashEnv();
showNotice('success', t("Copy Success"), 1000);
showNotice("success", t("Copy Success"), 1000);
}, []);
return (