diff --git a/package.json b/package.json index 297f3173..0e892b64 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@mui/icons-material": "^7.3.4", "@mui/lab": "7.0.0-beta.17", "@mui/material": "^7.3.4", - "@mui/x-data-grid": "^8.15.0", + "@mui/x-data-grid": "^7.29.9", "@tauri-apps/api": "2.9.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.1", "@tauri-apps/plugin-dialog": "^2.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd960284..15f49800 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: 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) '@mui/x-data-grid': - specifier: ^8.15.0 - version: 8.15.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) + specifier: ^7.29.9 + 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': specifier: 2.9.0 version: 2.9.0 @@ -1254,8 +1254,8 @@ packages: '@types/react': optional: true - '@mui/x-data-grid@8.15.0': - resolution: {integrity: sha512-JNPG2WSYJVKbUAbDpLCbWmIY25k9hyfUjAVnzDREbJMwPL+/5B9pIK0ikRQEXc0wRKY2T59SeR/Um2FZjBeeWQ==} + '@mui/x-data-grid@7.29.9': + resolution: {integrity: sha512-RfK7Fnuu4eyv/4eD3MEB1xxZsx0xRBsofb1kifghIjyQV1EKAeRcwvczyrzQggj7ZRT5AqkwCzhLsZDvE5O0nQ==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -1270,19 +1270,12 @@ packages: '@emotion/styled': optional: true - '@mui/x-internals@8.14.0': - resolution: {integrity: sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==} + '@mui/x-internals@7.29.0': + resolution: {integrity: sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==} engines: {node: '>=14.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@mui/x-virtualizer@0.2.5': - resolution: {integrity: sha512-kCo/i9YfNavbupqZGO1649CHwIABrwUDHVZh+GvGierHhIglUc9MHxYKsPhuojOg6izWa2HP+klt3nq2n/arOw==} - 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': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -5465,18 +5458,18 @@ snapshots: optionalDependencies: '@types/react': 19.2.2 - '@mui/x-data-grid@8.15.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: '@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/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/x-internals': 8.14.0(@types/react@19.2.2)(react@19.2.0) - '@mui/x-virtualizer': 0.2.5(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/x-internals': 7.29.0(@types/react@19.2.2)(react@19.2.0) clsx: 2.1.1 prop-types: 15.8.1 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) optionalDependencies: '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) @@ -5484,23 +5477,11 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.14.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: '@babel/runtime': 7.28.4 '@mui/utils': 7.3.3(@types/react@19.2.2)(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.5(@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.14.0(@types/react@19.2.2)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - '@types/react' diff --git a/renovate.json b/renovate.json index 7df27b15..e224fded 100644 --- a/renovate.json +++ b/renovate.json @@ -40,6 +40,10 @@ "description": "Group all GitHub Actions updates into a single PR", "matchManagers": ["github-actions"], "groupName": "github actions" + }, + { + "matchPackageNames": ["@mui/x-data-grid"], + "matchCurrentVersion": "<8.0.0" } ], "postUpdateOptions": ["pnpmDedupe"], diff --git a/src/components/base/base-error-boundary.tsx b/src/components/base/base-error-boundary.tsx index 8b94429b..3ba1d362 100644 --- a/src/components/base/base-error-boundary.tsx +++ b/src/components/base/base-error-boundary.tsx @@ -3,7 +3,7 @@ import { ErrorBoundary, FallbackProps } from "react-error-boundary"; function ErrorFallback({ error }: FallbackProps) { return ( -
+

Something went wrong:(

{error.message}
diff --git a/src/components/connection/connection-table.tsx b/src/components/connection/connection-table.tsx index 3eea11b1..ec06ce53 100644 --- a/src/components/connection/connection-table.tsx +++ b/src/components/connection/connection-table.tsx @@ -1,13 +1,16 @@ import { DataGrid, + GridActionsCellItem, + GridCloseIcon, GridColDef, GridColumnResizeParams, useGridApiRef, } from "@mui/x-data-grid"; import dayjs from "dayjs"; import { useLocalStorage } from "foxact/use-local-storage"; -import { useLayoutEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { closeConnections } from "tauri-plugin-mihomo-api"; import parseTraffic from "@/utils/parse-traffic"; import { truncateStr } from "@/utils/truncate-str"; @@ -21,129 +24,6 @@ export const ConnectionTable = (props: Props) => { const { connections, onShowDetail } = props; const { t } = useTranslation(); const apiRef = useGridApiRef(); - useLayoutEffect(() => { - const PATCH_FLAG_KEY = "__clashPatchedPublishEvent" as const; - const ORIGINAL_KEY = "__clashOriginalPublishEvent" as const; - let isUnmounted = false; - let retryHandle: ReturnType | 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; - 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< Partial> @@ -160,6 +40,30 @@ export const ConnectionTable = (props: Props) => { const columns = useMemo(() => { return [ + { + field: "actions", + type: "actions", + width: 30, + className: "actions", + getActions: ({ id }) => { + return [ + } + label="Cancel" + className="textPrimary" + onClick={() => closeConnections(id.toString())} + color="inherit" + />, + ]; + }, + }, + { + field: "type", + headerName: t("Type"), + width: columnWidths["type"] || 100, + minWidth: 100, + }, { field: "host", headerName: t("Host"), @@ -239,12 +143,6 @@ export const ConnectionTable = (props: Props) => { width: columnWidths["remoteDestination"] || 200, minWidth: 130, }, - { - field: "type", - headerName: t("Type"), - width: columnWidths["type"] || 160, - minWidth: 100, - }, ]; }, [columnWidths, t]); @@ -266,6 +164,7 @@ export const ConnectionTable = (props: Props) => { ? `${metadata.destinationIP}:${metadata.destinationPort}` : `${metadata.remoteDestination}:${metadata.destinationPort}`; return { + type: `${metadata.type}(${metadata.network})`, id: each.id, host: metadata.host ? `${metadata.host}:${metadata.destinationPort}` @@ -280,7 +179,6 @@ export const ConnectionTable = (props: Props) => { time: each.start, source: `${metadata.sourceIP}:${metadata.sourcePort}`, remoteDestination: Destination, - type: `${metadata.type}(${metadata.network})`, connectionData: each, }; }); @@ -289,7 +187,6 @@ export const ConnectionTable = (props: Props) => { return ( onShowDetail(e.row.connectionData)}