Files
clash-proxy/src/components/shared/ProxyControlSwitches.tsx

327 lines
9.9 KiB
TypeScript
Raw Normal View History

import { useRef } from "react";
2025-03-14 13:31:34 +08:00
import { useTranslation } from "react-i18next";
import useSWR, { mutate } from "swr";
import {
SettingsRounded,
PlayCircleOutlineRounded,
PauseCircleOutlineRounded,
BuildRounded,
} from "@mui/icons-material";
import {
Box,
Button,
Tooltip,
Typography,
alpha,
useTheme,
} from "@mui/material";
2025-05-04 22:17:08 +08:00
import { DialogRef, Switch } from "@/components/base";
2025-03-14 13:31:34 +08:00
import { GuardState } from "@/components/setting/mods/guard-state";
import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer";
import { TunViewer } from "@/components/setting/mods/tun-viewer";
import { useVerge } from "@/hooks/use-verge";
import {
getSystemProxy,
getAutotemProxy,
getRunningMode,
installService,
restartCore,
isServiceAvailable,
2025-03-14 13:31:34 +08:00
} from "@/services/cmds";
import { useLockFn } from "ahooks";
import { closeAllConnections } from "@/services/api";
2025-05-04 22:17:08 +08:00
import { showNotice } from "@/services/noticeService";
2025-03-14 13:31:34 +08:00
interface ProxySwitchProps {
label?: string;
onError?: (err: Error) => void;
}
/**
*
* Tun Mode System Proxy
*/
const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => {
const { t } = useTranslation();
const { verge, mutateVerge, patchVerge } = useVerge();
const theme = useTheme();
const { data: sysproxy } = useSWR("getSystemProxy", getSystemProxy);
const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy);
const { data: runningMode, mutate: mutateRunningMode } = useSWR(
"getRunningMode",
getRunningMode,
);
// 是否以sidecar模式运行
const isSidecarMode = runningMode === "Sidecar";
2025-03-14 13:31:34 +08:00
const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null);
const { enable_tun_mode, enable_system_proxy, proxy_auto_config } =
verge ?? {};
// 确定当前显示哪个开关
const isSystemProxyMode = label === t("System Proxy") || !label;
const isTunMode = label === t("Tun Mode");
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<IVergeConfig>) => {
mutateVerge({ ...verge, ...patch }, false);
};
const updateProxyStatus = async () => {
await new Promise((resolve) => setTimeout(resolve, 100));
await mutate("getSystemProxy");
await mutate("getAutotemProxy");
};
// 安装系统服务
const onInstallService = useLockFn(async () => {
try {
showNotice('info', t("Installing Service..."));
2025-03-14 13:31:34 +08:00
await installService();
showNotice('success', t("Service Installed Successfully"));
showNotice('info', t("Waiting for service to be ready..."));
let serviceReady = false;
for (let i = 0; i < 5; i++) {
try {
await new Promise(resolve => setTimeout(resolve, 1000));
const isAvailable = await isServiceAvailable();
if (isAvailable) {
serviceReady = true;
break;
}
showNotice('info', t("Service not ready, retrying..."));
} catch (error) {
console.error("检查服务状态失败:", error);
}
}
showNotice('info', t("Restarting Core..."));
await restartCore();
2025-03-14 13:31:34 +08:00
// 重新获取运行模式
await mutateRunningMode();
// 更新服务状态
const serviceStatus = await isServiceAvailable();
mutate("isServiceAvailable", serviceStatus, false);
if (serviceReady) {
showNotice('success', t("Service is ready and core restarted"));
} else {
showNotice('info', t("Service may not be fully ready"));
}
2025-03-14 13:31:34 +08:00
} catch (err: any) {
showNotice('error', err.message || err.toString());
2025-03-14 13:31:34 +08:00
}
});
return (
<Box>
{label && (
<Box
sx={{
fontSize: "15px",
fontWeight: "500",
mb: 0.5,
display: "none",
2025-03-14 13:31:34 +08:00
}}
>
{label}
</Box>
)}
{/* 仅显示当前选中的开关 */}
{isSystemProxyMode && (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
p: 1,
borderRadius: 1.5,
bgcolor: enable_system_proxy
? alpha(theme.palette.success.main, 0.07)
: "transparent",
transition: "background-color 0.3s",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
{enable_system_proxy ? (
2025-03-14 13:31:34 +08:00
<PlayCircleOutlineRounded
sx={{ color: "success.main", mr: 1.5, fontSize: 28 }}
/>
) : (
<PauseCircleOutlineRounded
sx={{ color: "text.disabled", mr: 1.5, fontSize: 28 }}
/>
)}
<Box>
<Typography
variant="subtitle1"
sx={{ fontWeight: 500, fontSize: "15px" }}
>
{t("System Proxy")}
</Typography>
{/* <Typography variant="caption" color="text.secondary">
{sysproxy?.enable
2025-03-14 13:31:34 +08:00
? t("Proxy is active")
: t("Enable this for most users")
}
</Typography> */}
</Box>
</Box>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip title={t("System Proxy Info")} arrow>
<Box
sx={{
mr: 1,
color: "text.secondary",
"&:hover": { color: "primary.main" },
cursor: "pointer",
}}
onClick={() => sysproxyRef.current?.open()}
>
<SettingsRounded fontSize="small" />
</Box>
</Tooltip>
<GuardState
value={enable_system_proxy ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => onChangeData({ enable_system_proxy: e })}
onGuard={async (e) => {
if (!e && verge?.auto_close_connection) {
closeAllConnections();
}
2025-03-14 13:31:34 +08:00
await patchVerge({ enable_system_proxy: e });
await updateProxyStatus();
}}
>
<Switch edge="end" />
</GuardState>
</Box>
</Box>
)}
{isTunMode && (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
p: 1,
borderRadius: 1.5,
bgcolor: enable_tun_mode
? alpha(theme.palette.success.main, 0.07)
: "transparent",
opacity: isSidecarMode ? 0.6 : 1,
transition: "background-color 0.3s",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
{enable_tun_mode ? (
<PlayCircleOutlineRounded
sx={{ color: "success.main", mr: 1.5, fontSize: 28 }}
/>
) : (
<PauseCircleOutlineRounded
sx={{ color: "text.disabled", mr: 1.5, fontSize: 28 }}
/>
)}
<Box>
<Typography
variant="subtitle1"
sx={{ fontWeight: 500, fontSize: "15px" }}
>
{t("Tun Mode")}
</Typography>
{/* <Typography variant="caption" color="text.secondary">
{isSidecarMode
? t("TUN requires Service Mode or Admin Mode")
2025-03-14 13:31:34 +08:00
: t("For special applications")
}
</Typography> */}
</Box>
</Box>
<Box sx={{ display: "flex", alignItems: "center" }}>
{isSidecarMode && (
<Tooltip title={t("Install Service")} arrow>
<Button
variant="outlined"
color="primary"
size="small"
onClick={onInstallService}
sx={{ mr: 1, minWidth: "32px", p: "4px" }}
>
<BuildRounded fontSize="small" />
</Button>
</Tooltip>
)}
<Tooltip title={t("Tun Mode Info")} arrow>
<Box
sx={{
mr: 1,
color: "text.secondary",
"&:hover": { color: "primary.main" },
cursor: "pointer",
}}
onClick={() => tunRef.current?.open()}
>
<SettingsRounded fontSize="small" />
</Box>
</Tooltip>
<GuardState
value={enable_tun_mode ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => {
2025-05-04 22:17:08 +08:00
if (isSidecarMode) {
showNotice('error', t("TUN requires Service Mode or Admin Mode"));
2025-05-04 22:17:08 +08:00
return Promise.reject(
new Error(t("TUN requires Service Mode or Admin Mode")),
2025-05-04 22:17:08 +08:00
);
}
2025-03-14 13:31:34 +08:00
onChangeData({ enable_tun_mode: e });
}}
onGuard={(e) => {
if (isSidecarMode) {
showNotice('error', t("TUN requires Service Mode or Admin Mode"));
2025-03-14 13:31:34 +08:00
return Promise.reject(
new Error(t("TUN requires Service Mode or Admin Mode")),
2025-03-14 13:31:34 +08:00
);
}
return patchVerge({ enable_tun_mode: e });
}}
>
<Switch edge="end" disabled={isSidecarMode} />
</GuardState>
</Box>
</Box>
)}
{/* 引用对话框组件 */}
<SysproxyViewer ref={sysproxyRef} />
<TunViewer ref={tunRef} />
</Box>
);
};
export default ProxyControlSwitches;