237 lines
6.6 KiB
TypeScript
237 lines
6.6 KiB
TypeScript
import {
|
|
BuildRounded,
|
|
DeleteForeverRounded,
|
|
PauseCircleOutlineRounded,
|
|
PlayCircleOutlineRounded,
|
|
SettingsRounded,
|
|
WarningRounded,
|
|
} from "@mui/icons-material";
|
|
import { Box, Typography, alpha, useTheme } from "@mui/material";
|
|
import { useLockFn } from "ahooks";
|
|
import React, { useCallback, useRef } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { DialogRef, Switch } from "@/components/base";
|
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
|
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 { useSystemProxyState } from "@/hooks/use-system-proxy-state";
|
|
import { useSystemState } from "@/hooks/use-system-state";
|
|
import { useVerge } from "@/hooks/use-verge";
|
|
import { useServiceInstaller } from "@/hooks/useServiceInstaller";
|
|
import { useServiceUninstaller } from "@/hooks/useServiceUninstaller";
|
|
import { showNotice } from "@/services/noticeService";
|
|
|
|
interface ProxySwitchProps {
|
|
label?: string;
|
|
onError?: (err: Error) => void;
|
|
noRightPadding?: boolean;
|
|
}
|
|
|
|
interface SwitchRowProps {
|
|
label: string;
|
|
active: boolean;
|
|
disabled?: boolean;
|
|
infoTitle: string;
|
|
onInfoClick?: () => void;
|
|
extraIcons?: React.ReactNode;
|
|
onToggle: (value: boolean) => Promise<void>;
|
|
onError?: (err: Error) => void;
|
|
highlight?: boolean;
|
|
}
|
|
|
|
/**
|
|
* 抽取的子组件:统一的开关 UI
|
|
*/
|
|
const SwitchRow = ({
|
|
label,
|
|
active,
|
|
disabled,
|
|
infoTitle,
|
|
onInfoClick,
|
|
extraIcons,
|
|
onToggle,
|
|
onError,
|
|
highlight,
|
|
}: SwitchRowProps) => {
|
|
const theme = useTheme();
|
|
return (
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
p: 1,
|
|
pr: 2,
|
|
borderRadius: 1.5,
|
|
bgcolor: highlight
|
|
? alpha(theme.palette.success.main, 0.07)
|
|
: "transparent",
|
|
opacity: disabled ? 0.6 : 1,
|
|
transition: "background-color 0.3s",
|
|
}}
|
|
>
|
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
{active ? (
|
|
<PlayCircleOutlineRounded sx={{ color: "success.main", mr: 1 }} />
|
|
) : (
|
|
<PauseCircleOutlineRounded sx={{ color: "text.disabled", mr: 1 }} />
|
|
)}
|
|
<Typography
|
|
variant="subtitle1"
|
|
sx={{ fontWeight: 500, fontSize: "15px" }}
|
|
>
|
|
{label}
|
|
</Typography>
|
|
<TooltipIcon
|
|
title={infoTitle}
|
|
icon={SettingsRounded}
|
|
onClick={onInfoClick}
|
|
sx={{ ml: 1 }}
|
|
/>
|
|
{extraIcons}
|
|
</Box>
|
|
|
|
<GuardState
|
|
value={active}
|
|
valueProps="checked"
|
|
onCatch={onError}
|
|
onFormat={(_, v) => v}
|
|
onGuard={onToggle}
|
|
>
|
|
<Switch edge="end" disabled={disabled} />
|
|
</GuardState>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const ProxyControlSwitches = ({
|
|
label,
|
|
onError,
|
|
noRightPadding = false,
|
|
}: ProxySwitchProps) => {
|
|
const { t } = useTranslation();
|
|
const { verge, mutateVerge, patchVerge } = useVerge();
|
|
const { installServiceAndRestartCore } = useServiceInstaller();
|
|
const { uninstallServiceAndRestartCore } = useServiceUninstaller();
|
|
const { actualState: systemProxyActualState, toggleSystemProxy } =
|
|
useSystemProxyState();
|
|
const {
|
|
isServiceMode,
|
|
isTunModeAvailable,
|
|
mutateRunningMode,
|
|
mutateServiceOk,
|
|
mutateTunModeAvailable,
|
|
} = useSystemState();
|
|
|
|
const sysproxyRef = useRef<DialogRef>(null);
|
|
const tunRef = useRef<DialogRef>(null);
|
|
|
|
const { enable_tun_mode, enable_system_proxy } = verge ?? {};
|
|
|
|
const showErrorNotice = useCallback(
|
|
(msg: string) => showNotice("error", t(msg)),
|
|
[t],
|
|
);
|
|
|
|
const handleTunToggle = async (value: boolean) => {
|
|
if (!isTunModeAvailable) {
|
|
const msg = "TUN requires Service Mode or Admin Mode";
|
|
showErrorNotice(msg);
|
|
throw new Error(t(msg));
|
|
}
|
|
mutateVerge({ ...verge, enable_tun_mode: value }, false);
|
|
await patchVerge({ enable_tun_mode: value });
|
|
};
|
|
|
|
const onInstallService = useLockFn(async () => {
|
|
try {
|
|
await installServiceAndRestartCore();
|
|
await mutateRunningMode();
|
|
await mutateServiceOk();
|
|
await mutateTunModeAvailable();
|
|
} catch (err) {
|
|
showNotice("error", (err as Error).message || String(err));
|
|
}
|
|
});
|
|
|
|
const onUninstallService = useLockFn(async () => {
|
|
try {
|
|
await handleTunToggle(false);
|
|
await uninstallServiceAndRestartCore();
|
|
await mutateRunningMode();
|
|
await mutateServiceOk();
|
|
await mutateTunModeAvailable();
|
|
} catch (err) {
|
|
showNotice("error", (err as Error).message || String(err));
|
|
}
|
|
});
|
|
|
|
const isSystemProxyMode = label === t("System Proxy") || !label;
|
|
const isTunMode = label === t("Tun Mode");
|
|
|
|
return (
|
|
<Box sx={{ width: "100%", pr: noRightPadding ? 1 : 2 }}>
|
|
{isSystemProxyMode && (
|
|
<SwitchRow
|
|
label={t("System Proxy")}
|
|
active={systemProxyActualState}
|
|
infoTitle={t("System Proxy Info")}
|
|
onInfoClick={() => sysproxyRef.current?.open()}
|
|
onToggle={(value) => toggleSystemProxy(value)}
|
|
onError={onError}
|
|
highlight={enable_system_proxy}
|
|
/>
|
|
)}
|
|
|
|
{isTunMode && (
|
|
<SwitchRow
|
|
label={t("Tun Mode")}
|
|
active={enable_tun_mode || false}
|
|
infoTitle={t("Tun Mode Info")}
|
|
onInfoClick={() => tunRef.current?.open()}
|
|
onToggle={handleTunToggle}
|
|
onError={onError}
|
|
disabled={!isTunModeAvailable}
|
|
highlight={enable_tun_mode || false}
|
|
extraIcons={
|
|
<>
|
|
{!isTunModeAvailable && (
|
|
<TooltipIcon
|
|
title={t("TUN requires Service Mode or Admin Mode")}
|
|
icon={WarningRounded}
|
|
sx={{ color: "warning.main", ml: 1 }}
|
|
/>
|
|
)}
|
|
{!isTunModeAvailable && (
|
|
<TooltipIcon
|
|
title={t("Install Service")}
|
|
icon={BuildRounded}
|
|
color="primary"
|
|
onClick={onInstallService}
|
|
sx={{ ml: 1 }}
|
|
/>
|
|
)}
|
|
{isServiceMode && (
|
|
<TooltipIcon
|
|
title={t("Uninstall Service")}
|
|
icon={DeleteForeverRounded}
|
|
color="secondary"
|
|
onClick={onUninstallService}
|
|
sx={{ ml: 1 }}
|
|
/>
|
|
)}
|
|
</>
|
|
}
|
|
/>
|
|
)}
|
|
|
|
<SysproxyViewer ref={sysproxyRef} />
|
|
<TunViewer ref={tunRef} />
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default ProxyControlSwitches;
|