diff --git a/src-tauri/src/data/verge.rs b/src-tauri/src/data/verge.rs index 90aa5b76adb478890b92476636e75f1cc62a1b40..9a5519e324ffc08422b2b1f588c6f28502a7e0cb 100644 --- a/src-tauri/src/data/verge.rs +++ b/src-tauri/src/data/verge.rs @@ -60,6 +60,12 @@ pub struct Verge { /// hotkey map /// format: {func},{key} pub hotkeys: Option<Vec<String>>, + + /// 切æ¢ä»£ç†æ—¶è‡ªåŠ¨å…³é—连接 + pub auto_close_connection: Option<bool>, + + /// 默认的延迟测试连接 + pub default_latency_test: Option<String>, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -122,6 +128,9 @@ impl Verge { patch!(clash_core); patch!(hotkeys); + patch!(auto_close_connection); + patch!(default_latency_test); + self.save_file() } } diff --git a/src/components/proxy/proxy-group.tsx b/src/components/proxy/proxy-group.tsx index 95d8adb2ebab1e44a51b32d190e9a74a3b8db501..d25e3950137f9db96944fb62f150c24e09f43c0f 100644 --- a/src/components/proxy/proxy-group.tsx +++ b/src/components/proxy/proxy-group.tsx @@ -15,8 +15,14 @@ import { ExpandLessRounded, ExpandMoreRounded, } from "@mui/icons-material"; -import { providerHealthCheck, updateProxy } from "@/services/api"; +import { + getConnections, + providerHealthCheck, + updateProxy, + deleteConnection, +} from "@/services/api"; import { getProfiles, patchProfile } from "@/services/cmds"; +import { useVergeConfig } from "@/hooks/use-verge-config"; import delayManager from "@/services/delay"; import useHeadState from "./use-head-state"; import useFilterSort from "./use-filter-sort"; @@ -42,6 +48,7 @@ const ProxyGroup = ({ group }: Props) => { ); const { data: profiles } = useSWR("getProfiles", getProfiles); + const { data: vergeConfig } = useVergeConfig(); const onChangeProxy = useLockFn(async (name: string) => { // Todo: support another proxy group type @@ -51,6 +58,16 @@ const ProxyGroup = ({ group }: Props) => { try { setNow(name); await updateProxy(group.name, name); + + if (vergeConfig?.auto_close_connection) { + getConnections().then((snapshot) => { + snapshot.connections.forEach((conn) => { + if (conn.chains.includes(oldValue!)) { + deleteConnection(conn.id); + } + }); + }); + } } catch { setNow(oldValue); return; // do not update profile diff --git a/src/components/setting/mods/misc-viewer.tsx b/src/components/setting/mods/misc-viewer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ab3abf1888393a5fd4e880aa3516d2c716aa304 --- /dev/null +++ b/src/components/setting/mods/misc-viewer.tsx @@ -0,0 +1,107 @@ +import { useEffect, useState } from "react"; +import { useLockFn } from "ahooks"; +import { useTranslation } from "react-i18next"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + List, + ListItem, + ListItemText, + Switch, + TextField, +} from "@mui/material"; +import { ModalHandler } from "@/hooks/use-modal-handler"; +import { useVergeConfig } from "@/hooks/use-verge-config"; +import Notice from "@/components/base/base-notice"; + +interface Props { + handler: ModalHandler; +} + +const MiscViewer = ({ handler }: Props) => { + const { t } = useTranslation(); + const { data, patchVerge } = useVergeConfig(); + + const [open, setOpen] = useState(false); + const [values, setValues] = useState({ + autoCloseConnection: false, + defaultLatencyTest: "", + }); + + if (handler) { + handler.current = { + open: () => setOpen(true), + close: () => setOpen(false), + }; + } + + useEffect(() => { + if (open) { + setValues({ + autoCloseConnection: data?.auto_close_connection || false, + defaultLatencyTest: data?.default_latency_test || "", + }); + } + }, [open, data]); + + const onSave = useLockFn(async () => { + try { + await patchVerge({ + auto_close_connection: values.autoCloseConnection, + default_latency_test: values.defaultLatencyTest, + }); + setOpen(false); + } catch (err: any) { + Notice.error(err.message || err.toString()); + } + }); + + return ( + <Dialog open={open} onClose={() => setOpen(false)}> + <DialogTitle>{t("Miscellaneous")}</DialogTitle> + + <DialogContent sx={{ width: 420 }}> + <List> + <ListItem sx={{ padding: "5px 2px" }}> + <ListItemText primary="Auto Close Connections" /> + <Switch + edge="end" + checked={values.autoCloseConnection} + onChange={(_, c) => + setValues((v) => ({ ...v, autoCloseConnection: c })) + } + /> + </ListItem> + + <ListItem sx={{ padding: "5px 2px" }}> + <ListItemText primary="Default Latency Test" /> + <TextField + size="small" + autoComplete="off" + sx={{ width: 200 }} + value={values.defaultLatencyTest} + placeholder="http://www.gstatic.com/generate_204" + onChange={(e) => + setValues((v) => ({ ...v, defaultLatencyTest: e.target.value })) + } + /> + </ListItem> + </List> + </DialogContent> + + <DialogActions> + <Button variant="outlined" onClick={() => setOpen(false)}> + {t("Cancel")} + </Button> + <Button onClick={onSave} variant="contained"> + {t("Save")} + </Button> + </DialogActions> + </Dialog> + ); +}; + +export default MiscViewer; diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index 8ddad18c0608a02ba8900bfbaf2620c400b34c31..fdc7a3879cbed4c53dfb4891d8652df4c9fb6309 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -124,7 +124,7 @@ const SettingClash = ({ onError }: Props) => { /> </SettingItem> - <SettingItem label={t("External Controller")}> + <SettingItem label={t("External")}> <IconButton color="inherit" size="small" diff --git a/src/components/setting/setting-verge.tsx b/src/components/setting/setting-verge.tsx index 8eaff19c63ae83c4431d3ac306ab65c8e466e548..ad05cf73098ec6cf4cd279f9631d7ce03e9e66d9 100644 --- a/src/components/setting/setting-verge.tsx +++ b/src/components/setting/setting-verge.tsx @@ -22,6 +22,7 @@ import ThemeModeSwitch from "./mods/theme-mode-switch"; import ConfigViewer from "./mods/config-viewer"; import HotkeyViewer from "./mods/hotkey-viewer"; import GuardState from "./mods/guard-state"; +import MiscViewer from "./mods/misc-viewer"; import SettingTheme from "./setting-theme"; interface Props { @@ -45,11 +46,13 @@ const SettingVerge = ({ onError }: Props) => { mutateVerge({ ...vergeConfig, ...patch }, false); }; + const miscHandler = useModalHandler(); const hotkeyHandler = useModalHandler(); return ( <SettingList title={t("Verge Setting")}> <HotkeyViewer handler={hotkeyHandler} /> + <MiscViewer handler={miscHandler} /> <SettingItem label={t("Language")}> <GuardState @@ -103,6 +106,17 @@ const SettingVerge = ({ onError }: Props) => { </GuardState> </SettingItem> + <SettingItem label={t("Miscellaneous")}> + <IconButton + color="inherit" + size="small" + sx={{ my: "2px" }} + onClick={() => miscHandler.current.open()} + > + <ArrowForward /> + </IconButton> + </SettingItem> + <SettingItem label={t("Theme Setting")}> <IconButton color="inherit" diff --git a/src/hooks/use-verge-config.ts b/src/hooks/use-verge-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..0de7a8077caacd1003a48a39992f4e4b6e269822 --- /dev/null +++ b/src/hooks/use-verge-config.ts @@ -0,0 +1,16 @@ +import useSWR from "swr"; +import { getVergeConfig, patchVergeConfig } from "@/services/cmds"; + +export const useVergeConfig = () => { + const { data, mutate } = useSWR("getVergeConfig", getVergeConfig); + + const patchVerge = async (value: Partial<CmdType.VergeConfig>) => { + await patchVergeConfig(value); + mutate(); + }; + + return { + data, + patchVerge, + }; +}; diff --git a/src/locales/zh.json b/src/locales/zh.json index a1fac5eaab7b687ba76fe937213caf1a3f701188..eb9d627261ae9de7009558fe9cb222deb809959c 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -60,7 +60,7 @@ "IPv6": "IPv6", "Log Level": "日志ç‰çº§", "Mixed Port": "端å£è®¾ç½®", - "External Controller": "外部控制", + "External": "外部控制", "Clash Core": "Clash å†…æ ¸", "Tun Mode": "Tun 模å¼", "Service Mode": "æœåŠ¡æ¨¡å¼", @@ -74,6 +74,7 @@ "Current System Proxy": "当å‰ç³»ç»Ÿä»£ç†", "Theme Mode": "主题模å¼", "Theme Blur": "背景模糊", + "Miscellaneous": "æ‚项设置", "Theme Setting": "主题设置", "Hotkey Setting": "çƒé”®è®¾ç½®", "Traffic Graph": "æµé‡å›¾æ˜¾", diff --git a/src/services/api.ts b/src/services/api.ts index 65594d558e3308e4c9c307165f15dc3d4dcbb4be..625f96eef28e3196ef6f8a793dd2eadf64ec0521 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -176,6 +176,12 @@ export async function providerHealthCheck(name: string) { ); } +export async function getConnections() { + const instance = await getAxios(); + const result = await instance.get("/connections"); + return result as any as ApiType.Connections; +} + // Close specific connection export async function deleteConnection(id: string) { const instance = await getAxios(); diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 804d343965411b436ef494c3cef383d648bbb95a..e84a281a1ff958e7efb208425cadf3a16f6a1f66 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -165,6 +165,8 @@ declare namespace CmdType { font_family?: string; css_injection?: string; }; + auto_close_connection?: boolean; + default_latency_test?: string; } type ClashConfigValue = any;