diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index be16c8520947419c20745113506252ebf543f648..708ce61df635f4119b0236170cdd970197e718b7 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -26,6 +26,9 @@ pub struct IVerge { /// enable traffic graph default is true pub traffic_graph: Option<bool>, + /// show memory info (only for Clash Meta) + pub enable_memory_usage: Option<bool>, + /// clash tun mode pub enable_tun_mode: Option<bool>, @@ -125,6 +128,7 @@ impl IVerge { theme_mode: Some("system".into()), theme_blur: Some(false), traffic_graph: Some(true), + enable_memory_usage: Some(true), enable_auto_launch: Some(false), enable_silent_start: Some(false), enable_system_proxy: Some(false), @@ -158,6 +162,7 @@ impl IVerge { patch!(theme_mode); patch!(theme_blur); patch!(traffic_graph); + patch!(enable_memory_usage); patch!(enable_tun_mode); patch!(enable_service_mode); diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 4095c055c6b038a0550ea3c9362b01a8776715ef..01e55e59ce140b1b702289846688d12045d99d0e 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -1,6 +1,10 @@ import { useEffect, useRef, useState } from "react"; import { Box, Typography } from "@mui/material"; -import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; +import { + ArrowDownward, + ArrowUpward, + MemoryOutlined, +} from "@mui/icons-material"; import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { TrafficGraph, type TrafficRef } from "./traffic-graph"; @@ -12,13 +16,15 @@ import parseTraffic from "@/utils/parse-traffic"; // setup the traffic const LayoutTraffic = () => { const { clashInfo } = useClashInfo(); + const { verge } = useVerge(); // whether hide traffic graph - const { verge } = useVerge(); const trafficGraph = verge?.traffic_graph ?? true; const trafficRef = useRef<TrafficRef>(null); const [traffic, setTraffic] = useState({ up: 0, down: 0 }); + const [memory, setMemory] = useState({ inuse: 0 }); + const pageVisible = useVisibility(); // setup log ws during layout useLogSetup(); @@ -29,8 +35,6 @@ const LayoutTraffic = () => { setTraffic(data); }); - const pageVisible = useVisibility(); - useEffect(() => { if (!clashInfo || !pageVisible) return; @@ -42,14 +46,38 @@ const LayoutTraffic = () => { }; }, [clashInfo, pageVisible]); + /* --------- meta memory information --------- */ + const isMetaCore = verge?.clash_core === "clash-meta"; + const displayMemory = isMetaCore && (verge?.enable_memory_usage ?? true); + + const memoryWs = useWebsocket( + (event) => { + setMemory(JSON.parse(event.data)); + }, + { onError: () => setMemory({ inuse: 0 }) } + ); + + useEffect(() => { + if (!clashInfo || !pageVisible || !displayMemory) return; + const { server = "", secret = "" } = clashInfo; + memoryWs.connect( + `ws://${server}/memory?token=${encodeURIComponent(secret)}` + ); + return () => memoryWs.disconnect(); + }, [clashInfo, pageVisible, displayMemory]); + const [up, upUnit] = parseTraffic(traffic.up); const [down, downUnit] = parseTraffic(traffic.down); + const [inuse, inuseUnit] = parseTraffic(memory.inuse); + const iconStyle: any = { + sx: { mr: "8px", fontSize: 16 }, + }; const valStyle: any = { component: "span", color: "primary", textAlign: "center", - sx: { flex: "1 1 54px", userSelect: "none" }, + sx: { flex: "1 1 56px", userSelect: "none" }, }; const unitStyle: any = { component: "span", @@ -71,22 +99,37 @@ const LayoutTraffic = () => { </div> )} - <Box mb={1.5} display="flex" alignItems="center" whiteSpace="nowrap"> - <ArrowUpward - sx={{ mr: 0.75, fontSize: 18 }} - color={+up > 0 ? "primary" : "disabled"} - /> - <Typography {...valStyle}>{up}</Typography> - <Typography {...unitStyle}>{upUnit}/s</Typography> - </Box> + <Box display="flex" flexDirection="column" gap={0.75}> + <Box display="flex" alignItems="center" whiteSpace="nowrap"> + <ArrowUpward + {...iconStyle} + color={+up > 0 ? "primary" : "disabled"} + /> + <Typography {...valStyle}>{up}</Typography> + <Typography {...unitStyle}>{upUnit}/s</Typography> + </Box> + + <Box display="flex" alignItems="center" whiteSpace="nowrap"> + <ArrowDownward + {...iconStyle} + color={+down > 0 ? "primary" : "disabled"} + /> + <Typography {...valStyle}>{down}</Typography> + <Typography {...unitStyle}>{downUnit}/s</Typography> + </Box> - <Box display="flex" alignItems="center" whiteSpace="nowrap"> - <ArrowDownward - sx={{ mr: 0.75, fontSize: 18 }} - color={+down > 0 ? "primary" : "disabled"} - /> - <Typography {...valStyle}>{down}</Typography> - <Typography {...unitStyle}>{downUnit}/s</Typography> + {displayMemory && ( + <Box + display="flex" + alignItems="center" + whiteSpace="nowrap" + title="Memory Usage" + > + <MemoryOutlined {...iconStyle} color="disabled" /> + <Typography {...valStyle}>{inuse}</Typography> + <Typography {...unitStyle}>{inuseUnit}</Typography> + </Box> + )} </Box> </Box> ); diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..baeca95ae1f4a456bf53bbef26ab7bb2fad54e39 --- /dev/null +++ b/src/components/setting/mods/layout-viewer.tsx @@ -0,0 +1,80 @@ +import { forwardRef, useImperativeHandle, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { List, Switch } from "@mui/material"; +import { useVerge } from "@/hooks/use-verge"; +import { BaseDialog, DialogRef, Notice } from "@/components/base"; +import { SettingItem } from "./setting-comp"; +import { GuardState } from "./guard-state"; + +export const LayoutViewer = forwardRef<DialogRef>((props, ref) => { + const { t } = useTranslation(); + const { verge, patchVerge, mutateVerge } = useVerge(); + + const [open, setOpen] = useState(false); + + useImperativeHandle(ref, () => ({ + open: () => setOpen(true), + close: () => setOpen(false), + })); + + const onSwitchFormat = (_e: any, value: boolean) => value; + const onError = (err: any) => { + Notice.error(err.message || err.toString()); + }; + const onChangeData = (patch: Partial<IVergeConfig>) => { + mutateVerge({ ...verge, ...patch }, false); + }; + + return ( + <BaseDialog + open={open} + title={t("Layout Setting")} + contentSx={{ width: 450 }} + disableOk + cancelBtn={t("Cancel")} + onClose={() => setOpen(false)} + onCancel={() => setOpen(false)} + > + <List> + <SettingItem label={t("Theme Blur")}> + <GuardState + value={verge?.theme_blur ?? false} + valueProps="checked" + onCatch={onError} + onFormat={onSwitchFormat} + onChange={(e) => onChangeData({ theme_blur: e })} + onGuard={(e) => patchVerge({ theme_blur: e })} + > + <Switch edge="end" /> + </GuardState> + </SettingItem> + + <SettingItem label={t("Traffic Graph")}> + <GuardState + value={verge?.traffic_graph ?? true} + valueProps="checked" + onCatch={onError} + onFormat={onSwitchFormat} + onChange={(e) => onChangeData({ traffic_graph: e })} + onGuard={(e) => patchVerge({ traffic_graph: e })} + > + <Switch edge="end" /> + </GuardState> + </SettingItem> + + <SettingItem label={t("Memory Usage")}> + <GuardState + value={verge?.enable_memory_usage ?? true} + valueProps="checked" + onCatch={onError} + onFormat={onSwitchFormat} + onChange={(e) => onChangeData({ enable_memory_usage: e })} + onGuard={(e) => patchVerge({ enable_memory_usage: e })} + > + <Switch edge="end" /> + </GuardState> + </SettingItem> + </List> + </BaseDialog> + ); +}); diff --git a/src/components/setting/mods/setting-comp.tsx b/src/components/setting/mods/setting-comp.tsx index 9b4225b5b9c804d1dec63feb7b3504dfbfd6e160..e0cd181e1fba5eef0945c14047b91a617b52f28d 100644 --- a/src/components/setting/mods/setting-comp.tsx +++ b/src/components/setting/mods/setting-comp.tsx @@ -11,10 +11,11 @@ interface ItemProps { label: ReactNode; extra?: ReactNode; children?: ReactNode; + secondary?: ReactNode; } export const SettingItem: React.FC<ItemProps> = (props) => { - const { label, extra, children } = props; + const { label, extra, children, secondary } = props; const primary = !extra ? ( label @@ -27,7 +28,7 @@ export const SettingItem: React.FC<ItemProps> = (props) => { return ( <ListItem sx={{ pt: "5px", pb: "5px" }}> - <ListItemText primary={primary} /> + <ListItemText primary={primary} secondary={secondary} /> {children} </ListItem> ); diff --git a/src/components/setting/setting-verge.tsx b/src/components/setting/setting-verge.tsx index 0de06e13749371b0631926f8a02936b5f7c49a66..9805e5e6b8bd653c4360cf2e8a52051eac6777b1 100644 --- a/src/components/setting/setting-verge.tsx +++ b/src/components/setting/setting-verge.tsx @@ -1,12 +1,6 @@ import { useRef } from "react"; import { useTranslation } from "react-i18next"; -import { - IconButton, - MenuItem, - Select, - Switch, - Typography, -} from "@mui/material"; +import { IconButton, MenuItem, Select, Typography } from "@mui/material"; import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds"; import { ArrowForward } from "@mui/icons-material"; import { useVerge } from "@/hooks/use-verge"; @@ -19,6 +13,7 @@ import { HotkeyViewer } from "./mods/hotkey-viewer"; import { MiscViewer } from "./mods/misc-viewer"; import { ThemeViewer } from "./mods/theme-viewer"; import { GuardState } from "./mods/guard-state"; +import { LayoutViewer } from "./mods/layout-viewer"; interface Props { onError?: (err: Error) => void; @@ -35,6 +30,7 @@ const SettingVerge = ({ onError }: Props) => { const hotkeyRef = useRef<DialogRef>(null); const miscRef = useRef<DialogRef>(null); const themeRef = useRef<DialogRef>(null); + const layoutRef = useRef<DialogRef>(null); const onSwitchFormat = (_e: any, value: boolean) => value; const onChangeData = (patch: Partial<IVergeConfig>) => { @@ -47,6 +43,7 @@ const SettingVerge = ({ onError }: Props) => { <ConfigViewer ref={configRef} /> <HotkeyViewer ref={hotkeyRef} /> <MiscViewer ref={miscRef} /> + <LayoutViewer ref={layoutRef} /> <SettingItem label={t("Language")}> <GuardState @@ -75,49 +72,34 @@ const SettingVerge = ({ onError }: Props) => { </GuardState> </SettingItem> - <SettingItem label={t("Theme Blur")}> - <GuardState - value={theme_blur ?? false} - valueProps="checked" - onCatch={onError} - onFormat={onSwitchFormat} - onChange={(e) => onChangeData({ theme_blur: e })} - onGuard={(e) => patchVerge({ theme_blur: e })} - > - <Switch edge="end" /> - </GuardState> - </SettingItem> - - <SettingItem label={t("Traffic Graph")}> - <GuardState - value={traffic_graph ?? true} - valueProps="checked" - onCatch={onError} - onFormat={onSwitchFormat} - onChange={(e) => onChangeData({ traffic_graph: e })} - onGuard={(e) => patchVerge({ traffic_graph: e })} + <SettingItem label={t("Theme Setting")}> + <IconButton + color="inherit" + size="small" + sx={{ my: "2px" }} + onClick={() => themeRef.current?.open()} > - <Switch edge="end" /> - </GuardState> + <ArrowForward /> + </IconButton> </SettingItem> - <SettingItem label={t("Miscellaneous")}> + <SettingItem label={t("Layout Setting")}> <IconButton color="inherit" size="small" sx={{ my: "2px" }} - onClick={() => miscRef.current?.open()} + onClick={() => layoutRef.current?.open()} > <ArrowForward /> </IconButton> </SettingItem> - <SettingItem label={t("Theme Setting")}> + <SettingItem label={t("Miscellaneous")}> <IconButton color="inherit" size="small" sx={{ my: "2px" }} - onClick={() => themeRef.current?.open()} + onClick={() => miscRef.current?.open()} > <ArrowForward /> </IconButton> diff --git a/src/hooks/use-websocket.ts b/src/hooks/use-websocket.ts index f6b5f3d30f1b4aff8d342127c8ca1c288b399ecf..810c7b13755d73608caadd952757d77d3be57aab 100644 --- a/src/hooks/use-websocket.ts +++ b/src/hooks/use-websocket.ts @@ -5,6 +5,7 @@ export type WsMsgFn = (event: MessageEvent<any>) => void; export interface WsOptions { errorCount?: number; // default is 5 retryInterval?: number; // default is 2500 + onError?: () => void; } export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { @@ -38,6 +39,9 @@ export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { if (errorCount >= 0) { timerRef.current = setTimeout(connectHelper, 2500); + } else { + disconnect(); + options?.onError?.(); } }); }; diff --git a/src/locales/en.json b/src/locales/en.json index f43abdc14e3337869504ef66f1fe29713e10c57d..3a14bc5caa66251922db047638ba4f5b582ce015 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -55,6 +55,8 @@ "Descriptions": "Descriptions", "Subscription URL": "Subscription URL", "Update Interval": "Update Interval", + "Use System Proxy": "Use System Proxy", + "Use Clash Proxy": "Use Clash Proxy", "Settings": "Settings", "Clash Setting": "Clash Setting", @@ -64,6 +66,7 @@ "IPv6": "IPv6", "Log Level": "Log Level", "Mixed Port": "Mixed Port", + "External": "External", "Clash Core": "Clash Core", "Tun Mode": "Tun Mode", "Service Mode": "Service Mode", @@ -78,8 +81,11 @@ "Theme Mode": "Theme Mode", "Theme Blur": "Theme Blur", "Theme Setting": "Theme Setting", + "Layout Setting": "Layout Setting", + "Miscellaneous": "Miscellaneous", "Hotkey Setting": "Hotkey Setting", "Traffic Graph": "Traffic Graph", + "Memory Usage": "Memory Usage", "Language": "Language", "Open App Dir": "Open App Dir", "Open Core Dir": "Open Core Dir", @@ -97,6 +103,10 @@ "Save": "Save", "Cancel": "Cancel", + "Default": "Default", + "Download Speed": "Download Speed", + "Upload Speed": "Upload Speed", + "clash_mode_rule": "Rule Mode", "clash_mode_global": "Global Mode", "clash_mode_direct": "Direct Mode", diff --git a/src/locales/zh.json b/src/locales/zh.json index 950ebf77921464d905414b3a52d72ab41f3e313a..65a1340e78fcaa77ad04ef2fdb2ea8371d2294e1 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -80,10 +80,12 @@ "Current System Proxy": "当å‰ç³»ç»Ÿä»£ç†", "Theme Mode": "主题模å¼", "Theme Blur": "背景模糊", - "Miscellaneous": "æ‚项设置", "Theme Setting": "主题设置", + "Layout Setting": "界é¢è®¾ç½®", + "Miscellaneous": "æ‚项设置", "Hotkey Setting": "çƒé”®è®¾ç½®", "Traffic Graph": "æµé‡å›¾æ˜¾", + "Memory Usage": "内å˜ä½¿ç”¨", "Language": "è¯è¨€è®¾ç½®", "Open App Dir": "应用目录", "Open Core Dir": "å†…æ ¸ç›®å½•", diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 819c5d07263438e25af4f0188ab8ab70801b6a81..57e3316ee248fac2374797ffdae10040c937551a 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -159,6 +159,7 @@ interface IVergeConfig { theme_mode?: "light" | "dark" | "system"; theme_blur?: boolean; traffic_graph?: boolean; + enable_memory_usage?: boolean; enable_tun_mode?: boolean; enable_auto_launch?: boolean; enable_service_mode?: boolean;