mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-20 17:22:08 +08:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b35d0ac16f | ||
|
|
3198c3f023 | ||
|
|
a3df3fc2a6 | ||
|
|
507d85ac2f | ||
|
|
236e5c56c3 | ||
|
|
5b63f350ed | ||
|
|
5f885bbe32 | ||
|
|
6e170993dc | ||
|
|
ee6b857336 | ||
|
|
195f470bca | ||
|
|
37c2428f49 | ||
|
|
34793f9880 | ||
|
|
04e50824f1 | ||
|
|
da7faf815f | ||
|
|
af9b67186e | ||
|
|
c5fa64a213 | ||
|
|
5c42658a2e | ||
|
|
3c1bcd7d72 | ||
|
|
328803b894 | ||
|
|
fbe44cf9c1 | ||
|
|
6db559f346 | ||
|
|
f0c7aca3ff | ||
|
|
8316c75c78 | ||
|
|
e56a989944 | ||
|
|
cea78289c1 | ||
|
|
1a2d45555d | ||
|
|
11879e224c | ||
|
|
c8628f32df | ||
|
|
1bb1cb3511 | ||
|
|
711aaf05d3 | ||
|
|
430c946b61 | ||
|
|
2995f61087 | ||
|
|
2aaa91f4cb | ||
|
|
104fd11b04 | ||
|
|
c7dd93004e | ||
|
|
86dcf0bdcf | ||
|
|
09a4e7e083 | ||
|
|
5bb4539e3f | ||
|
|
cad1cf2895 | ||
|
|
623ed69a37 | ||
|
|
7838fa1e75 | ||
|
|
dd34ced070 | ||
|
|
66483d0ef6 | ||
|
|
54858ce1fb | ||
|
|
dfa5cff1b7 | ||
|
|
2b552d6ddb | ||
|
|
af7b33d35b | ||
|
|
d8e386e394 | ||
|
|
bae65a523a | ||
|
|
aa767695af | ||
|
|
494e888b52 | ||
|
|
5292067284 | ||
|
|
9286e921a6 | ||
|
|
3ab985aacc | ||
|
|
418fc1c702 | ||
|
|
b5da9c9629 | ||
|
|
86936d5322 | ||
|
|
c1489647d1 | ||
|
|
af0a3be66d |
@@ -11,11 +11,11 @@ body:
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似 issue,否则请在已有的 issue 下进行讨论
|
||||
3. 请 **务必** 给 issue 填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 查看 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本更新日志
|
||||
5. 请 **务必** 尝试 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本,确定问题是否仍然存在
|
||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本,否则issue将会被直接关闭
|
||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 AutoBuild 版本,否则 issue 将会被直接关闭
|
||||
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ body:
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 确认软件不存在类似的功能
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似 issue,否则请在已有的 issue 下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本测试,确保该功能还未实现
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则 issue 将会被关闭
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) to confirm that the software does not have similar functions
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
|
||||
@@ -307,7 +307,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -187,11 +187,11 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -283,11 +283,11 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -433,11 +433,11 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -535,7 +535,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4.2.0
|
||||
name: Install pnpm
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -103,11 +103,11 @@ jobs:
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (macOS)
|
||||
if: matrix.os == 'macos-latest' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (Windows)
|
||||
if: matrix.os == 'windows-latest' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/bundle/nsis/*.exe
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (Linux)
|
||||
if: matrix.os == 'ubuntu-22.04' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
@@ -47,12 +47,12 @@ jobs:
|
||||
- uses: actions/setup-node@v6
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Restore pnpm cache
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
|
||||
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.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.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+7
-13
@@ -28,24 +28,18 @@ pnpm exec lint-staged
|
||||
RUST_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src-tauri/.*\.rs$' || true)"
|
||||
if [ -n "$RUST_FILES" ]; then
|
||||
echo "[pre-commit] Formatting Rust changes with cargo fmt..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo fmt
|
||||
)
|
||||
cargo fmt
|
||||
while IFS= read -r file; do
|
||||
[ -n "$file" ] && git add "$file"
|
||||
done <<< "$RUST_FILES"
|
||||
|
||||
echo "[pre-commit] Linting Rust changes with cargo clippy..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo clippy-all
|
||||
if ! command -v clash-verge-logging-check >/dev/null 2>&1; then
|
||||
echo "[pre-commit] Installing clash-verge-logging-check..."
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-logging-check.git
|
||||
fi
|
||||
clash-verge-logging-check
|
||||
)
|
||||
cargo clippy-all
|
||||
if ! command -v clash-verge-logging-check >/dev/null 2>&1; then
|
||||
echo "[pre-commit] Installing clash-verge-logging-check..."
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-logging-check.git
|
||||
fi
|
||||
clash-verge-logging-check
|
||||
fi
|
||||
|
||||
TS_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx)$' || true)"
|
||||
|
||||
+2
-8
@@ -25,16 +25,10 @@ pnpm typecheck
|
||||
|
||||
if command -v cargo >/dev/null 2>&1; then
|
||||
echo "[pre-push] Verifying Rust formatting..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo fmt --check
|
||||
)
|
||||
cargo fmt --check
|
||||
|
||||
echo "[pre-push] Running cargo clippy..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo clippy-all
|
||||
)
|
||||
cargo clippy-all
|
||||
else
|
||||
echo "[pre-push] ⚠️ cargo not found; skipping Rust checks."
|
||||
fi
|
||||
|
||||
+5
-1
@@ -119,13 +119,17 @@ cargo fmt
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### Signing your commit
|
||||
|
||||
Signed commits are required to verify authorship and ensure your contributions can be merged. Reference signing-commits [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).
|
||||
|
||||
### Submitting Your Changes
|
||||
|
||||
1. Fork the repository.
|
||||
|
||||
2. Create a new branch for your feature or bug fix.
|
||||
|
||||
3. Commit your changes with clear messages.
|
||||
3. Commit your changes with clear messages and make sure it's signed.
|
||||
|
||||
4. Push your branch and submit a pull request.
|
||||
|
||||
|
||||
Generated
+61
-77
@@ -1002,9 +1002,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
|
||||
checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -1059,9 +1059,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.48"
|
||||
version = "1.2.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -1186,7 +1186,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.4.4-rc"
|
||||
version = "2.4.4-rc.1"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -1312,8 +1312,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash_verge_service_ipc"
|
||||
version = "2.0.24"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#c5a557d5a9f15264697a8ac70d42946a881598d7"
|
||||
version = "2.0.26"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#37b9964a9bce767b5b95ea2be75613b23400c9f0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"compact_str",
|
||||
@@ -2134,9 +2134,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d65cde5fb0c42a3d5882d99807698b459f5928de035fa7f547c784fb7b34219"
|
||||
checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"
|
||||
dependencies = [
|
||||
"dlopen2_derive",
|
||||
"libc",
|
||||
@@ -2146,9 +2146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2_derive"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95f4a04e1bfbfa4835a6073177aafb95ead4de0722dbb339195fdc7e0a09599b"
|
||||
checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2497,13 +2497,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.7"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4035,9 +4035,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kode-bridge"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b158d8b9ab9f07c892a3f1b5d44a79e6c74e97541bf66488025796d429cf7aac"
|
||||
checksum = "0ba2fd0531052f4438d171509ee7686b6b2207314366a5e2dde80586f6d6a09b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
@@ -4155,6 +4155,15 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
@@ -4423,9 +4432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.10"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
@@ -4804,7 +4813,7 @@ dependencies = [
|
||||
"objc2-core-text",
|
||||
"objc2-core-video",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-quartz-core 0.3.2",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4959,18 +4968,6 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.3.2"
|
||||
@@ -4983,19 +4980,6 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
@@ -5004,6 +4988,7 @@ checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
@@ -5767,7 +5752,7 @@ version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.8",
|
||||
"toml_edit 0.23.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6261,9 +6246,9 @@ checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.24"
|
||||
version = "0.12.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
|
||||
checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -6298,7 +6283,7 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.6.7",
|
||||
"tower-http 0.6.8",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -7099,9 +7084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@@ -7197,24 +7182,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "softbuffer"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
|
||||
checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"core-graphics",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
"ndk",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle",
|
||||
"redox_syscall",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7400,8 +7385,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysproxy"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#0f844dd2639b0ac74da4548b1325335844947420"
|
||||
version = "0.4.1"
|
||||
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#279a5cf1c4470d0b28af9a5c3fb7c2fcb503a906"
|
||||
dependencies = [
|
||||
"interfaces",
|
||||
"iptools",
|
||||
@@ -7530,9 +7515,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.9.4"
|
||||
version = "2.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15524fc7959bfcaa051ba6d0b3fb1ef18e978de2176c7c6acb977f7fd14d35c7"
|
||||
checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7833,7 +7818,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-mihomo"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#85fc6b364227e69f5b1cfeb8ca82642c153b5210"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#153f16f7b3f979aa130a2d3d7c39a52a39987288"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"futures-util",
|
||||
@@ -7979,9 +7964,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.9.2"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7950f3bde6bcca6655bc5e76d3d6ec587ceb81032851ab4ddbe1f508bdea2729"
|
||||
checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.4.0",
|
||||
@@ -8490,9 +8475,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.8"
|
||||
version = "0.23.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9b7ac41d92f2d2803f233e297127bac397df7b337e0460a1cc39d6c006dee4"
|
||||
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.7.3",
|
||||
@@ -8680,9 +8665,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.7"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
@@ -10080,15 +10065,14 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.9.2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb"
|
||||
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"os_pipe",
|
||||
"rustix 0.38.44",
|
||||
"tempfile",
|
||||
"rustix 1.1.2",
|
||||
"thiserror 2.0.17",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
@@ -10448,9 +10432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7"
|
||||
checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
|
||||
+1
-4
@@ -9,9 +9,6 @@ members = [
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
@@ -49,7 +46,7 @@ clash-verge-signal = { path = "crates/clash-verge-signal" }
|
||||
clash-verge-types = { path = "crates/clash-verge-types" }
|
||||
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
||||
|
||||
tauri = { version = "2.9.4" }
|
||||
tauri = { version = "2.9.5" }
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
||||
anyhow = "1.0.100"
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
- 修复 macOS 在安装和卸载服务时提示与操作不匹配
|
||||
- 修复菜单排序模式拖拽异常
|
||||
- 修复托盘菜单代理组前的异常勾选状态
|
||||
- 修复 Windows 下自定义标题栏按钮在最小化 / 关闭后 hover 状态残留
|
||||
- 修复直接覆盖 `config.yaml` 使用时无法展开代理组
|
||||
- 修复 macOS 下应用启动时系统托盘图标颜色闪烁
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
@@ -61,6 +64,7 @@
|
||||
- 优化前端 WebSocket 连接机制
|
||||
- 改进旧版 Service 需要重新安装检测流程
|
||||
- 优化 macOS, Linux 和 Windows 系统信号处理
|
||||
- 链式代理仅显示 Selector 类型规则组
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
|
||||
<a href="./docs/README_es.md">Español</a> ·
|
||||
<a href="./docs/README_ru.md">Русский</a> ·
|
||||
<a href="./docs/README_ja.md">日本語</a> ·
|
||||
<a href="./docs/README_ko.md">한국어</a>
|
||||
<a href="./docs/README_ko.md">한국어</a> ·
|
||||
<a href="./docs/README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Preview
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "clash-verge-signal"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
clash-verge-logging = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "clash-verge-types"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "tauri-plugin-clash-verge-sysinfo"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true }
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ A Clash Meta GUI built with <a href="https://github.com/tauri-apps/tauri">Tauri<
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Preview
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ Una interfaz gráfica para Clash Meta construida con <a href="https://github.com
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Vista previa
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<h1 align="center">
|
||||
<img src="../src-tauri/icons/icon.png" alt="Clash" width="128" />
|
||||
<br>
|
||||
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<h3 align="center">
|
||||
یک رابط کاربری گرافیکی Clash Meta که با <a href="https://github.com/tauri-apps/tauri">Tauri</a> ساخته شده است.
|
||||
</h3>
|
||||
|
||||
<p align="center">
|
||||
زبانها:
|
||||
<a href="../README.md">简体中文</a> ·
|
||||
<a href="./README_en.md">English</a> ·
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## پیشنمایش
|
||||
|
||||
| تاریک | روشن |
|
||||
| ----------------------------------- | ------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
## نصب
|
||||
|
||||
برای دانلود فایل نصبی متناسب با پلتفرم خود، به [صفحه انتشار](https://github.com/clash-verge-rev/clash-verge-rev/releases) مراجعه کنید.<br> ما بستههایی برای ویندوز (x64/x86)، لینوکس (x64/arm64) و macOS 10.15+ (اینتل/اپل) ارائه میدهیم.
|
||||
|
||||
#### انتخاب کانال انتشار
|
||||
|
||||
| Channel | توضیحات | Link |
|
||||
| :---------- | :------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- |
|
||||
| Stable | ساخت رسمی با قابلیت اطمینان بالا، ایدهآل برای استفاده روزانه. | [Release](https://github.com/clash-verge-rev/clash-verge-rev/releases) |
|
||||
| Alpha (EOL) | نسخههای قدیمی (Legacy builds) برای اعتبارسنجی خط لوله انتشار (publish pipeline) استفاده میشوند. | [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) |
|
||||
| AutoBuild | نسخههای آزمایشی برای آزمایش و دریافت بازخورد. منتظر تغییرات آزمایشی باشید. | [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) |
|
||||
|
||||
#### راهنماهای نصب و سوالات متداول
|
||||
|
||||
برای مراحل نصب، عیبیابی و سوالات متداول، [مستندات پروژه](https://clash-verge-rev.github.io/) را مطالعه کنید.
|
||||
|
||||
---
|
||||
|
||||
### کانال تلگرام
|
||||
|
||||
برای اطلاع از آخرین اخبار به [@clash_verge_rev](https://t.me/clash_verge_re) بپیوندید.
|
||||
|
||||
## تبلیغات
|
||||
|
||||
#### [Doggygo VPN — شتابدهنده جهانی عملکردگرا](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- سرویس شبکه برون مرزی با عملکرد بالا به همراه دورههای آزمایشی رایگان، طرحهای تخفیفدار، امکان باز کردن قفل استریم و پشتیبانی درجه یک از پروتکل هیستریا.
|
||||
- از طریق لینک اختصاصی Clash Verge ثبت نام کنید تا یک دوره آزمایشی ۳ روزه با ۱ گیگابایت ترافیک در روز دریافت کنید: [ثبت نام](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- کوپن تخفیف ۲۰٪ ویژه کاربران Clash Verge: `verge20` (محدود به ۵۰۰ بار استفاده)
|
||||
- بسته تخفیفدار از ۱۵.۸ ین در ماه برای ۱۶۰ گیگابایت، به علاوه ۲۰٪ تخفیف اضافی برای صورتحساب سالانه
|
||||
- توسط یک تیم خارجی با خدمات قابل اعتماد و تا 50٪ سهم درآمد اداره میشود
|
||||
- کلاسترهای متعادل بار با مسیرهای اختصاصی پرسرعت (سازگار با کلاینتهای قدیمی)، تأخیر فوقالعاده کم، پخش روان 4K
|
||||
- اولین ارائهدهنده جهانی که از پروتکل «Hysteria2» پشتیبانی میکند - کاملاً مناسب برای کلاینت Clash Verge
|
||||
- پشتیبانی از سرویسهای استریم و دسترسی به ChatGPT
|
||||
- وبسایت رسمی: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### حامی زیرساخت ساخت — [سرورهای اختصاصی YXVM](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
بیلدها و نسخههای ما روی سرورهای اختصاصی YXVM اجرا میشوند که منابع ممتاز، عملکرد قوی و شبکه پرسرعت را ارائه میدهند. اگر دانلودها سریع و استفاده از آن سریع به نظر میرسد، به لطف سختافزار قوی است.
|
||||
🧩 نکات برجسته سرورهای اختصاصی YXVM:
|
||||
|
||||
- 🌎 مسیرهای جهانی بهینه شده برای دانلودهای بسیار سریعتر
|
||||
- 🔧 منابع فیزیکی به جای ظرفیت VPS مشترک برای حداکثر کارایی
|
||||
- 🧠 عالی برای بارهای کاری پروکسی، میزبانی سرویسهای وب/CDN، خطوط لوله CI/CD یا هرگونه کار با بار بالا
|
||||
- 💡 آماده استفاده فوری با گزینههای متعدد مرکز داده، از جمله CN2 و IEPL
|
||||
- 📦 پیکربندی مورد استفاده در این پروژه در حال فروش است - میتوانید همان تنظیمات را تهیه کنید.
|
||||
- 🎯 آیا محیط ساخت مشابهی میخواهید؟ [همین امروز یک سرور YXVM سفارش دهید](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
## ویژگیها
|
||||
|
||||
- ساخته شده بر اساس Rust با کارایی بالا و فریمورک Tauri 2
|
||||
- با هسته جاسازیشده [Clash.Meta (mihomo)](https://github.com/MetaCubeX/mihomo) ارائه میشود و از تغییر به کانال «آلفا» پشتیبانی میکند.
|
||||
- رابط کاربری تمیز و مرتب با کنترلهای رنگ تم، آیکونهای گروه/سینی پروکسی و `تزریق CSS`
|
||||
- مدیریت پروفایل پیشرفته (ادغام و کمککنندههای اسکریپت) با نکات مربوط به سینتکس پیکربندی
|
||||
- کنترلهای پروکسی سیستم، حالت محافظت و پشتیبانی از `TUN` (آداپتور شبکه مجازی)
|
||||
- ویرایشگرهای بصری برای گرهها و قوانین
|
||||
- پشتیبانگیری و همگامسازی مبتنی بر WebDAV برای تنظیمات
|
||||
|
||||
### سوالات متداول
|
||||
|
||||
برای راهنماییهای مربوط به هر پلتفرم، به [صفحه سوالات متداول](https://clash-verge-rev.github.io/faq/windows.html) مراجعه کنید.
|
||||
|
||||
### اهدا
|
||||
|
||||
[پشتیبانی از توسعه Clash Verge Rev](https://github.com/sponsors/clash-verge-rev)
|
||||
|
||||
## توسعه
|
||||
|
||||
برای دستورالعملهای دقیق مشارکت، به [CONTRIBUTING.md](../CONTRIBUTING.md) مراجعه کنید.
|
||||
|
||||
پس از نصب تمام پیشنیازهای **Tauri**، پوسته توسعه را با دستور زیر اجرا کنید:
|
||||
|
||||
```shell
|
||||
pnpm i
|
||||
pnpm run prebuild
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## مشارکتها
|
||||
|
||||
مشکلات و درخواستهای pull مورد استقبال قرار میگیرند!
|
||||
|
||||
## تقدیر و تشکر
|
||||
|
||||
Clash Verge Rev بر اساس این پروژهها ساخته شده یا از آنها الهام گرفته است:
|
||||
|
||||
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): یک رابط کاربری گرافیکی Clash مبتنی بر Tauri برای ویندوز، macOS و لینوکس..
|
||||
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): ساخت برنامههای دسکتاپ کوچکتر، سریعتر و امنتر با رابط کاربری وب.
|
||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash): یک تونل مبتنی بر قانون که با زبان Go نوشته شده است.
|
||||
- [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo): یک تونل مبتنی بر قانون که با زبان Go نوشته شده است.
|
||||
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): رابط کاربری گرافیکی Clash برای ویندوز و macOS.
|
||||
- [vitejs/vite](https://github.com/vitejs/vite): ابزارهای فرانتاند نسل بعدی با DX فوقالعاده سریع.
|
||||
|
||||
## مجوز
|
||||
|
||||
مجوز GPL-3.0. برای جزئیات بیشتر به [فایل مجوز](../LICENSE) مراجعه کنید.
|
||||
+2
-1
@@ -16,7 +16,8 @@
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## プレビュー
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## 미리보기
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ Clash Meta GUI базируется на <a href="https://github.com/tauri-apps/
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
## Предпросмотр
|
||||
|
||||
|
||||
+31
-21
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.4.4-rc",
|
||||
"version": "2.4.4-rc.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"prepare": "husky || true",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@mui/lab": "7.0.0-beta.17",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tanstack/react-virtual": "^3.13.13",
|
||||
"@tauri-apps/api": "2.9.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
@@ -57,32 +57,32 @@
|
||||
"axios": "^1.13.2",
|
||||
"dayjs": "1.11.19",
|
||||
"foxact": "^0.2.49",
|
||||
"i18next": "^25.7.1",
|
||||
"i18next": "^25.7.3",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-yaml": "^5.4.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-hook-form": "^7.67.0",
|
||||
"react-i18next": "16.4.0",
|
||||
"react-hook-form": "^7.68.0",
|
||||
"react-i18next": "16.5.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "^7.10.0",
|
||||
"react-virtuoso": "^4.16.1",
|
||||
"swr": "^2.3.7",
|
||||
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#main",
|
||||
"react-router": "^7.10.1",
|
||||
"react-virtuoso": "^4.17.0",
|
||||
"swr": "^2.3.8",
|
||||
"tauri-plugin-mihomo-api": "github:clash-verge-rev/tauri-plugin-mihomo#main",
|
||||
"types-pac": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^6.0.1",
|
||||
"@eslint-react/eslint-plugin": "^2.3.12",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@tauri-apps/cli": "2.9.5",
|
||||
"@eslint-react/eslint-plugin": "^2.3.13",
|
||||
"@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.1",
|
||||
"@types/node": "^24.10.4",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
@@ -91,13 +91,13 @@
|
||||
"cli-color": "^2.0.4",
|
||||
"commander": "^14.0.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"eslint-plugin-react-refresh": "^0.4.25",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"glob": "^13.0.0",
|
||||
"globals": "^16.5.0",
|
||||
@@ -107,12 +107,12 @@
|
||||
"lint-staged": "^16.2.7",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^3.7.4",
|
||||
"sass": "^1.94.2",
|
||||
"sass": "^1.96.0",
|
||||
"tar": "^7.5.2",
|
||||
"terser": "^5.44.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"vite": "^7.2.6",
|
||||
"typescript-eslint": "^8.50.0",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-svgr": "^4.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -125,5 +125,15 @@
|
||||
]
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.22.0"
|
||||
"packageManager": "pnpm@10.26.0",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@swc/core",
|
||||
"core-js",
|
||||
"es5-ext",
|
||||
"esbuild",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1415
-1391
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- "@parcel/watcher"
|
||||
- "@swc/core"
|
||||
- core-js
|
||||
- es5-ext
|
||||
- esbuild
|
||||
- unrs-resolver
|
||||
+1
-1
@@ -45,6 +45,6 @@
|
||||
groupName: "github actions",
|
||||
},
|
||||
],
|
||||
postUpdateOptions: ["pnpmDedupe", "updateCargoLock"],
|
||||
postUpdateOptions: ["pnpmDedupe"],
|
||||
ignoreDeps: ["criterion"],
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ const IGNORED_KEY_PREFIXES = new Set([
|
||||
|
||||
const NOTICE_METHOD_NAMES = new Set(["success", "error", "info", "warning"]);
|
||||
const NOTICE_SERVICE_IDENTIFIERS = new Set([
|
||||
"@/services/noticeService",
|
||||
"./noticeService",
|
||||
"../services/noticeService",
|
||||
"@/services/notice-service",
|
||||
"./notice-service",
|
||||
"../services/notice-service",
|
||||
]);
|
||||
|
||||
const WHITELIST_KEYS = new Set([
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.4.4-rc"
|
||||
version = "2.4.4-rc.1"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
||||
default-run = "clash-verge"
|
||||
build = "build.rs"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
@@ -22,6 +22,7 @@ tauri-dev = ["clash-verge-logging/tauri-dev"]
|
||||
tokio-trace = ["console-subscriber"]
|
||||
clippy = ["tauri/test"]
|
||||
tracing = []
|
||||
tracing-full = []
|
||||
|
||||
[package.metadata.bundle]
|
||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||
@@ -93,7 +94,7 @@ 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.24", features = [
|
||||
clash_verge_service_ipc = { version = "2.0.26", features = [
|
||||
"client",
|
||||
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
@@ -25,8 +25,8 @@ notifications:
|
||||
title: 应用已隐藏
|
||||
body: Clash Verge 正在后台运行。
|
||||
service:
|
||||
adminInstallPrompt: 安装服务需要管理员权限
|
||||
adminUninstallPrompt: 写在服务需要管理员权限
|
||||
adminInstallPrompt: 安装 Clash Verge 服务需要管理员权限
|
||||
adminUninstallPrompt: 卸载 Clash Verge 服务需要管理员权限
|
||||
tray:
|
||||
dashboard: 仪表板
|
||||
ruleMode: 规则模式
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
mpsc,
|
||||
},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
time::Instant,
|
||||
};
|
||||
use tauri::{Emitter as _, WebviewWindow};
|
||||
|
||||
@@ -85,15 +85,17 @@ impl NotificationSystem {
|
||||
}
|
||||
|
||||
fn worker_loop(rx: mpsc::Receiver<FrontendEvent>) {
|
||||
let handle = Handle::global();
|
||||
loop {
|
||||
let handle = Handle::global();
|
||||
if handle.is_exiting() {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(Duration::from_millis(1_000)) {
|
||||
match rx.recv() {
|
||||
Ok(event) => Self::process_event(handle, event),
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => (),
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
||||
Err(e) => {
|
||||
logging!(error, Type::System, "Notification System will exit, recv error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
config::{Config, IClashTemp},
|
||||
core::tray,
|
||||
core::tray::Tray,
|
||||
utils::{dirs, init::service_writer_config},
|
||||
};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
@@ -469,10 +469,23 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
||||
|
||||
/// 检查服务是否正在运行
|
||||
pub async fn is_service_available() -> Result<()> {
|
||||
if let Err(e) = Path::metadata(clash_verge_service_ipc::IPC_PATH.as_ref()) {
|
||||
logging!(warn, Type::Service, "Some issue with service IPC Path: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
clash_verge_service_ipc::connect().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 等待一会,再检查服务是否正在运行
|
||||
/// TODO 使用 tokio select 之类机制并结合 timeout 实现更优雅的等待机制,期望等待文件出现,再尝试连接
|
||||
pub async fn wait_and_check_service_available(status: &mut ServiceManager) -> Result<()> {
|
||||
status.0 = ServiceStatus::Unavailable("Waiting for service to be available".into());
|
||||
clash_verge_service_ipc::connect().await?;
|
||||
status.0 = ServiceStatus::Ready;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_service_ipc_path_exists() -> bool {
|
||||
Path::new(clash_verge_service_ipc::IPC_PATH).exists()
|
||||
}
|
||||
@@ -484,8 +497,8 @@ impl ServiceManager {
|
||||
|
||||
pub const fn config() -> clash_verge_service_ipc::IpcConfig {
|
||||
clash_verge_service_ipc::IpcConfig {
|
||||
default_timeout: Duration::from_millis(30),
|
||||
retry_delay: Duration::from_millis(250),
|
||||
default_timeout: Duration::from_millis(100),
|
||||
retry_delay: Duration::from_millis(200),
|
||||
max_retries: 6,
|
||||
}
|
||||
}
|
||||
@@ -504,6 +517,7 @@ impl ServiceManager {
|
||||
|
||||
pub async fn refresh(&mut self) -> Result<()> {
|
||||
let status = self.check_service_comprehensive().await;
|
||||
self.0 = status.clone();
|
||||
logging_error!(Type::Service, self.handle_service_status(&status).await);
|
||||
Ok(())
|
||||
}
|
||||
@@ -532,28 +546,22 @@ impl ServiceManager {
|
||||
match status {
|
||||
ServiceStatus::Ready => {
|
||||
logging!(info, Type::Service, "服务就绪,直接启动");
|
||||
self.0 = ServiceStatus::Ready;
|
||||
}
|
||||
ServiceStatus::NeedsReinstall | ServiceStatus::ReinstallRequired => {
|
||||
logging!(info, Type::Service, "服务需要重装,执行重装流程");
|
||||
reinstall_service().await?;
|
||||
self.0 = ServiceStatus::Ready;
|
||||
wait_and_check_service_available(self).await?;
|
||||
}
|
||||
ServiceStatus::ForceReinstallRequired => {
|
||||
logging!(info, Type::Service, "服务需要强制重装,执行强制重装流程");
|
||||
force_reinstall_service().await?;
|
||||
self.0 = ServiceStatus::Ready;
|
||||
wait_and_check_service_available(self).await?;
|
||||
}
|
||||
ServiceStatus::InstallRequired => {
|
||||
logging!(info, Type::Service, "需要安装服务,执行安装流程");
|
||||
install_service().await?;
|
||||
// compatible with older service version, force reinstall if service is unavailable
|
||||
// wait for service server is running
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
if is_service_available().await.is_err() {
|
||||
logging!(info, Type::Service, "服务需要强制重装,执行强制重装流程");
|
||||
force_reinstall_service().await?;
|
||||
}
|
||||
self.0 = ServiceStatus::Ready;
|
||||
wait_and_check_service_available(self).await?;
|
||||
}
|
||||
ServiceStatus::UninstallRequired => {
|
||||
logging!(info, Type::Service, "服务需要卸载,执行卸载流程");
|
||||
@@ -566,7 +574,9 @@ impl ServiceManager {
|
||||
return Err(anyhow::anyhow!("服务不可用: {}", reason));
|
||||
}
|
||||
}
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
|
||||
// 防止服务安装成功后,内核未完全启动导致系统托盘无法获取代理节点信息
|
||||
Tray::global().update_menu().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,25 +142,6 @@ impl Timer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 每 3 秒更新系统托盘菜单,总共执行 3 次
|
||||
pub fn add_update_tray_menu_task(&self) -> Result<()> {
|
||||
let tid = self.timer_count.fetch_add(1, Ordering::SeqCst);
|
||||
let task = TaskBuilder::default()
|
||||
.set_task_id(tid)
|
||||
.set_maximum_parallel_runnable_num(1)
|
||||
.set_frequency_count_down_by_seconds(3, 3)
|
||||
.spawn_async_routine(|| async move {
|
||||
logging!(debug, Type::Timer, "Updating tray menu");
|
||||
crate::core::tray::Tray::global().update_menu().await
|
||||
})
|
||||
.context("failed to create update tray menu timer task")?;
|
||||
self.delay_timer
|
||||
.write()
|
||||
.add_task(task)
|
||||
.context("failed to add update tray menu timer task")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Refresh timer tasks with better error handling
|
||||
pub async fn refresh(&self) -> Result<()> {
|
||||
// Generate diff outside of lock to minimize lock contention
|
||||
|
||||
@@ -198,8 +198,6 @@ impl Tray {
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: 初始化时,暂时使用此方法更新系统托盘菜单,有效避免代理节点菜单空白
|
||||
crate::core::timer::Timer::global().add_update_tray_menu_task()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -482,12 +480,16 @@ impl Tray {
|
||||
let show_menu_on_left_click = {
|
||||
// TODO 优化这里 复用 verge
|
||||
let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
|
||||
let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
|
||||
tray_event.as_str() == "tray_menu"
|
||||
tray_event.is_some_and(|v| v == "tray_menu")
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let mut builder = TrayIconBuilder::with_id("main").icon(icon).icon_as_template(false);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let is_monochrome = verge.tray_icon.clone().is_none_or(|v| v == "monochrome");
|
||||
builder = builder.icon_as_template(is_monochrome);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
{
|
||||
@@ -505,16 +507,16 @@ impl Tray {
|
||||
..
|
||||
} = event
|
||||
{
|
||||
// 添加防抖检查,防止快速连击
|
||||
if !should_handle_tray_click() {
|
||||
logging!(info, Type::Tray, "click tray icon too fast, ignore");
|
||||
return;
|
||||
}
|
||||
AsyncHandler::spawn(|| async move {
|
||||
let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
|
||||
let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
|
||||
logging!(debug, Type::Tray, "tray event: {tray_event:?}");
|
||||
|
||||
// 添加防抖检查,防止快速连击
|
||||
if !should_handle_tray_click() {
|
||||
return;
|
||||
}
|
||||
|
||||
match tray_event.as_str() {
|
||||
"system_proxy" => feat::toggle_system_proxy().await,
|
||||
"tun_mode" => feat::toggle_tun_mode(None).await,
|
||||
@@ -523,7 +525,9 @@ impl Tray {
|
||||
WindowManager::show_main_window().await;
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
logging!(warn, Type::Tray, "invalid tray event: {}", tray_event);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -722,7 +726,13 @@ async fn create_tray_menu(
|
||||
|
||||
i18n::sync_locale().await;
|
||||
|
||||
let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await.ok();
|
||||
// TODO: should update tray menu again when it was timeout error
|
||||
let proxy_nodes_data = tokio::time::timeout(
|
||||
Duration::from_millis(1000),
|
||||
handle::Handle::mihomo().await.get_proxies(),
|
||||
)
|
||||
.await
|
||||
.map_or(None, |res| res.ok());
|
||||
|
||||
let runtime_proxy_groups_order = cmd::get_runtime_config()
|
||||
.await
|
||||
@@ -967,7 +977,7 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
AsyncHandler::spawn(|| async move {
|
||||
match event.id.as_ref() {
|
||||
mode @ (MenuIds::RULE_MODE | MenuIds::GLOBAL_MODE | MenuIds::DIRECT_MODE) => {
|
||||
// Removing the the "tray_" preffix and "_mode" suffix
|
||||
// Removing the the "tray_" prefix and "_mode" suffix
|
||||
let mode = &mode[5..mode.len() - 5];
|
||||
logging!(info, Type::ProxyMode, "Switch Proxy Mode To: {}", mode);
|
||||
feat::change_clash_mode(mode.into()).await;
|
||||
|
||||
@@ -111,7 +111,7 @@ async fn perform_profile_update(
|
||||
let profile_name = profiles_arc
|
||||
.get_name_by_uid(uid)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| String::from("UnKown Profile"));
|
||||
.unwrap_or_else(|| String::from("UnKnown Profile"));
|
||||
|
||||
let mut last_err;
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@ mod feat;
|
||||
mod module;
|
||||
mod process;
|
||||
pub mod utils;
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::utils::linux;
|
||||
use crate::utils::resolve::init_signal;
|
||||
use crate::{constants::files, utils::resolve::prioritize_initialization};
|
||||
use crate::constants::files;
|
||||
use crate::{
|
||||
core::handle,
|
||||
process::AsyncHandler,
|
||||
@@ -232,9 +229,6 @@ pub fn run() {
|
||||
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
linux::configure_environment();
|
||||
|
||||
let builder = app_init::setup_plugins(tauri::Builder::default())
|
||||
.setup(|app| {
|
||||
#[allow(clippy::expect_used)]
|
||||
@@ -242,7 +236,7 @@ pub fn run() {
|
||||
.set(app.app_handle().clone())
|
||||
.expect("failed to set global app handle");
|
||||
|
||||
let _handle = AsyncHandler::block_on(async { prioritize_initialization().await });
|
||||
let _handle = resolve::init_work_dir_and_logger();
|
||||
|
||||
logging!(info, Type::Setup, "开始应用初始化...");
|
||||
if let Err(e) = app_init::setup_autostart(app) {
|
||||
@@ -258,7 +252,7 @@ pub fn run() {
|
||||
resolve::resolve_setup_handle();
|
||||
resolve::resolve_setup_async();
|
||||
resolve::resolve_setup_sync();
|
||||
init_signal();
|
||||
resolve::init_signal();
|
||||
resolve::resolve_done();
|
||||
|
||||
logging!(info, Type::Setup, "初始化已启动");
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use chrono::{Local, TimeZone as _};
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
#[cfg(all(not(feature = "tauri-dev"), not(feature = "tracing-full")))]
|
||||
use clash_verge_logging::NoModuleFilter;
|
||||
use clash_verge_logging::Type;
|
||||
use clash_verge_service_ipc::WriterConfig;
|
||||
@@ -65,7 +65,7 @@ pub async fn init_logger() -> Result<LoggerHandle> {
|
||||
},
|
||||
Cleanup::KeepLogFiles(log_max_count),
|
||||
);
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
#[cfg(all(not(feature = "tracing"), not(feature = "tracing-full")))]
|
||||
let logger = logger.filter(Box::new(NoModuleFilter(&[
|
||||
"wry",
|
||||
"tauri",
|
||||
|
||||
@@ -1,464 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DRM_PATH: &str = "/sys/class/drm";
|
||||
const INTEL_VENDOR_ID: &str = "0x8086";
|
||||
const NVIDIA_VENDOR_ID: &str = "0x10de";
|
||||
const NVIDIA_VERSION_PATH: &str = "/proc/driver/nvidia/version";
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct IntelGpuDetection {
|
||||
has_intel: bool,
|
||||
intel_is_primary: bool,
|
||||
inconclusive: bool,
|
||||
}
|
||||
|
||||
impl IntelGpuDetection {
|
||||
const fn should_disable_dmabuf(&self) -> bool {
|
||||
self.intel_is_primary || self.inconclusive
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct NvidiaGpuDetection {
|
||||
has_nvidia: bool,
|
||||
nvidia_is_primary: bool,
|
||||
missing_boot_vga: bool,
|
||||
open_kernel_module: bool,
|
||||
driver_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum NvidiaDmabufDisableReason {
|
||||
PrimaryOpenKernelModule,
|
||||
MissingBootVga,
|
||||
PreferNativeWayland,
|
||||
}
|
||||
|
||||
impl NvidiaGpuDetection {
|
||||
const fn disable_reason(&self, session: &SessionEnv) -> Option<NvidiaDmabufDisableReason> {
|
||||
if !session.is_wayland {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.has_nvidia {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.open_kernel_module {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.nvidia_is_primary {
|
||||
return Some(NvidiaDmabufDisableReason::PrimaryOpenKernelModule);
|
||||
}
|
||||
|
||||
if self.missing_boot_vga {
|
||||
return Some(NvidiaDmabufDisableReason::MissingBootVga);
|
||||
}
|
||||
|
||||
if session.prefer_native_wayland {
|
||||
return Some(NvidiaDmabufDisableReason::PreferNativeWayland);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SessionEnv {
|
||||
is_kde_plasma: bool,
|
||||
is_wayland: bool,
|
||||
prefer_native_wayland: bool,
|
||||
compositor_label: String,
|
||||
}
|
||||
|
||||
impl SessionEnv {
|
||||
fn gather() -> Self {
|
||||
let desktop_env = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().to_uppercase();
|
||||
let session_desktop = env::var("XDG_SESSION_DESKTOP").unwrap_or_default().to_uppercase();
|
||||
let desktop_session = env::var("DESKTOP_SESSION").unwrap_or_default().to_uppercase();
|
||||
|
||||
let is_kde_plasma = desktop_env.contains("KDE")
|
||||
|| session_desktop.contains("KDE")
|
||||
|| desktop_session.contains("KDE")
|
||||
|| desktop_env.contains("PLASMA")
|
||||
|| session_desktop.contains("PLASMA")
|
||||
|| desktop_session.contains("PLASMA");
|
||||
let is_hyprland =
|
||||
desktop_env.contains("HYPR") || session_desktop.contains("HYPR") || desktop_session.contains("HYPR");
|
||||
let is_wayland = env::var("XDG_SESSION_TYPE")
|
||||
.map(|value| value.eq_ignore_ascii_case("wayland"))
|
||||
.unwrap_or(false)
|
||||
|| env::var("WAYLAND_DISPLAY").is_ok();
|
||||
let prefer_native_wayland = is_wayland && (is_kde_plasma || is_hyprland);
|
||||
let compositor_label = if is_hyprland {
|
||||
String::from("Hyprland")
|
||||
} else if is_kde_plasma {
|
||||
String::from("KDE Plasma")
|
||||
} else {
|
||||
String::from("Wayland compositor")
|
||||
};
|
||||
|
||||
Self {
|
||||
is_kde_plasma,
|
||||
is_wayland,
|
||||
prefer_native_wayland,
|
||||
compositor_label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DmabufOverrides {
|
||||
user_preference: Option<bool>,
|
||||
dmabuf_override: Option<String>,
|
||||
}
|
||||
|
||||
impl DmabufOverrides {
|
||||
fn gather() -> Self {
|
||||
let user_preference =
|
||||
env::var("CLASH_VERGE_DMABUF")
|
||||
.ok()
|
||||
.and_then(|value| match value.trim().to_ascii_lowercase().as_str() {
|
||||
"1" | "true" | "enable" | "on" => Some(true),
|
||||
"0" | "false" | "disable" | "off" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
let dmabuf_override = env::var("WEBKIT_DISABLE_DMABUF_RENDERER").ok();
|
||||
|
||||
Self {
|
||||
user_preference,
|
||||
dmabuf_override,
|
||||
}
|
||||
}
|
||||
|
||||
const fn has_env_override(&self) -> bool {
|
||||
self.dmabuf_override.is_some()
|
||||
}
|
||||
|
||||
const fn should_override_env(&self, decision: &DmabufDecision) -> bool {
|
||||
if self.user_preference.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if decision.enable_dmabuf {
|
||||
return true;
|
||||
}
|
||||
|
||||
!self.has_env_override()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DmabufDecision {
|
||||
enable_dmabuf: bool,
|
||||
force_x11_backend: bool,
|
||||
warn: bool,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl DmabufDecision {
|
||||
fn resolve(
|
||||
session: &SessionEnv,
|
||||
overrides: &DmabufOverrides,
|
||||
intel_gpu: IntelGpuDetection,
|
||||
nvidia_gpu: &NvidiaGpuDetection,
|
||||
) -> Self {
|
||||
let mut decision = Self {
|
||||
enable_dmabuf: true,
|
||||
force_x11_backend: false,
|
||||
warn: false,
|
||||
message: None,
|
||||
};
|
||||
|
||||
match overrides.user_preference {
|
||||
Some(true) => {
|
||||
decision.enable_dmabuf = true;
|
||||
decision.message = Some("CLASH_VERGE_DMABUF=1: 强制启用 WebKit DMABUF 渲染。".into());
|
||||
}
|
||||
Some(false) => {
|
||||
decision.enable_dmabuf = false;
|
||||
decision.message = Some("CLASH_VERGE_DMABUF=0: 强制禁用 WebKit DMABUF 渲染。".into());
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if overrides.has_env_override() {
|
||||
if overrides.dmabuf_override.as_deref() == Some("1") {
|
||||
decision.enable_dmabuf = false;
|
||||
decision.message =
|
||||
Some("检测到 WEBKIT_DISABLE_DMABUF_RENDERER=1,沿用用户的软件渲染配置。".into());
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
} else {
|
||||
decision.enable_dmabuf = true;
|
||||
let value = overrides.dmabuf_override.clone().unwrap_or_default();
|
||||
decision.message = Some(format!(
|
||||
"检测到 WEBKIT_DISABLE_DMABUF_RENDERER={},沿用用户配置。",
|
||||
value
|
||||
));
|
||||
}
|
||||
} else if let Some(reason) = nvidia_gpu.disable_reason(session) {
|
||||
decision.enable_dmabuf = false;
|
||||
decision.warn = true;
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
let summary = nvidia_gpu
|
||||
.driver_summary
|
||||
.as_deref()
|
||||
.and_then(|line| {
|
||||
extract_nvidia_driver_version(line)
|
||||
.map(|version| format!("NVIDIA Open Kernel Module {}", version))
|
||||
})
|
||||
.unwrap_or_else(|| String::from("NVIDIA Open Kernel Module"));
|
||||
let message = match reason {
|
||||
NvidiaDmabufDisableReason::PrimaryOpenKernelModule => {
|
||||
format!(
|
||||
"Wayland 会话检测到 {}:禁用 WebKit DMABUF 渲染以规避协议错误。",
|
||||
summary
|
||||
)
|
||||
}
|
||||
NvidiaDmabufDisableReason::MissingBootVga => {
|
||||
format!(
|
||||
"Wayland 会话检测到 {},但缺少 boot_vga 信息:预防性禁用 WebKit DMABUF。",
|
||||
summary
|
||||
)
|
||||
}
|
||||
NvidiaDmabufDisableReason::PreferNativeWayland => format!(
|
||||
"Wayland ({}) + {}:检测到 NVIDIA Open Kernel Module 在辅 GPU 上运行,预防性禁用 WebKit DMABUF。",
|
||||
session.compositor_label, summary
|
||||
),
|
||||
};
|
||||
decision.message = Some(message);
|
||||
} else if session.prefer_native_wayland && !intel_gpu.should_disable_dmabuf() {
|
||||
decision.enable_dmabuf = true;
|
||||
decision.message = Some(format!(
|
||||
"Wayland + {} detected: 使用原生 DMABUF 渲染。",
|
||||
session.compositor_label
|
||||
));
|
||||
} else {
|
||||
decision.enable_dmabuf = false;
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
|
||||
if intel_gpu.should_disable_dmabuf() && session.is_wayland {
|
||||
decision.warn = true;
|
||||
if intel_gpu.inconclusive {
|
||||
decision.message = Some("Wayland 上检测到 Intel GPU,但缺少 boot_vga 信息:预防性禁用 WebKit DMABUF,若确认非主 GPU 可通过 CLASH_VERGE_DMABUF=1 覆盖。".into());
|
||||
} else {
|
||||
decision.message = Some(
|
||||
"Wayland 上检测到 Intel 主 GPU (0x8086):禁用 WebKit DMABUF 以避免帧缓冲失败。".into(),
|
||||
);
|
||||
}
|
||||
} else if session.is_wayland {
|
||||
decision.message = Some("Wayland 会话未匹配受支持的合成器:禁用 WebKit DMABUF 渲染。".into());
|
||||
} else {
|
||||
decision.message = Some("禁用 WebKit DMABUF 渲染以获得更稳定的输出。".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decision
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_intel_gpu() -> IntelGpuDetection {
|
||||
let Ok(entries) = fs::read_dir(DRM_PATH) else {
|
||||
return IntelGpuDetection::default();
|
||||
};
|
||||
|
||||
let mut detection = IntelGpuDetection::default();
|
||||
let mut seen_devices: HashSet<PathBuf> = HashSet::new();
|
||||
let mut missing_boot_vga = false;
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
|
||||
if !(name.starts_with("renderD") || name.starts_with("card")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device_path = entry.path().join("device");
|
||||
let device_key = fs::canonicalize(&device_path).unwrap_or(device_path);
|
||||
|
||||
if !seen_devices.insert(device_key.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor_path = device_key.join("vendor");
|
||||
let Ok(vendor) = fs::read_to_string(&vendor_path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !vendor.trim().eq_ignore_ascii_case(INTEL_VENDOR_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
detection.has_intel = true;
|
||||
|
||||
let boot_vga_path = device_key.join("boot_vga");
|
||||
match fs::read_to_string(&boot_vga_path) {
|
||||
Ok(flag) => {
|
||||
if flag.trim() == "1" {
|
||||
detection.intel_is_primary = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
missing_boot_vga = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if detection.has_intel && !detection.intel_is_primary && missing_boot_vga {
|
||||
detection.inconclusive = true;
|
||||
}
|
||||
|
||||
detection
|
||||
}
|
||||
|
||||
fn detect_nvidia_gpu() -> NvidiaGpuDetection {
|
||||
let mut detection = NvidiaGpuDetection::default();
|
||||
let entries = match fs::read_dir(DRM_PATH) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"无法读取 DRM 设备目录 {}({}),尝试通过 NVIDIA 驱动摘要进行降级检测。",
|
||||
DRM_PATH,
|
||||
err
|
||||
);
|
||||
detection.driver_summary = read_nvidia_driver_summary();
|
||||
if let Some(summary) = detection.driver_summary.as_ref() {
|
||||
detection.open_kernel_module = summary_indicates_open_kernel_module(summary);
|
||||
detection.has_nvidia = true;
|
||||
detection.missing_boot_vga = true;
|
||||
} else {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"降级检测失败:未能读取 NVIDIA 驱动摘要,保留 WebKit DMABUF。"
|
||||
);
|
||||
}
|
||||
return detection;
|
||||
}
|
||||
};
|
||||
|
||||
let mut seen_devices: HashSet<PathBuf> = HashSet::new();
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
|
||||
if !(name.starts_with("renderD") || name.starts_with("card")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device_path = entry.path().join("device");
|
||||
let device_key = fs::canonicalize(&device_path).unwrap_or(device_path);
|
||||
|
||||
if !seen_devices.insert(device_key.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor_path = device_key.join("vendor");
|
||||
let Ok(vendor) = fs::read_to_string(&vendor_path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !vendor.trim().eq_ignore_ascii_case(NVIDIA_VENDOR_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
detection.has_nvidia = true;
|
||||
|
||||
let boot_vga_path = device_key.join("boot_vga");
|
||||
match fs::read_to_string(&boot_vga_path) {
|
||||
Ok(flag) => {
|
||||
if flag.trim() == "1" {
|
||||
detection.nvidia_is_primary = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
detection.missing_boot_vga = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if detection.has_nvidia {
|
||||
detection.driver_summary = read_nvidia_driver_summary();
|
||||
match detection.driver_summary.as_ref() {
|
||||
Some(summary) => {
|
||||
detection.open_kernel_module = summary_indicates_open_kernel_module(summary);
|
||||
}
|
||||
None => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"检测到 NVIDIA 设备,但无法读取 {},默认视为未启用开源内核模块。",
|
||||
NVIDIA_VERSION_PATH
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detection
|
||||
}
|
||||
|
||||
fn read_nvidia_driver_summary() -> Option<String> {
|
||||
match fs::read_to_string(NVIDIA_VERSION_PATH) {
|
||||
Ok(content) => content
|
||||
.lines()
|
||||
.next()
|
||||
.map(|line| line.trim().to_string())
|
||||
.filter(|line| !line.is_empty()),
|
||||
Err(err) => {
|
||||
logging!(info, Type::Setup, "读取 {} 失败:{}", NVIDIA_VERSION_PATH, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn summary_indicates_open_kernel_module(summary: &str) -> bool {
|
||||
let normalized = summary.to_ascii_lowercase();
|
||||
const PATTERNS: [&str; 4] = [
|
||||
"open kernel module",
|
||||
"open kernel modules",
|
||||
"open gpu kernel module",
|
||||
"open gpu kernel modules",
|
||||
];
|
||||
|
||||
let is_open = PATTERNS.iter().any(|pattern| normalized.contains(pattern));
|
||||
|
||||
if !is_open && normalized.contains("open") {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"检测到 NVIDIA 驱动摘要包含 open 关键字但未匹配已知开源模块格式:{}",
|
||||
summary
|
||||
);
|
||||
}
|
||||
|
||||
is_open
|
||||
}
|
||||
|
||||
fn extract_nvidia_driver_version(summary: &str) -> Option<&str> {
|
||||
summary
|
||||
.split_whitespace()
|
||||
.find(|token| token.chars().all(|c| c.is_ascii_digit() || c == '.'))
|
||||
}
|
||||
|
||||
pub fn ensure_mimeapps_entries(desktop_file: &str, schemes: &[&str]) -> Result<()> {
|
||||
let Some(path) = mimeapps_list_path() else {
|
||||
return Ok(());
|
||||
@@ -716,52 +261,3 @@ fn match_scheme<'a>(key: &str, schemes: &'a [&str]) -> Option<&'a str> {
|
||||
|
||||
schemes.iter().copied().find(|candidate| *candidate == key)
|
||||
}
|
||||
|
||||
pub fn configure_environment() {
|
||||
let session = SessionEnv::gather();
|
||||
let overrides = DmabufOverrides::gather();
|
||||
let intel_gpu = detect_intel_gpu();
|
||||
let nvidia_gpu = detect_nvidia_gpu();
|
||||
let decision = DmabufDecision::resolve(&session, &overrides, intel_gpu, &nvidia_gpu);
|
||||
|
||||
if overrides.should_override_env(&decision) {
|
||||
unsafe {
|
||||
if decision.enable_dmabuf {
|
||||
env::remove_var("WEBKIT_DISABLE_DMABUF_RENDERER");
|
||||
} else {
|
||||
env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = decision.message {
|
||||
if decision.warn {
|
||||
logging!(warn, Type::Setup, "{}", message);
|
||||
} else {
|
||||
logging!(info, Type::Setup, "{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
if decision.force_x11_backend {
|
||||
unsafe {
|
||||
env::set_var("GDK_BACKEND", "x11");
|
||||
env::remove_var("WAYLAND_DISPLAY");
|
||||
}
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"Wayland detected: Forcing X11 backend for WebKit stability."
|
||||
);
|
||||
}
|
||||
|
||||
if session.is_kde_plasma {
|
||||
unsafe {
|
||||
env::set_var("GTK_CSD", "0");
|
||||
}
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"KDE/Plasma detected: Disabled GTK CSD for better titlebar stability."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,21 @@ pub mod window_script;
|
||||
|
||||
static RESOLVE_DONE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub async fn prioritize_initialization() -> Option<LoggerHandle> {
|
||||
init_work_config().await;
|
||||
init_resources().await;
|
||||
pub fn init_work_dir_and_logger() -> Option<LoggerHandle> {
|
||||
AsyncHandler::block_on(async {
|
||||
init_work_config().await;
|
||||
init_resources().await;
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
{
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
init::init_logger().await.ok()
|
||||
}
|
||||
#[cfg(feature = "tauri-dev")]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
{
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
init::init_logger().await.ok()
|
||||
}
|
||||
#[cfg(feature = "tauri-dev")]
|
||||
{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_setup_handle() {
|
||||
@@ -58,8 +60,7 @@ pub fn resolve_setup_async() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
logging!(info, Type::ClashVergeRev, "Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
futures::join!(init_work_config(), init_resources(), init_startup_script());
|
||||
|
||||
init_startup_script().await;
|
||||
init_verge_config().await;
|
||||
Config::verify_config_initialization().await;
|
||||
init_window().await;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.4.4-rc",
|
||||
"version": "2.4.4-rc.1",
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"bundle": {
|
||||
"active": true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
subscribeNotices,
|
||||
hideNotice,
|
||||
getSnapshotNotices,
|
||||
} from "@/services/noticeService";
|
||||
} from "@/services/notice-service";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
|
||||
export const NoticeManager: React.FC = () => {
|
||||
|
||||
@@ -478,6 +478,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
overflow: "auto",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
overscrollBehavior: "contain",
|
||||
borderRadius: 1,
|
||||
border: "none",
|
||||
"&::-webkit-scrollbar": {
|
||||
|
||||
@@ -12,10 +12,7 @@ import {
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
useTrafficGraphDataEnhanced,
|
||||
type ITrafficDataPoint,
|
||||
} from "@/hooks/use-traffic-monitor";
|
||||
import { useTrafficGraphDataEnhanced } from "@/hooks/use-traffic-monitor";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useNavigate } from "react-router";
|
||||
|
||||
import { useRefreshAll } from "@/hooks/use-clash-data";
|
||||
import { openWebUrl, updateProfile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
import { EnhancedCard } from "./enhanced-card";
|
||||
|
||||
@@ -17,11 +17,11 @@ import {
|
||||
import { useState, useMemo, memo, FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
||||
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
|
||||
import { useSystemProxyState } from "@/hooks/use-system-proxy-state";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useServiceInstaller } from "@/hooks/use-service-installer";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { getSystemInfo } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
import { version as appVersion } from "@root/package.json";
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useTranslation } from "react-i18next";
|
||||
import pac from "types-pac/pac.d.ts?raw";
|
||||
|
||||
import { BaseLoadingOverlay } from "@/components/base";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import debounce from "@/utils/debounce";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
readProfileFile,
|
||||
saveProfileFile,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
saveProfileFile,
|
||||
getNextUpdateTime,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useLoadingCache, useSetLoadingCache } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { viewProfile, readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { LogViewer } from "./log-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
import { createProfile, patchProfile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { version } from "@root/package.json";
|
||||
|
||||
import { FileInput } from "./file-input";
|
||||
|
||||
@@ -42,7 +42,7 @@ import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
import { ProxyItem } from "@/components/profile/proxy-item";
|
||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import parseUri from "@/utils/uri-parser";
|
||||
|
||||
@@ -45,7 +45,7 @@ import { Virtuoso } from "react-virtuoso";
|
||||
import { Switch } from "@/components/base";
|
||||
import { RuleItem } from "@/components/profile/rule-item";
|
||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useProxiesData, useProxyProvidersData } from "@/hooks/use-clash-data";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
// 样式化组件 - 类型框
|
||||
|
||||
@@ -285,23 +285,37 @@ export const ProxyChain = ({
|
||||
|
||||
const handleConnect = useCallback(async () => {
|
||||
if (isConnected) {
|
||||
// 如果已连接,则断开连接
|
||||
setIsConnecting(true);
|
||||
try {
|
||||
// 清空链式代理配置
|
||||
await updateProxyChainConfigInRuntime(null);
|
||||
|
||||
// 切换到 DIRECT 模式断开代理连接
|
||||
// await updateProxyAndSync("GLOBAL", "DIRECT");
|
||||
const targetGroup =
|
||||
mode === "global"
|
||||
? "GLOBAL"
|
||||
: selectedGroup || localStorage.getItem("proxy-chain-group");
|
||||
|
||||
if (targetGroup) {
|
||||
try {
|
||||
await selectNodeForGroup(targetGroup, "DIRECT");
|
||||
} catch {
|
||||
if (proxyChain.length >= 1) {
|
||||
try {
|
||||
await selectNodeForGroup(targetGroup, proxyChain[0].name);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.removeItem("proxy-chain-group");
|
||||
localStorage.removeItem("proxy-chain-exit-node");
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
|
||||
// 关闭所有连接
|
||||
await closeAllConnections();
|
||||
await mutateProxies();
|
||||
|
||||
// 刷新代理信息以更新连接状态
|
||||
mutateProxies();
|
||||
|
||||
// 清空链式代理配置UI
|
||||
// onUpdateChain([]);
|
||||
onUpdateChain([]);
|
||||
} catch (error) {
|
||||
console.error("Failed to disconnect from proxy chain:", error);
|
||||
alert(t("proxies.page.chain.disconnectFailed") || "断开链式代理失败");
|
||||
@@ -348,7 +362,15 @@ export const ProxyChain = ({
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
}
|
||||
}, [proxyChain, isConnected, t, mutateProxies, mode, selectedGroup]);
|
||||
}, [
|
||||
proxyChain,
|
||||
isConnected,
|
||||
t,
|
||||
mutateProxies,
|
||||
mode,
|
||||
selectedGroup,
|
||||
onUpdateChain,
|
||||
]);
|
||||
|
||||
const proxyChainRef = useRef(proxyChain);
|
||||
const onUpdateChainRef = useRef(onUpdateChain);
|
||||
@@ -376,10 +398,11 @@ export const ProxyChain = ({
|
||||
type: proxy.type,
|
||||
delay: undefined,
|
||||
})) || [];
|
||||
onUpdateChain(chainItems);
|
||||
if (chainItems.length > 0) {
|
||||
onUpdateChain(chainItems);
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error("Failed to parse YAML:", parseError);
|
||||
onUpdateChain([]);
|
||||
}
|
||||
})
|
||||
.catch((importError) => {
|
||||
@@ -399,19 +422,16 @@ export const ProxyChain = ({
|
||||
type: proxy.type,
|
||||
delay: undefined,
|
||||
})) || [];
|
||||
onUpdateChain(chainItems);
|
||||
if (chainItems.length > 0) {
|
||||
onUpdateChain(chainItems);
|
||||
}
|
||||
} catch (jsonError) {
|
||||
console.error("Failed to parse as JSON either:", jsonError);
|
||||
onUpdateChain([]);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to process chain config data:", error);
|
||||
onUpdateChain([]);
|
||||
}
|
||||
} else if (chainConfigData === "") {
|
||||
// Empty string means no proxies available, show empty state
|
||||
onUpdateChain([]);
|
||||
}
|
||||
}, [chainConfigData, onUpdateChain]);
|
||||
|
||||
@@ -481,6 +501,9 @@ export const ProxyChain = ({
|
||||
size="small"
|
||||
onClick={() => {
|
||||
updateProxyChainConfigInRuntime(null);
|
||||
localStorage.removeItem("proxy-chain-group");
|
||||
localStorage.removeItem("proxy-chain-exit-node");
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
onUpdateChain([]);
|
||||
}}
|
||||
sx={{
|
||||
|
||||
@@ -27,8 +27,8 @@ import { ScrollTopButton } from "../layout/scroll-top-button";
|
||||
|
||||
import { ProxyChain } from "./proxy-chain";
|
||||
import {
|
||||
ProxyGroupNavigator,
|
||||
DEFAULT_HOVER_DELAY,
|
||||
ProxyGroupNavigator,
|
||||
} from "./proxy-group-navigator";
|
||||
import { ProxyRender } from "./proxy-render";
|
||||
import { useRenderList } from "./use-render-list";
|
||||
@@ -51,8 +51,26 @@ const VirtuosoFooter = () => <div style={{ height: "8px" }} />;
|
||||
export const ProxyGroups = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { mode, isChainMode = false, chainConfigData } = props;
|
||||
const [proxyChain, setProxyChain] = useState<ProxyChainItem[]>([]);
|
||||
const [proxyChain, setProxyChain] = useState<ProxyChainItem[]>(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem("proxy-chain-items");
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (proxyChain.length > 0) {
|
||||
localStorage.setItem("proxy-chain-items", JSON.stringify(proxyChain));
|
||||
} else {
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
}
|
||||
}, [proxyChain]);
|
||||
const [ruleMenuAnchor, setRuleMenuAnchor] = useState<null | HTMLElement>(
|
||||
null,
|
||||
);
|
||||
@@ -64,7 +82,13 @@ export const ProxyGroups = (props: Props) => {
|
||||
const { verge } = useVerge();
|
||||
const { proxies: proxiesData } = useProxiesData();
|
||||
const groups = proxiesData?.groups;
|
||||
const availableGroups = useMemo(() => groups ?? [], [groups]);
|
||||
const availableGroups = useMemo(() => {
|
||||
if (!groups) return [];
|
||||
// 在链式代理模式下,仅显示支持选择节点的 Selector 代理组
|
||||
return isChainMode
|
||||
? groups.filter((g: any) => g.type === "Selector")
|
||||
: groups;
|
||||
}, [groups, isChainMode]);
|
||||
|
||||
const defaultRuleGroup = useMemo(() => {
|
||||
if (isChainMode && mode === "rule" && availableGroups.length > 0) {
|
||||
@@ -225,10 +249,11 @@ export const ProxyGroups = (props: Props) => {
|
||||
setSelectedGroup(groupName);
|
||||
handleGroupMenuClose();
|
||||
|
||||
// 在链式代理模式的规则模式下,切换代理组时清空链式代理配置
|
||||
if (isChainMode && mode === "rule") {
|
||||
updateProxyChainConfigInRuntime(null);
|
||||
// 同时清空右侧链式代理配置
|
||||
localStorage.removeItem("proxy-chain-group");
|
||||
localStorage.removeItem("proxy-chain-exit-node");
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
setProxyChain([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,11 +55,6 @@ export function useHeadStateNew() {
|
||||
const [state, dispatch] = useReducer(headStateReducer, {});
|
||||
|
||||
useEffect(() => {
|
||||
if (!current) {
|
||||
dispatch({ type: "reset" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(
|
||||
localStorage.getItem(HEAD_STATE_KEY)!,
|
||||
@@ -78,8 +73,6 @@ export function useHeadStateNew() {
|
||||
}, [current]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!current) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
const item = localStorage.getItem(HEAD_STATE_KEY);
|
||||
@@ -99,10 +92,9 @@ export function useHeadStateNew() {
|
||||
|
||||
const setHeadState = useCallback(
|
||||
(groupName: string, obj: Partial<HeadState>) => {
|
||||
if (!current) return;
|
||||
dispatch({ type: "update", groupName, patch: obj });
|
||||
},
|
||||
[current],
|
||||
[],
|
||||
);
|
||||
|
||||
return [state, setHeadState] as const;
|
||||
|
||||
@@ -25,7 +25,7 @@ import type {
|
||||
useRuleProvidersData,
|
||||
useRulesData,
|
||||
} from "@/hooks/use-clash-data";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
// 辅助组件 - 类型框
|
||||
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Switch } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
const MIN_INTERVAL_HOURS = 1;
|
||||
const MAX_INTERVAL_HOURS = 168;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { saveWebdavConfig, createWebdavBackup } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { isValidUrl } from "@/utils/helper";
|
||||
|
||||
interface BackupConfigViewerProps {
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
restoreLocalBackup,
|
||||
restoreWebDavBackup,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { createLocalBackup, createWebdavBackup } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { AutoBackupSettings } from "./auto-backup-settings";
|
||||
import { BackupHistoryViewer } from "./backup-history-viewer";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, BaseLoadingOverlay } from "@/components/base";
|
||||
import { listWebDavBackup } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { BackupConfigViewer } from "./backup-config-viewer";
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { closeAllConnections, upgradeCore } from "tauri-plugin-mihomo-api";
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { changeClashCore, restartCore } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
const VALID_CORE = [
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
const OS = getSystem();
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
// 定义开发环境的URL列表
|
||||
// 这些URL在开发模式下会被自动包含在允许的来源中
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { HotkeyInput } from "./hotkey-input";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { DEFAULT_HOVER_DELAY } from "@/components/proxy/proxy-group-navigator";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useWindowDecorations } from "@/hooks/use-window";
|
||||
import { copyIconFile, getAppDir } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { GuardState } from "./guard-state";
|
||||
|
||||
@@ -15,7 +15,7 @@ import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { entry_lightweight_mode } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -8,7 +8,7 @@ import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
getSystemProxy,
|
||||
patchVergeConfig,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { defaultDarkTheme, defaultTheme } from "@/pages/_theme";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
||||
const { ref } = props;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { enhanceProfiles } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { StackModeSwitch } from "./stack-mode-switch";
|
||||
|
||||
@@ -11,7 +11,7 @@ import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { portableFlag } from "@/pages/_layout";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useSetUpdateState, useUpdateState } from "@/services/states";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseDialog, BaseEmpty, DialogRef } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { openWebUrl } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { WebUIItem } from "./web-ui-item";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useClash } from "@/hooks/use-clash";
|
||||
import { useClashLog } from "@/hooks/use-clash-log";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { invoke_uwp_tool } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||
|
||||
@@ -6,10 +6,10 @@ import { mutate } from "swr";
|
||||
|
||||
import { DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
||||
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { GuardState } from "./mods/guard-state";
|
||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
openDevTools,
|
||||
openLogsDir,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
import { version } from "@root/package.json";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useVerge } from "@/hooks/use-verge";
|
||||
import { navItems } from "@/pages/_routers";
|
||||
import { copyClashEnv } from "@/services/cmds";
|
||||
import { supportedLanguages } from "@/services/i18n";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { BackupViewer } from "./mods/backup-viewer";
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import { useServiceUninstaller } from "@/hooks/use-service-uninstaller";
|
||||
import { useSystemProxyState } from "@/hooks/use-system-proxy-state";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
interface ProxySwitchProps {
|
||||
label?: string;
|
||||
@@ -12,7 +12,7 @@ import { BaseLoading } from "@/components/base";
|
||||
import { useListen } from "@/hooks/use-listen";
|
||||
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
|
||||
import delayManager from "@/services/delay";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
|
||||
import { TestBox } from "./test-box";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
interface Props {
|
||||
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void;
|
||||
|
||||
+13
-13
@@ -1,20 +1,20 @@
|
||||
import { event } from "@tauri-apps/api";
|
||||
import { listen, UnlistenFn, EventCallback } from "@tauri-apps/api/event";
|
||||
import { useRef } from "react";
|
||||
import { useCallback, useRef } from "react";
|
||||
|
||||
export const useListen = () => {
|
||||
const unlistenFns = useRef<UnlistenFn[]>([]);
|
||||
|
||||
const addListener = async <T>(
|
||||
eventName: string,
|
||||
handler: EventCallback<T>,
|
||||
) => {
|
||||
const unlisten = await listen(eventName, handler);
|
||||
unlistenFns.current.push(unlisten);
|
||||
return unlisten;
|
||||
};
|
||||
const addListener = useCallback(
|
||||
async <T>(eventName: string, handler: EventCallback<T>) => {
|
||||
const unlisten = await listen(eventName, handler);
|
||||
unlistenFns.current.push(unlisten);
|
||||
return unlisten;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const removeAllListeners = () => {
|
||||
const removeAllListeners = useCallback(() => {
|
||||
const errors: Error[] = [];
|
||||
|
||||
unlistenFns.current.forEach((unlisten) => {
|
||||
@@ -33,13 +33,13 @@ export const useListen = () => {
|
||||
}
|
||||
|
||||
unlistenFns.current.length = 0;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const setupCloseListener = async function () {
|
||||
const setupCloseListener = useCallback(async () => {
|
||||
await event.once("tauri://close-requested", async () => {
|
||||
removeAllListeners();
|
||||
});
|
||||
};
|
||||
}, [removeAllListeners]);
|
||||
|
||||
return {
|
||||
addListener,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { installService, restartCore } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { useSystemState } from "./use-system-state";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { restartCore, stopCore, uninstallService } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { useSystemState } from "./use-system-state";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { getRunningMode, isAdmin, isServiceAvailable } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { useVerge } from "./use-verge";
|
||||
|
||||
|
||||
@@ -8,18 +8,9 @@ import {
|
||||
} from "react";
|
||||
import { Traffic } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import type {
|
||||
ISamplerStats,
|
||||
ITrafficDataPoint,
|
||||
ITrafficWorkerSnapshotMessage,
|
||||
TrafficWorkerRequestMessage,
|
||||
TrafficWorkerResponseMessage,
|
||||
} from "@/types/traffic-monitor";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import { TrafficDataSampler, formatTrafficName } from "@/utils/traffic-sampler";
|
||||
|
||||
export type { ITrafficDataPoint } from "@/types/traffic-monitor";
|
||||
|
||||
// 引用计数管理器
|
||||
class ReferenceCounter {
|
||||
private count = 0;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"DOMAIN-SUFFIX": "Match domain suffix (DOMAIN-SUFFIX)",
|
||||
"DOMAIN-KEYWORD": "Match domain keyword (DOMAIN-KEYWORD)",
|
||||
"DOMAIN-REGEX": "Match domain using regex (DOMAIN-REGEX)",
|
||||
"GEOSITE": "Match domains in Geosite (GEOSITE)",
|
||||
"GEOSITE": "Match domains in GeoSite (GEOSITE)",
|
||||
"GEOIP": "Match IP country code (GEOIP)",
|
||||
"SRC-GEOIP": "Match source IP country code (SRC-GEOIP)",
|
||||
"IP-ASN": "Match IP ASN (IP-ASN)",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"systemProxy": "پراکسی سیستم"
|
||||
},
|
||||
"tooltips": {
|
||||
"autoLaunchAdmin": "Administrator mode may not support auto launch",
|
||||
"autoLaunchAdmin": "حالت ادمین ممکن است از راهاندازی خودکار پشتیبانی نکند",
|
||||
"silentStart": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید"
|
||||
},
|
||||
"fields": {
|
||||
@@ -24,8 +24,8 @@
|
||||
},
|
||||
"notifications": {
|
||||
"tunMode": {
|
||||
"autoDisabled": "TUN Mode automatically disabled due to service unavailable",
|
||||
"autoDisableFailed": "Failed to disable TUN Mode automatically"
|
||||
"autoDisabled": "حالت TUN به دلیل عدم دسترسی به سرویس، به طور خودکار غیرفعال میشود",
|
||||
"autoDisableFailed": "غیرفعال کردن خودکار حالت TUN ناموفق بود"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -33,11 +33,11 @@
|
||||
"tooltips": {
|
||||
"systemProxy": "به امکانات تنظیم پروکسی سیستم عامل دسترسی پیدا کنید. اگر فعالسازی ناموفق بود، پروکسی سیستم عامل را بهصورت دستی تغییر دهید",
|
||||
"tunMode": "حالت Tun (NIC مجازی): تمام ترافیک سیستم را ضبط می کند، وقتی فعال باشد، نیازی به فعال کردن پروکسی سیستم نیست.",
|
||||
"tunUnavailable": "TUN requires Service Mode or Admin Mode"
|
||||
"tunUnavailable": "TUN به حالت سرویس یا حالت ادمین نیاز دارد"
|
||||
},
|
||||
"actions": {
|
||||
"installService": "نصب سرویس",
|
||||
"uninstallService": "Uninstall Service"
|
||||
"uninstallService": "حذف سرویس"
|
||||
},
|
||||
"fields": {
|
||||
"systemProxy": "پراکسی سیستم",
|
||||
@@ -47,22 +47,22 @@
|
||||
"externalController": {
|
||||
"title": "کنترلکننده خارجی",
|
||||
"fields": {
|
||||
"enable": "Enable External Controller",
|
||||
"enable": "فعال کردن کنترلکننده خارجی",
|
||||
"address": "کنترلکننده خارجی",
|
||||
"secret": "رمز اصلی"
|
||||
},
|
||||
"placeholders": {
|
||||
"address": "Required",
|
||||
"address": "مورد نیاز",
|
||||
"secret": "توصیه شده"
|
||||
},
|
||||
"tooltips": {
|
||||
"copy": "Copy to clipboard"
|
||||
"copy": "کپی در کلیپ بورد"
|
||||
},
|
||||
"messages": {
|
||||
"addressRequired": "Controller address cannot be empty",
|
||||
"secretRequired": "Secret cannot be empty",
|
||||
"copyFailed": "Failed to copy",
|
||||
"controllerCopied": "Controller address copied to clipboard",
|
||||
"addressRequired": "آدرس کنترلر نمیتواند خالی باشد",
|
||||
"secretRequired": "راز نمیتواند خالی باشد",
|
||||
"copyFailed": "کپی نشد",
|
||||
"controllerCopied": "آدرس کنترلر در کلیپ بورد کپی شد",
|
||||
"secretCopied": "Secret copied to clipboard"
|
||||
}
|
||||
},
|
||||
@@ -73,10 +73,10 @@
|
||||
"allowedOrigins": "Allowed Origins"
|
||||
},
|
||||
"placeholders": {
|
||||
"origin": "Please enter a valid url"
|
||||
"origin": "لطفا یک url معتبر وارد کنید"
|
||||
},
|
||||
"actions": {
|
||||
"add": "Add"
|
||||
"add": "افزودن"
|
||||
},
|
||||
"messages": {
|
||||
"alwaysIncluded": "Always included origins: {{urls}}"
|
||||
@@ -95,7 +95,7 @@
|
||||
"form": {
|
||||
"fields": {
|
||||
"allowLan": "اجازه LAN",
|
||||
"dnsOverwrite": "DNS Overwrite",
|
||||
"dnsOverwrite": "بازنویسی DNS",
|
||||
"ipv6": "IPv6",
|
||||
"unifiedDelay": "معادلDELAY",
|
||||
"logLevel": "سطح لاگ",
|
||||
@@ -157,11 +157,11 @@
|
||||
"liteMode": "رابط کاربری گرافیکی را ببندید و فقط هسته را در حال اجرا نگه دارید"
|
||||
},
|
||||
"actions": {
|
||||
"copyVersion": "Copy Version"
|
||||
"copyVersion": "کپی نسخه"
|
||||
},
|
||||
"notifications": {
|
||||
"latestVersion": "در حال حاضر در آخرین نسخه",
|
||||
"versionCopied": "Version copied to clipboard"
|
||||
"versionCopied": "نسخه در کلیپ بورد کپی شد"
|
||||
},
|
||||
"fields": {
|
||||
"backupSetting": "تنظیمات پشتیبان گیری",
|
||||
@@ -263,13 +263,13 @@
|
||||
"autoEnter": "Auto Enter LightWeight Mode"
|
||||
},
|
||||
"tooltips": {
|
||||
"autoEnter": "Enable to automatically activate LightWeight Mode after the window is closed for a period of time"
|
||||
"autoEnter": "فعال کردن حالت LightWeight به صورت خودکار پس از بسته شدن پنجره برای مدت زمانی خاص"
|
||||
},
|
||||
"fields": {
|
||||
"delay": "Auto Enter LightWeight Mode Delay"
|
||||
"delay": "تأخیر ورود خودکار به حالت LightWeight"
|
||||
},
|
||||
"messages": {
|
||||
"autoEnterHint": "When closing the window, LightWeight Mode will be automatically activated after {{n}} minutes"
|
||||
"autoEnterHint": "هنگام بستن پنجره، حالت LightWeight پس از {{n}} دقیقه به طور خودکار فعال میشود"
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
@@ -325,18 +325,18 @@
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"title": "Manual backup",
|
||||
"local": "Creates a snapshot on this device, stored under the app data directory.",
|
||||
"webdav": "Upload a snapshot to your WebDAV server once credentials are set.",
|
||||
"configureWebdav": "Configure WebDAV"
|
||||
"title": "پشتیبان گیری دستی",
|
||||
"local": "یک اسنپشات روی این دستگاه ایجاد میکند که در پوشهی دادههای برنامه ذخیره میشود.",
|
||||
"webdav": "پس از تنظیم اعتبارنامهها، یک اسنپشات (عکس فوری) در سرور WebDAV خود آپلود کنید.",
|
||||
"configureWebdav": "پیکربندی WebDAV"
|
||||
},
|
||||
"history": {
|
||||
"title": "Backup history",
|
||||
"title": "تاریخچه پشتیبان گیری",
|
||||
"summary": "{{count}} backups • latest {{recent}}",
|
||||
"empty": "No backups available"
|
||||
"empty": "هیچ نسخه پشتیبان در دسترس نیست"
|
||||
},
|
||||
"webdav": {
|
||||
"title": "WebDAV settings"
|
||||
"title": "پیکربندی WebDAV"
|
||||
},
|
||||
"table": {
|
||||
"filename": "نام فایل",
|
||||
@@ -431,8 +431,8 @@
|
||||
"autoRedirect": "Auto Redirect"
|
||||
},
|
||||
"tooltips": {
|
||||
"dnsHijack": "Please use , to separate multiple DNS servers",
|
||||
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
|
||||
"dnsHijack": "لطفا برای جدا کردن چندین سرور DNS از , استفاده کنید.",
|
||||
"autoRedirect": "پیکربندی خودکار ریدایرکتهای TCP در nftables/iptables"
|
||||
},
|
||||
"messages": {
|
||||
"applied": "تنظیمات اعمال شد"
|
||||
@@ -440,11 +440,11 @@
|
||||
},
|
||||
"dns": {
|
||||
"dialog": {
|
||||
"title": "DNS Overwrite",
|
||||
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite enabled"
|
||||
"title": "بازنویسی DNS",
|
||||
"warning": "اگر با این تنظیمات آشنا نیستید، لطفاً آنها را تغییر ندهید و DNS Overwrite را فعال نگه دارید."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS Settings",
|
||||
"general": "تنظیمات DNS",
|
||||
"fallbackFilter": "Fallback Filter Settings",
|
||||
"hosts": "Hosts Settings"
|
||||
},
|
||||
@@ -464,7 +464,7 @@
|
||||
},
|
||||
"respectRules": {
|
||||
"label": "Respect Rules",
|
||||
"description": "DNS connections follow routing rules"
|
||||
"description": "اتصالات DNS از قوانین مسیریابی پیروی میکنند"
|
||||
},
|
||||
"useHosts": {
|
||||
"label": "Use Hosts",
|
||||
@@ -484,11 +484,11 @@
|
||||
},
|
||||
"nameserver": {
|
||||
"label": "Nameserver",
|
||||
"description": "List of DNS servers, comma separated"
|
||||
"description": "فهرست سرورهای DNS، جدا شده با کاما"
|
||||
},
|
||||
"fallback": {
|
||||
"label": "Fallback",
|
||||
"description": "List of fallback DNS servers, comma separated"
|
||||
"description": "فهرست سرورهای DNS جایگزین، جدا شده با کاما"
|
||||
},
|
||||
"proxy": {
|
||||
"label": "Proxy Server Nameserver",
|
||||
@@ -516,21 +516,21 @@
|
||||
"description": "IP CIDRs not using fallback servers, comma separated"
|
||||
},
|
||||
"fallbackDomain": {
|
||||
"label": "Fallback Domain",
|
||||
"description": "Domains using fallback servers, comma separated"
|
||||
"label": "دامنه جایگزین",
|
||||
"description": "دامنههایی که از سرورهای جایگزین استفاده میکنند، با کاما از هم جدا شدهاند"
|
||||
},
|
||||
"hosts": {
|
||||
"label": "Hosts",
|
||||
"description": "Custom domain to IP or domain mapping"
|
||||
"label": "میزبانها",
|
||||
"description": "تبدیل دامنه به IP یا نگاشت دامنه سفارشی"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"saved": "DNS settings saved",
|
||||
"configError": "DNS configuration error:"
|
||||
"saved": "تنظیمات DNS ذخیره شد",
|
||||
"configError": "خطای پیکربندی DNS:"
|
||||
},
|
||||
"errors": {
|
||||
"invalid": "Invalid configuration",
|
||||
"invalidYaml": "Invalid YAML format"
|
||||
"invalid": "پیکربندی نامعتبر",
|
||||
"invalidYaml": "قالب YAML نامعتبر است"
|
||||
}
|
||||
},
|
||||
"webUI": {
|
||||
@@ -587,8 +587,8 @@
|
||||
"uninstallSuccess": "سرویس با موفقیت حذف نصب شد"
|
||||
},
|
||||
"updater": {
|
||||
"withClashProxySuccess": "Update with Clash proxy successfully",
|
||||
"withClashProxyFailed": "Update failed even with Clash proxy"
|
||||
"withClashProxySuccess": "با موفقیت با پروکسی کلش بهروزرسانی شد",
|
||||
"withClashProxyFailed": "بهروزرسانی حتی با پروکسی کلش هم انجام نشد"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -599,7 +599,7 @@
|
||||
},
|
||||
"clashService": {
|
||||
"installing": "در حال نصب سرویس...",
|
||||
"uninstalling": "Uninstalling Service..."
|
||||
"uninstalling": "در حال حذف سرویس..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
"relay": "根据定义的代理链传递"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "直连 (DIRECT)",
|
||||
"DIRECT": "直接连接 (DIRECT)",
|
||||
"REJECT": "拦截请求 (REJECT)",
|
||||
"REJECT-DROP": "抛弃请求 (REJECT-DROP)",
|
||||
"PASS": "跳过此规则 (PASS)"
|
||||
"REJECT-DROP": "丢弃请求 (REJECT-DROP)",
|
||||
"PASS": "跳过此项 (PASS)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"DOMAIN-SUFFIX": "匹配域名后缀 (DOMAIN-SUFFIX)",
|
||||
"DOMAIN-KEYWORD": "匹配域名关键字 (DOMAIN-KEYWORD)",
|
||||
"DOMAIN-REGEX": "匹配域名正则表达式 (DOMAIN-REGEX)",
|
||||
"GEOSITE": "匹配 Geosite 内的域名 (GEOSITE)",
|
||||
"GEOSITE": "匹配 GeoSite 内的域名 (GEOSITE)",
|
||||
"GEOIP": "匹配 IP 所属国家代码 (GEOIP)",
|
||||
"SRC-GEOIP": "匹配来源 IP 所属国家代码 (SRC-GEOIP)",
|
||||
"IP-ASN": "匹配 IP 所属 ASN (IP-ASN)",
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
"relay": "根據定義的代理鏈傳送"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "直連 (DIRECT)",
|
||||
"REJECT": "拒絕請求 (REJECT)",
|
||||
"DIRECT": "直接連線 (DIRECT)",
|
||||
"REJECT": "攔截請求 (REJECT)",
|
||||
"REJECT-DROP": "丟棄請求 (REJECT-DROP)",
|
||||
"PASS": "跳過此規則 (PASS)"
|
||||
"PASS": "跳過此項 (PASS)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"DOMAIN-SUFFIX": "配對網域後綴 (DOMAIN-SUFFIX)",
|
||||
"DOMAIN-KEYWORD": "配對網域關鍵字 (DOMAIN-KEYWORD)",
|
||||
"DOMAIN-REGEX": "配對網域正規表示式 (DOMAIN-REGEX)",
|
||||
"GEOSITE": "配對 Geosite 內的網域 (GEOSITE)",
|
||||
"GEOSITE": "配對 GeoSite 內的網域 (GEOSITE)",
|
||||
"GEOIP": "配對 IP 所屬國家代碼 (GEOIP)",
|
||||
"SRC-GEOIP": "配對來源 IP 所屬國家代碼 (SRC-GEOIP)",
|
||||
"IP-ASN": "配對 IP 所屬 ASN (IP-ASN)",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
type NavigateFunction = (path: string, options?: any) => void;
|
||||
type TranslateFunction = (key: string) => string;
|
||||
|
||||
@@ -129,8 +129,9 @@ const ConnectionsPage = () => {
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "auto",
|
||||
overflow: "hidden",
|
||||
borderRadius: "8px",
|
||||
minHeight: 0,
|
||||
}}
|
||||
header={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
@@ -243,6 +244,8 @@ const ConnectionsPage = () => {
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: "8px",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
overscrollBehavior: "contain",
|
||||
}}
|
||||
data={filterConn}
|
||||
itemContent={(_, item) => (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user