mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
feat: support closed connections (#5244)
* feat: support closed connections * fix: clear closed connections * feat: show footer * feat: show closed connection detail * docs: update Changelog.md * chore: update
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
- **Mihomo(Meta) 内核升级至 v1.19.16**
|
- **Mihomo(Meta) 内核升级至 v1.19.16**
|
||||||
- 支持连接页面各个项目的排序
|
- 支持连接页面各个项目的排序
|
||||||
- 实现可选的自动备份
|
- 实现可选的自动备份
|
||||||
|
- 连接页面支持查看已关闭的连接(最近最多 500 个已关闭连接)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,21 @@ import { closeConnections } from "tauri-plugin-mihomo-api";
|
|||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|
||||||
export interface ConnectionDetailRef {
|
export interface ConnectionDetailRef {
|
||||||
open: (detail: IConnectionsItem) => void;
|
open: (detail: IConnectionsItem, closed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [detail, setDetail] = useState<IConnectionsItem>(null!);
|
const [detail, setDetail] = useState<IConnectionsItem>(null!);
|
||||||
|
const [closed, setClosed] = useState(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
open: (detail: IConnectionsItem) => {
|
open: (detail: IConnectionsItem, closed: boolean) => {
|
||||||
if (open) return;
|
if (open) return;
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setDetail(detail);
|
setDetail(detail);
|
||||||
|
setClosed(closed);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -42,7 +44,11 @@ export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
|||||||
}}
|
}}
|
||||||
message={
|
message={
|
||||||
detail ? (
|
detail ? (
|
||||||
<InnerConnectionDetail data={detail} onClose={onClose} />
|
<InnerConnectionDetail
|
||||||
|
data={detail}
|
||||||
|
closed={closed}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -51,10 +57,11 @@ export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
|||||||
|
|
||||||
interface InnerProps {
|
interface InnerProps {
|
||||||
data: IConnectionsItem;
|
data: IConnectionsItem;
|
||||||
|
closed: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
|
const InnerConnectionDetail = ({ data, closed, onClose }: InnerProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { metadata, rulePayload } = data;
|
const { metadata, rulePayload } = data;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -134,18 +141,20 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Box sx={{ textAlign: "right" }}>
|
{!closed && (
|
||||||
<Button
|
<Box sx={{ textAlign: "right" }}>
|
||||||
variant="contained"
|
<Button
|
||||||
title={t("connections.components.actions.closeConnection")}
|
variant="contained"
|
||||||
onClick={() => {
|
title={t("connections.components.actions.closeConnection")}
|
||||||
onDelete();
|
onClick={() => {
|
||||||
onClose?.();
|
onDelete();
|
||||||
}}
|
onClose?.();
|
||||||
>
|
}}
|
||||||
{t("connections.components.actions.closeConnection")}
|
>
|
||||||
</Button>
|
{t("connections.components.actions.closeConnection")}
|
||||||
</Box>
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ const Tag = styled("span")(({ theme }) => ({
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: IConnectionsItem;
|
value: IConnectionsItem;
|
||||||
|
closed: boolean;
|
||||||
onShowDetail?: () => void;
|
onShowDetail?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConnectionItem = (props: Props) => {
|
export const ConnectionItem = (props: Props) => {
|
||||||
const { value, onShowDetail } = props;
|
const { value, closed, onShowDetail } = props;
|
||||||
|
|
||||||
const { id, metadata, chains, start, curUpload, curDownload } = value;
|
const { id, metadata, chains, start, curUpload, curDownload } = value;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -44,15 +45,17 @@ export const ConnectionItem = (props: Props) => {
|
|||||||
dense
|
dense
|
||||||
sx={{ borderBottom: "1px solid var(--divider-color)" }}
|
sx={{ borderBottom: "1px solid var(--divider-color)" }}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
<IconButton
|
!closed && (
|
||||||
edge="end"
|
<IconButton
|
||||||
color="inherit"
|
edge="end"
|
||||||
onClick={onDelete}
|
color="inherit"
|
||||||
title={t("connections.components.actions.closeConnection")}
|
onClick={onDelete}
|
||||||
aria-label={t("connections.components.actions.closeConnection")}
|
title={t("connections.components.actions.closeConnection")}
|
||||||
>
|
aria-label={t("connections.components.actions.closeConnection")}
|
||||||
<CloseRounded />
|
>
|
||||||
</IconButton>
|
<CloseRounded />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
|
|||||||
@@ -516,7 +516,6 @@ export const ConnectionTable = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
apiRef={apiRef}
|
apiRef={apiRef}
|
||||||
hideFooter
|
|
||||||
rows={connRows}
|
rows={connRows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
onRowClick={(e) => onShowDetail(e.row.connectionData)}
|
onRowClick={(e) => onShowDetail(e.row.connectionData)}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export const EnhancedTrafficStats = () => {
|
|||||||
uploadTotalUnit,
|
uploadTotalUnit,
|
||||||
downloadTotal,
|
downloadTotal,
|
||||||
downloadTotalUnit,
|
downloadTotalUnit,
|
||||||
connectionsCount: connections?.connections.length,
|
connectionsCount: connections?.activeConnections.length,
|
||||||
};
|
};
|
||||||
}, [traffic, memory, connections]);
|
}, [traffic, memory, connections]);
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,22 @@ import { mutate } from "swr";
|
|||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
export const initConnData: IConnections = {
|
export const initConnData: ConnectionMonitorData = {
|
||||||
uploadTotal: 0,
|
uploadTotal: 0,
|
||||||
downloadTotal: 0,
|
downloadTotal: 0,
|
||||||
connections: [],
|
activeConnections: [],
|
||||||
|
closedConnections: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ConnectionMonitorData {
|
||||||
|
uploadTotal: number;
|
||||||
|
downloadTotal: number;
|
||||||
|
activeConnections: IConnectionsItem[];
|
||||||
|
closedConnections: IConnectionsItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_CLOSED_CONNS_NUM = 500;
|
||||||
|
|
||||||
export const useConnectionData = () => {
|
export const useConnectionData = () => {
|
||||||
const [date, setDate] = useLocalStorage("mihomo_connection_date", Date.now());
|
const [date, setDate] = useLocalStorage("mihomo_connection_date", Date.now());
|
||||||
const subscriptKey = `getClashConnection-${date}`;
|
const subscriptKey = `getClashConnection-${date}`;
|
||||||
@@ -18,7 +28,11 @@ export const useConnectionData = () => {
|
|||||||
const wsFirstConnection = useRef<boolean>(true);
|
const wsFirstConnection = useRef<boolean>(true);
|
||||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||||
|
|
||||||
const response = useSWRSubscription<IConnections, any, string | null>(
|
const response = useSWRSubscription<
|
||||||
|
ConnectionMonitorData,
|
||||||
|
any,
|
||||||
|
string | null
|
||||||
|
>(
|
||||||
subscriptKey,
|
subscriptKey,
|
||||||
(_key, { next }) => {
|
(_key, { next }) => {
|
||||||
const reconnect = async () => {
|
const reconnect = async () => {
|
||||||
@@ -41,28 +55,44 @@ export const useConnectionData = () => {
|
|||||||
} else {
|
} else {
|
||||||
const data = JSON.parse(msg.data) as IConnections;
|
const data = JSON.parse(msg.data) as IConnections;
|
||||||
next(null, (old = initConnData) => {
|
next(null, (old = initConnData) => {
|
||||||
const oldConn = old.connections;
|
const oldConn = old.activeConnections;
|
||||||
const maxLen = data.connections?.length;
|
const maxLen = data.connections?.length;
|
||||||
const connections: IConnectionsItem[] = [];
|
const activeConns: IConnectionsItem[] = [];
|
||||||
const rest = (data.connections || []).filter((each) => {
|
const rest = (data.connections || []).filter((each) => {
|
||||||
const index = oldConn.findIndex((o) => o.id === each.id);
|
const index = oldConn.findIndex((o) => o.id === each.id);
|
||||||
if (index >= 0 && index < maxLen) {
|
if (index >= 0 && index < maxLen) {
|
||||||
const old = oldConn[index];
|
const old = oldConn[index];
|
||||||
each.curUpload = each.upload - old.upload;
|
each.curUpload = each.upload - old.upload;
|
||||||
each.curDownload = each.download - old.download;
|
each.curDownload = each.download - old.download;
|
||||||
connections[index] = each;
|
activeConns[index] = each;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
for (let i = 0; i < maxLen; ++i) {
|
for (let i = 0; i < maxLen; ++i) {
|
||||||
if (!connections[i] && rest.length > 0) {
|
if (!activeConns[i] && rest.length > 0) {
|
||||||
connections[i] = rest.shift()!;
|
activeConns[i] = rest.shift()!;
|
||||||
connections[i].curUpload = 0;
|
activeConns[i].curUpload = 0;
|
||||||
connections[i].curDownload = 0;
|
activeConns[i].curDownload = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { ...data, connections };
|
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,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,5 +139,14 @@ export const useConnectionData = () => {
|
|||||||
setDate(Date.now());
|
setDate(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
return { response, refreshGetClashConnection };
|
const clearClosedConnections = () => {
|
||||||
|
mutate(`$sub$${subscriptKey}`, {
|
||||||
|
uploadTotal: response.data?.uploadTotal ?? 0,
|
||||||
|
downloadTotal: response.data?.downloadTotal ?? 0,
|
||||||
|
activeConnections: response.data?.activeConnections ?? [],
|
||||||
|
closedConnections: [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { response, refreshGetClashConnection, clearClosedConnections };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "سرعة التنزيل"
|
"downloadSpeed": "سرعة التنزيل"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "إغلاق الاتصال"
|
"closeConnection": "إغلاق الاتصال"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "Download-Geschwindigkeit"
|
"downloadSpeed": "Download-Geschwindigkeit"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Verbindung schließen"
|
"closeConnection": "Verbindung schließen"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "Download Speed"
|
"downloadSpeed": "Download Speed"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Close Connection"
|
"closeConnection": "Close Connection"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "Velocidad de descarga"
|
"downloadSpeed": "Velocidad de descarga"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Cerrar conexión"
|
"closeConnection": "Cerrar conexión"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "سرعت دانلود"
|
"downloadSpeed": "سرعت دانلود"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "بستن اتصال"
|
"closeConnection": "بستن اتصال"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "Kecepatan Unduh"
|
"downloadSpeed": "Kecepatan Unduh"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Tutup Koneksi"
|
"closeConnection": "Tutup Koneksi"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "ダウンロード速度"
|
"downloadSpeed": "ダウンロード速度"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "接続を閉じる"
|
"closeConnection": "接続を閉じる"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "다운로드 속도"
|
"downloadSpeed": "다운로드 속도"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "연결 닫기"
|
"closeConnection": "연결 닫기"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "Скорость скачивания"
|
"downloadSpeed": "Скорость скачивания"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Закрыть соединение"
|
"closeConnection": "Закрыть соединение"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "İndirme Hızı"
|
"downloadSpeed": "İndirme Hızı"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Bağlantıyı Kapat"
|
"closeConnection": "Bağlantıyı Kapat"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "Йөкләү тизлеге"
|
"downloadSpeed": "Йөкләү тизлеге"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "Active",
|
||||||
|
"closed": "Closed",
|
||||||
"closeConnection": "Тоташуны ябу"
|
"closeConnection": "Тоташуны ябу"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "下载速度"
|
"downloadSpeed": "下载速度"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "活跃",
|
||||||
|
"closed": "已关闭",
|
||||||
"closeConnection": "关闭连接"
|
"closeConnection": "关闭连接"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"downloadSpeed": "下載速度"
|
"downloadSpeed": "下載速度"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"active": "活躍",
|
||||||
|
"closed": "已關閉",
|
||||||
"closeConnection": "關閉連線"
|
"closeConnection": "關閉連線"
|
||||||
},
|
},
|
||||||
"columnManager": {
|
"columnManager": {
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
PauseCircleOutlineRounded,
|
DeleteForeverRounded,
|
||||||
PlayCircleOutlineRounded,
|
|
||||||
TableChartRounded,
|
TableChartRounded,
|
||||||
TableRowsRounded,
|
TableRowsRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { Box, Button, IconButton, MenuItem } from "@mui/material";
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Fab,
|
||||||
|
IconButton,
|
||||||
|
MenuItem,
|
||||||
|
Zoom,
|
||||||
|
} from "@mui/material";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -21,16 +28,9 @@ import {
|
|||||||
import { ConnectionItem } from "@/components/connection/connection-item";
|
import { ConnectionItem } from "@/components/connection/connection-item";
|
||||||
import { ConnectionTable } from "@/components/connection/connection-table";
|
import { ConnectionTable } from "@/components/connection/connection-table";
|
||||||
import { useConnectionData } from "@/hooks/use-connection-data";
|
import { useConnectionData } from "@/hooks/use-connection-data";
|
||||||
import { useVisibility } from "@/hooks/use-visibility";
|
|
||||||
import { useConnectionSetting } from "@/services/states";
|
import { useConnectionSetting } from "@/services/states";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|
||||||
const initConn: IConnections = {
|
|
||||||
uploadTotal: 0,
|
|
||||||
downloadTotal: 0,
|
|
||||||
connections: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[];
|
type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[];
|
||||||
|
|
||||||
const ORDER_OPTIONS = [
|
const ORDER_OPTIONS = [
|
||||||
@@ -70,61 +70,42 @@ const orderFunctionMap = ORDER_OPTIONS.reduce<Record<OrderKey, OrderFunc>>(
|
|||||||
|
|
||||||
const ConnectionsPage = () => {
|
const ConnectionsPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const pageVisible = useVisibility();
|
|
||||||
const [match, setMatch] = useState<(input: string) => boolean>(
|
const [match, setMatch] = useState<(input: string) => boolean>(
|
||||||
() => () => true,
|
() => () => true,
|
||||||
);
|
);
|
||||||
const [curOrderOpt, setCurOrderOpt] = useState<OrderKey>("default");
|
const [curOrderOpt, setCurOrderOpt] = useState<OrderKey>("default");
|
||||||
|
const [connectionsType, setConnectionsType] = useState<"active" | "closed">(
|
||||||
|
"active",
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
response: { data: connections },
|
response: { data: connections },
|
||||||
|
clearClosedConnections,
|
||||||
} = useConnectionData();
|
} = useConnectionData();
|
||||||
|
|
||||||
const [setting, setSetting] = useConnectionSetting();
|
const [setting, setSetting] = useConnectionSetting();
|
||||||
|
|
||||||
const isTableLayout = setting.layout === "table";
|
const isTableLayout = setting.layout === "table";
|
||||||
|
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
|
||||||
const [frozenData, setFrozenData] = useState<IConnections | null>(null);
|
|
||||||
const [isColumnManagerOpen, setIsColumnManagerOpen] = useState(false);
|
const [isColumnManagerOpen, setIsColumnManagerOpen] = useState(false);
|
||||||
|
|
||||||
// 使用全局连接数据
|
|
||||||
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 [filterConn] = useMemo(() => {
|
||||||
const orderFunc = orderFunctionMap[curOrderOpt];
|
const orderFunc = orderFunctionMap[curOrderOpt];
|
||||||
let conns: IConnectionsItem[] = (displayData.connections ?? []).filter(
|
const conns =
|
||||||
(conn) => {
|
(connectionsType === "active"
|
||||||
const { host, destinationIP, process } = conn.metadata;
|
? connections?.activeConnections
|
||||||
return (
|
: connections?.closedConnections) ?? [];
|
||||||
match(host || "") ||
|
let matchConns = conns.filter((conn) => {
|
||||||
match(destinationIP || "") ||
|
const { host, destinationIP, process } = conn.metadata;
|
||||||
match(process || "")
|
return (
|
||||||
);
|
match(host || "") || match(destinationIP || "") || match(process || "")
|
||||||
},
|
);
|
||||||
);
|
});
|
||||||
if (orderFunc) conns = orderFunc(conns);
|
|
||||||
|
|
||||||
return [conns];
|
if (orderFunc) matchConns = orderFunc(matchConns ?? []);
|
||||||
}, [displayData, match, curOrderOpt]);
|
|
||||||
|
return [matchConns];
|
||||||
|
}, [connections, connectionsType, match, curOrderOpt]);
|
||||||
|
|
||||||
const onCloseAll = useLockFn(closeAllConnections);
|
const onCloseAll = useLockFn(closeAllConnections);
|
||||||
|
|
||||||
@@ -134,21 +115,6 @@ const ConnectionsPage = () => {
|
|||||||
setMatch(() => match);
|
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]);
|
|
||||||
|
|
||||||
const hasTableData = filterConn.length > 0;
|
const hasTableData = filterConn.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -170,11 +136,11 @@ const ConnectionsPage = () => {
|
|||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||||
<Box sx={{ mx: 1 }}>
|
<Box sx={{ mx: 1 }}>
|
||||||
{t("shared.labels.downloaded")}:{" "}
|
{t("shared.labels.downloaded")}:{" "}
|
||||||
{parseTraffic(displayData.downloadTotal)}
|
{parseTraffic(connections?.downloadTotal)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ mx: 1 }}>
|
<Box sx={{ mx: 1 }}>
|
||||||
{t("shared.labels.uploaded")}:{" "}
|
{t("shared.labels.uploaded")}:{" "}
|
||||||
{parseTraffic(displayData.uploadTotal)}
|
{parseTraffic(connections?.uploadTotal)}
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton
|
<IconButton
|
||||||
color="inherit"
|
color="inherit"
|
||||||
@@ -193,20 +159,6 @@ const ConnectionsPage = () => {
|
|||||||
<TableChartRounded titleAccess={t("shared.actions.tableView")} />
|
<TableChartRounded titleAccess={t("shared.actions.tableView")} />
|
||||||
)}
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={handlePauseToggle}
|
|
||||||
title={
|
|
||||||
isPaused ? t("shared.actions.resume") : t("shared.actions.pause")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{isPaused ? (
|
|
||||||
<PlayCircleOutlineRounded />
|
|
||||||
) : (
|
|
||||||
<PauseCircleOutlineRounded />
|
|
||||||
)}
|
|
||||||
</IconButton>
|
|
||||||
<Button size="small" variant="contained" onClick={onCloseAll}>
|
<Button size="small" variant="contained" onClick={onCloseAll}>
|
||||||
<span style={{ whiteSpace: "nowrap" }}>
|
<span style={{ whiteSpace: "nowrap" }}>
|
||||||
{t("shared.actions.closeAll")}
|
{t("shared.actions.closeAll")}
|
||||||
@@ -230,6 +182,24 @@ const ConnectionsPage = () => {
|
|||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ButtonGroup sx={{ mr: 1, flexBasis: "content" }}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant={connectionsType === "active" ? "contained" : "outlined"}
|
||||||
|
onClick={() => setConnectionsType("active")}
|
||||||
|
>
|
||||||
|
{t("connections.components.actions.active")}{" "}
|
||||||
|
{connections?.activeConnections.length}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant={connectionsType === "closed" ? "contained" : "outlined"}
|
||||||
|
onClick={() => setConnectionsType("closed")}
|
||||||
|
>
|
||||||
|
{t("connections.components.actions.closed")}{" "}
|
||||||
|
{connections?.closedConnections.length}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
{!isTableLayout && (
|
{!isTableLayout && (
|
||||||
<BaseStyledSelect
|
<BaseStyledSelect
|
||||||
value={curOrderOpt}
|
value={curOrderOpt}
|
||||||
@@ -261,7 +231,9 @@ const ConnectionsPage = () => {
|
|||||||
) : isTableLayout ? (
|
) : isTableLayout ? (
|
||||||
<ConnectionTable
|
<ConnectionTable
|
||||||
connections={filterConn}
|
connections={filterConn}
|
||||||
onShowDetail={(detail) => detailRef.current?.open(detail)}
|
onShowDetail={(detail) =>
|
||||||
|
detailRef.current?.open(detail, connectionsType === "closed")
|
||||||
|
}
|
||||||
columnManagerOpen={isTableLayout && isColumnManagerOpen}
|
columnManagerOpen={isTableLayout && isColumnManagerOpen}
|
||||||
onOpenColumnManager={() => setIsColumnManagerOpen(true)}
|
onOpenColumnManager={() => setIsColumnManagerOpen(true)}
|
||||||
onCloseColumnManager={() => setIsColumnManagerOpen(false)}
|
onCloseColumnManager={() => setIsColumnManagerOpen(false)}
|
||||||
@@ -276,12 +248,34 @@ const ConnectionsPage = () => {
|
|||||||
itemContent={(_, item) => (
|
itemContent={(_, item) => (
|
||||||
<ConnectionItem
|
<ConnectionItem
|
||||||
value={item}
|
value={item}
|
||||||
onShowDetail={() => detailRef.current?.open(item)}
|
closed={connectionsType === "closed"}
|
||||||
|
onShowDetail={() =>
|
||||||
|
detailRef.current?.open(item, connectionsType === "closed")
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ConnectionDetail ref={detailRef} />
|
<ConnectionDetail ref={detailRef} />
|
||||||
|
<Zoom
|
||||||
|
in={connectionsType === "closed" && filterConn.length > 0}
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
<Fab
|
||||||
|
size="medium"
|
||||||
|
variant="extended"
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 16,
|
||||||
|
bottom: isTableLayout ? 70 : 16,
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
onClick={() => clearClosedConnections()}
|
||||||
|
>
|
||||||
|
<DeleteForeverRounded sx={{ mr: 1 }} fontSize="small" />
|
||||||
|
{t("shared.actions.clear")}
|
||||||
|
</Fab>
|
||||||
|
</Zoom>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export const translationKeys = [
|
|||||||
"connections.components.order.default",
|
"connections.components.order.default",
|
||||||
"connections.components.order.uploadSpeed",
|
"connections.components.order.uploadSpeed",
|
||||||
"connections.components.order.downloadSpeed",
|
"connections.components.order.downloadSpeed",
|
||||||
|
"connections.components.actions.active",
|
||||||
|
"connections.components.actions.closed",
|
||||||
"connections.components.actions.closeConnection",
|
"connections.components.actions.closeConnection",
|
||||||
"connections.components.columnManager.title",
|
"connections.components.columnManager.title",
|
||||||
"connections.components.columnManager.dragHandle",
|
"connections.components.columnManager.dragHandle",
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export interface TranslationResources {
|
|||||||
connections: {
|
connections: {
|
||||||
components: {
|
components: {
|
||||||
actions: {
|
actions: {
|
||||||
|
active: string;
|
||||||
closeConnection: string;
|
closeConnection: string;
|
||||||
|
closed: string;
|
||||||
};
|
};
|
||||||
columnManager: {
|
columnManager: {
|
||||||
dragHandle: string;
|
dragHandle: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user