feat: support clash meta memory usage display

This commit is contained in:
GyDi 2023-08-05 19:40:23 +08:00
parent 6f5acee1c3
commit 96ffbe2f84
No known key found for this signature in database
GPG Key ID: 9C3AD40F1F99880A
9 changed files with 189 additions and 61 deletions

View File

@ -26,6 +26,9 @@ pub struct IVerge {
/// enable traffic graph default is true /// enable traffic graph default is true
pub traffic_graph: Option<bool>, pub traffic_graph: Option<bool>,
/// show memory info (only for Clash Meta)
pub enable_memory_usage: Option<bool>,
/// clash tun mode /// clash tun mode
pub enable_tun_mode: Option<bool>, pub enable_tun_mode: Option<bool>,
@ -125,6 +128,7 @@ impl IVerge {
theme_mode: Some("system".into()), theme_mode: Some("system".into()),
theme_blur: Some(false), theme_blur: Some(false),
traffic_graph: Some(true), traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_auto_launch: Some(false), enable_auto_launch: Some(false),
enable_silent_start: Some(false), enable_silent_start: Some(false),
enable_system_proxy: Some(false), enable_system_proxy: Some(false),
@ -158,6 +162,7 @@ impl IVerge {
patch!(theme_mode); patch!(theme_mode);
patch!(theme_blur); patch!(theme_blur);
patch!(traffic_graph); patch!(traffic_graph);
patch!(enable_memory_usage);
patch!(enable_tun_mode); patch!(enable_tun_mode);
patch!(enable_service_mode); patch!(enable_service_mode);

View File

@ -1,6 +1,10 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Box, Typography } from "@mui/material"; 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 { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { TrafficGraph, type TrafficRef } from "./traffic-graph"; import { TrafficGraph, type TrafficRef } from "./traffic-graph";
@ -12,13 +16,15 @@ import parseTraffic from "@/utils/parse-traffic";
// setup the traffic // setup the traffic
const LayoutTraffic = () => { const LayoutTraffic = () => {
const { clashInfo } = useClashInfo(); const { clashInfo } = useClashInfo();
const { verge } = useVerge();
// whether hide traffic graph // whether hide traffic graph
const { verge } = useVerge();
const trafficGraph = verge?.traffic_graph ?? true; const trafficGraph = verge?.traffic_graph ?? true;
const trafficRef = useRef<TrafficRef>(null); const trafficRef = useRef<TrafficRef>(null);
const [traffic, setTraffic] = useState({ up: 0, down: 0 }); const [traffic, setTraffic] = useState({ up: 0, down: 0 });
const [memory, setMemory] = useState({ inuse: 0 });
const pageVisible = useVisibility();
// setup log ws during layout // setup log ws during layout
useLogSetup(); useLogSetup();
@ -29,8 +35,6 @@ const LayoutTraffic = () => {
setTraffic(data); setTraffic(data);
}); });
const pageVisible = useVisibility();
useEffect(() => { useEffect(() => {
if (!clashInfo || !pageVisible) return; if (!clashInfo || !pageVisible) return;
@ -42,14 +46,38 @@ const LayoutTraffic = () => {
}; };
}, [clashInfo, pageVisible]); }, [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 [up, upUnit] = parseTraffic(traffic.up);
const [down, downUnit] = parseTraffic(traffic.down); const [down, downUnit] = parseTraffic(traffic.down);
const [inuse, inuseUnit] = parseTraffic(memory.inuse);
const iconStyle: any = {
sx: { mr: "8px", fontSize: 16 },
};
const valStyle: any = { const valStyle: any = {
component: "span", component: "span",
color: "primary", color: "primary",
textAlign: "center", textAlign: "center",
sx: { flex: "1 1 54px", userSelect: "none" }, sx: { flex: "1 1 56px", userSelect: "none" },
}; };
const unitStyle: any = { const unitStyle: any = {
component: "span", component: "span",
@ -71,22 +99,37 @@ const LayoutTraffic = () => {
</div> </div>
)} )}
<Box mb={1.5} display="flex" alignItems="center" whiteSpace="nowrap"> <Box display="flex" flexDirection="column" gap={0.75}>
<ArrowUpward <Box display="flex" alignItems="center" whiteSpace="nowrap">
sx={{ mr: 0.75, fontSize: 18 }} <ArrowUpward
color={+up > 0 ? "primary" : "disabled"} {...iconStyle}
/> color={+up > 0 ? "primary" : "disabled"}
<Typography {...valStyle}>{up}</Typography> />
<Typography {...unitStyle}>{upUnit}/s</Typography> <Typography {...valStyle}>{up}</Typography>
</Box> <Typography {...unitStyle}>{upUnit}/s</Typography>
</Box>
<Box display="flex" alignItems="center" whiteSpace="nowrap"> <Box display="flex" alignItems="center" whiteSpace="nowrap">
<ArrowDownward <ArrowDownward
sx={{ mr: 0.75, fontSize: 18 }} {...iconStyle}
color={+down > 0 ? "primary" : "disabled"} color={+down > 0 ? "primary" : "disabled"}
/> />
<Typography {...valStyle}>{down}</Typography> <Typography {...valStyle}>{down}</Typography>
<Typography {...unitStyle}>{downUnit}/s</Typography> <Typography {...unitStyle}>{downUnit}/s</Typography>
</Box>
{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>
</Box> </Box>
); );

View File

@ -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>
);
});

View File

@ -11,10 +11,11 @@ interface ItemProps {
label: ReactNode; label: ReactNode;
extra?: ReactNode; extra?: ReactNode;
children?: ReactNode; children?: ReactNode;
secondary?: ReactNode;
} }
export const SettingItem: React.FC<ItemProps> = (props) => { export const SettingItem: React.FC<ItemProps> = (props) => {
const { label, extra, children } = props; const { label, extra, children, secondary } = props;
const primary = !extra ? ( const primary = !extra ? (
label label
@ -27,7 +28,7 @@ export const SettingItem: React.FC<ItemProps> = (props) => {
return ( return (
<ListItem sx={{ pt: "5px", pb: "5px" }}> <ListItem sx={{ pt: "5px", pb: "5px" }}>
<ListItemText primary={primary} /> <ListItemText primary={primary} secondary={secondary} />
{children} {children}
</ListItem> </ListItem>
); );

View File

@ -1,12 +1,6 @@
import { useRef } from "react"; import { useRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { IconButton, MenuItem, Select, Typography } from "@mui/material";
IconButton,
MenuItem,
Select,
Switch,
Typography,
} from "@mui/material";
import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds"; import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
import { ArrowForward } from "@mui/icons-material"; import { ArrowForward } from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
@ -19,6 +13,7 @@ import { HotkeyViewer } from "./mods/hotkey-viewer";
import { MiscViewer } from "./mods/misc-viewer"; import { MiscViewer } from "./mods/misc-viewer";
import { ThemeViewer } from "./mods/theme-viewer"; import { ThemeViewer } from "./mods/theme-viewer";
import { GuardState } from "./mods/guard-state"; import { GuardState } from "./mods/guard-state";
import { LayoutViewer } from "./mods/layout-viewer";
interface Props { interface Props {
onError?: (err: Error) => void; onError?: (err: Error) => void;
@ -35,6 +30,7 @@ const SettingVerge = ({ onError }: Props) => {
const hotkeyRef = useRef<DialogRef>(null); const hotkeyRef = useRef<DialogRef>(null);
const miscRef = useRef<DialogRef>(null); const miscRef = useRef<DialogRef>(null);
const themeRef = useRef<DialogRef>(null); const themeRef = useRef<DialogRef>(null);
const layoutRef = useRef<DialogRef>(null);
const onSwitchFormat = (_e: any, value: boolean) => value; const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<IVergeConfig>) => { const onChangeData = (patch: Partial<IVergeConfig>) => {
@ -47,6 +43,7 @@ const SettingVerge = ({ onError }: Props) => {
<ConfigViewer ref={configRef} /> <ConfigViewer ref={configRef} />
<HotkeyViewer ref={hotkeyRef} /> <HotkeyViewer ref={hotkeyRef} />
<MiscViewer ref={miscRef} /> <MiscViewer ref={miscRef} />
<LayoutViewer ref={layoutRef} />
<SettingItem label={t("Language")}> <SettingItem label={t("Language")}>
<GuardState <GuardState
@ -75,30 +72,26 @@ const SettingVerge = ({ onError }: Props) => {
</GuardState> </GuardState>
</SettingItem> </SettingItem>
<SettingItem label={t("Theme Blur")}> <SettingItem label={t("Theme Setting")}>
<GuardState <IconButton
value={theme_blur ?? false} color="inherit"
valueProps="checked" size="small"
onCatch={onError} sx={{ my: "2px" }}
onFormat={onSwitchFormat} onClick={() => themeRef.current?.open()}
onChange={(e) => onChangeData({ theme_blur: e })}
onGuard={(e) => patchVerge({ theme_blur: e })}
> >
<Switch edge="end" /> <ArrowForward />
</GuardState> </IconButton>
</SettingItem> </SettingItem>
<SettingItem label={t("Traffic Graph")}> <SettingItem label={t("Layout Setting")}>
<GuardState <IconButton
value={traffic_graph ?? true} color="inherit"
valueProps="checked" size="small"
onCatch={onError} sx={{ my: "2px" }}
onFormat={onSwitchFormat} onClick={() => layoutRef.current?.open()}
onChange={(e) => onChangeData({ traffic_graph: e })}
onGuard={(e) => patchVerge({ traffic_graph: e })}
> >
<Switch edge="end" /> <ArrowForward />
</GuardState> </IconButton>
</SettingItem> </SettingItem>
<SettingItem label={t("Miscellaneous")}> <SettingItem label={t("Miscellaneous")}>
@ -112,17 +105,6 @@ const SettingVerge = ({ onError }: Props) => {
</IconButton> </IconButton>
</SettingItem> </SettingItem>
<SettingItem label={t("Theme Setting")}>
<IconButton
color="inherit"
size="small"
sx={{ my: "2px" }}
onClick={() => themeRef.current?.open()}
>
<ArrowForward />
</IconButton>
</SettingItem>
<SettingItem label={t("Hotkey Setting")}> <SettingItem label={t("Hotkey Setting")}>
<IconButton <IconButton
color="inherit" color="inherit"

View File

@ -5,6 +5,7 @@ export type WsMsgFn = (event: MessageEvent<any>) => void;
export interface WsOptions { export interface WsOptions {
errorCount?: number; // default is 5 errorCount?: number; // default is 5
retryInterval?: number; // default is 2500 retryInterval?: number; // default is 2500
onError?: () => void;
} }
export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => {
@ -38,6 +39,9 @@ export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => {
if (errorCount >= 0) { if (errorCount >= 0) {
timerRef.current = setTimeout(connectHelper, 2500); timerRef.current = setTimeout(connectHelper, 2500);
} else {
disconnect();
options?.onError?.();
} }
}); });
}; };

View File

@ -55,6 +55,8 @@
"Descriptions": "Descriptions", "Descriptions": "Descriptions",
"Subscription URL": "Subscription URL", "Subscription URL": "Subscription URL",
"Update Interval": "Update Interval", "Update Interval": "Update Interval",
"Use System Proxy": "Use System Proxy",
"Use Clash Proxy": "Use Clash Proxy",
"Settings": "Settings", "Settings": "Settings",
"Clash Setting": "Clash Setting", "Clash Setting": "Clash Setting",
@ -64,6 +66,7 @@
"IPv6": "IPv6", "IPv6": "IPv6",
"Log Level": "Log Level", "Log Level": "Log Level",
"Mixed Port": "Mixed Port", "Mixed Port": "Mixed Port",
"External": "External",
"Clash Core": "Clash Core", "Clash Core": "Clash Core",
"Tun Mode": "Tun Mode", "Tun Mode": "Tun Mode",
"Service Mode": "Service Mode", "Service Mode": "Service Mode",
@ -78,8 +81,11 @@
"Theme Mode": "Theme Mode", "Theme Mode": "Theme Mode",
"Theme Blur": "Theme Blur", "Theme Blur": "Theme Blur",
"Theme Setting": "Theme Setting", "Theme Setting": "Theme Setting",
"Layout Setting": "Layout Setting",
"Miscellaneous": "Miscellaneous",
"Hotkey Setting": "Hotkey Setting", "Hotkey Setting": "Hotkey Setting",
"Traffic Graph": "Traffic Graph", "Traffic Graph": "Traffic Graph",
"Memory Usage": "Memory Usage",
"Language": "Language", "Language": "Language",
"Open App Dir": "Open App Dir", "Open App Dir": "Open App Dir",
"Open Core Dir": "Open Core Dir", "Open Core Dir": "Open Core Dir",
@ -97,6 +103,10 @@
"Save": "Save", "Save": "Save",
"Cancel": "Cancel", "Cancel": "Cancel",
"Default": "Default",
"Download Speed": "Download Speed",
"Upload Speed": "Upload Speed",
"clash_mode_rule": "Rule Mode", "clash_mode_rule": "Rule Mode",
"clash_mode_global": "Global Mode", "clash_mode_global": "Global Mode",
"clash_mode_direct": "Direct Mode", "clash_mode_direct": "Direct Mode",

View File

@ -80,10 +80,12 @@
"Current System Proxy": "当前系统代理", "Current System Proxy": "当前系统代理",
"Theme Mode": "主题模式", "Theme Mode": "主题模式",
"Theme Blur": "背景模糊", "Theme Blur": "背景模糊",
"Miscellaneous": "杂项设置",
"Theme Setting": "主题设置", "Theme Setting": "主题设置",
"Layout Setting": "界面设置",
"Miscellaneous": "杂项设置",
"Hotkey Setting": "热键设置", "Hotkey Setting": "热键设置",
"Traffic Graph": "流量图显", "Traffic Graph": "流量图显",
"Memory Usage": "内存使用",
"Language": "语言设置", "Language": "语言设置",
"Open App Dir": "应用目录", "Open App Dir": "应用目录",
"Open Core Dir": "内核目录", "Open Core Dir": "内核目录",

View File

@ -159,6 +159,7 @@ interface IVergeConfig {
theme_mode?: "light" | "dark" | "system"; theme_mode?: "light" | "dark" | "system";
theme_blur?: boolean; theme_blur?: boolean;
traffic_graph?: boolean; traffic_graph?: boolean;
enable_memory_usage?: boolean;
enable_tun_mode?: boolean; enable_tun_mode?: boolean;
enable_auto_launch?: boolean; enable_auto_launch?: boolean;
enable_service_mode?: boolean; enable_service_mode?: boolean;