Compare commits

...

20 Commits

Author SHA1 Message Date
Tunglies
5c9b46f031 chore: bump version to prerelease 2.4.5-rc.1 2026-01-14 16:55:27 +08:00
renovate[bot]
f5e75d5287 chore(deps): update dependency node to v24.13.0 (#6087)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 14:40:47 +08:00
Slinetrac
c2d8277a1a fix(connections): allow full-width header sorting without triggering on resize 2026-01-14 11:23:50 +08:00
Tunglies
66e98518a7 chore(ci): update autobuild setup for ARM architecture support 2026-01-13 18:42:41 +08:00
Tunglies
089b73bbfd chore(deps): update clash_verge_service_ipc to version 2.0.29 (#6073) 2026-01-13 18:30:54 +08:00
Slinetrac
d2c52d09e1 chore(renovate): disable lockfile maintenance automerge 2026-01-12 15:10:52 +08:00
Slinetrac
84143ec761 chore(deps): bump npm deps 2026-01-12 14:50:54 +08:00
renovate[bot]
f451a26f8c chore(deps): lock file maintenance (#6063)
* chore(deps): lock file maintenance

* chore(deps): update Cargo.toml

* chore(deps): use git repo until the next release

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2026-01-12 14:45:33 +08:00
renovate[bot]
e1220a189b chore(deps): lock file maintenance npm dependencies (#6064)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 05:12:01 +00:00
Tunglies
57d4149807 fix(config): improve runtime config fallback handling 2026-01-11 15:27:54 +08:00
Slinetrac
86c3b241b1 docs: Changelog.md 2026-01-10 11:04:55 +08:00
Sline
a49000712d feat(tun-viewer): route-exclude-address GUI support (#6053) 2026-01-10 10:50:44 +08:00
歳納七夏
35b2066d4c build(tauri): add libayatana-appindicator3 dependency for linux packages (#6051) 2026-01-10 08:42:57 +08:00
renovate[bot]
92e0762fc4 chore(deps): update dependency @actions/github to v7 (#6042)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 00:27:00 +08:00
Slinetrac
6b8630d357 docs: Changelog.md 2026-01-08 22:44:11 +08:00
Slinetrac
a1e77070f0 chore(deps): bump clash-verge-service-ipc to 2.0.29 2026-01-08 22:29:10 +08:00
Slinetrac
6926744ca2 docs: Changelog.md 2026-01-08 14:12:48 +08:00
Slinetrac
13855b9bc2 perf(tun-viewer): run enhanceProfiles in background to avoid save blocking 2026-01-08 14:03:00 +08:00
Slinetrac
1889f18183 feat(notice): override context menu to copy error details 2026-01-07 13:17:56 +08:00
Slinetrac
a981be80ef refactor(base): expand barrel exports and standardize imports 2026-01-06 15:02:10 +08:00
58 changed files with 1133 additions and 1311 deletions

View File

@@ -307,7 +307,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -377,7 +377,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
@@ -505,7 +505,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm

View File

@@ -46,7 +46,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -196,7 +196,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
cache: "pnpm"
- name: Pnpm Cache
@@ -292,7 +292,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
cache: "pnpm"
- name: Pnpm Cache
@@ -313,7 +313,7 @@ jobs:
- name: Setup for linux
run: |
sudo ls -lR /etc/apt/
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo rm -f /etc/apt/sources.list.d/ubuntu.sources
sudo tee /etc/apt/sources.list << EOF
@@ -322,27 +322,28 @@ jobs:
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-backports main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse
EOF
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt-get update -y
sudo apt-get -f install -y
sudo apt-get install -y libglib2.0-dev-bin
sudo apt-get install -y \
linux-libc-dev:${{ matrix.arch }} \
libc6-dev:${{ matrix.arch }}
libc6-dev:${{ matrix.arch }} \
libicu-dev:${{ matrix.arch }}
sudo apt-get install -y \
sudo apt-get install -y --no-install-recommends \
libxslt1-dev:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
librsvg2-dev:${{ matrix.arch }} \
patchelf
- name: Install aarch64 tools
if: matrix.target == 'aarch64-unknown-linux-gnu'
@@ -440,7 +441,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
cache: "pnpm"
- name: Pnpm Cache
@@ -542,7 +543,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4.2.0
name: Install pnpm

View File

@@ -43,7 +43,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm

View File

@@ -103,7 +103,7 @@ jobs:
if: github.event.inputs[matrix.input] == 'true'
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
cache: "pnpm"
- name: Pnpm Cache

View File

@@ -47,7 +47,7 @@ jobs:
- uses: actions/setup-node@v6
if: steps.check_frontend.outputs.frontend == 'true'
with:
node-version: "24.12.0"
node-version: "24.13.0"
cache: "pnpm"
- name: Restore pnpm cache

View File

@@ -197,7 +197,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -281,7 +281,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
@@ -420,7 +420,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -505,7 +505,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -531,7 +531,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -593,7 +593,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm

View File

@@ -15,7 +15,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -39,7 +39,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: "24.12.0"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm

945
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@ smartstring = { version = "1.0.1" }
compact_str = { version = "0.9.0", features = ["serde"] }
serde = { version = "1.0.228" }
serde_json = { version = "1.0.148" }
serde_json = { version = "1.0.149" }
serde_yaml_ng = { version = "0.10.0" }
bitflags = { version = "2.10.0" }

View File

@@ -11,6 +11,7 @@
- 修复恢复备份时 `config.yaml` / `profiles.yaml` 文件内字段未正确恢复
- 修复 Windows 下系统主题同步问题
- 修复 URL Schemes 无法正常导入
- 修复 Linux 下无法安装 TUN 服务
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
@@ -23,7 +24,7 @@
- 允许禁用在托盘中显示代理组
- 支持在「编辑节点」中直接导入 AnyTLS URI 配置
- 支持关闭「验证代理绕过格式」
- 新增系统代理绕过的可视化编辑器
- 新增系统代理绕过和 TUN 排除自定义网段的可视化编辑器
</details>
@@ -40,5 +41,9 @@
- 完善对 AnyTLS / Mieru / Sudoku 的 GUI 支持
- macOS 和 Linux 对服务 IPC 权限进一步限制
- 移除 Windows 自启动计划任务中冗余的 3 秒延时
- 右键错误通知可复制错误详情
- 保存 TUN 设置时优化执行流程,避免界面卡顿
- 补充 `deb` / `rpm` 依赖 `libayatana-appindicator`
- 「连接」表格标题的排序点击区域扩展到整列宽度
</details>

View File

@@ -11,7 +11,7 @@ parking_lot = { workspace = true }
sysinfo = { version = "0.37.2", features = ["network", "system"] }
[target.'cfg(not(windows))'.dependencies]
libc = "0.2.179"
libc = "0.2.180"
[target.'cfg(windows)'.dependencies]
deelevate = { workspace = true }

View File

@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.4.5",
"version": "2.4.5-rc.1",
"license": "GPL-3.0-only",
"scripts": {
"prepare": "husky || true",
@@ -41,24 +41,24 @@
"@emotion/styled": "^11.14.1",
"@juggle/resize-observer": "^3.4.0",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^7.3.6",
"@mui/icons-material": "^7.3.7",
"@mui/lab": "7.0.0-beta.17",
"@mui/material": "^7.3.6",
"@mui/material": "^7.3.7",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.16",
"@tanstack/react-virtual": "^3.13.18",
"@tauri-apps/api": "2.9.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.4.4",
"@tauri-apps/plugin-http": "~2.5.4",
"@tauri-apps/plugin-dialog": "^2.5.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-http": "~2.5.5",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "2.3.3",
"@tauri-apps/plugin-shell": "2.3.4",
"@tauri-apps/plugin-updater": "2.9.0",
"ahooks": "^3.9.6",
"axios": "^1.13.2",
"dayjs": "1.11.19",
"foxact": "^0.2.49",
"i18next": "^25.7.3",
"foxact": "^0.2.52",
"i18next": "^25.7.4",
"js-yaml": "^4.1.1",
"lodash-es": "^4.17.22",
"monaco-editor": "^0.55.1",
@@ -66,11 +66,11 @@
"nanoid": "^5.1.6",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-error-boundary": "6.0.2",
"react-hook-form": "^7.70.0",
"react-i18next": "16.5.1",
"react-error-boundary": "6.0.3",
"react-hook-form": "^7.71.0",
"react-i18next": "16.5.2",
"react-markdown": "10.1.0",
"react-router": "^7.11.0",
"react-router": "^7.12.0",
"react-virtuoso": "^4.18.1",
"rehype-raw": "^7.0.0",
"swr": "^2.3.8",
@@ -78,14 +78,14 @@
"types-pac": "^1.0.3"
},
"devDependencies": {
"@actions/github": "^6.0.1",
"@eslint-react/eslint-plugin": "^2.5.1",
"@actions/github": "^7.0.0",
"@eslint-react/eslint-plugin": "^2.5.5",
"@eslint/js": "^9.39.2",
"@tauri-apps/cli": "2.9.6",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.10.4",
"@types/react": "19.2.7",
"@types/node": "^24.10.7",
"@types/react": "19.2.8",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-react-swc": "^4.2.2",
@@ -110,12 +110,12 @@
"lint-staged": "^16.2.7",
"node-fetch": "^3.3.2",
"prettier": "^3.7.4",
"sass": "^1.97.1",
"sass": "^1.97.2",
"tar": "^7.5.2",
"terser": "^5.44.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.51.0",
"vite": "^7.3.0",
"typescript-eslint": "^8.52.0",
"vite": "^7.3.1",
"vite-plugin-svgr": "^4.5.0"
},
"lint-staged": {
@@ -128,7 +128,7 @@
]
},
"type": "module",
"packageManager": "pnpm@10.27.0",
"packageManager": "pnpm@10.28.0",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

1093
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -49,8 +49,7 @@
"ignoreDeps": ["criterion"],
"lockFileMaintenance": {
"enabled": true,
"description": "Force update Cargo.lock to track latest commits of git dependencies",
"automerge": true,
"description": "Force update lockfile to track latest commits of git dependencies",
"schedule": ["before 5am on monday"]
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "2.4.5"
version = "2.4.5-rc.1"
description = "clash verge"
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@@ -76,11 +76,11 @@ sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", features =
"guard",
] }
network-interface = { version = "2.0.5", features = ["serde"] }
tauri-plugin-shell = "2.3.3"
tauri-plugin-dialog = "2.4.2"
tauri-plugin-fs = "2.4.4"
tauri-plugin-shell = "2.3.4"
tauri-plugin-dialog = "2.5.0"
tauri-plugin-fs = "2.4.5"
tauri-plugin-process = "2.3.1"
tauri-plugin-deep-link = "2.4.5"
tauri-plugin-deep-link = "2.4.6"
tauri-plugin-window-state = "2.4.1"
zip = "7.0.0"
reqwest_dav = "0.2.2"
@@ -93,18 +93,19 @@ scopeguard = "1.2.0"
tauri-plugin-notification = "2.3.3"
tokio-stream = "0.1.18"
backoff = { version = "0.4.0", features = ["tokio"] }
tauri-plugin-http = "2.5.4"
tauri-plugin-http = "2.5.5"
console-subscriber = { version = "0.5.0", optional = true }
tauri-plugin-devtools = { version = "2.0.1" }
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" }
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
async-trait = "0.1.89"
clash_verge_service_ipc = { version = "2.0.28", features = [
clash_verge_service_ipc = { version = "2.0.29", features = [
"client",
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
arc-swap = "1.8.0"
rust_iso3166 = "0.1.14"
dark-light = "2.0.0"
# Use the git repo until the next release after v2.0.0.
dark-light = { git = "https://github.com/rust-dark-light/dark-light" }
governor = "0.10.4"
[target.'cfg(windows)'.dependencies]

View File

@@ -174,11 +174,14 @@ impl Config {
};
let runtime = Self::runtime().await;
let runtime_arc = runtime.latest_arc();
let config = runtime_arc
let runtime_lastest = runtime.latest_arc();
// Fall back to committed config if runtime config is missing
let runtime_data = runtime.data_arc();
let config = runtime_lastest
.config
.as_ref()
.ok_or_else(|| anyhow!("failed to get runtime config"))?;
.or_else(|| runtime_data.config.as_ref())
.ok_or_else(|| anyhow!("failed to generate runtime config, might need to restart application"))?;
help::save_yaml(&path, config, Some("# Generated by Clash Verge")).await?;
Ok(path)

View File

@@ -1,5 +1,5 @@
{
"version": "2.4.5",
"version": "2.4.5-rc.1",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,

View File

@@ -5,7 +5,7 @@
"targets": ["deb", "rpm"],
"linux": {
"deb": {
"depends": ["openssl"],
"depends": ["openssl", "libayatana-appindicator3-1"],
"desktopTemplate": "./packages/linux/clash-verge.desktop",
"provides": ["clash-verge"],
"conflicts": ["clash-verge"],
@@ -14,7 +14,7 @@
"preRemoveScript": "./packages/linux/pre-remove.sh"
},
"rpm": {
"depends": ["openssl"],
"depends": ["openssl", "libayatana-appindicator-gtk3"],
"desktopTemplate": "./packages/linux/clash-verge.desktop",
"provides": ["clash-verge"],
"conflicts": ["clash-verge"],

View File

@@ -1,8 +1,16 @@
export { BaseDialog, type DialogRef } from "./base-dialog";
export { BasePage } from "./base-page";
export { BaseEmpty } from "./base-empty";
export { BaseLoading } from "./base-loading";
export { BaseErrorBoundary } from "./base-error-boundary";
export { BaseSplitChipEditor } from "./base-split-chip-editor";
export { Switch } from "./base-switch";
export { BaseFieldset } from "./base-fieldset";
export { BaseLoading } from "./base-loading";
export { BaseLoadingOverlay } from "./base-loading-overlay";
export { BasePage } from "./base-page";
export { BaseSearchBox, type SearchState } from "./base-search-box";
export {
BaseSplitChipEditor,
type BaseSplitChipEditorMode,
} from "./base-split-chip-editor";
export { BaseStyledSelect } from "./base-styled-select";
export { BaseStyledTextField } from "./base-styled-text-field";
export { Switch } from "./base-switch";
export { TooltipIcon } from "./base-tooltip-icon";

View File

@@ -582,15 +582,10 @@ export const ConnectionTable = (props: Props) => {
alignItems: "center",
position: "relative",
boxSizing: "border-box",
px: 1,
py: 1,
fontSize: 13,
fontWeight: 600,
color: "text.secondary",
userSelect: "none",
justifyContent:
meta?.align === "right" ? "flex-end" : "flex-start",
gap: 0.25,
"&:hover": {
backgroundColor: (theme) =>
theme.palette.action.hover,
@@ -599,15 +594,26 @@ export const ConnectionTable = (props: Props) => {
>
<Box
component="span"
onClick={
header.column.getCanSort()
? header.column.getToggleSortingHandler()
: undefined
}
sx={{
display: "inline-flex",
flex: 1,
display: "flex",
alignItems: "center",
justifyContent:
meta?.align === "right"
? "flex-end"
: "flex-start",
gap: 0.5,
px: 1,
py: 1,
cursor: header.column.getCanSort()
? "pointer"
: "default",
}}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
@@ -620,8 +626,15 @@ export const ConnectionTable = (props: Props) => {
</Box>
{header.column.getCanResize() && (
<Box
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
onClick={(event) => event.stopPropagation()}
onMouseDown={(event) => {
event.stopPropagation();
header.getResizeHandler()(event);
}}
onTouchStart={(event) => {
event.stopPropagation();
header.getResizeHandler()(event);
}}
sx={{
cursor: "col-resize",
position: "absolute",

View File

@@ -6,13 +6,14 @@ import {
Box,
type SnackbarOrigin,
} from "@mui/material";
import React, { useMemo, useSyncExternalStore } from "react";
import React, { useCallback, useMemo, useSyncExternalStore } from "react";
import { useTranslation } from "react-i18next";
import {
subscribeNotices,
hideNotice,
getSnapshotNotices,
showNotice,
} from "@/services/notice-service";
import type { TranslationKey } from "@/types/generated/i18n-keys";
@@ -85,6 +86,45 @@ const resolveNoticeMessage = (
});
};
const extractNoticeCopyText = (input: unknown): string | undefined => {
if (input === null || input === undefined) return undefined;
if (typeof input === "string") return input;
if (typeof input === "number" || typeof input === "boolean") {
return String(input);
}
if (input instanceof Error) {
return input.message || input.name;
}
if (React.isValidElement(input)) return undefined;
if (typeof input === "object") {
const maybeMessage = (input as { message?: unknown }).message;
if (typeof maybeMessage === "string") return maybeMessage;
}
try {
return JSON.stringify(input);
} catch {
return String(input);
}
};
const resolveNoticeCopyText = (
notice: NoticeItem,
t: TranslationFn,
): string | undefined => {
if (
notice.i18n?.key === "shared.feedback.notices.prefixedRaw" ||
notice.i18n?.key === "shared.feedback.notices.raw"
) {
const rawText = extractNoticeCopyText(notice.i18n?.params?.message);
if (rawText) return rawText;
}
return (
extractNoticeCopyText(resolveNoticeMessage(notice, t)) ??
extractNoticeCopyText(notice.message)
);
};
interface NoticeManagerProps {
position?: NoticePosition | null;
}
@@ -105,6 +145,23 @@ export const NoticeManager: React.FC<NoticeManagerProps> = ({ position }) => {
hideNotice(id);
};
const handleNoticeCopy = useCallback(
async (notice: NoticeItem) => {
const text = resolveNoticeCopyText(notice, t);
if (!text) return;
try {
await navigator.clipboard.writeText(text);
showNotice.success(
"shared.feedback.notifications.common.copySuccess",
1000,
);
} catch (error) {
console.warn("[NoticeManager] copy to clipboard failed:", error);
}
},
[t],
);
return (
<Box
sx={{
@@ -139,6 +196,11 @@ export const NoticeManager: React.FC<NoticeManagerProps> = ({ position }) => {
severity={notice.type}
variant="filled"
sx={{ width: "100%" }}
onContextMenu={(event) => {
event.preventDefault();
event.stopPropagation();
void handleNoticeCopy(notice);
}}
action={
<IconButton
size="small"

View File

@@ -2,10 +2,10 @@ import { Button } from "@mui/material";
import { useRef } from "react";
import useSWR from "swr";
import { DialogRef } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { checkUpdateSafe } from "@/services/update";
import { DialogRef } from "../base";
import { UpdateViewer } from "../setting/mods/update-viewer";
interface Props {

View File

@@ -1,7 +1,7 @@
import { styled, Box } from "@mui/material";
import type { ReactNode } from "react";
import { SearchState } from "@/components/base/base-search-box";
import type { SearchState } from "@/components/base";
const Item = styled(Box)(({ theme: { palette, typography } }) => ({
padding: "8px 0",

View File

@@ -48,7 +48,7 @@ import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Virtuoso } from "react-virtuoso";
import { Switch } from "@/components/base";
import { BaseSearchBox, Switch } from "@/components/base";
import { GroupItem } from "@/components/profile/group-item";
import {
getNetworkInterfaces,
@@ -60,8 +60,6 @@ import { useThemeMode } from "@/services/states";
import type { TranslationKey } from "@/types/generated/i18n-keys";
import getSystem from "@/utils/get-system";
import { BaseSearchBox } from "../base/base-search-box";
interface Props {
proxiesUid: string;
mergeUid: string;

View File

@@ -40,6 +40,7 @@ import {
import { useTranslation } from "react-i18next";
import { Virtuoso } from "react-virtuoso";
import { BaseSearchBox } from "@/components/base";
import { ProxyItem } from "@/components/profile/proxy-item";
import { readProfileFile, saveProfileFile } from "@/services/cmds";
import { showNotice } from "@/services/notice-service";
@@ -47,8 +48,6 @@ import { useThemeMode } from "@/services/states";
import getSystem from "@/utils/get-system";
import parseUri from "@/utils/uri-parser";
import { BaseSearchBox } from "../base/base-search-box";
interface Props {
profileUid: string;
property: string;

View File

@@ -42,7 +42,7 @@ import {
import { useTranslation } from "react-i18next";
import { Virtuoso } from "react-virtuoso";
import { Switch } from "@/components/base";
import { BaseSearchBox, Switch } from "@/components/base";
import { RuleItem } from "@/components/profile/rule-item";
import { readProfileFile, saveProfileFile } from "@/services/cmds";
import { showNotice } from "@/services/notice-service";
@@ -50,8 +50,6 @@ import { useThemeMode } from "@/services/states";
import type { TranslationKey } from "@/types/generated/i18n-keys";
import getSystem from "@/utils/get-system";
import { BaseSearchBox } from "../base/base-search-box";
interface Props {
groupsUid: string;
mergeUid: string;

View File

@@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
import { BaseEmpty } from "@/components/base";
import { useProxiesData } from "@/hooks/use-clash-data";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
@@ -22,7 +23,6 @@ import { updateProxyChainConfigInRuntime } from "@/services/cmds";
import delayManager from "@/services/delay";
import { debugLog } from "@/utils/debug";
import { BaseEmpty } from "../base";
import { ScrollTopButton } from "../layout/scroll-top-button";
import { ProxyChain } from "./proxy-chain";

View File

@@ -15,7 +15,7 @@ import { Box, IconButton, TextField, SxProps } from "@mui/material";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseSearchBox } from "@/components/base/base-search-box";
import { BaseSearchBox } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import delayManager from "@/services/delay";
import { debugLog } from "@/utils/debug";

View File

@@ -17,8 +17,7 @@ import { exists } from "@tauri-apps/plugin-fs";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { BaseDialog, DialogRef, Switch, TooltipIcon } from "@/components/base";
import { DEFAULT_HOVER_DELAY } from "@/components/proxy/proxy-group-navigator";
import { useVerge } from "@/hooks/use-verge";
import { useWindowDecorations } from "@/hooks/use-window";

View File

@@ -11,8 +11,7 @@ import type { Ref } from "react";
import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { BaseDialog, DialogRef, Switch, TooltipIcon } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { entry_lightweight_mode } from "@/services/cmds";
import { showNotice } from "@/services/notice-service";

View File

@@ -11,8 +11,7 @@ import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { BaseDialog, DialogRef, Switch, TooltipIcon } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/notice-service";

View File

@@ -26,12 +26,12 @@ import { mutate } from "swr";
import {
BaseDialog,
BaseFieldset,
BaseSplitChipEditor,
DialogRef,
Switch,
TooltipIcon,
} from "@/components/base";
import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer";
import {
useClashConfig,

View File

@@ -12,8 +12,13 @@ import type { Ref } from "react";
import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import {
BaseDialog,
BaseSplitChipEditor,
TooltipIcon,
DialogRef,
Switch,
} from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { enhanceProfiles } from "@/services/cmds";
import { showNotice } from "@/services/notice-service";
@@ -23,6 +28,12 @@ import { StackModeSwitch } from "./stack-mode-switch";
const OS = getSystem();
const splitRouteExcludeAddress = (value: string) =>
value
.split(/[,\n;\r]+/)
.map((item) => item.trim())
.filter(Boolean);
export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation();
@@ -33,6 +44,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
stack: "mixed",
device: OS === "macos" ? "utun1024" : "Mihomo",
autoRoute: true,
routeExcludeAddress: "",
autoRedirect: false,
autoDetectInterface: true,
dnsHijack: ["any:53"],
@@ -51,6 +63,9 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
stack: clash?.tun.stack ?? "gvisor",
device: clash?.tun.device ?? (OS === "macos" ? "utun1024" : "Mihomo"),
autoRoute: nextAutoRoute,
routeExcludeAddress: (clash?.tun["route-exclude-address"] ?? []).join(
",",
),
autoRedirect: computedAutoRedirect,
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53"],
@@ -63,6 +78,9 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
const onSave = useLockFn(async () => {
try {
const routeExcludeAddress = splitRouteExcludeAddress(
values.routeExcludeAddress,
);
const tun: IConfigData["tun"] = {
stack: values.stack,
device:
@@ -72,6 +90,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
: "Mihomo"
: values.device,
"auto-route": values.autoRoute,
"route-exclude-address": routeExcludeAddress,
...(OS === "linux"
? {
"auto-redirect": values.autoRedirect,
@@ -90,13 +109,11 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
}),
false,
);
try {
await enhanceProfiles();
showNotice.success("settings.modals.tun.messages.applied");
} catch (err: any) {
showNotice.error(err);
}
setOpen(false);
showNotice.success("settings.modals.tun.messages.applied");
void enhanceProfiles().catch((err: any) => {
showNotice.error(err);
});
} catch (err: any) {
showNotice.error(err);
}
@@ -123,6 +140,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
: {}),
"auto-detect-interface": true,
"dns-hijack": ["any:53"],
"route-exclude-address": [],
"strict-route": false,
mtu: 1500,
};
@@ -130,6 +148,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
stack: "gvisor",
device: OS === "macos" ? "utun1024" : "Mihomo",
autoRoute: true,
routeExcludeAddress: "",
autoRedirect: false,
autoDetectInterface: true,
dnsHijack: ["any:53"],
@@ -287,6 +306,26 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
}
/>
</ListItem>
<BaseSplitChipEditor
value={values.routeExcludeAddress}
placeholder="192.168.0.0/16"
ariaLabel={t("settings.modals.tun.fields.routeExcludeAddress")}
disabled={!values.autoRoute}
onChange={(nextValue) =>
setValues((v) => ({ ...v, routeExcludeAddress: nextValue }))
}
renderHeader={(modeToggle) => (
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("settings.modals.tun.fields.routeExcludeAddress")}
/>
{modeToggle ? (
<Box sx={{ marginLeft: "auto" }}>{modeToggle}</Box>
) : null}
</ListItem>
)}
/>
</List>
</BaseDialog>
);

View File

@@ -6,8 +6,7 @@ import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { updateGeo } from "tauri-plugin-mihomo-api";
import { DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { DialogRef, Switch, TooltipIcon } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { useClashLog } from "@/hooks/use-clash-log";
import { useVerge } from "@/hooks/use-verge";

View File

@@ -2,8 +2,7 @@ import React, { useRef } from "react";
import { useTranslation } from "react-i18next";
import { mutate } from "swr";
import { DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { DialogRef, Switch, TooltipIcon } from "@/components/base";
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
import { useVerge } from "@/hooks/use-verge";

View File

@@ -3,8 +3,7 @@ import { Typography } from "@mui/material";
import { useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
import { DialogRef } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { DialogRef, TooltipIcon } from "@/components/base";
import {
exitApp,
exportDiagnosticInfo,

View File

@@ -4,8 +4,7 @@ import { open } from "@tauri-apps/plugin-dialog";
import { useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
import { DialogRef } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { DialogRef, TooltipIcon } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { navItems } from "@/pages/_routers";
import { copyClashEnv } from "@/services/cmds";

View File

@@ -11,8 +11,7 @@ import { useLockFn } from "ahooks";
import React, { useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
import { DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { DialogRef, Switch, TooltipIcon } from "@/components/base";
import { GuardState } from "@/components/setting/mods/guard-state";
import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer";
import { TunViewer } from "@/components/setting/mods/tun-viewer";

View File

@@ -444,6 +444,7 @@
"stack": "مكدس TUN",
"device": "Device Name",
"autoRoute": "توجيه تلقائي",
"routeExcludeAddress": "عناوين مستثناة من التوجيه",
"strictRoute": "توجيه صارم",
"autoDetectInterface": "الكشف التلقائي عن الواجهة",
"dnsHijack": "اختطاف DNS",

View File

@@ -444,6 +444,7 @@
"stack": "TUN-Modus-Stack",
"device": "Device Name",
"autoRoute": "Globale Routing automatisch einstellen",
"routeExcludeAddress": "Routen-Ausnahmeadressen",
"strictRoute": "Strenges Routing",
"autoDetectInterface": "Netzwerkschnittstelle automatisch auswählen",
"dnsHijack": "DNS-Hijacking",

View File

@@ -444,6 +444,7 @@
"stack": "Tun Stack",
"device": "Device Name",
"autoRoute": "Auto Route",
"routeExcludeAddress": "Route Exclude Address",
"strictRoute": "Strict Route",
"autoDetectInterface": "Auto Detect Interface",
"dnsHijack": "DNS Hijack",

View File

@@ -444,6 +444,7 @@
"stack": "Pila del modo TUN",
"device": "Device Name",
"autoRoute": "Configurar enrutamiento global automáticamente",
"routeExcludeAddress": "Direcciones excluidas de ruta",
"strictRoute": "Enrutamiento estricto",
"autoDetectInterface": "Detectar automáticamente la interfaz de salida del tráfico",
"dnsHijack": "Secuestro de DNS",

View File

@@ -444,6 +444,7 @@
"stack": "انباشته Tun",
"device": "Device Name",
"autoRoute": "مسیر خودکار",
"routeExcludeAddress": "نشانی‌های مستثنا از مسیردهی",
"strictRoute": "مسیر دقیق",
"autoDetectInterface": "تشخیص خودکار رابط",
"dnsHijack": "ربایش DNS",

View File

@@ -444,6 +444,7 @@
"stack": "Tumpukan Tun",
"device": "Device Name",
"autoRoute": "Rute Otomatis",
"routeExcludeAddress": "Alamat Pengecualian Rute",
"strictRoute": "Rute Ketat",
"autoDetectInterface": "Deteksi Antarmuka Otomatis",
"dnsHijack": "Pembajakan DNS",

View File

@@ -444,6 +444,7 @@
"stack": "TUNモードスタック",
"device": "Device Name",
"autoRoute": "グローバルルートを自動設定",
"routeExcludeAddress": "ルート除外アドレス",
"strictRoute": "厳格なルート",
"autoDetectInterface": "トラフィックの出口インターフェースを自動選択",
"dnsHijack": "DNSハイジャック",

View File

@@ -444,6 +444,7 @@
"stack": "스택",
"device": "장치 이름",
"autoRoute": "자동 라우팅",
"routeExcludeAddress": "라우트 제외 주소",
"strictRoute": "엄격 라우팅",
"autoDetectInterface": "인터페이스 자동 감지",
"dnsHijack": "DNS 하이재킹",

View File

@@ -444,6 +444,7 @@
"stack": "Стек",
"device": "Device Name",
"autoRoute": "Автоматическая маршрутизация",
"routeExcludeAddress": "Адреса исключения маршрута",
"strictRoute": "Строгая маршрутизация",
"autoDetectInterface": "Автоопределение интерфейса",
"dnsHijack": "DNS-перехват",

View File

@@ -444,6 +444,7 @@
"stack": "Tun Yığını",
"device": "Device Name",
"autoRoute": "Otomatik Yönlendirme",
"routeExcludeAddress": "Yönlendirme Hariç Adresler",
"strictRoute": "Katı Yönlendirme",
"autoDetectInterface": "Arayüzü Otomatik Algıla",
"dnsHijack": "DNS Ele Geçirme",

View File

@@ -444,6 +444,7 @@
"stack": "Стек",
"device": "Device Name",
"autoRoute": "Авто-маршрутлау",
"routeExcludeAddress": "Route Exclude Address",
"strictRoute": "Катгый маршрутлау",
"autoDetectInterface": "Интерфейсны автоматик ачыклау",
"dnsHijack": "DNS'ны үзгәртеп тоту (hijack)",

View File

@@ -444,6 +444,7 @@
"stack": "TUN 模式堆栈",
"device": "虚拟网卡名称",
"autoRoute": "自动设置全局路由",
"routeExcludeAddress": "排除自定义网段",
"strictRoute": "严格路由",
"autoDetectInterface": "自动选择流量出口接口",
"dnsHijack": "DNS 劫持",

View File

@@ -444,6 +444,7 @@
"stack": "虛擬網路介面卡模式堆疊",
"device": "Device Name",
"autoRoute": "自動設定全域路由",
"routeExcludeAddress": "排除自訂網段",
"strictRoute": "嚴格路由",
"autoDetectInterface": "自動偵測流量輸出介面",
"dnsHijack": "DNS 綁架",

View File

@@ -18,9 +18,12 @@ import { useTranslation } from "react-i18next";
import { Virtuoso } from "react-virtuoso";
import { closeAllConnections } from "tauri-plugin-mihomo-api";
import { BaseEmpty, BasePage } from "@/components/base";
import { BaseSearchBox } from "@/components/base/base-search-box";
import { BaseStyledSelect } from "@/components/base/base-styled-select";
import {
BaseEmpty,
BasePage,
BaseSearchBox,
BaseStyledSelect,
} from "@/components/base";
import {
ConnectionDetail,
ConnectionDetailRef,

View File

@@ -8,10 +8,13 @@ import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Virtuoso } from "react-virtuoso";
import { BaseEmpty, BasePage } from "@/components/base";
import { BaseSearchBox } from "@/components/base/base-search-box";
import { SearchState } from "@/components/base/base-search-box";
import { BaseStyledSelect } from "@/components/base/base-styled-select";
import {
BaseEmpty,
BasePage,
BaseSearchBox,
BaseStyledSelect,
type SearchState,
} from "@/components/base";
import LogItem from "@/components/log/log-item";
import { useClashLog } from "@/hooks/use-clash-log";
import { useLogData } from "@/hooks/use-log-data";

View File

@@ -36,8 +36,7 @@ import { useLocation } from "react-router";
import useSWR, { mutate } from "swr";
import { closeAllConnections } from "tauri-plugin-mihomo-api";
import { BasePage, DialogRef } from "@/components/base";
import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
import { BasePage, BaseStyledTextField, DialogRef } from "@/components/base";
import { ProfileItem } from "@/components/profile/profile-item";
import { ProfileMore } from "@/components/profile/profile-more";
import {

View File

@@ -3,8 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
import { BaseEmpty, BasePage } from "@/components/base";
import { BaseSearchBox } from "@/components/base/base-search-box";
import { BaseEmpty, BasePage, BaseSearchBox } from "@/components/base";
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
import { ProviderButton } from "@/components/rule/provider-button";
import RuleItem from "@/components/rule/rule-item";

View File

@@ -572,6 +572,7 @@ export const translationKeys = [
"settings.modals.tun.fields.stack",
"settings.modals.tun.fields.device",
"settings.modals.tun.fields.autoRoute",
"settings.modals.tun.fields.routeExcludeAddress",
"settings.modals.tun.fields.strictRoute",
"settings.modals.tun.fields.autoDetectInterface",
"settings.modals.tun.fields.dnsHijack",

View File

@@ -1015,6 +1015,7 @@ export interface TranslationResources {
device: string;
dnsHijack: string;
mtu: string;
routeExcludeAddress: string;
stack: string;
strictRoute: string;
};

View File

@@ -44,6 +44,7 @@ interface IConfigData {
"auto-redirect"?: boolean;
"auto-detect-interface": boolean;
"dns-hijack": string[];
"route-exclude-address"?: string[];
"strict-route": boolean;
mtu: number;
};