feat: add External Controller toggle switch

This commit is contained in:
wonfen 2025-04-15 21:16:13 +08:00
parent 18d24d5952
commit 4ce376f830
11 changed files with 134 additions and 34 deletions

View File

@ -1,32 +1,53 @@
import { forwardRef, useImperativeHandle, useState } from "react";
import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
import { List, ListItem, ListItemText, TextField } from "@mui/material";
import { List, ListItem, ListItemText, TextField, Typography, Box } from "@mui/material";
import { useClashInfo } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge();
const [controller, setController] = useState(clashInfo?.server || "");
const [secret, setSecret] = useState(clashInfo?.secret || "");
// 获取外部控制器开关状态
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, () => ({
open: () => {
setOpen(true);
setController(clashInfo?.server || "");
setSecret(clashInfo?.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),
}));
const onSave = useLockFn(async () => {
try {
// 只有在启用外部控制器时才更新配置
if (enableController) {
await patchInfo({ "external-controller": controller, secret });
Notice.success(t("External Controller Address Modified"), 1000);
}
Notice.success(t("External Controller Settings Saved"), 1000);
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString(), 4000);
@ -44,9 +65,17 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<Box>
<Typography variant="body2" color={enableController ? "warning.main" : "text.secondary"}>
{enableController
? t("External controller is enabled info")
: t("External controller is disabled info")}
</Typography>
</Box>
<List>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("External Controller")} />
<ListItemText primary={t("External Controller Address")} />
<TextField
autoComplete="new-password"
size="small"
@ -54,6 +83,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
value={controller}
placeholder="Required"
onChange={(e) => setController(e.target.value)}
disabled={!enableController}
/>
</ListItem>
@ -68,6 +98,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
onChange={(e) =>
setSecret(e.target.value?.replace(/[^\x00-\x7F]/g, ""))
}
disabled={!enableController}
/>
</ListItem>
</List>

View File

@ -701,7 +701,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
<Item>
<ListItemText
primary={t("Prefer H3")}
secondary={t("DNS DOH使用HTTP/3")}
secondary={t("DNS DOH uses HTTP/3")}
/>
<Switch
edge="end"
@ -713,7 +713,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
<Item>
<ListItemText
primary={t("Respect Rules")}
secondary={t("DNS连接遵守路由规则")}
secondary={t("DNS connections follow routing rules")}
/>
<Switch
edge="end"
@ -749,7 +749,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
<Item>
<ListItemText
primary={t("Direct Nameserver Follow Policy")}
secondary={t("是否遵循nameserver-policy")}
secondary={t("Whether to follow nameserver policy")}
/>
<Switch
edge="end"

View File

@ -7,7 +7,7 @@ import {
LanRounded,
} from "@mui/icons-material";
import { DialogRef, Notice, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { useClash, useClashInfo } from "@/hooks/use-clash";
import { GuardState } from "./mods/guard-state";
import { WebUIViewer } from "./mods/web-ui-viewer";
import { ClashPortViewer } from "./mods/clash-port-viewer";
@ -36,6 +36,7 @@ const SettingClash = ({ onError }: Props) => {
const { clash, version, mutateClash, patchClash } = useClash();
const { verge, mutateVerge, patchVerge } = useVerge();
const { clashInfo, patchInfo } = useClashInfo();
const {
ipv6,
@ -58,6 +59,15 @@ const SettingClash = ({ onError }: Props) => {
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 webRef = useRef<DialogRef>(null);
@ -108,6 +118,26 @@ const SettingClash = ({ onError }: Props) => {
}
});
// 处理外部控制器开关状态变化
const handleControllerToggle = useLockFn(async (enable: boolean) => {
try {
setEnableController(enable);
localStorage.setItem("enable_external_controller", String(enable));
await patchVerge({ enable_external_controller: enable });
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) {
setEnableController(!enable);
localStorage.setItem("enable_external_controller", String(!enable));
Notice.error(err.message || err.toString());
}
});
return (
<SettingList title={t("Clash Setting")}>
<WebUIViewer ref={webRef} />
@ -248,11 +278,41 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem
label={t("External Controller")}
extra={
<TooltipIcon
icon={SettingsRounded}
onClick={() => ctrlRef.current?.open()}
label={t("External")}
/>
}
>
<Switch
edge="end"
checked={enableController}
onChange={(_, checked) => handleControllerToggle(checked)}
/>
</SettingItem>
<SettingItem onClick={() => webRef.current?.open()} label={t("Web UI")} />
<SettingItem
onClick={enableController ? () => webRef.current?.open() : undefined}
label={
<Typography
component="span"
color={!enableController ? "text.disabled" : "text.primary"}
sx={{ fontSize: "inherit" }}
>
{t("Web UI")}
</Typography>
}
extra={
!enableController && (
<TooltipIcon
title={t("Web UI info")}
sx={{ opacity: "0.7" }}
/>
)
}
/>
<SettingItem
label={t("Clash Core")}

View File

@ -257,8 +257,8 @@
"Http Port": "منفذ HTTP(S)",
"Redir Port": "منفذ إعادة التوجيه",
"Tproxy Port": "منفذ Tproxy",
"External": "خارجي",
"External Controller": "وحدة التحكم الخارجية",
"External Controller Address": "عنوان وحدة التحكم الخارجية",
"Core Secret": "المفتاح السري للنواة",
"Recommended": "موصى به",
"Open URL": "فتح الرابط",

View File

@ -278,8 +278,11 @@
"Http Port": "Http(s) Port",
"Redir Port": "Redir Port",
"Tproxy Port": "Tproxy Port",
"External": "External",
"External Controller": "External Controller",
"External Controller Address": "External Controller Address",
"External Controller Settings Saved": "External Controller Settings Saved",
"External Controller is enabled info": "External Controller is enabled. Please set a secret password to avoid security risks.",
"External controller is disabled info": "External controller is disabled. You only need to enable it if you want to control Clash core via web UI.",
"Core Secret": "Core Secret",
"Recommended": "Recommended",
"Open URL": "Open URL",
@ -434,6 +437,7 @@
"Menu Icon": "Menu Icon",
"PAC File": "PAC File",
"Web UI": "Web UI",
"Web UI info": "External controller is disabled, web UI cannot be used",
"Hotkeys": "Hotkeys",
"Verge Mixed Port": "Verge Mixed Port",
"Verge Socks Port": "Verge Socks Port",
@ -502,15 +506,15 @@
"Fake IP Filter Mode": "Fake IP Filter Mode",
"Enable IPv6 DNS resolution": "Enable IPv6 DNS resolution",
"Prefer H3": "Prefer H3",
"DNS DOH使用HTTP/3": "DNS DOH uses HTTP/3",
"DNS DOH uses HTTP/3": "DNS DOH uses HTTP/3",
"Respect Rules": "Respect Rules",
"DNS连接遵守路由规则": "DNS connections follow routing rules",
"DNS connections follow routing rules": "DNS connections follow routing rules",
"Use Hosts": "Use Hosts",
"Enable to resolve hosts through hosts file": "Enable to resolve hosts through hosts file",
"Use System Hosts": "Use System Hosts",
"Enable to resolve hosts through system hosts file": "Enable to resolve hosts through system hosts file",
"Direct Nameserver Follow Policy": "Direct Nameserver Follow Policy",
"是否遵循nameserver-policy": "Whether to follow nameserver policy",
"Whether to follow nameserver policy": "Whether to follow nameserver policy",
"Default Nameserver": "Default Nameserver",
"Default DNS servers used to resolve DNS servers": "Default DNS servers used to resolve DNS servers",
"Nameserver": "Nameserver",

View File

@ -259,8 +259,8 @@
"Http Port": "پورت پروکسی Http(s)",
"Redir Port": "پورت پروکسی شفاف Redir",
"Tproxy Port": "پورت پروکسی شفاف Tproxy",
"External": "خارجی",
"External Controller": "کنترل‌کننده خارجی",
"External Controller Address": "کنترل‌کننده خارجی",
"Core Secret": "رمز اصلی",
"Recommended": "توصیه شده",
"Open URL": "باز کردن آدرس اینترنتی",

View File

@ -290,8 +290,8 @@
"Http Port": "Port Http(s)",
"Redir Port": "Port Redir",
"Tproxy Port": "Port Tproxy",
"External": "Eksternal",
"External Controller": "Alamat Pengendali Eksternal",
"External Controller": "Eksternal",
"External Controller Address": "Alamat Pengendali Eksternal",
"Core Secret": "Rahasia Inti",
"Recommended": "Direkomendasikan",
"Open URL": "Buka URL",

View File

@ -274,8 +274,8 @@
"Http Port": "Порт Http(s)-прокси",
"Redir Port": "Порт прозрачного прокси Redir",
"Tproxy Port": "Порт прозрачного прокси Tproxy",
"External": "Внешний контроллер",
"External Controller": "Адрес прослушивания внешнего контроллера",
"External Controller": "Внешний контроллер",
"External Controller Address": "Адрес прослушивания внешнего контроллера",
"Core Secret": "Секрет",
"Recommended": "Рекомендуется",
"Open URL": "Перейти по адресу",
@ -496,15 +496,15 @@
"Fake IP Range": "Диапазон FakeIP",
"Fake IP Filter Mode": "FakeIP Filter Mode",
"Prefer H3": "Предпочитать H3",
"DNS DOH使用HTTP/3": "DNS DOH использует http/3",
"DNS DOH uses HTTP/3": "DNS DOH использует http/3",
"Respect Rules": "Приоритизировать правила",
"DNS连接遵守路由规则": "Соединения DNS следуют правилам маршрутизации",
"DNS connections follow routing rules": "Соединения DNS следуют правилам маршрутизации",
"Use Hosts": "Использовать файл Hosts",
"Enable to resolve hosts through hosts file": "Включить разрешение хостов через файл Hosts",
"Use System Hosts": "Использовать системный файл Hosts",
"Enable to resolve hosts through system hosts file": "Включить разрешение хостов через системный файл Hosts",
"Direct Nameserver Follow Policy": "Прямой сервер имен следует политике",
"是否遵循nameserver-policy": "Следовать ли политике DNS-серверов",
"Whether to follow nameserver policy": "Следовать ли политике DNS-серверов",
"Default Nameserver": "DNS-сервер по умолчанию",
"Default DNS servers used to resolve DNS servers": "DNS-серверы по умолчанию, используемые для разрешения адресов серверов DNS",
"Nameserver": "DNS-сервер",

View File

@ -258,8 +258,8 @@
"Http Port": "HTTP(s) прокси порты",
"Redir Port": "Redir — үтә күренмәле прокси порты",
"Tproxy Port": "Tproxy — үтә күренмәле прокси порты",
"External": "Тышкы",
"External Controller": "Тышкы контроллер адресы",
"External Controller": "Тышкы контроллер",
"External Controller Address": "Тышкы контроллер адресы",
"Core Secret": "Серсүз",
"Recommended": "Тавсия ителә",
"Open URL": "URL ачарга",

View File

@ -278,8 +278,11 @@
"Http Port": "HTTP(S) 代理端口",
"Redir Port": "Redir 透明代理端口",
"TPROXY Port": "TPROXY 透明代理端口",
"External": "外部控制",
"External Controller": "外部控制器监听地址",
"External Controller": "外部控制",
"External Controller Address": "外部控制器监听地址",
"External Controller Settings Saved": "外部控制器设置已保存",
"External controller is enabled info": "外部控制器已启用。请设置一个密钥密码以避免安全风险。",
"External controller is disabled info": "外部控制器已禁用。如果您想通过网页界面控制 Clash 核心才需启用。",
"Core Secret": "API 访问密钥",
"Recommended": "建议设置",
"Open URL": "打开链接",
@ -434,6 +437,7 @@
"Menu Icon": "菜单图标",
"PAC File": "PAC 文件",
"Web UI": "网页界面",
"Web UI info": "外部控制器已禁用,网页界面无法使用",
"Hotkeys": "快捷键",
"Verge Mixed Port": "Verge 混合端口",
"Verge Socks Port": "Verge SOCKS 端口",
@ -502,15 +506,15 @@
"Fake IP Filter Mode": "Fake IP 过滤模式",
"Enable IPv6 DNS resolution": "启用 IPv6 DNS 解析",
"Prefer H3": "优先使用 HTTP/3",
"DNS DOH使用HTTP/3": "DNS DOH 使用 HTTP/3 协议",
"DNS DOH uses HTTP/3": "DNS DOH 使用 HTTP/3 协议",
"Respect Rules": "遵循路由规则",
"DNS连接遵守路由规则": "DNS 连接遵循路由规则",
"DNS connections follow routing rules": "DNS 连接遵循路由规则",
"Use Hosts": "使用 Hosts",
"Enable to resolve hosts through hosts file": "启用通过 hosts 文件解析域名",
"Use System Hosts": "使用系统 Hosts",
"Enable to resolve hosts through system hosts file": "启用通过系统 hosts 文件解析域名",
"Direct Nameserver Follow Policy": "直连域名服务器遵循策略",
"是否遵循nameserver-policy": "是否遵循 nameserver-policy 设置",
"Whether to follow nameserver policy": "是否遵循 nameserver-policy 设置",
"Default Nameserver": "默认域名服务器",
"Default DNS servers used to resolve DNS servers": "用于解析 DNS 服务器的默认 DNS 服务器",
"Nameserver": "域名服务器",

View File

@ -747,6 +747,7 @@ interface IVergeConfig {
enable_system_proxy?: boolean;
enable_global_hotkey?: boolean;
enable_dns_settings?: boolean;
enable_external_controller?: boolean;
proxy_auto_config?: boolean;
pac_file_content?: string;
enable_random_port?: boolean;