2024-12-03 16:18:07 +08:00
|
|
|
|
import { EditRounded } from "@mui/icons-material";
|
2022-09-07 01:51:43 +08:00
|
|
|
|
import {
|
2025-04-16 10:22:53 +08:00
|
|
|
|
Autocomplete,
|
2024-12-03 16:18:07 +08:00
|
|
|
|
Button,
|
2022-09-07 01:51:43 +08:00
|
|
|
|
InputAdornment,
|
|
|
|
|
|
List,
|
|
|
|
|
|
ListItem,
|
|
|
|
|
|
ListItemText,
|
|
|
|
|
|
styled,
|
|
|
|
|
|
TextField,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
} from "@mui/material";
|
2024-12-03 16:18:07 +08:00
|
|
|
|
import { useLockFn } from "ahooks";
|
2025-09-30 18:13:02 +08:00
|
|
|
|
import {
|
|
|
|
|
|
forwardRef,
|
|
|
|
|
|
useEffect,
|
|
|
|
|
|
useImperativeHandle,
|
|
|
|
|
|
useMemo,
|
2025-10-15 18:57:44 +08:00
|
|
|
|
useRef,
|
2025-09-30 18:13:02 +08:00
|
|
|
|
useState,
|
|
|
|
|
|
} from "react";
|
2024-12-03 16:18:07 +08:00
|
|
|
|
import { useTranslation } from "react-i18next";
|
2025-06-21 10:03:06 +08:00
|
|
|
|
import useSWR, { mutate } from "swr";
|
2025-10-08 12:32:40 +08:00
|
|
|
|
import { getBaseConfig } from "tauri-plugin-mihomo-api";
|
2025-06-21 10:03:06 +08:00
|
|
|
|
|
2025-09-30 18:13:02 +08:00
|
|
|
|
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
2025-09-18 23:34:21 +08:00
|
|
|
|
import { BaseFieldset } from "@/components/base/base-fieldset";
|
|
|
|
|
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
|
|
|
|
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
|
|
|
|
|
import { useVerge } from "@/hooks/use-verge";
|
2025-10-04 21:20:31 +08:00
|
|
|
|
import { useAppData } from "@/providers/app-data-context";
|
2025-09-18 23:34:21 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getAutotemProxy,
|
|
|
|
|
|
getNetworkInterfacesInfo,
|
|
|
|
|
|
getSystemHostname,
|
|
|
|
|
|
getSystemProxy,
|
|
|
|
|
|
patchVergeConfig,
|
|
|
|
|
|
} from "@/services/cmds";
|
|
|
|
|
|
import { showNotice } from "@/services/noticeService";
|
|
|
|
|
|
import getSystem from "@/utils/get-system";
|
|
|
|
|
|
|
2025-10-15 18:57:44 +08:00
|
|
|
|
const sleep = (ms: number) =>
|
|
|
|
|
|
new Promise<void>((resolve) => {
|
|
|
|
|
|
setTimeout(resolve, ms);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-05-26 17:59:39 +08:00
|
|
|
|
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
2025-04-16 10:22:53 +08:00
|
|
|
|
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
|
2024-05-26 17:59:39 +08:00
|
|
|
|
}`;
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
2024-07-14 12:26:02 +08:00
|
|
|
|
/** NO_PROXY validation */
|
|
|
|
|
|
|
|
|
|
|
|
// *., cdn*., *, etc.
|
|
|
|
|
|
const domain_subdomain_part = String.raw`(?:[a-z0-9\-\*]+\.|\*)*`;
|
|
|
|
|
|
// .*, .cn, .moe, .co*, *
|
|
|
|
|
|
const domain_tld_part = String.raw`(?:\w{2,64}\*?|\*)`;
|
|
|
|
|
|
// *epicgames*, *skk.moe, *.skk.moe, skk.*, sponsor.cdn.skk.moe, *.*, etc.
|
|
|
|
|
|
// also matches 192.168.*, 10.*, 127.0.0.*, etc. (partial ipv4)
|
|
|
|
|
|
const rDomainSimple = domain_subdomain_part + domain_tld_part;
|
|
|
|
|
|
|
|
|
|
|
|
const ipv4_part = String.raw`\d{1,3}`;
|
|
|
|
|
|
|
|
|
|
|
|
const ipv6_part = "(?:[a-fA-F0-9:])+";
|
|
|
|
|
|
|
|
|
|
|
|
const rLocal = `localhost|<local>|localdomain`;
|
|
|
|
|
|
|
|
|
|
|
|
const getValidReg = (isWindows: boolean) => {
|
2024-07-16 12:22:11 +08:00
|
|
|
|
// 127.0.0.1 (full ipv4)
|
|
|
|
|
|
const rIPv4Unix = String.raw`(?:${ipv4_part}\.){3}${ipv4_part}(?:\/\d{1,2})?`;
|
|
|
|
|
|
const rIPv4Windows = String.raw`(?:${ipv4_part}\.){3}${ipv4_part}`;
|
|
|
|
|
|
|
|
|
|
|
|
const rIPv6Unix = String.raw`(?:${ipv6_part}:+)+${ipv6_part}(?:\/\d{1,3})?`;
|
|
|
|
|
|
const rIPv6Windows = String.raw`(?:${ipv6_part}:+)+${ipv6_part}`;
|
|
|
|
|
|
|
|
|
|
|
|
const rValidPart = `${rDomainSimple}|${
|
|
|
|
|
|
isWindows ? rIPv4Windows : rIPv4Unix
|
|
|
|
|
|
}|${isWindows ? rIPv6Windows : rIPv6Unix}|${rLocal}`;
|
2024-07-14 12:26:02 +08:00
|
|
|
|
const separator = isWindows ? ";" : ",";
|
|
|
|
|
|
const rValid = String.raw`^(${rValidPart})(?:${separator}\s?(${rValidPart}))*${separator}?$`;
|
|
|
|
|
|
|
|
|
|
|
|
return new RegExp(rValid);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-30 18:13:02 +08:00
|
|
|
|
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
2022-09-07 01:51:43 +08:00
|
|
|
|
const { t } = useTranslation();
|
2024-07-14 12:26:02 +08:00
|
|
|
|
const isWindows = getSystem() === "windows";
|
|
|
|
|
|
const validReg = useMemo(() => getValidReg(isWindows), [isWindows]);
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
|
|
|
|
|
const [open, setOpen] = useState(false);
|
2024-05-26 17:59:39 +08:00
|
|
|
|
const [editorOpen, setEditorOpen] = useState(false);
|
2025-04-30 23:09:19 +08:00
|
|
|
|
const [saving, setSaving] = useState(false);
|
2025-06-21 21:38:42 +08:00
|
|
|
|
const { verge, patchVerge, mutateVerge } = useVerge();
|
2025-04-16 10:22:53 +08:00
|
|
|
|
const [hostOptions, setHostOptions] = useState<string[]>([]);
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
2022-11-20 21:48:39 +08:00
|
|
|
|
type SysProxy = Awaited<ReturnType<typeof getSystemProxy>>;
|
|
|
|
|
|
const [sysproxy, setSysproxy] = useState<SysProxy>();
|
|
|
|
|
|
|
2024-05-26 17:59:39 +08:00
|
|
|
|
type AutoProxy = Awaited<ReturnType<typeof getAutotemProxy>>;
|
|
|
|
|
|
const [autoproxy, setAutoproxy] = useState<AutoProxy>();
|
|
|
|
|
|
|
2022-09-07 01:51:43 +08:00
|
|
|
|
const {
|
|
|
|
|
|
enable_system_proxy: enabled,
|
2024-05-26 17:59:39 +08:00
|
|
|
|
proxy_auto_config,
|
|
|
|
|
|
pac_file_content,
|
2022-09-07 01:51:43 +08:00
|
|
|
|
enable_proxy_guard,
|
2024-07-01 22:53:06 +08:00
|
|
|
|
use_default_bypass,
|
2022-09-07 01:51:43 +08:00
|
|
|
|
system_proxy_bypass,
|
|
|
|
|
|
proxy_guard_duration,
|
2025-04-16 10:22:53 +08:00
|
|
|
|
proxy_host,
|
2022-11-20 20:12:58 +08:00
|
|
|
|
} = verge ?? {};
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
|
|
|
|
|
const [value, setValue] = useState({
|
|
|
|
|
|
guard: enable_proxy_guard,
|
|
|
|
|
|
bypass: system_proxy_bypass,
|
|
|
|
|
|
duration: proxy_guard_duration ?? 10,
|
2024-07-01 22:53:06 +08:00
|
|
|
|
use_default: use_default_bypass ?? true,
|
2024-05-26 17:59:39 +08:00
|
|
|
|
pac: proxy_auto_config,
|
|
|
|
|
|
pac_content: pac_file_content ?? DEFAULT_PAC,
|
2025-04-16 10:22:53 +08:00
|
|
|
|
proxy_host: proxy_host ?? "127.0.0.1",
|
2022-09-07 01:51:43 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2024-12-10 14:37:11 +08:00
|
|
|
|
const defaultBypass = () => {
|
|
|
|
|
|
if (isWindows) {
|
|
|
|
|
|
return "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
|
|
|
|
|
|
}
|
|
|
|
|
|
if (getSystem() === "linux") {
|
2024-12-23 06:06:46 +08:00
|
|
|
|
return "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,::1";
|
2024-12-10 14:37:11 +08:00
|
|
|
|
}
|
2024-12-23 06:06:46 +08:00
|
|
|
|
return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,<local>";
|
2024-12-10 14:37:11 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-08 12:32:40 +08:00
|
|
|
|
const { data: clashConfig } = useSWR("getClashConfig", getBaseConfig, {
|
2025-08-22 18:48:56 +08:00
|
|
|
|
revalidateOnFocus: false,
|
|
|
|
|
|
revalidateIfStale: true,
|
|
|
|
|
|
dedupingInterval: 1000,
|
|
|
|
|
|
errorRetryInterval: 5000,
|
|
|
|
|
|
});
|
2025-06-21 10:03:06 +08:00
|
|
|
|
|
2025-10-15 18:57:44 +08:00
|
|
|
|
const prevMixedPortRef = useRef(clashConfig?.mixedPort);
|
2025-06-21 10:03:06 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-10-15 18:57:44 +08:00
|
|
|
|
const mixedPort = clashConfig?.mixedPort;
|
|
|
|
|
|
if (!mixedPort || mixedPort === prevMixedPortRef.current) {
|
|
|
|
|
|
return;
|
2025-06-21 10:03:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-15 18:57:44 +08:00
|
|
|
|
prevMixedPortRef.current = mixedPort;
|
|
|
|
|
|
|
|
|
|
|
|
const updateProxy = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const currentSysProxy = await getSystemProxy();
|
|
|
|
|
|
const currentAutoProxy = await getAutotemProxy();
|
|
|
|
|
|
|
|
|
|
|
|
if (value.pac ? currentAutoProxy?.enable : currentSysProxy?.enable) {
|
|
|
|
|
|
await patchVergeConfig({ enable_system_proxy: false });
|
|
|
|
|
|
await sleep(200);
|
|
|
|
|
|
await patchVergeConfig({ enable_system_proxy: true });
|
|
|
|
|
|
await Promise.all([
|
|
|
|
|
|
mutate("getSystemProxy"),
|
|
|
|
|
|
mutate("getAutotemProxy"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
showNotice("error", err.toString());
|
2025-06-21 10:03:06 +08:00
|
|
|
|
}
|
2025-10-15 18:57:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
updateProxy();
|
|
|
|
|
|
}, [clashConfig?.mixedPort, value.pac]);
|
2025-06-21 10:03:06 +08:00
|
|
|
|
|
2025-06-22 23:19:11 +08:00
|
|
|
|
const { systemProxyAddress } = useAppData();
|
|
|
|
|
|
|
|
|
|
|
|
// 为当前状态计算系统代理地址
|
|
|
|
|
|
const getSystemProxyAddress = useMemo(() => {
|
|
|
|
|
|
if (!clashConfig) return "-";
|
|
|
|
|
|
|
|
|
|
|
|
const isPacMode = value.pac ?? false;
|
|
|
|
|
|
|
|
|
|
|
|
if (isPacMode) {
|
|
|
|
|
|
const host = value.proxy_host || "127.0.0.1";
|
2025-10-08 12:32:40 +08:00
|
|
|
|
const port = verge?.verge_mixed_port || clashConfig.mixedPort || 7897;
|
2025-06-22 23:19:11 +08:00
|
|
|
|
return `${host}:${port}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return systemProxyAddress;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [
|
|
|
|
|
|
value.pac,
|
|
|
|
|
|
value.proxy_host,
|
|
|
|
|
|
verge?.verge_mixed_port,
|
|
|
|
|
|
clashConfig,
|
|
|
|
|
|
systemProxyAddress,
|
|
|
|
|
|
]);
|
|
|
|
|
|
const getCurrentPacUrl = useMemo(() => {
|
|
|
|
|
|
const host = value.proxy_host || "127.0.0.1";
|
|
|
|
|
|
// 根据环境判断PAC端口
|
|
|
|
|
|
const port = import.meta.env.DEV ? 11233 : 33331;
|
|
|
|
|
|
return `http://${host}:${port}/commands/pac`;
|
|
|
|
|
|
}, [value.proxy_host]);
|
|
|
|
|
|
|
2022-11-20 21:48:39 +08:00
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
|
|
|
|
open: () => {
|
|
|
|
|
|
setOpen(true);
|
|
|
|
|
|
setValue({
|
|
|
|
|
|
guard: enable_proxy_guard,
|
|
|
|
|
|
bypass: system_proxy_bypass,
|
|
|
|
|
|
duration: proxy_guard_duration ?? 10,
|
2024-07-01 22:53:06 +08:00
|
|
|
|
use_default: use_default_bypass ?? true,
|
2024-05-26 17:59:39 +08:00
|
|
|
|
pac: proxy_auto_config,
|
|
|
|
|
|
pac_content: pac_file_content ?? DEFAULT_PAC,
|
2025-04-16 10:22:53 +08:00
|
|
|
|
proxy_host: proxy_host ?? "127.0.0.1",
|
2022-11-20 21:48:39 +08:00
|
|
|
|
});
|
|
|
|
|
|
getSystemProxy().then((p) => setSysproxy(p));
|
2024-05-26 17:59:39 +08:00
|
|
|
|
getAutotemProxy().then((p) => setAutoproxy(p));
|
2025-04-16 10:22:53 +08:00
|
|
|
|
fetchNetworkInterfaces();
|
2022-11-20 21:48:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
close: () => setOpen(false),
|
|
|
|
|
|
}));
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
2025-04-16 10:22:53 +08:00
|
|
|
|
// 获取网络接口和主机名
|
|
|
|
|
|
const fetchNetworkInterfaces = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取系统网络接口信息
|
|
|
|
|
|
const interfaces = await getNetworkInterfacesInfo();
|
|
|
|
|
|
const ipAddresses: string[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 从interfaces中提取IPv4和IPv6地址
|
|
|
|
|
|
interfaces.forEach((iface) => {
|
|
|
|
|
|
iface.addr.forEach((address) => {
|
|
|
|
|
|
if (address.V4 && address.V4.ip) {
|
|
|
|
|
|
ipAddresses.push(address.V4.ip);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (address.V6 && address.V6.ip) {
|
|
|
|
|
|
ipAddresses.push(address.V6.ip);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前系统的主机名
|
|
|
|
|
|
let hostname = "";
|
|
|
|
|
|
try {
|
|
|
|
|
|
hostname = await getSystemHostname();
|
|
|
|
|
|
console.log("获取到主机名:", hostname);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error("获取主机名失败:", err);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建选项列表
|
|
|
|
|
|
const options = ["127.0.0.1", "localhost"];
|
|
|
|
|
|
|
|
|
|
|
|
// 确保主机名添加到列表,即使它是空字符串也记录下来
|
|
|
|
|
|
if (hostname) {
|
|
|
|
|
|
// 如果主机名不是localhost或127.0.0.1,则添加它
|
|
|
|
|
|
if (hostname !== "localhost" && hostname !== "127.0.0.1") {
|
|
|
|
|
|
hostname = hostname + ".local";
|
|
|
|
|
|
options.push(hostname);
|
|
|
|
|
|
console.log("主机名已添加到选项中:", hostname);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log("主机名与已有选项重复:", hostname);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log("主机名为空");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加IP地址
|
|
|
|
|
|
options.push(...ipAddresses);
|
|
|
|
|
|
|
|
|
|
|
|
// 去重
|
|
|
|
|
|
const uniqueOptions = Array.from(new Set(options));
|
|
|
|
|
|
console.log("最终选项列表:", uniqueOptions);
|
|
|
|
|
|
setHostOptions(uniqueOptions);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取网络接口失败:", error);
|
|
|
|
|
|
// 失败时至少提供基本选项
|
|
|
|
|
|
setHostOptions(["127.0.0.1", "localhost"]);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-09-07 01:51:43 +08:00
|
|
|
|
const onSave = useLockFn(async () => {
|
2022-11-20 21:48:39 +08:00
|
|
|
|
if (value.duration < 1) {
|
2025-06-06 21:11:14 +08:00
|
|
|
|
showNotice(
|
|
|
|
|
|
"error",
|
2025-11-02 13:10:15 +08:00
|
|
|
|
t("components.settings.sysproxy.messages.durationTooShort"),
|
2025-06-06 21:11:14 +08:00
|
|
|
|
);
|
2022-09-07 01:51:43 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-12-10 14:37:11 +08:00
|
|
|
|
if (value.bypass && !validReg.test(value.bypass)) {
|
2025-11-02 13:10:15 +08:00
|
|
|
|
showNotice(
|
|
|
|
|
|
"error",
|
|
|
|
|
|
t("components.settings.sysproxy.messages.invalidBypass"),
|
|
|
|
|
|
);
|
2024-12-10 14:37:11 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
2025-04-16 10:22:53 +08:00
|
|
|
|
// 修改验证规则,允许IP和主机名
|
|
|
|
|
|
const ipv4Regex =
|
|
|
|
|
|
/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
|
|
|
|
const ipv6Regex =
|
|
|
|
|
|
/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
|
|
|
|
|
const hostnameRegex =
|
2025-08-22 18:48:56 +08:00
|
|
|
|
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
2025-04-16 10:22:53 +08:00
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
!ipv4Regex.test(value.proxy_host) &&
|
|
|
|
|
|
!ipv6Regex.test(value.proxy_host) &&
|
|
|
|
|
|
!hostnameRegex.test(value.proxy_host)
|
|
|
|
|
|
) {
|
2025-11-02 13:10:15 +08:00
|
|
|
|
showNotice(
|
|
|
|
|
|
"error",
|
|
|
|
|
|
t("components.settings.sysproxy.messages.invalidProxyHost"),
|
|
|
|
|
|
);
|
2025-04-16 10:22:53 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-30 23:09:19 +08:00
|
|
|
|
setSaving(true);
|
2025-06-21 21:38:42 +08:00
|
|
|
|
setOpen(false);
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
const patch: Partial<IVergeConfig> = {};
|
2025-04-16 10:22:53 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
if (value.guard !== enable_proxy_guard) {
|
|
|
|
|
|
patch.enable_proxy_guard = value.guard;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value.duration !== proxy_guard_duration) {
|
|
|
|
|
|
patch.proxy_guard_duration = value.duration;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value.bypass !== system_proxy_bypass) {
|
|
|
|
|
|
patch.system_proxy_bypass = value.bypass;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value.pac !== proxy_auto_config) {
|
|
|
|
|
|
patch.proxy_auto_config = value.pac;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value.use_default !== use_default_bypass) {
|
|
|
|
|
|
patch.use_default_bypass = value.use_default;
|
|
|
|
|
|
}
|
2025-06-06 21:11:14 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
let pacContent = value.pac_content;
|
|
|
|
|
|
if (pacContent) {
|
|
|
|
|
|
pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host);
|
|
|
|
|
|
// 将 mixed-port 转换为字符串
|
2025-10-08 12:32:40 +08:00
|
|
|
|
const mixedPortStr = (clashConfig?.mixedPort || "").toString();
|
2025-06-21 21:38:42 +08:00
|
|
|
|
pacContent = pacContent.replace(/%mixed-port%/g, mixedPortStr);
|
|
|
|
|
|
}
|
2025-06-06 21:11:14 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
if (pacContent !== pac_file_content) {
|
|
|
|
|
|
patch.pac_file_content = pacContent;
|
|
|
|
|
|
}
|
2025-06-06 21:11:14 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
// 处理IPv6地址,如果是IPv6地址但没有被方括号包围,则添加方括号
|
|
|
|
|
|
let proxyHost = value.proxy_host;
|
|
|
|
|
|
if (
|
|
|
|
|
|
ipv6Regex.test(proxyHost) &&
|
|
|
|
|
|
!proxyHost.startsWith("[") &&
|
|
|
|
|
|
!proxyHost.endsWith("]")
|
|
|
|
|
|
) {
|
|
|
|
|
|
proxyHost = `[${proxyHost}]`;
|
|
|
|
|
|
}
|
2025-06-06 21:11:14 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
if (proxyHost !== proxy_host) {
|
|
|
|
|
|
patch.proxy_host = proxyHost;
|
|
|
|
|
|
}
|
2025-06-06 21:11:14 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
// 判断是否需要重置系统代理
|
|
|
|
|
|
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;
|
2025-06-06 21:11:14 +08:00
|
|
|
|
|
2025-06-21 21:38:42 +08:00
|
|
|
|
Promise.resolve().then(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 乐观更新本地状态
|
|
|
|
|
|
if (Object.keys(patch).length > 0) {
|
|
|
|
|
|
mutateVerge({ ...verge, ...patch }, false);
|
2025-04-30 23:09:19 +08:00
|
|
|
|
}
|
2025-06-21 21:38:42 +08:00
|
|
|
|
if (Object.keys(patch).length > 0) {
|
|
|
|
|
|
await patchVerge(patch);
|
|
|
|
|
|
}
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await Promise.all([
|
|
|
|
|
|
mutate("getSystemProxy"),
|
|
|
|
|
|
mutate("getAutotemProxy"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果需要重置代理且代理当前启用
|
|
|
|
|
|
if (needResetProxy && enabled) {
|
|
|
|
|
|
const [currentSysProxy, currentAutoProxy] = await Promise.all([
|
|
|
|
|
|
getSystemProxy(),
|
|
|
|
|
|
getAutotemProxy(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const isProxyActive = value.pac
|
|
|
|
|
|
? currentAutoProxy?.enable
|
|
|
|
|
|
: currentSysProxy?.enable;
|
|
|
|
|
|
|
|
|
|
|
|
if (isProxyActive) {
|
|
|
|
|
|
await patchVergeConfig({ enable_system_proxy: false });
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
|
|
await patchVergeConfig({ enable_system_proxy: true });
|
|
|
|
|
|
await Promise.all([
|
|
|
|
|
|
mutate("getSystemProxy"),
|
|
|
|
|
|
mutate("getAutotemProxy"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.warn("代理状态更新失败:", err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
console.error("配置保存失败:", err);
|
|
|
|
|
|
mutateVerge();
|
|
|
|
|
|
showNotice("error", err.toString());
|
|
|
|
|
|
// setOpen(true);
|
2025-04-24 01:10:01 +08:00
|
|
|
|
}
|
2025-06-21 21:38:42 +08:00
|
|
|
|
});
|
2022-09-07 01:51:43 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2022-11-20 21:48:39 +08:00
|
|
|
|
<BaseDialog
|
|
|
|
|
|
open={open}
|
2025-11-02 13:10:15 +08:00
|
|
|
|
title={t("components.settings.sysproxy.title")}
|
2024-06-07 10:32:27 +08:00
|
|
|
|
contentSx={{ width: 450, maxHeight: 565 }}
|
2022-11-20 21:48:39 +08:00
|
|
|
|
okBtn={t("Save")}
|
|
|
|
|
|
cancelBtn={t("Cancel")}
|
|
|
|
|
|
onClose={() => setOpen(false)}
|
|
|
|
|
|
onCancel={() => setOpen(false)}
|
|
|
|
|
|
onOk={onSave}
|
2025-04-30 23:09:19 +08:00
|
|
|
|
loading={saving}
|
|
|
|
|
|
disableOk={saving}
|
2022-11-20 21:48:39 +08:00
|
|
|
|
>
|
|
|
|
|
|
<List>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<BaseFieldset
|
|
|
|
|
|
label={t("components.settings.sysproxy.fieldsets.currentStatus")}
|
|
|
|
|
|
padding="15px 10px"
|
|
|
|
|
|
>
|
2024-06-07 10:32:27 +08:00
|
|
|
|
<FlexBox>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<Typography className="label">
|
|
|
|
|
|
{t("components.settings.sysproxy.fields.enableStatus")}
|
|
|
|
|
|
</Typography>
|
2024-06-07 10:32:27 +08:00
|
|
|
|
<Typography className="value">
|
|
|
|
|
|
{value.pac
|
|
|
|
|
|
? autoproxy?.enable
|
|
|
|
|
|
? t("Enabled")
|
|
|
|
|
|
: t("Disabled")
|
|
|
|
|
|
: sysproxy?.enable
|
2024-11-26 01:05:30 +08:00
|
|
|
|
? t("Enabled")
|
|
|
|
|
|
: t("Disabled")}
|
2024-06-07 10:32:27 +08:00
|
|
|
|
</Typography>
|
|
|
|
|
|
</FlexBox>
|
|
|
|
|
|
{!value.pac && (
|
2025-10-04 21:20:31 +08:00
|
|
|
|
<FlexBox>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<Typography className="label">
|
|
|
|
|
|
{t("components.settings.sysproxy.fields.serverAddr")}
|
|
|
|
|
|
</Typography>
|
2025-10-04 21:20:31 +08:00
|
|
|
|
<Typography className="value">{getSystemProxyAddress}</Typography>
|
|
|
|
|
|
</FlexBox>
|
2024-06-07 10:32:27 +08:00
|
|
|
|
)}
|
2024-06-09 13:37:47 +08:00
|
|
|
|
{value.pac && (
|
|
|
|
|
|
<FlexBox>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<Typography className="label">
|
|
|
|
|
|
{t("components.settings.sysproxy.fields.pacUrl")}
|
|
|
|
|
|
</Typography>
|
2025-06-22 23:19:11 +08:00
|
|
|
|
<Typography className="value">
|
|
|
|
|
|
{getCurrentPacUrl || "-"}
|
|
|
|
|
|
</Typography>
|
2024-06-09 13:37:47 +08:00
|
|
|
|
</FlexBox>
|
|
|
|
|
|
)}
|
2024-06-09 11:16:13 +08:00
|
|
|
|
</BaseFieldset>
|
2024-05-26 17:59:39 +08:00
|
|
|
|
<ListItem sx={{ padding: "5px 2px" }}>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<ListItemText
|
|
|
|
|
|
primary={t("components.settings.sysproxy.fields.proxyHost")}
|
|
|
|
|
|
/>
|
2025-04-16 10:22:53 +08:00
|
|
|
|
<Autocomplete
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
sx={{ width: 150 }}
|
|
|
|
|
|
options={hostOptions}
|
|
|
|
|
|
value={value.proxy_host}
|
|
|
|
|
|
freeSolo
|
|
|
|
|
|
renderInput={(params) => (
|
|
|
|
|
|
<TextField {...params} placeholder="127.0.0.1" size="small" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
onChange={(_, newValue) => {
|
|
|
|
|
|
setValue((v) => ({
|
|
|
|
|
|
...v,
|
|
|
|
|
|
proxy_host: newValue || "127.0.0.1",
|
|
|
|
|
|
}));
|
|
|
|
|
|
}}
|
|
|
|
|
|
onInputChange={(_, newInputValue) => {
|
|
|
|
|
|
setValue((v) => ({
|
|
|
|
|
|
...v,
|
|
|
|
|
|
proxy_host: newInputValue || "127.0.0.1",
|
|
|
|
|
|
}));
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
<ListItem sx={{ padding: "5px 2px" }}>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<ListItemText
|
|
|
|
|
|
primary={t("components.settings.sysproxy.fields.usePacMode")}
|
|
|
|
|
|
/>
|
2024-05-26 17:59:39 +08:00
|
|
|
|
<Switch
|
|
|
|
|
|
edge="end"
|
|
|
|
|
|
disabled={!enabled}
|
|
|
|
|
|
checked={value.pac}
|
|
|
|
|
|
onChange={(_, e) => setValue((v) => ({ ...v, pac: e }))}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ListItem>
|
2024-06-07 10:32:27 +08:00
|
|
|
|
|
2022-11-20 21:48:39 +08:00
|
|
|
|
<ListItem sx={{ padding: "5px 2px" }}>
|
2024-06-26 05:33:06 +08:00
|
|
|
|
<ListItemText
|
2025-11-02 13:10:15 +08:00
|
|
|
|
primary={t("components.settings.sysproxy.fields.proxyGuard")}
|
2024-06-26 05:33:06 +08:00
|
|
|
|
sx={{ maxWidth: "fit-content" }}
|
|
|
|
|
|
/>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<TooltipIcon
|
|
|
|
|
|
title={t("components.settings.sysproxy.tooltips.proxyGuard")}
|
|
|
|
|
|
sx={{ opacity: "0.7" }}
|
|
|
|
|
|
/>
|
2022-11-20 21:48:39 +08:00
|
|
|
|
<Switch
|
|
|
|
|
|
edge="end"
|
|
|
|
|
|
disabled={!enabled}
|
|
|
|
|
|
checked={value.guard}
|
|
|
|
|
|
onChange={(_, e) => setValue((v) => ({ ...v, guard: e }))}
|
2024-06-26 05:33:06 +08:00
|
|
|
|
sx={{ marginLeft: "auto" }}
|
2022-11-20 21:48:39 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
|
|
<ListItem sx={{ padding: "5px 2px" }}>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<ListItemText
|
|
|
|
|
|
primary={t("components.settings.sysproxy.fields.guardDuration")}
|
|
|
|
|
|
/>
|
2022-11-20 21:48:39 +08:00
|
|
|
|
<TextField
|
|
|
|
|
|
disabled={!enabled}
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
value={value.duration}
|
|
|
|
|
|
sx={{ width: 100 }}
|
2025-05-05 00:26:04 +08:00
|
|
|
|
slotProps={{
|
|
|
|
|
|
input: {
|
|
|
|
|
|
endAdornment: <InputAdornment position="end">s</InputAdornment>,
|
2025-06-06 21:11:14 +08:00
|
|
|
|
},
|
2022-11-20 21:48:39 +08:00
|
|
|
|
}}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
setValue((v) => ({
|
|
|
|
|
|
...v,
|
|
|
|
|
|
duration: +e.target.value.replace(/\D/, ""),
|
|
|
|
|
|
}));
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ListItem>
|
2024-07-01 22:53:06 +08:00
|
|
|
|
{!value.pac && (
|
|
|
|
|
|
<ListItem sx={{ padding: "5px 2px" }}>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<ListItemText
|
|
|
|
|
|
primary={t(
|
|
|
|
|
|
"components.settings.sysproxy.fields.alwaysUseDefaultBypass",
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
2024-07-01 22:53:06 +08:00
|
|
|
|
<Switch
|
|
|
|
|
|
edge="end"
|
|
|
|
|
|
disabled={!enabled}
|
|
|
|
|
|
checked={value.use_default}
|
2025-06-06 21:11:14 +08:00
|
|
|
|
onChange={(_, e) =>
|
|
|
|
|
|
setValue((v) => ({
|
|
|
|
|
|
...v,
|
|
|
|
|
|
use_default: e,
|
|
|
|
|
|
// 当取消选择use_default且当前bypass为空时,填充默认值
|
|
|
|
|
|
bypass: !e && !v.bypass ? defaultBypass() : v.bypass,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2024-07-01 22:53:06 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
)}
|
2024-12-10 14:37:11 +08:00
|
|
|
|
|
2024-12-10 13:59:13 +08:00
|
|
|
|
{!value.pac && !value.use_default && (
|
2024-05-26 17:59:39 +08:00
|
|
|
|
<>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<ListItemText
|
|
|
|
|
|
primary={t("components.settings.sysproxy.fields.proxyBypass")}
|
|
|
|
|
|
/>
|
2024-06-09 13:37:47 +08:00
|
|
|
|
<TextField
|
2024-12-10 14:37:11 +08:00
|
|
|
|
error={value.bypass ? !validReg.test(value.bypass) : false}
|
2024-06-09 13:37:47 +08:00
|
|
|
|
disabled={!enabled}
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
multiline
|
|
|
|
|
|
rows={4}
|
|
|
|
|
|
sx={{ width: "100%" }}
|
|
|
|
|
|
value={value.bypass}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
setValue((v) => ({ ...v, bypass: e.target.value }));
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
2024-12-10 14:37:11 +08:00
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{!value.pac && value.use_default && (
|
|
|
|
|
|
<>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
<ListItemText
|
|
|
|
|
|
primary={t("components.settings.sysproxy.fields.bypass")}
|
|
|
|
|
|
/>
|
2024-06-07 10:32:27 +08:00
|
|
|
|
<FlexBox>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
disabled={true}
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
multiline
|
|
|
|
|
|
rows={4}
|
|
|
|
|
|
sx={{ width: "100%" }}
|
2024-12-10 14:37:11 +08:00
|
|
|
|
value={defaultBypass()}
|
2024-06-07 10:32:27 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</FlexBox>
|
2024-05-26 17:59:39 +08:00
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2024-12-10 14:37:11 +08:00
|
|
|
|
|
2024-05-26 17:59:39 +08:00
|
|
|
|
{value.pac && (
|
2025-10-04 21:20:31 +08:00
|
|
|
|
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
|
|
|
|
|
|
<ListItemText
|
2025-11-02 13:10:15 +08:00
|
|
|
|
primary={t(
|
|
|
|
|
|
"components.settings.sysproxy.fields.pacScriptContent",
|
|
|
|
|
|
)}
|
2025-10-04 21:20:31 +08:00
|
|
|
|
sx={{ padding: "3px 0" }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
startIcon={<EditRounded />}
|
|
|
|
|
|
variant="outlined"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setEditorOpen(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-11-02 13:10:15 +08:00
|
|
|
|
{t("components.settings.sysproxy.actions.editPac")}
|
2025-10-04 21:20:31 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
{editorOpen && (
|
|
|
|
|
|
<EditorViewer
|
|
|
|
|
|
open={true}
|
2025-11-02 13:10:15 +08:00
|
|
|
|
title={t("components.settings.sysproxy.actions.editPac")}
|
2025-10-04 21:20:31 +08:00
|
|
|
|
initialData={Promise.resolve(value.pac_content ?? "")}
|
|
|
|
|
|
language="javascript"
|
|
|
|
|
|
onSave={(_prev, curr) => {
|
|
|
|
|
|
let pac = DEFAULT_PAC;
|
|
|
|
|
|
if (curr && curr.trim().length > 0) {
|
|
|
|
|
|
pac = curr;
|
|
|
|
|
|
}
|
|
|
|
|
|
setValue((v) => ({ ...v, pac_content: pac }));
|
2024-05-26 17:59:39 +08:00
|
|
|
|
}}
|
2025-10-04 21:20:31 +08:00
|
|
|
|
onClose={() => setEditorOpen(false)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</ListItem>
|
2024-05-26 17:59:39 +08:00
|
|
|
|
)}
|
2024-06-07 10:32:27 +08:00
|
|
|
|
</List>
|
2022-11-20 21:48:39 +08:00
|
|
|
|
</BaseDialog>
|
2022-09-07 01:51:43 +08:00
|
|
|
|
);
|
2025-09-30 18:13:02 +08:00
|
|
|
|
});
|
2022-09-07 01:51:43 +08:00
|
|
|
|
|
2022-11-20 21:48:39 +08:00
|
|
|
|
const FlexBox = styled("div")`
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
.label {
|
|
|
|
|
|
flex: none;
|
2024-06-07 10:32:27 +08:00
|
|
|
|
//width: 85px;
|
2022-11-20 21:48:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
`;
|