Compare commits
2 Commits
feat/suppo
...
renovate/c
34
src-tauri/Cargo.lock
generated
34
src-tauri/Cargo.lock
generated
@@ -147,7 +147,7 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
"parking_lot 0.12.5",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
@@ -1243,7 +1243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1935,7 +1935,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2191,7 +2191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3356,7 +3356,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@@ -3376,7 +3376,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.58.0",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4456,7 +4456,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4950,7 +4950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5751,7 +5751,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -5788,9 +5788,9 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6283,7 +6283,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6296,7 +6296,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7266,9 +7266,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.9.1"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9871670c6711f50fddd4e20350be6b9dd6e6c2b5d77d8ee8900eb0d58cd837a"
|
||||
checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7799,7 +7799,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix 1.1.2",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9165,7 +9165,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -43,7 +43,7 @@ serde = { version = "1.0.228", features = ["derive"] }
|
||||
reqwest = { version = "0.12.24", features = ["json", "cookies"] }
|
||||
regex = "1.12.2"
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
||||
tauri = { version = "2.9.1", features = [
|
||||
tauri = { version = "2.9.2", features = [
|
||||
"protocol-asset",
|
||||
"devtools",
|
||||
"tray-icon",
|
||||
|
||||
@@ -96,10 +96,17 @@ impl NotificationSystem {
|
||||
let handle = Handle::global();
|
||||
|
||||
while !handle.is_exiting() {
|
||||
match rx.recv_timeout(std::time::Duration::from_millis(100)) {
|
||||
match rx.recv() {
|
||||
Ok(event) => Self::process_event(handle, event),
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => break,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::System,
|
||||
"receive event error, stop notification worker: {}",
|
||||
e
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ export const EnhancedTrafficStats = () => {
|
||||
uploadTotalUnit,
|
||||
downloadTotal,
|
||||
downloadTotalUnit,
|
||||
connectionsCount: connections?.activeConnections.length,
|
||||
connectionsCount: connections?.connections.length,
|
||||
};
|
||||
}, [traffic, memory, connections]);
|
||||
|
||||
|
||||
@@ -4,22 +4,12 @@ import { mutate } from "swr";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
||||
|
||||
export const initConnData: ConnectionMonitorData = {
|
||||
export const initConnData: IConnections = {
|
||||
uploadTotal: 0,
|
||||
downloadTotal: 0,
|
||||
activeConnections: [],
|
||||
closedConnections: [],
|
||||
connections: [],
|
||||
};
|
||||
|
||||
export interface ConnectionMonitorData {
|
||||
uploadTotal: 0;
|
||||
downloadTotal: 0;
|
||||
activeConnections: IConnectionsItem[];
|
||||
closedConnections: IConnectionsItem[];
|
||||
}
|
||||
|
||||
const MAX_CLOSED_CONNS_NUM = 500;
|
||||
|
||||
export const useConnectionData = () => {
|
||||
const [date, setDate] = useLocalStorage("mihomo_connection_date", Date.now());
|
||||
const subscriptKey = `getClashConnection-${date}`;
|
||||
@@ -28,11 +18,7 @@ export const useConnectionData = () => {
|
||||
const wsFirstConnection = useRef<boolean>(true);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
const response = useSWRSubscription<
|
||||
ConnectionMonitorData,
|
||||
any,
|
||||
string | null
|
||||
>(
|
||||
const response = useSWRSubscription<IConnections, any, string | null>(
|
||||
subscriptKey,
|
||||
(_key, { next }) => {
|
||||
const reconnect = async () => {
|
||||
@@ -55,44 +41,28 @@ export const useConnectionData = () => {
|
||||
} else {
|
||||
const data = JSON.parse(msg.data) as IConnections;
|
||||
next(null, (old = initConnData) => {
|
||||
const oldConn = old.activeConnections;
|
||||
const oldConn = old.connections;
|
||||
const maxLen = data.connections?.length;
|
||||
const activeConns: IConnectionsItem[] = [];
|
||||
const connections: IConnectionsItem[] = [];
|
||||
const rest = (data.connections || []).filter((each) => {
|
||||
const index = oldConn.findIndex((o) => o.id === each.id);
|
||||
if (index >= 0 && index < maxLen) {
|
||||
const old = oldConn[index];
|
||||
each.curUpload = each.upload - old.upload;
|
||||
each.curDownload = each.download - old.download;
|
||||
activeConns[index] = each;
|
||||
connections[index] = each;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
for (let i = 0; i < maxLen; ++i) {
|
||||
if (!activeConns[i] && rest.length > 0) {
|
||||
activeConns[i] = rest.shift()!;
|
||||
activeConns[i].curUpload = 0;
|
||||
activeConns[i].curDownload = 0;
|
||||
if (!connections[i] && rest.length > 0) {
|
||||
connections[i] = rest.shift()!;
|
||||
connections[i].curUpload = 0;
|
||||
connections[i].curDownload = 0;
|
||||
}
|
||||
}
|
||||
const currentClosedConns = oldConn.filter((each) => {
|
||||
const index = activeConns.findIndex(
|
||||
(o) => o.id === each.id,
|
||||
);
|
||||
return index < 0;
|
||||
});
|
||||
let closedConns =
|
||||
old.closedConnections.concat(currentClosedConns);
|
||||
if (closedConns.length > 500) {
|
||||
closedConns = closedConns.slice(-MAX_CLOSED_CONNS_NUM);
|
||||
}
|
||||
return {
|
||||
uploadTotal: data.uploadTotal,
|
||||
downloadTotal: data.downloadTotal,
|
||||
activeConnections: activeConns,
|
||||
closedConnections: closedConns,
|
||||
} as ConnectionMonitorData;
|
||||
return { ...data, connections };
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -139,14 +109,5 @@ export const useConnectionData = () => {
|
||||
setDate(Date.now());
|
||||
};
|
||||
|
||||
const clearClosedConnections = () => {
|
||||
mutate(`$sub$${subscriptKey}`, {
|
||||
uploadTotal: response.data?.uploadTotal ?? 0,
|
||||
downloadTotal: response.data?.downloadTotal ?? 0,
|
||||
activeConnections: response.data?.activeConnections ?? [],
|
||||
closedConnections: [],
|
||||
} as ConnectionMonitorData);
|
||||
};
|
||||
|
||||
return { response, refreshGetClashConnection, clearClosedConnections };
|
||||
return { response, refreshGetClashConnection };
|
||||
};
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import {
|
||||
Clear,
|
||||
PauseCircleOutlineRounded,
|
||||
PlayCircleOutlineRounded,
|
||||
TableChartRounded,
|
||||
TableRowsRounded,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Fab,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
} from "@mui/material";
|
||||
import { Box, Button, IconButton, MenuItem } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -27,24 +21,28 @@ import {
|
||||
import { ConnectionItem } from "@/components/connection/connection-item";
|
||||
import { ConnectionTable } from "@/components/connection/connection-table";
|
||||
import { useConnectionData } from "@/hooks/use-connection-data";
|
||||
import { useVisibility } from "@/hooks/use-visibility";
|
||||
import { useConnectionSetting } from "@/services/states";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
const initConn: IConnections = {
|
||||
uploadTotal: 0,
|
||||
downloadTotal: 0,
|
||||
connections: [],
|
||||
};
|
||||
|
||||
type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[];
|
||||
|
||||
const ConnectionsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const pageVisible = useVisibility();
|
||||
const [match, setMatch] = useState<(input: string) => boolean>(
|
||||
() => () => true,
|
||||
);
|
||||
const [curOrderOpt, setCurOrderOpt] = useState("Default");
|
||||
const [connectionsType, setConnectionsType] = useState<"active" | "closed">(
|
||||
"active",
|
||||
);
|
||||
|
||||
const {
|
||||
response: { data: connections },
|
||||
clearClosedConnections,
|
||||
} = useConnectionData();
|
||||
|
||||
const [setting, setSetting] = useConnectionSetting();
|
||||
@@ -67,23 +65,43 @@ const ConnectionsPage = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [frozenData, setFrozenData] = useState<IConnections | null>(null);
|
||||
|
||||
// 使用全局连接数据
|
||||
const displayData = useMemo(() => {
|
||||
if (!pageVisible) return initConn;
|
||||
|
||||
if (isPaused) {
|
||||
return (
|
||||
frozenData ?? {
|
||||
uploadTotal: connections?.uploadTotal,
|
||||
downloadTotal: connections?.downloadTotal,
|
||||
connections: connections?.connections,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
uploadTotal: connections?.uploadTotal,
|
||||
downloadTotal: connections?.downloadTotal,
|
||||
connections: connections?.connections,
|
||||
};
|
||||
}, [isPaused, frozenData, connections, pageVisible]);
|
||||
|
||||
const [filterConn] = useMemo(() => {
|
||||
const orderFunc = orderOpts[curOrderOpt];
|
||||
const conns =
|
||||
(connectionsType === "active"
|
||||
? connections?.activeConnections
|
||||
: connections?.closedConnections) ?? [];
|
||||
let matchConns = conns.filter((conn) => {
|
||||
let conns = displayData.connections?.filter((conn) => {
|
||||
const { host, destinationIP, process } = conn.metadata;
|
||||
return (
|
||||
match(host || "") || match(destinationIP || "") || match(process || "")
|
||||
);
|
||||
});
|
||||
|
||||
if (orderFunc) matchConns = orderFunc(matchConns ?? []);
|
||||
if (orderFunc) conns = orderFunc(conns ?? []);
|
||||
|
||||
return [matchConns];
|
||||
}, [connections, connectionsType, match, curOrderOpt, orderOpts]);
|
||||
return [conns];
|
||||
}, [displayData, match, curOrderOpt, orderOpts]);
|
||||
|
||||
const onCloseAll = useLockFn(closeAllConnections);
|
||||
|
||||
@@ -93,6 +111,21 @@ const ConnectionsPage = () => {
|
||||
setMatch(() => match);
|
||||
}, []);
|
||||
|
||||
const handlePauseToggle = useCallback(() => {
|
||||
setIsPaused((prev) => {
|
||||
if (!prev) {
|
||||
setFrozenData({
|
||||
uploadTotal: connections?.uploadTotal ?? 0,
|
||||
downloadTotal: connections?.downloadTotal ?? 0,
|
||||
connections: connections?.connections ?? [],
|
||||
});
|
||||
} else {
|
||||
setFrozenData(null);
|
||||
}
|
||||
return !prev;
|
||||
});
|
||||
}, [connections]);
|
||||
|
||||
return (
|
||||
<BasePage
|
||||
full
|
||||
@@ -107,10 +140,10 @@ const ConnectionsPage = () => {
|
||||
header={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ mx: 1 }}>
|
||||
{t("Downloaded")}: {parseTraffic(connections?.downloadTotal)}
|
||||
{t("Downloaded")}: {parseTraffic(displayData.downloadTotal)}
|
||||
</Box>
|
||||
<Box sx={{ mx: 1 }}>
|
||||
{t("Uploaded")}: {parseTraffic(connections?.uploadTotal)}
|
||||
{t("Uploaded")}: {parseTraffic(displayData.uploadTotal)}
|
||||
</Box>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
@@ -129,6 +162,18 @@ const ConnectionsPage = () => {
|
||||
<TableChartRounded titleAccess={t("Table View")} />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={handlePauseToggle}
|
||||
title={isPaused ? t("Resume") : t("Pause")}
|
||||
>
|
||||
{isPaused ? (
|
||||
<PlayCircleOutlineRounded />
|
||||
) : (
|
||||
<PauseCircleOutlineRounded />
|
||||
)}
|
||||
</IconButton>
|
||||
<Button size="small" variant="contained" onClick={onCloseAll}>
|
||||
<span style={{ whiteSpace: "nowrap" }}>{t("Close All")}</span>
|
||||
</Button>
|
||||
@@ -149,22 +194,6 @@ const ConnectionsPage = () => {
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<ButtonGroup sx={{ mr: 1, flexBasis: "content" }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant={connectionsType === "active" ? "contained" : "outlined"}
|
||||
onClick={() => setConnectionsType("active")}
|
||||
>
|
||||
{t("Active")} {connections?.activeConnections.length}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant={connectionsType === "closed" ? "contained" : "outlined"}
|
||||
onClick={() => setConnectionsType("closed")}
|
||||
>
|
||||
{t("Closed")} {connections?.closedConnections.length}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{!isTableLayout && (
|
||||
<BaseStyledSelect
|
||||
value={curOrderOpt}
|
||||
@@ -203,17 +232,6 @@ const ConnectionsPage = () => {
|
||||
/>
|
||||
)}
|
||||
<ConnectionDetail ref={detailRef} />
|
||||
{connectionsType === "closed" && (
|
||||
<Fab
|
||||
variant="extended"
|
||||
sx={{ position: "absolute", right: 16, bottom: 16 }}
|
||||
color="primary"
|
||||
onClick={() => clearClosedConnections()}
|
||||
>
|
||||
<Clear sx={{ mr: 1 }} />
|
||||
{t("Clear")}
|
||||
</Fab>
|
||||
)}
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user