Compare commits

...

4 Commits

6 changed files with 45 additions and 163 deletions

View File

@@ -36,6 +36,7 @@
- 修复 macOS 从 Dock 栏退出轻量模式状态不同步 - 修复 macOS 从 Dock 栏退出轻量模式状态不同步
- 修复 Linux 系统主题切换不生效 - 修复 Linux 系统主题切换不生效
- 修复 `允许自动更新` 字段使手动订阅刷新失效 - 修复 `允许自动更新` 字段使手动订阅刷新失效
- 修复连接界面长时间显示后报错问题
<details> <details>
<summary><strong> ✨ 新增功能 </strong></summary> <summary><strong> ✨ 新增功能 </strong></summary>

View File

@@ -43,7 +43,7 @@
"@mui/icons-material": "^7.3.4", "@mui/icons-material": "^7.3.4",
"@mui/lab": "7.0.0-beta.17", "@mui/lab": "7.0.0-beta.17",
"@mui/material": "^7.3.4", "@mui/material": "^7.3.4",
"@mui/x-data-grid": "^8.16.0", "@mui/x-data-grid": "^7.29.9",
"@tauri-apps/api": "2.9.0", "@tauri-apps/api": "2.9.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.4.2", "@tauri-apps/plugin-dialog": "^2.4.2",

39
pnpm-lock.yaml generated
View File

@@ -36,8 +36,8 @@ importers:
specifier: ^7.3.4 specifier: ^7.3.4
version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@mui/x-data-grid': '@mui/x-data-grid':
specifier: ^8.16.0 specifier: ^7.29.9
version: 8.16.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 7.29.9(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@tauri-apps/api': '@tauri-apps/api':
specifier: 2.9.0 specifier: 2.9.0
version: 2.9.0 version: 2.9.0
@@ -1239,8 +1239,8 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
'@mui/x-data-grid@8.16.0': '@mui/x-data-grid@7.29.9':
resolution: {integrity: sha512-yJ+v+E1yI1HxrEUdOfgrUTCxobAFvotGggU6cy6MnM7c7/TPPg9d5mDzjzxb0imOCJ6WyiM/vtd5WKbY/5sUNw==} resolution: {integrity: sha512-RfK7Fnuu4eyv/4eD3MEB1xxZsx0xRBsofb1kifghIjyQV1EKAeRcwvczyrzQggj7ZRT5AqkwCzhLsZDvE5O0nQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
peerDependencies: peerDependencies:
'@emotion/react': ^11.9.0 '@emotion/react': ^11.9.0
@@ -1255,19 +1255,12 @@ packages:
'@emotion/styled': '@emotion/styled':
optional: true optional: true
'@mui/x-internals@8.16.0': '@mui/x-internals@7.29.0':
resolution: {integrity: sha512-JR53WOFqmQYQzurOpB0H91K7/9uMcte1ooxHxTLGB+97PgB+rKY6siRWvUALGS56XyPV+1a2ALI33hd2E7+Rgg==} resolution: {integrity: sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
peerDependencies: peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0
'@mui/x-virtualizer@0.2.6':
resolution: {integrity: sha512-t45EHhD9kStSwIYMkqYYQIFbZNVQws9LRANktf0e/+j+MxsRTFk41r0rgiazMSOSugJlCuSh/H8xUUuMCZdtow==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
'@napi-rs/wasm-runtime@0.2.12': '@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -5481,18 +5474,18 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.2.2 '@types/react': 19.2.2
'@mui/x-data-grid@8.16.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': '@mui/x-data-grid@7.29.9(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies: dependencies:
'@babel/runtime': 7.28.4 '@babel/runtime': 7.28.4
'@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0)
'@mui/x-internals': 8.16.0(@types/react@19.2.2)(react@19.2.0) '@mui/x-internals': 7.29.0(@types/react@19.2.2)(react@19.2.0)
'@mui/x-virtualizer': 0.2.6(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
clsx: 2.1.1 clsx: 2.1.1
prop-types: 15.8.1 prop-types: 15.8.1
react: 19.2.0 react: 19.2.0
react-dom: 19.2.0(react@19.2.0) react-dom: 19.2.0(react@19.2.0)
reselect: 5.1.1
use-sync-external-store: 1.6.0(react@19.2.0) use-sync-external-store: 1.6.0(react@19.2.0)
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0)
@@ -5500,23 +5493,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
'@mui/x-internals@8.16.0(@types/react@19.2.2)(react@19.2.0)': '@mui/x-internals@7.29.0(@types/react@19.2.2)(react@19.2.0)':
dependencies: dependencies:
'@babel/runtime': 7.28.4 '@babel/runtime': 7.28.4
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0)
react: 19.2.0 react: 19.2.0
reselect: 5.1.1
use-sync-external-store: 1.6.0(react@19.2.0)
transitivePeerDependencies:
- '@types/react'
'@mui/x-virtualizer@0.2.6(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0)
'@mui/x-internals': 8.16.0(@types/react@19.2.2)(react@19.2.0)
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'

View File

@@ -40,6 +40,10 @@
"description": "Group all GitHub Actions updates into a single PR", "description": "Group all GitHub Actions updates into a single PR",
"matchManagers": ["github-actions"], "matchManagers": ["github-actions"],
"groupName": "github actions" "groupName": "github actions"
},
{
"matchPackageNames": ["@mui/x-data-grid"],
"matchCurrentVersion": "<8.0.0"
} }
], ],
"postUpdateOptions": ["pnpmDedupe", "updateCargoLock"], "postUpdateOptions": ["pnpmDedupe", "updateCargoLock"],

View File

@@ -3,7 +3,7 @@ import { ErrorBoundary, FallbackProps } from "react-error-boundary";
function ErrorFallback({ error }: FallbackProps) { function ErrorFallback({ error }: FallbackProps) {
return ( return (
<div role="alert" style={{ padding: 16 }}> <div role="alert" style={{ padding: 16, height: "100%", overflow: "auto" }}>
<h4>Something went wrong:(</h4> <h4>Something went wrong:(</h4>
<pre>{error.message}</pre> <pre>{error.message}</pre>

View File

@@ -1,13 +1,16 @@
import { import {
DataGrid, DataGrid,
GridActionsCellItem,
GridCloseIcon,
GridColDef, GridColDef,
GridColumnResizeParams, GridColumnResizeParams,
useGridApiRef, useGridApiRef,
} from "@mui/x-data-grid"; } from "@mui/x-data-grid";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useLocalStorage } from "foxact/use-local-storage"; import { useLocalStorage } from "foxact/use-local-storage";
import { useLayoutEffect, useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { closeConnections } from "tauri-plugin-mihomo-api";
import parseTraffic from "@/utils/parse-traffic"; import parseTraffic from "@/utils/parse-traffic";
import { truncateStr } from "@/utils/truncate-str"; import { truncateStr } from "@/utils/truncate-str";
@@ -21,129 +24,6 @@ export const ConnectionTable = (props: Props) => {
const { connections, onShowDetail } = props; const { connections, onShowDetail } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const apiRef = useGridApiRef(); const apiRef = useGridApiRef();
useLayoutEffect(() => {
const PATCH_FLAG_KEY = "__clashPatchedPublishEvent" as const;
const ORIGINAL_KEY = "__clashOriginalPublishEvent" as const;
let isUnmounted = false;
let retryHandle: ReturnType<typeof setTimeout> | null = null;
let cleanupOriginal: (() => void) | null = null;
const scheduleRetry = () => {
if (isUnmounted || retryHandle !== null) return;
retryHandle = setTimeout(() => {
retryHandle = null;
ensurePatched();
}, 16);
};
// Safari occasionally emits grid events without an event object,
// and MUI expects `defaultMuiPrevented` to exist. Normalize here to avoid crashes.
const createFallbackEvent = () => {
const fallback = {
defaultMuiPrevented: false,
preventDefault() {
fallback.defaultMuiPrevented = true;
},
};
return fallback;
};
const ensureMuiEvent = (
value: unknown,
): {
defaultMuiPrevented: boolean;
preventDefault: () => void;
[key: string]: unknown;
} => {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return createFallbackEvent();
}
const eventObject = value as {
defaultMuiPrevented?: unknown;
preventDefault?: () => void;
[key: string]: unknown;
};
if (typeof eventObject.defaultMuiPrevented !== "boolean") {
eventObject.defaultMuiPrevented = false;
}
if (typeof eventObject.preventDefault !== "function") {
eventObject.preventDefault = () => {
eventObject.defaultMuiPrevented = true;
};
}
return eventObject as {
defaultMuiPrevented: boolean;
preventDefault: () => void;
[key: string]: unknown;
};
};
const ensurePatched = () => {
if (isUnmounted) return;
const api = apiRef.current;
if (!api?.publishEvent) {
scheduleRetry();
return;
}
const metadataApi = api as unknown as typeof api &
Record<string, unknown>;
if (metadataApi[PATCH_FLAG_KEY] === true) return;
const originalPublishEvent = api.publishEvent;
// Use Proxy to create a more resilient wrapper that always normalizes events
const patchedPublishEvent = new Proxy(originalPublishEvent, {
apply(target, thisArg, rawArgs: unknown[]) {
rawArgs[2] = ensureMuiEvent(rawArgs[2]);
return Reflect.apply(
target as (...args: unknown[]) => unknown,
thisArg,
rawArgs,
);
},
}) as typeof originalPublishEvent;
api.publishEvent = patchedPublishEvent;
metadataApi[PATCH_FLAG_KEY] = true;
metadataApi[ORIGINAL_KEY] = originalPublishEvent;
cleanupOriginal = () => {
const storedOriginal = metadataApi[ORIGINAL_KEY] as
| typeof originalPublishEvent
| undefined;
api.publishEvent = (
typeof storedOriginal === "function"
? storedOriginal
: originalPublishEvent
) as typeof originalPublishEvent;
delete metadataApi[PATCH_FLAG_KEY];
delete metadataApi[ORIGINAL_KEY];
};
};
ensurePatched();
return () => {
isUnmounted = true;
if (retryHandle !== null) {
clearTimeout(retryHandle);
retryHandle = null;
}
if (cleanupOriginal) {
cleanupOriginal();
cleanupOriginal = null;
}
};
}, [apiRef]);
const [columnVisible, setColumnVisible] = useState< const [columnVisible, setColumnVisible] = useState<
Partial<Record<keyof IConnectionsItem, boolean>> Partial<Record<keyof IConnectionsItem, boolean>>
@@ -160,6 +40,29 @@ export const ConnectionTable = (props: Props) => {
const columns = useMemo<GridColDef[]>(() => { const columns = useMemo<GridColDef[]>(() => {
return [ return [
{
field: "actions",
type: "actions",
width: 30,
className: "actions",
getActions: ({ id }) => {
return [
<GridActionsCellItem
key={id.toString()}
icon={<GridCloseIcon />}
label="Close"
onClick={() => closeConnections(id.toString())}
color="inherit"
/>,
];
},
},
{
field: "type",
headerName: t("Type"),
width: columnWidths["type"] || 100,
minWidth: 100,
},
{ {
field: "host", field: "host",
headerName: t("Host"), headerName: t("Host"),
@@ -239,12 +142,6 @@ export const ConnectionTable = (props: Props) => {
width: columnWidths["remoteDestination"] || 200, width: columnWidths["remoteDestination"] || 200,
minWidth: 130, minWidth: 130,
}, },
{
field: "type",
headerName: t("Type"),
width: columnWidths["type"] || 160,
minWidth: 100,
},
]; ];
}, [columnWidths, t]); }, [columnWidths, t]);
@@ -266,6 +163,7 @@ export const ConnectionTable = (props: Props) => {
? `${metadata.destinationIP}:${metadata.destinationPort}` ? `${metadata.destinationIP}:${metadata.destinationPort}`
: `${metadata.remoteDestination}:${metadata.destinationPort}`; : `${metadata.remoteDestination}:${metadata.destinationPort}`;
return { return {
type: `${metadata.type}(${metadata.network})`,
id: each.id, id: each.id,
host: metadata.host host: metadata.host
? `${metadata.host}:${metadata.destinationPort}` ? `${metadata.host}:${metadata.destinationPort}`
@@ -280,7 +178,6 @@ export const ConnectionTable = (props: Props) => {
time: each.start, time: each.start,
source: `${metadata.sourceIP}:${metadata.sourcePort}`, source: `${metadata.sourceIP}:${metadata.sourcePort}`,
remoteDestination: Destination, remoteDestination: Destination,
type: `${metadata.type}(${metadata.network})`,
connectionData: each, connectionData: each,
}; };
}); });
@@ -289,7 +186,6 @@ export const ConnectionTable = (props: Props) => {
return ( return (
<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)}