添加代理主机的设置,允许代理设置为其他IP(非127.0.0.1) (#2963)

允许下拉选择ip地址(支持IPv6)、localhost、以及当前系统的主机名,同时兼容手工输入
This commit is contained in:
逐雁南飛
2025-04-16 10:22:53 +08:00
committed by Tunglies
parent 1282cc56bf
commit b70cad537c
17 changed files with 223 additions and 21 deletions

View File

@@ -3,10 +3,17 @@ 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";
import { getAutotemProxy, getSystemProxy } from "@/services/cmds";
import {
getAutotemProxy,
getNetworkInterfaces,
getNetworkInterfacesInfo,
getSystemHostname,
getSystemProxy,
} from "@/services/cmds";
import getSystem from "@/utils/get-system";
import { EditRounded } from "@mui/icons-material";
import {
Autocomplete,
Button,
InputAdornment,
List,
@@ -17,10 +24,16 @@ import {
Typography,
} from "@mui/material";
import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useMemo, useState } from "react";
import {
forwardRef,
useImperativeHandle,
useEffect,
useMemo,
useState,
} from "react";
import { useTranslation } from "react-i18next";
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
}`;
/** NO_PROXY validation */
@@ -64,6 +77,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const [open, setOpen] = useState(false);
const [editorOpen, setEditorOpen] = useState(false);
const { verge, patchVerge } = useVerge();
const [hostOptions, setHostOptions] = useState<string[]>([]);
type SysProxy = Awaited<ReturnType<typeof getSystemProxy>>;
const [sysproxy, setSysproxy] = useState<SysProxy>();
@@ -79,6 +93,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
use_default_bypass,
system_proxy_bypass,
proxy_guard_duration,
proxy_host,
} = verge ?? {};
const [value, setValue] = useState({
@@ -88,6 +103,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
use_default: use_default_bypass ?? true,
pac: proxy_auto_config,
pac_content: pac_file_content ?? DEFAULT_PAC,
proxy_host: proxy_host ?? "127.0.0.1",
});
const defaultBypass = () => {
@@ -110,13 +126,74 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
use_default: use_default_bypass ?? true,
pac: proxy_auto_config,
pac_content: pac_file_content ?? DEFAULT_PAC,
proxy_host: proxy_host ?? "127.0.0.1",
});
getSystemProxy().then((p) => setSysproxy(p));
getAutotemProxy().then((p) => setAutoproxy(p));
fetchNetworkInterfaces();
},
close: () => setOpen(false),
}));
// 获取网络接口和主机名
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"]);
}
};
const onSave = useLockFn(async () => {
if (value.duration < 1) {
Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second"));
@@ -127,6 +204,23 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
return;
}
// 修改验证规则允许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 =
/^(([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])$/;
if (
!ipv4Regex.test(value.proxy_host) &&
!ipv6Regex.test(value.proxy_host) &&
!hostnameRegex.test(value.proxy_host)
) {
Notice.error(t("Invalid Proxy Host Format"));
return;
}
const patch: Partial<IVergeConfig> = {};
if (value.guard !== enable_proxy_guard) {
@@ -138,15 +232,34 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
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;
}
if (value.pac_content !== pac_file_content) {
patch.pac_file_content = value.pac_content;
let pacContent = value.pac_content;
if (pacContent) {
pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host);
}
if (pacContent !== pac_file_content) {
patch.pac_file_content = pacContent;
}
// 处理IPv6地址如果是IPv6地址但没有被方括号包围则添加方括号
let proxyHost = value.proxy_host;
if (
ipv6Regex.test(proxyHost) &&
!proxyHost.startsWith("[") &&
!proxyHost.endsWith("]")
) {
proxyHost = `[${proxyHost}]`;
}
if (proxyHost !== proxy_host) {
patch.proxy_host = proxyHost;
}
try {
@@ -199,6 +312,31 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
</FlexBox>
)}
</BaseFieldset>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Proxy Host")} />
<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" }}>
<ListItemText primary={t("Use PAC Mode")} />
<Switch