refactor: external toggle control logic & disable external-controller by default

This commit is contained in:
wonfen 2025-04-16 20:29:06 +08:00
parent d4a42f4ede
commit d84b5456ff
3 changed files with 78 additions and 69 deletions

View File

@ -50,7 +50,6 @@ impl IClashTemp {
map.insert("allow-lan".into(), false.into()); map.insert("allow-lan".into(), false.into());
map.insert("ipv6".into(), true.into()); map.insert("ipv6".into(), true.into());
map.insert("mode".into(), "rule".into()); map.insert("mode".into(), "rule".into());
map.insert("external-controller".into(), "127.0.0.1:9097".into());
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
map.insert("external-controller-unix".into(), "mihomo.sock".into()); map.insert("external-controller-unix".into(), "mihomo.sock".into());
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -216,6 +215,10 @@ impl IClashTemp {
Some(val_str) => { Some(val_str) => {
let val_str = val_str.trim(); let val_str = val_str.trim();
if val_str.is_empty() {
return None;
}
let val = match val_str.starts_with(':') { let val = match val_str.starts_with(':') {
true => format!("127.0.0.1{val_str}"), true => format!("127.0.0.1{val_str}"),
false => val_str.to_owned(), false => val_str.to_owned(),
@ -227,11 +230,15 @@ impl IClashTemp {
} }
None => None, None => None,
}) })
.unwrap_or("127.0.0.1:9097".into()) .unwrap_or_else(|| String::new())
} }
pub fn guard_client_ctrl(config: &Mapping) -> String { pub fn guard_client_ctrl(config: &Mapping) -> String {
let value = Self::guard_server_ctrl(config); let value = Self::guard_server_ctrl(config);
if value.is_empty() {
return value;
}
match SocketAddr::from_str(value.as_str()) { match SocketAddr::from_str(value.as_str()) {
Ok(mut socket) => { Ok(mut socket) => {
if socket.ip().is_unspecified() { if socket.ip().is_unspecified() {
@ -239,7 +246,7 @@ impl IClashTemp {
} }
socket.to_string() socket.to_string()
} }
Err(_) => "127.0.0.1:9097".into(), Err(_) => String::new(),
} }
} }
} }
@ -278,12 +285,12 @@ fn test_clash_info() {
assert_eq!( assert_eq!(
IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(), IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(),
get_result(7897, "127.0.0.1:9097") get_result(7897, "")
); );
assert_eq!(get_case("", ""), get_result(7897, "127.0.0.1:9097")); assert_eq!(get_case("", ""), get_result(7897, ""));
assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9097")); assert_eq!(get_case(65537, ""), get_result(1, ""));
assert_eq!( assert_eq!(
get_case(8888, "127.0.0.1:8888"), get_case(8888, "127.0.0.1:8888"),
@ -292,7 +299,7 @@ fn test_clash_info() {
assert_eq!( assert_eq!(
get_case(8888, " :98888 "), get_case(8888, " :98888 "),
get_result(8888, "127.0.0.1:9097") get_result(8888, "")
); );
assert_eq!( assert_eq!(
@ -317,7 +324,7 @@ fn test_clash_info() {
assert_eq!( assert_eq!(
get_case(8888, "192.168.1.1:80800"), get_case(8888, "192.168.1.1:80800"),
get_result(8888, "127.0.0.1:9097") get_result(8888, "")
); );
} }

View File

@ -3,8 +3,9 @@ import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { List, ListItem, ListItemText, TextField, Typography, Box } from "@mui/material"; import { List, ListItem, ListItemText, TextField, Typography, Box } from "@mui/material";
import { useClashInfo } from "@/hooks/use-clash"; import { useClashInfo } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base"; import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { useClash } from "@/hooks/use-clash";
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => { export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -12,43 +13,39 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
const { clashInfo, patchInfo } = useClashInfo(); const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge(); const { verge, patchVerge } = useVerge();
const { clash } = useClash();
const [controller, setController] = useState(clashInfo?.server || ""); const [controller, setController] = useState("");
const [secret, setSecret] = useState(clashInfo?.secret || ""); const [secret, setSecret] = useState("");
// 获取外部控制器开关状态 const enableController = Boolean(clash?.["external-controller"] && clash?.["external-controller"] !== "");
const [enableController, setEnableController] = useState(() => {
const savedState = localStorage.getItem("enable_external_controller");
if (savedState !== null) {
return savedState === "true";
}
return verge?.enable_external_controller ?? true;
});
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
open: () => { open: () => {
setOpen(true); setOpen(true);
setController(clashInfo?.server || ""); setController(clash?.["external-controller"] || "");
setSecret(clashInfo?.secret || ""); setSecret(clash?.secret || "");
// 从localStorage更新开关状态
const savedState = localStorage.getItem("enable_external_controller");
if (savedState !== null) {
setEnableController(savedState === "true");
} else {
setEnableController(verge?.enable_external_controller ?? true);
}
}, },
close: () => setOpen(false), close: () => setOpen(false),
})); }));
const onSave = useLockFn(async () => { const onSave = useLockFn(async () => {
try { try {
// 只有在启用外部控制器时才更新配置
if (enableController) {
await patchInfo({ "external-controller": controller, secret });
}
Notice.success(t("External Controller Settings Saved"), 1000);
setOpen(false); setOpen(false);
const promises = [];
promises.push(
patchInfo({
"external-controller": controller || "127.0.0.1:9097",
secret
})
);
// 同步verge配置
if (controller && controller !== "") {
promises.push(patchVerge({ enable_external_controller: true }));
}
await Promise.all(promises);
Notice.success(t("External Controller Settings Saved"), 1000);
} catch (err: any) { } catch (err: any) {
Notice.error(err.message || err.toString(), 4000); Notice.error(err.message || err.toString(), 4000);
} }
@ -61,6 +58,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
contentSx={{ width: 400 }} contentSx={{ width: 400 }}
okBtn={t("Save")} okBtn={t("Save")}
cancelBtn={t("Cancel")} cancelBtn={t("Cancel")}
disableOk={!enableController}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
onCancel={() => setOpen(false)} onCancel={() => setOpen(false)}
onOk={onSave} onOk={onSave}
@ -81,7 +79,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
size="small" size="small"
sx={{ width: 175 }} sx={{ width: 175 }}
value={controller} value={controller}
placeholder="Required" placeholder="127.0.0.1:9097"
onChange={(e) => setController(e.target.value)} onChange={(e) => setController(e.target.value)}
disabled={!enableController} disabled={!enableController}
/> />

View File

@ -44,6 +44,7 @@ const SettingClash = ({ onError }: Props) => {
"log-level": logLevel, "log-level": logLevel,
"unified-delay": unifiedDelay, "unified-delay": unifiedDelay,
dns, dns,
"external-controller": externalController
} = clash ?? {}; } = clash ?? {};
const { enable_random_port = false, verge_mixed_port } = verge ?? {}; const { enable_random_port = false, verge_mixed_port } = verge ?? {};
@ -59,15 +60,6 @@ const SettingClash = ({ onError }: Props) => {
return verge?.enable_dns_settings ?? false; return verge?.enable_dns_settings ?? false;
}); });
// 添加外部控制器开关状态
const [enableController, setEnableController] = useState(() => {
const savedState = localStorage.getItem("enable_external_controller");
if (savedState !== null) {
return savedState === "true";
}
return verge?.enable_external_controller ?? true;
});
const { addListener } = useListen(); const { addListener } = useListen();
const webRef = useRef<DialogRef>(null); const webRef = useRef<DialogRef>(null);
@ -118,25 +110,14 @@ const SettingClash = ({ onError }: Props) => {
} }
}); });
// 处理外部控制器开关状态变化 // 同步外部控制器配置和开关状态
const handleControllerToggle = useLockFn(async (enable: boolean) => { useEffect(() => {
try { const hasController = Boolean(externalController && externalController !== "");
setEnableController(enable); const isEnabled = Boolean(verge?.enable_external_controller);
localStorage.setItem("enable_external_controller", String(enable)); if (hasController !== isEnabled) {
await patchVerge({ enable_external_controller: enable }); patchVerge({ enable_external_controller: hasController });
if (!enable) {
await patchInfo({ "external-controller": "", secret: "" });
} else {
// 如果开启,恢复默认值或之前的值
const server = clashInfo?.server || "127.0.0.1:9097";
await patchInfo({ "external-controller": server, secret: clashInfo?.secret || "" });
} }
} catch (err: any) { }, [externalController, verge?.enable_external_controller]);
setEnableController(!enable);
localStorage.setItem("enable_external_controller", String(!enable));
Notice.error(err.message || err.toString());
}
});
return ( return (
<SettingList title={t("Clash Setting")}> <SettingList title={t("Clash Setting")}>
@ -286,26 +267,49 @@ const SettingClash = ({ onError }: Props) => {
/> />
} }
> >
<Switch <GuardState
edge="end" // 依据配置文件中是否有值来决定开关状态
checked={enableController} value={Boolean(externalController && externalController !== "")}
onChange={(_, checked) => handleControllerToggle(checked)} valueProps="checked"
/> onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => {
onChangeVerge({ enable_external_controller: e });
onChangeData({
"external-controller": e ? (externalController || "127.0.0.1:9097") : ""
});
}}
onGuard={async (e) => {
const promises = [
patchVerge({ enable_external_controller: e })
];
if (!e) {
// 如果禁用,清空配置
promises.push(patchClash({ "external-controller": "" }));
} else if (!externalController || externalController === "") {
promises.push(patchClash({ "external-controller": "127.0.0.1:9097" }));
}
await Promise.all(promises);
}}
>
<Switch edge="end" />
</GuardState>
</SettingItem> </SettingItem>
<SettingItem <SettingItem
onClick={enableController ? () => webRef.current?.open() : undefined} onClick={(externalController && externalController !== "") ? () => webRef.current?.open() : undefined}
label={ label={
<Typography <Typography
component="span" component="span"
color={!enableController ? "text.disabled" : "text.primary"} color={(!externalController || externalController === "") ? "text.disabled" : "text.primary"}
sx={{ fontSize: "inherit" }} sx={{ fontSize: "inherit" }}
> >
{t("Web UI")} {t("Web UI")}
</Typography> </Typography>
} }
extra={ extra={
!enableController && ( (!externalController || externalController === "") && (
<TooltipIcon <TooltipIcon
title={t("Web UI info")} title={t("Web UI info")}
sx={{ opacity: "0.7" }} sx={{ opacity: "0.7" }}