mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-07 01:22:15 +08:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
438fc20142 | ||
|
|
7fd537af08 | ||
|
|
38faf20f90 | ||
|
|
87d9f95990 | ||
|
|
b9ab3b2015 | ||
|
|
a45c681389 | ||
|
|
a15ff6b448 | ||
|
|
dae542f772 | ||
|
|
39ba1e64a6 | ||
|
|
27da92b5ff | ||
|
|
f076d4cd5d | ||
|
|
97dc1622ee | ||
|
|
f65a7f1348 | ||
|
|
14abc4c4ee | ||
|
|
84bbe790e8 | ||
|
|
e4d82a1b3f | ||
|
|
9fb46607b5 | ||
|
|
943e18e8ca | ||
|
|
8be05c178b | ||
|
|
01fbc5714f | ||
|
|
5d41f99eb1 |
@@ -5,7 +5,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:2": {
|
"ghcr.io/devcontainers/features/node:2": {
|
||||||
"version": "26.13.0",
|
"version": "26.13.0",
|
||||||
"pnpmVersion": "12.16.1"
|
"pnpmVersion": "12.18.0"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/rust:1": {
|
"ghcr.io/devcontainers/features/rust:1": {
|
||||||
"version": "1.99.1",
|
"version": "1.99.1",
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
run: bash ./scripts/extract_update_logs.sh
|
run: bash ./scripts/extract_update_logs.sh
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6.1.0
|
- uses: pnpm/action-setup@v6.1.1
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
@@ -174,7 +174,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6.1.0
|
- uses: pnpm/action-setup@v6.1.1
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
@@ -255,7 +255,7 @@ jobs:
|
|||||||
cache-workspace-crates: true
|
cache-workspace-crates: true
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v6.1.0
|
uses: pnpm/action-setup@v6.1.1
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ jobs:
|
|||||||
cache-workspace-crates: true
|
cache-workspace-crates: true
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v6.1.0
|
uses: pnpm/action-setup@v6.1.1
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
@@ -481,7 +481,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '26.13.0'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6.1.0
|
- uses: pnpm/action-setup@v6.1.1
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "src/**"
|
- "src/**"
|
||||||
|
- "scripts/**"
|
||||||
- "**/*.js"
|
- "**/*.js"
|
||||||
- "**/*.ts"
|
- "**/*.ts"
|
||||||
- "**/*.tsx"
|
- "**/*.tsx"
|
||||||
- "**/*.css"
|
- "**/*.css"
|
||||||
- "**/*.scss"
|
- "**/*.scss"
|
||||||
- "**/*.json"
|
- "**/*.json"
|
||||||
- "**/*.md"
|
|
||||||
- "pnpm-lock.yaml"
|
- "pnpm-lock.yaml"
|
||||||
- "pnpm-workspace.yaml"
|
- "pnpm-workspace.yaml"
|
||||||
- "vite.config.*"
|
- "vite.config.*"
|
||||||
@@ -41,6 +41,14 @@ jobs:
|
|||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Check generated i18n types are up to date
|
||||||
|
run: |
|
||||||
|
pnpm i18n:types
|
||||||
|
if ! git diff --exit-code -- src/types/generated; then
|
||||||
|
echo "::error::src/types/generated is stale; run pnpm i18n:types and commit the result"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run Prettier check
|
- name: Run Prettier check
|
||||||
run: pnpm format:check
|
run: pnpm format:check
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
nodejs 26.13.0
|
nodejs 26.13.0
|
||||||
pnpm 12.16.1
|
pnpm 12.18.0
|
||||||
rust 1.99.1
|
rust 1.99.1
|
||||||
|
|||||||
Generated
+188
-151
@@ -150,7 +150,7 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"parking_lot",
|
"parking_lot 0.12.5",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
"wl-clipboard-rs",
|
"wl-clipboard-rs",
|
||||||
@@ -159,9 +159,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.9.2"
|
version = "1.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
|
checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
@@ -1089,7 +1089,6 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"backon",
|
"backon",
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"bitflags 2.13.2",
|
|
||||||
"boa_engine",
|
"boa_engine",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -1100,7 +1099,6 @@ dependencies = [
|
|||||||
"clash-verge-media-unlock",
|
"clash-verge-media-unlock",
|
||||||
"clash-verge-signal",
|
"clash-verge-signal",
|
||||||
"clash_verge_service_ipc",
|
"clash_verge_service_ipc",
|
||||||
"criterion",
|
|
||||||
"dark-light",
|
"dark-light",
|
||||||
"deelevate",
|
"deelevate",
|
||||||
"dirs 7.0.0",
|
"dirs 7.0.0",
|
||||||
@@ -1117,9 +1115,8 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"open",
|
"open",
|
||||||
"parking_lot",
|
"parking_lot 0.13.0",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"regex",
|
|
||||||
"reqwest 0.13.5",
|
"reqwest 0.13.5",
|
||||||
"reqwest_dav",
|
"reqwest_dav",
|
||||||
"rfd",
|
"rfd",
|
||||||
@@ -1149,7 +1146,6 @@ dependencies = [
|
|||||||
"tauri-plugin-mihomo",
|
"tauri-plugin-mihomo",
|
||||||
"tauri-plugin-notification",
|
"tauri-plugin-notification",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-process",
|
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-wdio-webdriver",
|
"tauri-plugin-wdio-webdriver",
|
||||||
@@ -1157,7 +1153,6 @@ dependencies = [
|
|||||||
"tauri-specta",
|
"tauri-specta",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"warp",
|
"warp",
|
||||||
@@ -1175,7 +1170,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"criterion",
|
"criterion",
|
||||||
"parking_lot",
|
"parking_lot 0.13.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1198,7 +1193,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"flexi_logger",
|
"flexi_logger",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot 0.13.0",
|
||||||
"tracing-estuary",
|
"tracing-estuary",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1255,8 +1250,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "facfbc410a385e0b0cbca8179dd9e363ed70e8455960bf7545137e90ca753b66"
|
checksum = "facfbc410a385e0b0cbca8179dd9e363ed70e8455960bf7545137e90ca753b66"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"lock_api",
|
"lock_api 0.4.14",
|
||||||
"parking_lot_core",
|
"parking_lot_core 0.9.12",
|
||||||
"polonius-the-crab",
|
"polonius-the-crab",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1333,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1767,8 +1762,18 @@ version = "0.23.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.23.0",
|
||||||
"darling_macro",
|
"darling_macro 0.23.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.24.1",
|
||||||
|
"darling_macro 0.24.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1784,17 +1789,41 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
|
||||||
|
dependencies = [
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn 3.0.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.23.0"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.23.0",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.24.1",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dashmap"
|
name = "dashmap"
|
||||||
version = "6.2.1"
|
version = "6.2.1"
|
||||||
@@ -1804,9 +1833,9 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"lock_api",
|
"lock_api 0.4.14",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core",
|
"parking_lot_core 0.9.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1884,9 +1913,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "devtools-core"
|
name = "devtools-core"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c53d416e473377276435d49d731a75d253b96ee4339831cd85653702b94d8298"
|
checksum = "c9e85926bda763dd434fcfbcfb24af4b2c1a8c3e3c34bcd5d844c48f655cca51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"devtools-wire-format",
|
"devtools-wire-format",
|
||||||
@@ -2014,7 +2043,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users 0.5.2",
|
"redox_users 0.5.2",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2273,7 +2302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2789,7 +2818,7 @@ dependencies = [
|
|||||||
"gobject-sys 0.21.5",
|
"gobject-sys 0.21.5",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps 7.0.8",
|
"system-deps 7.0.8",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3343,7 +3372,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3357,12 +3386,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ico"
|
name = "ico"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
checksum = "d75e94d66081e4d8dcd098bd8dc5254800fea14a26389b298410158d834de167"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"png 0.17.16",
|
"png",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3548,7 +3577,7 @@ dependencies = [
|
|||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
"moxcms",
|
"moxcms",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png 0.18.1",
|
"png",
|
||||||
"tiff",
|
"tiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3857,7 +3886,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"httparse",
|
"httparse",
|
||||||
"libc",
|
"libc",
|
||||||
"parking_lot",
|
"parking_lot 0.12.5",
|
||||||
"path-tree",
|
"path-tree",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
@@ -4018,10 +4047,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "lock_api"
|
||||||
version = "0.4.34"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
checksum = "65d91b22da179613dcf4d6ec8349563f52a363f6849e03b00dd0a185ec4a59af"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-slab"
|
name = "lru-slab"
|
||||||
@@ -4031,9 +4069,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lzma-rust2"
|
name = "lzma-rust2"
|
||||||
version = "0.23.0"
|
version = "0.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1c46f3fcd2e888afdb01a07efc071cb21300e07c68d32c3e8b01b5c8cb1fcaa"
|
checksum = "490750560fa7851fbbf5d3ab1a8750f09350ed361ed8aed85b1e1e40587812ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sha2 0.11.1",
|
"sha2 0.11.1",
|
||||||
]
|
]
|
||||||
@@ -4211,10 +4249,10 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.18.1",
|
"png",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4381,7 +4419,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4651,17 +4689,6 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-open-directory"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d"
|
|
||||||
dependencies = [
|
|
||||||
"objc2",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-osa-kit"
|
name = "objc2-osa-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -4763,7 +4790,7 @@ version = "1.21.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parking_lot_core",
|
"parking_lot_core 0.9.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4786,9 +4813,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
version = "5.4.4"
|
version = "5.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc"
|
checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dunce",
|
"dunce",
|
||||||
"is-wsl",
|
"is-wsl",
|
||||||
@@ -4858,7 +4885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4922,8 +4949,18 @@ version = "0.12.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api 0.4.14",
|
||||||
"parking_lot_core",
|
"parking_lot_core 0.9.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9cf937ca4e1d0b7578784559063422fc39ae7c5a00f02471f2614d5486fd226"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api 0.5.0",
|
||||||
|
"parking_lot_core 0.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4934,7 +4971,19 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cfff060068cc18aa7815010fcaf80c68c7fb9e85fc7f5c0f9bf142c21d19826"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_syscall 0.9.4",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
@@ -5225,19 +5274,6 @@ dependencies = [
|
|||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "png"
|
|
||||||
version = "0.17.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"crc32fast",
|
|
||||||
"fdeflate",
|
|
||||||
"flate2",
|
|
||||||
"miniz_oxide 0.8.9",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
@@ -5523,7 +5559,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5667,6 +5703,15 @@ dependencies = [
|
|||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -5864,9 +5909,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfd"
|
name = "rfd"
|
||||||
version = "0.16.0"
|
version = "0.17.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
@@ -5874,16 +5919,18 @@ dependencies = [
|
|||||||
"gobject-sys 0.18.0",
|
"gobject-sys 0.18.0",
|
||||||
"gtk-sys",
|
"gtk-sys",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
|
"percent-encoding",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6027,7 +6074,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6040,7 +6087,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.12.1",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6099,7 +6146,7 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs",
|
"webpki-root-certs",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6477,7 +6524,7 @@ version = "3.21.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.23.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
@@ -6719,7 +6766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6737,7 +6784,7 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@@ -6772,9 +6819,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "specta"
|
name = "specta"
|
||||||
version = "2.0.0-rc.25"
|
version = "2.0.0-rc.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38f9a30cbcbb7011f1da7d73483983bf838af123883e45f2b36ed76328df9c50"
|
checksum = "ed155e448f42fc4be7a5cbc12bd43f6c549981f7c32fcba429ff6bc57cc4f30c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"rustc_version 0.4.1",
|
"rustc_version 0.4.1",
|
||||||
@@ -6784,9 +6831,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "specta-macros"
|
name = "specta-macros"
|
||||||
version = "2.0.0-rc.25"
|
version = "2.0.0-rc.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ce14957ecc2897f1f848b8255b6531d13ddf49cbcf506b7c2c9fb1d005593bb"
|
checksum = "b34b2516e16a9ddd3dc4e73cd6454cfe21fe209f8cb5a6f37346fe3774d759b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -6796,9 +6843,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "specta-serde"
|
name = "specta-serde"
|
||||||
version = "0.0.12"
|
version = "0.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee8a72b755ddb8949fd8f17c5db43f0e8a806ea587d9bc602ee3f73240c00029"
|
checksum = "4983177ed0eb28235080261953de5f9ebae8a2d107851b5852420bd2ab246245"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"specta",
|
"specta",
|
||||||
"specta-macros",
|
"specta-macros",
|
||||||
@@ -6806,18 +6853,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "specta-typescript"
|
name = "specta-typescript"
|
||||||
version = "0.0.12"
|
version = "0.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "639404ee95557f2f8b7e4cb773ffefd45304c7ab8ba21ac83b69051595e083c0"
|
checksum = "3ecbe75dc1d27b36141b50b02b7c73bed00945710cee6edffa86d296a159a6f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"specta",
|
"specta",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "specta-util"
|
name = "specta-util"
|
||||||
version = "0.0.12"
|
version = "0.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29b1fc02b446f7244a92924fe68c0555921209f1d342990cd1539e9138e69502"
|
checksum = "d9bf531215ea70fba8264edecf98f12600462cf359f2038602a47aeda5ecd5b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"specta",
|
"specta",
|
||||||
]
|
]
|
||||||
@@ -6847,7 +6894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"new_debug_unreachable",
|
"new_debug_unreachable",
|
||||||
"parking_lot",
|
"parking_lot 0.12.5",
|
||||||
"phf_shared 0.13.1",
|
"phf_shared 0.13.1",
|
||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
]
|
]
|
||||||
@@ -6983,16 +7030,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.39.6"
|
version = "0.40.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6"
|
checksum = "0e8a3edbe0394ce115f3917a00349dd242be09eb5a091ed509eaf67b55cfb8a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ntapi",
|
"ntapi",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-io-kit",
|
"objc2-io-kit",
|
||||||
"objc2-open-directory",
|
|
||||||
"windows 0.62.2",
|
"windows 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7094,7 +7140,7 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"parking_lot",
|
"parking_lot 0.12.5",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
@@ -7148,9 +7194,9 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.12.4"
|
version = "2.12.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f316751027fc2644221d1a448564d519efb07067fda584879d9cda966391d212"
|
checksum = "5fb0244332cfb9cee7e2a568bf5da29a36ed8fdbd717f6f47a66b6b7b358c072"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -7203,9 +7249,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "2.7.3"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "641dd5fd297c99ab92717b0428cca5b9c6f960b2ac96ca131b3eebc9d70b8ddc"
|
checksum = "adddb0e648752aaac5da9c3c3c6a68e1e87cab55e3b757b18a9441ef8c3ac614"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@@ -7226,16 +7272,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "2.7.3"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6585c5b536c00f039d12ce70a9755172d5423bbcfd3969863baa70314c3e02f"
|
checksum = "fb37631cf71dab5e8ab55e1bdd701e18a52653d90b78e71d0e5598b1e0a61316"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
"ico",
|
"ico",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"plist",
|
"plist",
|
||||||
"png 0.18.1",
|
"png",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"semver 1.0.28",
|
"semver 1.0.28",
|
||||||
@@ -7253,9 +7299,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "2.7.3"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2e83a0cfa1bae376e8fbbdc0b1692707aed50cd6072ce85cae4789af5d6c19f"
|
checksum = "0cc38db58854c10fb5f91a887ed8bf82d3239bb92b5d8db86eda09e96d9963d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -7301,7 +7347,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"deelevate",
|
"deelevate",
|
||||||
"libc",
|
"libc",
|
||||||
"parking_lot",
|
"parking_lot 0.13.0",
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
"specta",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
@@ -7326,9 +7372,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-deep-link"
|
name = "tauri-plugin-deep-link"
|
||||||
version = "2.6.2"
|
version = "2.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b3c64e24ebcbc63aaa2d88a9c51e2507084b46620baf39ecb0baa0738a08bb4"
|
checksum = "ab551d9bec27bb79d1c7fe5ed38c5cfd1cebb86efa745acc65f5853f345f6e56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dunce",
|
"dunce",
|
||||||
"plist",
|
"plist",
|
||||||
@@ -7347,9 +7393,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-devtools"
|
name = "tauri-plugin-devtools"
|
||||||
version = "2.2.2"
|
version = "2.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1acb4d1b8490ee79166b72c3f8c989e066650a11aa94dfd0d71fb05c61cdbed0"
|
checksum = "299a62101a16c14b914ebfaf529b4b64ad9ca534a0ece1c21fc9308d1e3ba9db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -7374,9 +7420,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.8.3"
|
version = "2.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7461aaacf1901915ea7b9f5cd74f8224a351ed96af0a62c07fce934003cb34b"
|
checksum = "ce0fb9c2b6d34a9720627ed930ebc8af26f61e648695aecaeb03d5c33c5896f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@@ -7480,13 +7526,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-notification"
|
name = "tauri-plugin-notification"
|
||||||
version = "2.7.0"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b939d7b19c8af1381a72ef8474b3aba2b56c1ddac2c23f0c8e96abbcf86b198b"
|
checksum = "5bc6c194a95a535abdfbe3bc1af9a43935b8b9b29c216b8e71af8b59132703cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
"rand 0.9.5",
|
"rand 0.10.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -7520,21 +7566,11 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-process"
|
|
||||||
version = "2.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74ae3d8549ae564c0397377373e262f340908eb23a5b9fbe03f2a00007dc0038"
|
|
||||||
dependencies = [
|
|
||||||
"tauri",
|
|
||||||
"tauri-plugin",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-shell"
|
name = "tauri-plugin-shell"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8567d42b2c7264355daa1df415ec77f52aa42e885924c86a969be2e920e05521"
|
checksum = "7375b25f72f70f6168f27f1ba3a413e30eaa0a2098cff53a4046ebfe9e2f142d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -7589,9 +7625,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-wdio-webdriver"
|
name = "tauri-plugin-wdio-webdriver"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f76676d510d271d80abcf8b0552987b5b4f0eae5e3f906e9b5660d43c97c62e"
|
checksum = "5a775b0b59f6f12b38d1f28667311a89cd07f532f540e27d65c6eadfba1156d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -7662,9 +7698,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "2.12.2"
|
version = "2.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d61892587aea2a85245bcaec98c6fe46268874c0a0deae5535fb71ca7b93eae"
|
checksum = "e1aa9d8b7a120f879f44fdc1053f7b7dbf416bf0bbc6a72b714dec46b782b52f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
@@ -7672,7 +7708,6 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"once_cell",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"softbuffer",
|
"softbuffer",
|
||||||
@@ -7689,11 +7724,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-specta"
|
name = "tauri-specta"
|
||||||
version = "2.0.0-rc.25"
|
version = "2.0.0-rc.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee080f36d2ac17ce2f3a82fb53f02d664e8345457de51b56dad3c394dacc41a2"
|
checksum = "fe71fc209fa4c49f959644f1ae2d70f72d5675bcb3d3ad8518122fa7b330994e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
|
"paste",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
"specta",
|
||||||
@@ -7707,15 +7743,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-specta-macros"
|
name = "tauri-specta-macros"
|
||||||
version = "2.0.0-rc.25"
|
version = "2.0.0-rc.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a59dfdce06c98d8d211619bea5fdb39486d8a8c558e12b2d2ce255972320012"
|
checksum = "dbf5689a1dbf2611e2351b79ba042ca089d0a3ce45274695e79084cd732a5aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.24.1",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7785,10 +7821,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.4.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8041,9 +8077,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.53.3"
|
version = "1.54.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e"
|
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -8399,7 +8435,7 @@ dependencies = [
|
|||||||
"flexi_logger",
|
"flexi_logger",
|
||||||
"log",
|
"log",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"parking_lot",
|
"parking_lot 0.12.5",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
@@ -8451,10 +8487,10 @@ dependencies = [
|
|||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.18.1",
|
"png",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8563,7 +8599,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9009,9 +9045,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
@@ -9105,7 +9141,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9126,7 +9162,7 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9701,7 +9737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -10033,9 +10069,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "9.1.3"
|
version = "9.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb"
|
checksum = "eaeb7f16d2605b49aef586996ded29e65642fab7259a3077e537351df6d80426"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
@@ -10046,6 +10082,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
"hmac",
|
"hmac",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
|
"libc",
|
||||||
"lzma-rust2",
|
"lzma-rust2",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nix",
|
"nix",
|
||||||
|
|||||||
+4
-6
@@ -61,21 +61,20 @@ tracing-estuary = { git = "https://github.com/Tunglies/tracing-estuary.git", bra
|
|||||||
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
|
|
||||||
tauri = { version = "2.12.4" }
|
tauri = { version = "2.12.5" }
|
||||||
tauri-plugin-clipboard-manager = "2.5.0"
|
tauri-plugin-clipboard-manager = "2.5.0"
|
||||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
parking_lot = "0.13.0"
|
||||||
anyhow = "1.0.104"
|
anyhow = "1.0.104"
|
||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||||
tokio = { version = "1.53.3", features = [
|
tokio = { version = "1.54.1", features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"macros",
|
"macros",
|
||||||
"time",
|
"time",
|
||||||
"sync",
|
"sync",
|
||||||
] }
|
] }
|
||||||
flexi_logger = "0.31.10"
|
flexi_logger = "0.31.10"
|
||||||
log = "0.4.34"
|
log = "0.4.35"
|
||||||
regex = "1.13.1"
|
|
||||||
reqwest = { version = "0.13.5", features = [
|
reqwest = { version = "0.13.5", features = [
|
||||||
"json",
|
"json",
|
||||||
"cookies",
|
"cookies",
|
||||||
@@ -90,7 +89,6 @@ smartstring = { version = "1.0.1" }
|
|||||||
serde = { version = "1.0.229" }
|
serde = { version = "1.0.229" }
|
||||||
serde_json = { version = "1.0.154" }
|
serde_json = { version = "1.0.154" }
|
||||||
serde_yaml_ng = { version = "0.10.0" }
|
serde_yaml_ng = { version = "0.10.0" }
|
||||||
bitflags = { version = "2.13.2" }
|
|
||||||
socket2 = "0.6.5"
|
socket2 = "0.6.5"
|
||||||
|
|
||||||
# *** For Windows platform only ***
|
# *** For Windows platform only ***
|
||||||
|
|||||||
@@ -25,6 +25,13 @@
|
|||||||
<summary><strong> 🐞 修复问题 </strong></summary>
|
<summary><strong> 🐞 修复问题 </strong></summary>
|
||||||
|
|
||||||
- 修复检测更新后又发布新版时,应用内更新失败
|
- 修复检测更新后又发布新版时,应用内更新失败
|
||||||
|
- 修复全局模式默认停在 `DIRECT` 或 `REJECT` 导致流量未经代理,现自动切换到第一个策略组
|
||||||
|
- 修复订阅中未加引号的 REALITY `short-id`(如 `1234e123`)被误读为数字,导致订阅无法启用
|
||||||
|
|
||||||
|
**🍎 macOS**
|
||||||
|
|
||||||
|
- 修复 macOS TUN 模式下原有 DNS 被清除、关闭后 DNS 未恢复及联网后设置未生效
|
||||||
|
- 修复 macOS 断网时误报代理已回退直连,恢复联网后自动重试系统代理
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -37,5 +44,10 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||||
|
|
||||||
|
- 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组
|
||||||
|
|
||||||
|
**🍎 macOS**
|
||||||
|
|
||||||
|
- 优化 macOS TUN 模式的系统 DNS,增加阿里和 Cloudflare 地址
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function"] }
|
specta = { version = "=2.0.0-rc.26", features = ["derive", "function"] }
|
||||||
clash-verge-logging = { workspace = true }
|
clash-verge-logging = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ edition = "2024"
|
|||||||
rust-version = "1.91"
|
rust-version = "1.91"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function"] }
|
specta = { version = "=2.0.0-rc.26", features = ["derive", "function"] }
|
||||||
tauri = { workspace = true }
|
tauri = { workspace = true }
|
||||||
tauri-plugin-clipboard-manager = { workspace = true }
|
tauri-plugin-clipboard-manager = { workspace = true }
|
||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
# sysinfo 0.38.2 conflicts with dark-light
|
# sysinfo 0.38.2 conflicts with dark-light
|
||||||
# see https://github.com/GuillaumeGomez/sysinfo/issues/1623
|
# see https://github.com/GuillaumeGomez/sysinfo/issues/1623
|
||||||
sysinfo = { version = "0.39.6", features = ["network", "system"] }
|
sysinfo = { version = "0.40.1", default-features = false, features = ["network", "system"] }
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies]
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
libc = "0.2.191"
|
libc = "0.2.191"
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ pub struct SysInfo {
|
|||||||
impl Default for SysInfo {
|
impl Default for SysInfo {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let system_name = System::name().unwrap_or_else(|| "Null".into());
|
let system_name = System::name().unwrap_or_else(|_| "Null".into());
|
||||||
let system_version = System::long_os_version().unwrap_or_else(|| "Null".into());
|
let system_version = System::long_os_version().unwrap_or_else(|_| "Null".into());
|
||||||
let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into());
|
let system_kernel_version = System::kernel_version().unwrap_or_else(|_| "Null".into());
|
||||||
let system_arch = System::cpu_arch();
|
let system_arch = System::cpu_arch();
|
||||||
Self {
|
Self {
|
||||||
system_name,
|
system_name,
|
||||||
@@ -130,9 +130,14 @@ pub fn current_gid() -> u32 {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn list_network_interfaces() -> Vec<String> {
|
pub fn list_network_interfaces() -> Vec<String> {
|
||||||
let mut networks = Networks::new();
|
let Ok(mut networks) = Networks::new() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
networks.refresh(false);
|
networks.refresh(false);
|
||||||
networks.keys().map(|name| name.to_owned()).collect()
|
networks
|
||||||
|
.keys()
|
||||||
|
.map(|name| name.to_string_lossy().into_owned())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
+4
-5
@@ -56,7 +56,7 @@
|
|||||||
"@tanstack/react-virtual": "^3.14.13",
|
"@tanstack/react-virtual": "^3.14.13",
|
||||||
"@tauri-apps/api": "2.12.4",
|
"@tauri-apps/api": "2.12.4",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.5.0",
|
"@tauri-apps/plugin-clipboard-manager": "2.5.0",
|
||||||
"@tauri-apps/plugin-dialog": "2.8.3",
|
"@tauri-apps/plugin-dialog": "2.8.4",
|
||||||
"@tauri-apps/plugin-fs": "2.7.1",
|
"@tauri-apps/plugin-fs": "2.7.1",
|
||||||
"@tauri-apps/plugin-http": "2.8.3",
|
"@tauri-apps/plugin-http": "2.8.3",
|
||||||
"@tauri-apps/plugin-opener": "2.7.2",
|
"@tauri-apps/plugin-opener": "2.7.2",
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
"@biomejs/biome": "^2.5.14",
|
"@biomejs/biome": "^2.5.14",
|
||||||
"@eslint-react/eslint-plugin": "^5.20.5",
|
"@eslint-react/eslint-plugin": "^5.20.5",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@tauri-apps/cli": "2.12.3",
|
"@tauri-apps/cli": "2.12.4",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^26.0.0",
|
"@types/node": "^26.0.0",
|
||||||
"@types/react": "19.3.0",
|
"@types/react": "19.3.0",
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
"@types/validator": "^13.15.10",
|
"@types/validator": "^13.15.10",
|
||||||
"@vitejs/plugin-legacy": "^8.2.3",
|
"@vitejs/plugin-legacy": "^8.2.3",
|
||||||
"@vitejs/plugin-react": "^6.1.1",
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
"@wdio/tauri-service": "1.5.0",
|
"@wdio/tauri-service": "1.5.1",
|
||||||
"adm-zip": "^0.6.1",
|
"adm-zip": "^0.6.1",
|
||||||
"axios": "^1.20.0",
|
"axios": "^1.20.0",
|
||||||
"cli-color": "^2.0.4",
|
"cli-color": "^2.0.4",
|
||||||
@@ -117,7 +117,6 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.5.7",
|
"eslint-plugin-react-refresh": "^0.5.7",
|
||||||
"eslint-plugin-sukka": "^10.2.0",
|
"eslint-plugin-sukka": "^10.2.0",
|
||||||
"eslint-plugin-unused-imports": "^4.4.1",
|
"eslint-plugin-unused-imports": "^4.4.1",
|
||||||
"glob": "^14.0.0",
|
|
||||||
"globals": "^17.12.0",
|
"globals": "^17.12.0",
|
||||||
"https-proxy-agent": "^9.1.0",
|
"https-proxy-agent": "^9.1.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
@@ -146,5 +145,5 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.16.1"
|
"packageManager": "pnpm@12.18.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+162
-198
@@ -7,153 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.16.1
|
specifier: 12.18.0
|
||||||
version: 12.16.1
|
version: 12.18.0
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.android-arm64@12.16.1':
|
'@pnpm/exe.android-arm64@12.18.0':
|
||||||
resolution: {integrity: sha512-PKf78Ew+i0CBYf0Xc+eMcqQiqyBT+u8XCVg1F5roc98/6wWfdYlWhrUMmEBiKWaJ3JqHxrtt8l1NwZ7qfJt2/Q==}
|
resolution: {integrity: sha512-fOF6IwgvnUKEmVedGqiAsNPfN2Ry8GvaqzSQOiF3RJFbpBfPKO/6YT54s1GjToyYinRxw9preHHPe4y2N3SzhA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@pnpm/exe.android-x64@12.16.1':
|
'@pnpm/exe.android-x64@12.18.0':
|
||||||
resolution: {integrity: sha512-xdu7gMbB98pdvhwMeA6612Of515FDlRUtfA/1f7nWPZfPLnEdSRBO2u+1N0XBtQ/ennhPH/lrxB1gC6gnR/cdg==}
|
resolution: {integrity: sha512-6OiolnaCsB3/GcJ9XNrenbUH3zFHRJ+5QynrMUUEyThbgnnCzWxa199hpQlOmxN4rAXuYQ0IYgWFNUZxyzg31Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.16.1':
|
'@pnpm/exe.darwin-arm64@12.18.0':
|
||||||
resolution: {integrity: sha512-pVbmIIbhixiLvADLu1WA3TaW8FZy/D72R1De27WbbFx8/2mxvnsTyFVm4fO9kjpjMY7RzJd48CiQ96p8fO3ebQ==}
|
resolution: {integrity: sha512-zkq+4YzVIX39ETYLyC3W7LY7MpHEU/J3nRhHl4i2yPld2Cf2+rEKRh3ejJ4AmaQ77HHJLCktY4Z3jq0fpcxFXg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.16.1':
|
'@pnpm/exe.darwin-x64@12.18.0':
|
||||||
resolution: {integrity: sha512-xZvQBFqcZjO7vFKTK3XsJpqY6lnBL9EWSLCzVUPq3kZ94+D96gwJCa76xxGNK8Cj32f4eeM91Qg+yNRR4sRpDw==}
|
resolution: {integrity: sha512-qMsL9qx92unOTa6hgkfiTJv8ceZaTx0IQg7InhYDVX2ZrTECujaVEEVypnJK5m3eGUsP1KD7Y4qIgleCwfRsYw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.freebsd-x64@12.16.1':
|
'@pnpm/exe.freebsd-x64@12.18.0':
|
||||||
resolution: {integrity: sha512-zTtunfi9mBhRXiXEJYfwiJj0tk7MgTSfqDSRh+Az9FZ9THazVIiQiVNS8jpcsftDOqu7LKNffQdjmgwHDThfUg==}
|
resolution: {integrity: sha512-/9vy/4ms52cU4MjZ5NhHd6N2d9L9W0kARf5/ff2tweAHu0VeUzs/0qp/7ZHFuddm2Wa/m0AKQoPum9AVUCqKsw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.16.1':
|
'@pnpm/exe.linux-arm64-musl@12.18.0':
|
||||||
resolution: {integrity: sha512-BJ3mOmcmQgcUo92Ee/WfEOt1pRiv3y2MMAkhEiYHd85UiPpHTfFhv+SpCbALmfExzXk3UlaRtMYwHlkQymc4VA==}
|
resolution: {integrity: sha512-Vp+TIMPPyackVCXkOdEKH/RmAn/r07EcIyrgZYQK7VrkksqZOie0GnWIx+85mPqeFEYCRtXIZAFJ9L4ceTndDg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.16.1':
|
'@pnpm/exe.linux-arm64@12.18.0':
|
||||||
resolution: {integrity: sha512-JaRd5gNxwdNGBBkrZFt8bknrK4YmHVJBW0JMhUQ0fJ0dEYp6C2GT9ExaCqOO8EwYEyqM+jpKgJIK+8WFaJy3ew==}
|
resolution: {integrity: sha512-2dpyVBCHT+FfpuQh9i+7MXAqNWYLCvD5vYr5p6Eimxoga8USLnjbUgyj72AOrO0hjc5ri3TEFP3jiyBHjkSZpQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-ppc64@12.16.1':
|
'@pnpm/exe.linux-ppc64@12.18.0':
|
||||||
resolution: {integrity: sha512-FnSV0Jv40S1cMv7vFNWwvu0X567Lec4L4HCRead3CFcD6zQDNbGw81s8N5bcKPahtPD9ApVuhsbmFCx4rw6RMw==}
|
resolution: {integrity: sha512-BBOOOfNJ2QwIIya3eKqOjiN4WNTszfRz7HCvw9XggcT7+ftMjNP8XJ/GfW+ltCBeUFR9d8WBVsr2m/NLjX2CZQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-riscv64@12.16.1':
|
'@pnpm/exe.linux-riscv64@12.18.0':
|
||||||
resolution: {integrity: sha512-C1Sww4FN5IttD4atRRhAKmQtSGWVp04ZAKNxO8pfu2bkRPwOW+7v+2DH1Xs1W+kZYYpO86BYDvUcadefF+AgaQ==}
|
resolution: {integrity: sha512-7kMN77xSOYJX8GPSH5RbNAc5oM61LMmcSUukDDUwllnZPXm8GRPskCNsTWy8SCw01Kr1FB+5SwGNMiTdiziLNQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-s390x@12.16.1':
|
'@pnpm/exe.linux-s390x@12.18.0':
|
||||||
resolution: {integrity: sha512-kn2fautsTcxIMODoovQVqZyfpDRdNgGkoeVqNdrKqbeBBvrEl+fXLkmZiq5xDht1LMr4sYiCNcsMfNMT6JPFmg==}
|
resolution: {integrity: sha512-htuSCpEcmDzhKoJqXbPkvfr176dBHU0QIF5mWcRiqxRXAB/1JoYiMYotfjWpk+ztgpB9qlx4XnWlLFAf7gzc6g==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.16.1':
|
'@pnpm/exe.linux-x64-musl@12.18.0':
|
||||||
resolution: {integrity: sha512-PM6ccHCOe/O9SsTflTJTYWfwogJZa6EmUvpnwZgiPguJBddh20wDQKgxUPXf48rDsPm9vMsIij0uHswwwbXL8g==}
|
resolution: {integrity: sha512-Uj+vzfIQ62kDP10x36yHZIXVs5ohdNtiPYwn5FhCLlNScHPo0OAI+ShGDtLofiBXKE7umu71Jmjm7N8a8zVcDw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.16.1':
|
'@pnpm/exe.linux-x64@12.18.0':
|
||||||
resolution: {integrity: sha512-RK//8ZLcXGW8A2k8bQOXZDBN1Qu92zN4dYnG1bzj1zoQ1fkZ6jNzZ1AvXtsfGVma795CJtvoaF5bLXf4l/FIUw==}
|
resolution: {integrity: sha512-OJ+X2wcraldCwj75pCIerWARQSRpdfj0eBTs/2ODyhfeW46ylOnjTIE/GKra6Mn6E72/gBmXS8igrUioa+F9fQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.16.1':
|
'@pnpm/exe.win32-arm64@12.18.0':
|
||||||
resolution: {integrity: sha512-Ox3kNNVxtGw6pK7SVejmeOUgHhUTZgAM7NQQ6cU7frPw3ix9+j7d2MGbRg3fyEFAaKWq8ZAZLTQGcDyLnAu+qA==}
|
resolution: {integrity: sha512-wEY6jiACGcbhhi5yD59doHqNTndS7AaujaPmtWAu1DKkHru2hy5fJ9yUGecW72Ypaw2/I3BEprfMOopap2NQtw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.16.1':
|
'@pnpm/exe.win32-x64@12.18.0':
|
||||||
resolution: {integrity: sha512-k+FhRJ0ttphFoFIVB1Sk4v76YJAulafmA/Ly57cysesUhhIzVlApeOShUz0jXubKx317TThlEfpQrukldnqORA==}
|
resolution: {integrity: sha512-5FJkghO7RLRkQpWxeVEygW9XsmnNuxzPfXbgwFWCHsFyFhpDn/vOdL88ToQEM0Kt5igdW5s6U0/IDH8Dvkt3pg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.16.1:
|
pnpm@12.18.0:
|
||||||
resolution: {integrity: sha512-CR0d7nknV1Zhsl2rPCrjHK/BxU1rce1qZgDPDByxBDSkR+rhQBXxsj4ua+3u1535SpMuIWTSgyKA3OP7MvZW0Q==}
|
resolution: {integrity: sha512-nXiC1dyCFH7PxOBiqjSDTgrQKWcQoYN6ERWuASEn02txCeMhrBWYod0KZVl2gbHpmRfhLtDXSerpEYeUM2O+sw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.android-arm64@12.16.1':
|
'@pnpm/exe.android-arm64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.android-x64@12.16.1':
|
'@pnpm/exe.android-x64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.16.1':
|
'@pnpm/exe.darwin-arm64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.16.1':
|
'@pnpm/exe.darwin-x64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.freebsd-x64@12.16.1':
|
'@pnpm/exe.freebsd-x64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.16.1':
|
'@pnpm/exe.linux-arm64-musl@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.16.1':
|
'@pnpm/exe.linux-arm64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-ppc64@12.16.1':
|
'@pnpm/exe.linux-ppc64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-riscv64@12.16.1':
|
'@pnpm/exe.linux-riscv64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-s390x@12.16.1':
|
'@pnpm/exe.linux-s390x@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.16.1':
|
'@pnpm/exe.linux-x64-musl@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.16.1':
|
'@pnpm/exe.linux-x64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.16.1':
|
'@pnpm/exe.win32-arm64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.16.1':
|
'@pnpm/exe.win32-x64@12.18.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
pnpm@12.16.1:
|
pnpm@12.18.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@pnpm/exe.android-arm64': 12.16.1
|
'@pnpm/exe.android-arm64': 12.18.0
|
||||||
'@pnpm/exe.android-x64': 12.16.1
|
'@pnpm/exe.android-x64': 12.18.0
|
||||||
'@pnpm/exe.darwin-arm64': 12.16.1
|
'@pnpm/exe.darwin-arm64': 12.18.0
|
||||||
'@pnpm/exe.darwin-x64': 12.16.1
|
'@pnpm/exe.darwin-x64': 12.18.0
|
||||||
'@pnpm/exe.freebsd-x64': 12.16.1
|
'@pnpm/exe.freebsd-x64': 12.18.0
|
||||||
'@pnpm/exe.linux-arm64': 12.16.1
|
'@pnpm/exe.linux-arm64': 12.18.0
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.16.1
|
'@pnpm/exe.linux-arm64-musl': 12.18.0
|
||||||
'@pnpm/exe.linux-ppc64': 12.16.1
|
'@pnpm/exe.linux-ppc64': 12.18.0
|
||||||
'@pnpm/exe.linux-riscv64': 12.16.1
|
'@pnpm/exe.linux-riscv64': 12.18.0
|
||||||
'@pnpm/exe.linux-s390x': 12.16.1
|
'@pnpm/exe.linux-s390x': 12.18.0
|
||||||
'@pnpm/exe.linux-x64': 12.16.1
|
'@pnpm/exe.linux-x64': 12.18.0
|
||||||
'@pnpm/exe.linux-x64-musl': 12.16.1
|
'@pnpm/exe.linux-x64-musl': 12.18.0
|
||||||
'@pnpm/exe.win32-arm64': 12.16.1
|
'@pnpm/exe.win32-arm64': 12.18.0
|
||||||
'@pnpm/exe.win32-x64': 12.16.1
|
'@pnpm/exe.win32-x64': 12.18.0
|
||||||
|
|
||||||
---
|
---
|
||||||
lockfileVersion: '9.0'
|
lockfileVersion: '9.0'
|
||||||
@@ -206,8 +206,8 @@ importers:
|
|||||||
specifier: 2.5.0
|
specifier: 2.5.0
|
||||||
version: 2.5.0
|
version: 2.5.0
|
||||||
'@tauri-apps/plugin-dialog':
|
'@tauri-apps/plugin-dialog':
|
||||||
specifier: 2.8.3
|
specifier: 2.8.4
|
||||||
version: 2.8.3
|
version: 2.8.4
|
||||||
'@tauri-apps/plugin-fs':
|
'@tauri-apps/plugin-fs':
|
||||||
specifier: 2.7.1
|
specifier: 2.7.1
|
||||||
version: 2.7.1
|
version: 2.7.1
|
||||||
@@ -315,8 +315,8 @@ importers:
|
|||||||
specifier: ^10.0.1
|
specifier: ^10.0.1
|
||||||
version: 10.0.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))
|
version: 10.0.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))
|
||||||
'@tauri-apps/cli':
|
'@tauri-apps/cli':
|
||||||
specifier: 2.12.3
|
specifier: 2.12.4
|
||||||
version: 2.12.3
|
version: 2.12.4
|
||||||
'@types/lodash-es':
|
'@types/lodash-es':
|
||||||
specifier: ^4.17.12
|
specifier: ^4.17.12
|
||||||
version: 4.17.12
|
version: 4.17.12
|
||||||
@@ -339,8 +339,8 @@ importers:
|
|||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))
|
version: 6.1.1(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))
|
||||||
'@wdio/tauri-service':
|
'@wdio/tauri-service':
|
||||||
specifier: 1.5.0
|
specifier: 1.5.1
|
||||||
version: 1.5.0(expect-webdriverio@6.1.0)(supports-color@7.2.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
|
version: 1.5.1(expect-webdriverio@6.1.0)(supports-color@7.2.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
|
||||||
adm-zip:
|
adm-zip:
|
||||||
specifier: ^0.6.1
|
specifier: ^0.6.1
|
||||||
version: 0.6.1
|
version: 0.6.1
|
||||||
@@ -383,9 +383,6 @@ importers:
|
|||||||
eslint-plugin-unused-imports:
|
eslint-plugin-unused-imports:
|
||||||
specifier: ^4.4.1
|
specifier: ^4.4.1
|
||||||
version: 4.4.1(@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))
|
version: 4.4.1(@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))
|
||||||
glob:
|
|
||||||
specifier: ^14.0.0
|
|
||||||
version: 14.0.3
|
|
||||||
globals:
|
globals:
|
||||||
specifier: ^17.12.0
|
specifier: ^17.12.0
|
||||||
version: 17.12.0
|
version: 17.12.0
|
||||||
@@ -1384,9 +1381,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@isaacs/nonrecursive-glob-match@1.0.2':
|
|
||||||
resolution: {integrity: sha512-GJPHIgPPtFl+nLhFHyF0zZoFcOFLsZWbHFFzo+Zr+PWDXJbl9hHxb2w+9SiF2fHmLgKaXIpUzQo3SbBBZ9WObw==}
|
|
||||||
|
|
||||||
'@jest/diff-sequences@30.5.0':
|
'@jest/diff-sequences@30.5.0':
|
||||||
resolution: {integrity: sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==}
|
resolution: {integrity: sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==}
|
||||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
@@ -2127,87 +2121,87 @@ packages:
|
|||||||
'@tauri-apps/api@2.12.4':
|
'@tauri-apps/api@2.12.4':
|
||||||
resolution: {integrity: sha512-pVmfis+4zT8H0Qhp3xPZx9Ju7kpzvA6rr/PSJPyuwfvucTQ8sVt61NYU9KMkK703UFlYadFmjKA4HhybOtN0gg==}
|
resolution: {integrity: sha512-pVmfis+4zT8H0Qhp3xPZx9Ju7kpzvA6rr/PSJPyuwfvucTQ8sVt61NYU9KMkK703UFlYadFmjKA4HhybOtN0gg==}
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-arm64@2.12.3':
|
'@tauri-apps/cli-darwin-arm64@2.12.4':
|
||||||
resolution: {integrity: sha512-xmVNoJOOKzKLj7GLHXmXLimRby9M42aBIJa0sB80N7o24eKV5JiVmToKTRLVH9yhL05GZ/B6rSSOmbzNFJwBAA==}
|
resolution: {integrity: sha512-OxqKSckIkoJ6vX2AjifIJpTasFC0Hbdoo7gEiCmAjauABO3U8m2MRsLWe+QTiBwDx1mIrvgA+rvI7tvDnbnHVg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-x64@2.12.3':
|
'@tauri-apps/cli-darwin-x64@2.12.4':
|
||||||
resolution: {integrity: sha512-URd+0usfmUQQyb5kNPiKt2OT8gp7oGU7QR+qOppB6e35mpVBFFVNhWc9oWb3p0u71fDJ09DpF4T6m9xYxoIB8g==}
|
resolution: {integrity: sha512-fz76/77wMMJOaaUN6n3CybRG0P/MTEjjqYk81mGPyhi9m6Ue5T+LWBnJP/AkPooNatBPI2vtulGqomyMJ44lCQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.12.3':
|
'@tauri-apps/cli-linux-arm-gnueabihf@2.12.4':
|
||||||
resolution: {integrity: sha512-0kwUVx2LbmOoTRlwF1oDww7th4/NSluv4ZBQ6KRZsFKg4g0TlTcIiJ0VehqkQYUeC9xIT1Mk6XtF52mI155VSQ==}
|
resolution: {integrity: sha512-K0w8Bof6Lt8QKLAYhRy0yy36ZH5iA/35VuSOvnxyjvCUZ+A3JKsUTlALviH/sVgAlisaPzzmeJSH8XOn4Q2HBA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-gnu@2.12.3':
|
'@tauri-apps/cli-linux-arm64-gnu@2.12.4':
|
||||||
resolution: {integrity: sha512-gKSGDx6fx+HBLQmG6guChFdisbrCSdx9Yli+j5teOa64gm1CeM+C6NLru5Pj3ZkkEep05BmCFjxvX+cjqAycZQ==}
|
resolution: {integrity: sha512-j5rOnjmRUwWrm1oosf0zBYl4D+fqydgUROFCk0hvKZxvbTT8SOERes0QB7rBV/XWNy+kM6pCKzb3oSm2sRglzg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@2.12.3':
|
'@tauri-apps/cli-linux-arm64-musl@2.12.4':
|
||||||
resolution: {integrity: sha512-Yr59qMy/gcvAfBuFdCGlAmGiLsYWMFDbsP4bAI6iNBzcJTGrYWZGeiQhGDo29MQwfF/NUBqyM2qljXu5tZ6wjQ==}
|
resolution: {integrity: sha512-aOue3Fy6Jb7+oWcfAVUsVHfeTybSCt2CZoU06aO4jv4d2Ks/KnaTPwMAK3CLEduGOvBLY1lbBBrIyomqLLH5Sg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-riscv64-gnu@2.12.3':
|
'@tauri-apps/cli-linux-riscv64-gnu@2.12.4':
|
||||||
resolution: {integrity: sha512-jKr0/REBMnLKC0OwPQZkyge0mKDaNZ0b/AbfGwVyCgFfwS730EVLfVDo3nt7MUu6jJaytRq08+xSj5qfy+U1+Q==}
|
resolution: {integrity: sha512-Fx6GShY/RvST+t6LMlTHAwd66+1tEaAAGrnU7cysVfaiRP1BKng5Vl6rVmO6rSLOA1Sp1dRzNYM4f0OWQGb7qA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@2.12.3':
|
'@tauri-apps/cli-linux-x64-gnu@2.12.4':
|
||||||
resolution: {integrity: sha512-gTkEqJAGCMROGanxHnzaJzQ+/rXl39HWVg0/hDODS1wDWBGqoceqGC6Qm1+mQbyT11xnsB8mKu/gyVID/YpFIw==}
|
resolution: {integrity: sha512-tNzZSa79Z1nRpHxEpQ38lsAFpKlFlYvoXzjTV+lRRn/zSf3wrdUC3wnLcq2XEadchPu4ttK0zivhYzr+XGD19Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@2.12.3':
|
'@tauri-apps/cli-linux-x64-musl@2.12.4':
|
||||||
resolution: {integrity: sha512-T2dTxL1svRym71fZtaW4pZYXbthbfFsEJr1NeOUg/p7laWXZbUt/8DMSZf8recu9f7+1F/Lv00HtzsEu6vDO9w==}
|
resolution: {integrity: sha512-jmZ1pr0xEqUhuEusWtt9Yt+GF6lCnGHUmIL+KIknpbjK/XQ2qkhDBhaxK/JMHM2A8dAacVXCxXJk2EXpwhQUfA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@2.12.3':
|
'@tauri-apps/cli-win32-arm64-msvc@2.12.4':
|
||||||
resolution: {integrity: sha512-eoiQwpEZWbpsVHcM9BAzqqe4TCHszvr3rsM7pUQ4ofLK0lF1i1d/jX0Qo5DYMqcrUtwQuDdDuOCbcT0Izf1VwQ==}
|
resolution: {integrity: sha512-fGo8pgEHZxdSYgZ/J98m1usJ7NHKmKbq7k6uT0x1rpOq6Bg2YVzzpaAblWk710f5LHYBJIL5QIFS5IrD+4gmTg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-ia32-msvc@2.12.3':
|
'@tauri-apps/cli-win32-ia32-msvc@2.12.4':
|
||||||
resolution: {integrity: sha512-kE+KUg6Dbbkgvcw0m99wJVIToFQzJqpMBAXah74WqxaLzssNwSTUccrHp6+QVzMhjQP330tvekFmnQF+0NbE6Q==}
|
resolution: {integrity: sha512-ezl8sQIcjYsye3TUnTH5VcWmWzccZASlQm7hlZt0dYyj1XD0qpVW47u2xYVrIKui0F8uQv/7QGZixdWiClBsbg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-x64-msvc@2.12.3':
|
'@tauri-apps/cli-win32-x64-msvc@2.12.4':
|
||||||
resolution: {integrity: sha512-L7wndWAgX2mLW7alswpL3d1R0P9FN2WYBgHaQkAYEBxHHGkY4vFX9CjcRQ1LAxDAMIJxrY7/aDziFJeChS84NQ==}
|
resolution: {integrity: sha512-DJhDeCqjLDNMWYFy7ZS8cfeAHX2RK0RbQMLiA3bMVVkXRDJ4NBYiHriGqsUhsr9iG41aTb+dJGkFPmPw1gWwVw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tauri-apps/cli@2.12.3':
|
'@tauri-apps/cli@2.12.4':
|
||||||
resolution: {integrity: sha512-f7/LsY74SVb1ChmOPw1ajOpxii0GSzDAgmWHRci8JUYuuai04rXsSbFobpmB0sP2DdlvMD/B5ZotEn8GOGMRUg==}
|
resolution: {integrity: sha512-JI9bHfudSRJikGOrjbXEm6RjzzvmKuLc+JixB1NE8hNzQkNlCCIC3pTES3uypkR9lxHrsbxzcnSwhI2IxBQphw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@tauri-apps/plugin-clipboard-manager@2.5.0':
|
'@tauri-apps/plugin-clipboard-manager@2.5.0':
|
||||||
resolution: {integrity: sha512-dibUy1nKURYDXRMvjNk4deAbTZ5jPQ8wCE4nolt5xniryWgXQ4sK2GkFXoXlHbRdPzfZptZj95BRLfalA7m/2Q==}
|
resolution: {integrity: sha512-dibUy1nKURYDXRMvjNk4deAbTZ5jPQ8wCE4nolt5xniryWgXQ4sK2GkFXoXlHbRdPzfZptZj95BRLfalA7m/2Q==}
|
||||||
|
|
||||||
'@tauri-apps/plugin-dialog@2.8.3':
|
'@tauri-apps/plugin-dialog@2.8.4':
|
||||||
resolution: {integrity: sha512-Hd/yBfk2R5v2MFrz/0VArSXeh8xKLLXGunqSiTtcHKn1zNZwaKujuA0VSwUvNfwC4hDoF1PveS+gBDNe3nHspw==}
|
resolution: {integrity: sha512-+6ZOiXenOaxXwb8qCbBPdT2jX2cFPaVVHGIskrpmJLpmWj+qKRTD6X/z9x7hTdSQn7NedcoXCkWGej4a7dC4lw==}
|
||||||
|
|
||||||
'@tauri-apps/plugin-fs@2.7.1':
|
'@tauri-apps/plugin-fs@2.7.1':
|
||||||
resolution: {integrity: sha512-4TBVjpi/q8PuNzjpkAc2iBGXMTLjOrAyfTiiOL8ORaJCZJRG3tjzmQ4sqs2CMG75FvLyRkOkERfP7AyY4iBNwQ==}
|
resolution: {integrity: sha512-4TBVjpi/q8PuNzjpkAc2iBGXMTLjOrAyfTiiOL8ORaJCZJRG3tjzmQ4sqs2CMG75FvLyRkOkERfP7AyY4iBNwQ==}
|
||||||
@@ -2623,20 +2617,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-0ZAEIo6PNyMIJPlOGkIgyOJUjcd0pC8/QHlVAAe1c91/IcjZ1X+k0yidXHaboJdN7dq1XPUacmhRdtua0U5EZg==}
|
resolution: {integrity: sha512-0ZAEIo6PNyMIJPlOGkIgyOJUjcd0pC8/QHlVAAe1c91/IcjZ1X+k0yidXHaboJdN7dq1XPUacmhRdtua0U5EZg==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
|
|
||||||
'@wdio/native-core@1.3.0':
|
'@wdio/native-core@1.3.1':
|
||||||
resolution: {integrity: sha512-wUwc0QTdvhD0LwH4YcRaN9JWLX/yac8sovddUR5/4sqHMNJr3yaN11bpIfYoD/bEk+1nTLjWuDCFpe5l5wUgLQ==}
|
resolution: {integrity: sha512-fdGk2lEr2vS+bClJXuZEnsG30cLzQv+B9W+dAt/Yte/fE33s/vDBn7hHwFx8aZLTOS0pPExErUxYiZEMXX+04g==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
|
|
||||||
'@wdio/native-spy@1.4.0':
|
'@wdio/native-spy@1.4.1':
|
||||||
resolution: {integrity: sha512-unsC2Vgu5T9atD478Zj/p2N+rVSK4i/NibIaaEw4rZF/T5u5bmAHChFntEiBEfyMzd62VwbhKhzwwOSKjzrfJA==}
|
resolution: {integrity: sha512-KGOmgaicHqDkqj+s2bxXsRsb27IzW6carlyTu1xcrJI34yUbC0v14F8mbhuZcewW+mt2xhmC17yITwAbNa8PKw==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
|
|
||||||
'@wdio/native-types@2.6.0':
|
'@wdio/native-types@2.6.1':
|
||||||
resolution: {integrity: sha512-MVMVpZTH+UY6nUtLClCzY4BQSIs/nCCHo81i4ClLR+b2NehyBvwccttXDAGPgf0icyHUhd+PgIPXaLgRcm4M9A==}
|
resolution: {integrity: sha512-Rm5chX+MrRhVgJYcYdcX/yyTZaLjxvi0w/fnSgPaDT16gMIntXlk22K2x3hwEdaUonOKepZyhh4jr9ZgRz7UaQ==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
|
|
||||||
'@wdio/native-utils@2.8.0':
|
'@wdio/native-utils@2.8.1':
|
||||||
resolution: {integrity: sha512-TmIkPAd19FkJ747AdFVEtawbakVV3HHCh8zk3Kl/FLc6B2bbea7hn90jtLxqBZgCVqUuJIBcMNFg/nV/hgBFOw==}
|
resolution: {integrity: sha512-QbmZjGkLUC2bmUJSzyqsHM11HND1D7tkxHFssUV1Kmx43+oPAeHHVR0NSZu2hMzSNrDZMBAmNYycR+3X8rqWcA==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
tsx: ^4.22.4
|
tsx: ^4.22.4
|
||||||
@@ -2660,8 +2654,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-lNLLPUk4y1Yp5pWwBFF+tjZIy7bOv0TPTk4HDWV4LnnHnkc24abI0nN+kb2y5/JV8ZgwLCCVKUFRdRUxfKsAaQ==}
|
resolution: {integrity: sha512-lNLLPUk4y1Yp5pWwBFF+tjZIy7bOv0TPTk4HDWV4LnnHnkc24abI0nN+kb2y5/JV8ZgwLCCVKUFRdRUxfKsAaQ==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
|
|
||||||
'@wdio/tauri-service@1.5.0':
|
'@wdio/tauri-service@1.5.1':
|
||||||
resolution: {integrity: sha512-S3fs8T+jgNOFi4x/X2z69unS/1K3ZB28+owBouhHtVnrMKeBWAKP46IHnp8c8AZXppn6fiPNC4M1jfRZbLKeKw==}
|
resolution: {integrity: sha512-nfzJU2x7bxvIXtYo+yvrX4Xwuba8h5hXOlJ0IyHanGg0ELavY/2UfyxygJDTcSGO+pCQMdC9JJEyMvaS50AtZQ==}
|
||||||
engines: {node: '>=18.20.0'}
|
engines: {node: '>=18.20.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
webdriverio: ^9.0.0
|
webdriverio: ^9.0.0
|
||||||
@@ -3508,8 +3502,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-dUnV6Uok578HuGYTC/EM0LqeXewXBCHsh/kmLkdcqN1zgSy7K3gCIrshPBS5JwW20XSaRv4Vnz7z8lMjOV8jWw==}
|
resolution: {integrity: sha512-dUnV6Uok578HuGYTC/EM0LqeXewXBCHsh/kmLkdcqN1zgSy7K3gCIrshPBS5JwW20XSaRv4Vnz7z8lMjOV8jWw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
follow-redirects@1.16.0:
|
follow-redirects@1.16.2:
|
||||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
resolution: {integrity: sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
debug: '*'
|
debug: '*'
|
||||||
@@ -3590,10 +3584,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
glob@14.0.3:
|
|
||||||
resolution: {integrity: sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow==}
|
|
||||||
engines: {node: 18 || 20 || >=22}
|
|
||||||
|
|
||||||
globals@17.12.0:
|
globals@17.12.0:
|
||||||
resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==}
|
resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -4131,10 +4121,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
|
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
minimatch@11.0.2:
|
|
||||||
resolution: {integrity: sha512-pWTgz5CN3tjqyZNSK2tePlSmu9IwM41q8yn8Usdqqwe9TIHh+9dEpLLjF4uApsfPEAZY71qtSiA2B2APEr+1Yw==}
|
|
||||||
engines: {node: 18 || 20 || >=22}
|
|
||||||
|
|
||||||
minipass@7.1.3:
|
minipass@7.1.3:
|
||||||
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
@@ -4296,10 +4282,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
|
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
path-scurry@3.0.0:
|
|
||||||
resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==}
|
|
||||||
engines: {node: 18 || 20 || >=22}
|
|
||||||
|
|
||||||
path-type@4.0.0:
|
path-type@4.0.0:
|
||||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4602,8 +4584,8 @@ packages:
|
|||||||
snake-case@3.0.4:
|
snake-case@3.0.4:
|
||||||
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
|
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.2:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
source-map-support@0.5.21:
|
||||||
@@ -4825,16 +4807,16 @@ packages:
|
|||||||
undici-types@8.9.0:
|
undici-types@8.9.0:
|
||||||
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
|
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
|
||||||
|
|
||||||
undici@6.28.1:
|
undici@6.29.1:
|
||||||
resolution: {integrity: sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==}
|
resolution: {integrity: sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==}
|
||||||
engines: {node: '>=18.17'}
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
undici@7.29.1:
|
undici@7.30.1:
|
||||||
resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
|
resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
||||||
undici@8.11.2:
|
undici@8.12.3:
|
||||||
resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==}
|
resolution: {integrity: sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==}
|
||||||
engines: {node: '>=22.19.0'}
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.1:
|
unicode-canonical-property-names-ecmascript@2.0.1:
|
||||||
@@ -5156,12 +5138,12 @@ snapshots:
|
|||||||
'@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.8)
|
'@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.8)
|
||||||
'@octokit/request': 10.0.16
|
'@octokit/request': 10.0.16
|
||||||
'@octokit/request-error': 7.1.2
|
'@octokit/request-error': 7.1.2
|
||||||
undici: 6.28.1
|
undici: 6.29.1
|
||||||
|
|
||||||
'@actions/http-client@3.0.2':
|
'@actions/http-client@3.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
tunnel: 0.0.6
|
tunnel: 0.0.6
|
||||||
undici: 6.28.1
|
undici: 6.29.1
|
||||||
|
|
||||||
'@babel/code-frame@7.29.7':
|
'@babel/code-frame@7.29.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6260,8 +6242,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minipass: 7.1.3
|
minipass: 7.1.3
|
||||||
|
|
||||||
'@isaacs/nonrecursive-glob-match@1.0.2': {}
|
|
||||||
|
|
||||||
'@jest/diff-sequences@30.5.0': {}
|
'@jest/diff-sequences@30.5.0': {}
|
||||||
|
|
||||||
'@jest/expect-utils@30.5.2':
|
'@jest/expect-utils@30.5.2':
|
||||||
@@ -6824,58 +6804,58 @@ snapshots:
|
|||||||
|
|
||||||
'@tauri-apps/api@2.12.4': {}
|
'@tauri-apps/api@2.12.4': {}
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-arm64@2.12.3':
|
'@tauri-apps/cli-darwin-arm64@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-x64@2.12.3':
|
'@tauri-apps/cli-darwin-x64@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.12.3':
|
'@tauri-apps/cli-linux-arm-gnueabihf@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-gnu@2.12.3':
|
'@tauri-apps/cli-linux-arm64-gnu@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@2.12.3':
|
'@tauri-apps/cli-linux-arm64-musl@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-riscv64-gnu@2.12.3':
|
'@tauri-apps/cli-linux-riscv64-gnu@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@2.12.3':
|
'@tauri-apps/cli-linux-x64-gnu@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@2.12.3':
|
'@tauri-apps/cli-linux-x64-musl@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@2.12.3':
|
'@tauri-apps/cli-win32-arm64-msvc@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-ia32-msvc@2.12.3':
|
'@tauri-apps/cli-win32-ia32-msvc@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-x64-msvc@2.12.3':
|
'@tauri-apps/cli-win32-x64-msvc@2.12.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli@2.12.3':
|
'@tauri-apps/cli@2.12.4':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tauri-apps/cli-darwin-arm64': 2.12.3
|
'@tauri-apps/cli-darwin-arm64': 2.12.4
|
||||||
'@tauri-apps/cli-darwin-x64': 2.12.3
|
'@tauri-apps/cli-darwin-x64': 2.12.4
|
||||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.12.3
|
'@tauri-apps/cli-linux-arm-gnueabihf': 2.12.4
|
||||||
'@tauri-apps/cli-linux-arm64-gnu': 2.12.3
|
'@tauri-apps/cli-linux-arm64-gnu': 2.12.4
|
||||||
'@tauri-apps/cli-linux-arm64-musl': 2.12.3
|
'@tauri-apps/cli-linux-arm64-musl': 2.12.4
|
||||||
'@tauri-apps/cli-linux-riscv64-gnu': 2.12.3
|
'@tauri-apps/cli-linux-riscv64-gnu': 2.12.4
|
||||||
'@tauri-apps/cli-linux-x64-gnu': 2.12.3
|
'@tauri-apps/cli-linux-x64-gnu': 2.12.4
|
||||||
'@tauri-apps/cli-linux-x64-musl': 2.12.3
|
'@tauri-apps/cli-linux-x64-musl': 2.12.4
|
||||||
'@tauri-apps/cli-win32-arm64-msvc': 2.12.3
|
'@tauri-apps/cli-win32-arm64-msvc': 2.12.4
|
||||||
'@tauri-apps/cli-win32-ia32-msvc': 2.12.3
|
'@tauri-apps/cli-win32-ia32-msvc': 2.12.4
|
||||||
'@tauri-apps/cli-win32-x64-msvc': 2.12.3
|
'@tauri-apps/cli-win32-x64-msvc': 2.12.4
|
||||||
|
|
||||||
'@tauri-apps/plugin-clipboard-manager@2.5.0':
|
'@tauri-apps/plugin-clipboard-manager@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.12.4
|
'@tauri-apps/api': 2.12.4
|
||||||
|
|
||||||
'@tauri-apps/plugin-dialog@2.8.3':
|
'@tauri-apps/plugin-dialog@2.8.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.12.4
|
'@tauri-apps/api': 2.12.4
|
||||||
|
|
||||||
@@ -7275,11 +7255,11 @@ snapshots:
|
|||||||
safe-regex2: 5.1.1
|
safe-regex2: 5.1.1
|
||||||
strip-ansi: 7.2.0
|
strip-ansi: 7.2.0
|
||||||
|
|
||||||
'@wdio/native-core@1.3.0(supports-color@7.2.0)':
|
'@wdio/native-core@1.3.1(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@wdio/logger': 9.29.1
|
'@wdio/logger': 9.29.1
|
||||||
'@wdio/native-types': 2.6.0
|
'@wdio/native-types': 2.6.1
|
||||||
'@wdio/native-utils': 2.8.0(supports-color@7.2.0)
|
'@wdio/native-utils': 2.8.1(supports-color@7.2.0)
|
||||||
debug: 4.4.3(supports-color@7.2.0)
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
get-port: 7.2.0
|
get-port: 7.2.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -7287,11 +7267,11 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- tsx
|
- tsx
|
||||||
|
|
||||||
'@wdio/native-spy@1.4.0': {}
|
'@wdio/native-spy@1.4.1': {}
|
||||||
|
|
||||||
'@wdio/native-types@2.6.0': {}
|
'@wdio/native-types@2.6.1': {}
|
||||||
|
|
||||||
'@wdio/native-utils@2.8.0(supports-color@7.2.0)':
|
'@wdio/native-utils@2.8.1(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@wdio/logger': 9.29.1
|
'@wdio/logger': 9.29.1
|
||||||
debug: 4.4.3(supports-color@7.2.0)
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
@@ -7325,14 +7305,14 @@ snapshots:
|
|||||||
easy-table: 1.2.0
|
easy-table: 1.2.0
|
||||||
pretty-ms: 9.3.1
|
pretty-ms: 9.3.1
|
||||||
|
|
||||||
'@wdio/tauri-service@1.5.0(expect-webdriverio@6.1.0)(supports-color@7.2.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))':
|
'@wdio/tauri-service@1.5.1(expect-webdriverio@6.1.0)(supports-color@7.2.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@wdio/globals': 9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
|
'@wdio/globals': 9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
|
||||||
'@wdio/logger': 9.29.1
|
'@wdio/logger': 9.29.1
|
||||||
'@wdio/native-core': 1.3.0(supports-color@7.2.0)
|
'@wdio/native-core': 1.3.1(supports-color@7.2.0)
|
||||||
'@wdio/native-spy': 1.4.0
|
'@wdio/native-spy': 1.4.1
|
||||||
'@wdio/native-types': 2.6.0
|
'@wdio/native-types': 2.6.1
|
||||||
'@wdio/native-utils': 2.8.0(supports-color@7.2.0)
|
'@wdio/native-utils': 2.8.1(supports-color@7.2.0)
|
||||||
'@wdio/spec-reporter': 9.31.2
|
'@wdio/spec-reporter': 9.31.2
|
||||||
'@wdio/types': 9.31.2
|
'@wdio/types': 9.31.2
|
||||||
debug: 4.4.3(supports-color@7.2.0)
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
@@ -7458,7 +7438,7 @@ snapshots:
|
|||||||
|
|
||||||
axios@1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0):
|
axios@1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0))
|
follow-redirects: 1.16.2(debug@4.4.3(supports-color@7.2.0))
|
||||||
form-data: 4.0.6
|
form-data: 4.0.6
|
||||||
https-proxy-agent: 5.0.1(supports-color@7.2.0)
|
https-proxy-agent: 5.0.1(supports-color@7.2.0)
|
||||||
proxy-from-env: 2.1.0
|
proxy-from-env: 2.1.0
|
||||||
@@ -7628,7 +7608,7 @@ snapshots:
|
|||||||
parse5: 7.3.0
|
parse5: 7.3.0
|
||||||
parse5-htmlparser2-tree-adapter: 7.1.0
|
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||||
parse5-parser-stream: 7.1.2
|
parse5-parser-stream: 7.1.2
|
||||||
undici: 7.29.1
|
undici: 7.30.1
|
||||||
whatwg-mimetype: 4.0.0
|
whatwg-mimetype: 4.0.0
|
||||||
|
|
||||||
chokidar@5.0.0:
|
chokidar@5.0.0:
|
||||||
@@ -7850,7 +7830,7 @@ snapshots:
|
|||||||
'@wdio/logger': 10.1.0
|
'@wdio/logger': 10.1.0
|
||||||
'@zip.js/zip.js': 2.24.0
|
'@zip.js/zip.js': 2.24.0
|
||||||
decamelize: 6.0.1
|
decamelize: 6.0.1
|
||||||
undici: 8.11.2
|
undici: 8.12.3
|
||||||
|
|
||||||
electron-to-chromium@1.5.444: {}
|
electron-to-chromium@1.5.444: {}
|
||||||
|
|
||||||
@@ -8309,7 +8289,7 @@ snapshots:
|
|||||||
|
|
||||||
fnv1a52@1.0.0: {}
|
fnv1a52@1.0.0: {}
|
||||||
|
|
||||||
follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)):
|
follow-redirects@1.16.2(debug@4.4.3(supports-color@7.2.0)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
debug: 4.4.3(supports-color@7.2.0)
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
|
|
||||||
@@ -8351,7 +8331,7 @@ snapshots:
|
|||||||
'@zip.js/zip.js': 2.24.0
|
'@zip.js/zip.js': 2.24.0
|
||||||
decamelize: 6.0.1
|
decamelize: 6.0.1
|
||||||
modern-tar: 0.8.5
|
modern-tar: 0.8.5
|
||||||
undici: 8.11.2
|
undici: 8.12.3
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
@@ -8393,12 +8373,6 @@ snapshots:
|
|||||||
minipass: 7.1.3
|
minipass: 7.1.3
|
||||||
path-scurry: 2.0.2
|
path-scurry: 2.0.2
|
||||||
|
|
||||||
glob@14.0.3:
|
|
||||||
dependencies:
|
|
||||||
minimatch: 11.0.2
|
|
||||||
minipass: 7.1.3
|
|
||||||
path-scurry: 3.0.0
|
|
||||||
|
|
||||||
globals@17.12.0: {}
|
globals@17.12.0: {}
|
||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
@@ -9089,11 +9063,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.12
|
brace-expansion: 5.0.12
|
||||||
|
|
||||||
minimatch@11.0.2:
|
|
||||||
dependencies:
|
|
||||||
'@isaacs/nonrecursive-glob-match': 1.0.2
|
|
||||||
brace-expansion: 5.0.12
|
|
||||||
|
|
||||||
minipass@7.1.3: {}
|
minipass@7.1.3: {}
|
||||||
|
|
||||||
minizlib@3.1.0:
|
minizlib@3.1.0:
|
||||||
@@ -9294,11 +9263,6 @@ snapshots:
|
|||||||
lru-cache: 11.5.3
|
lru-cache: 11.5.3
|
||||||
minipass: 7.1.3
|
minipass: 7.1.3
|
||||||
|
|
||||||
path-scurry@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
lru-cache: 11.5.3
|
|
||||||
minipass: 7.1.3
|
|
||||||
|
|
||||||
path-type@4.0.0: {}
|
path-type@4.0.0: {}
|
||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
@@ -9314,7 +9278,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.19
|
nanoid: 3.3.19
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
@@ -9561,7 +9525,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
chokidar: 5.0.0
|
chokidar: 5.0.0
|
||||||
immutable: 5.1.9
|
immutable: 5.1.9
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@parcel/watcher': 2.6.0
|
'@parcel/watcher': 2.6.0
|
||||||
|
|
||||||
@@ -9599,7 +9563,7 @@ snapshots:
|
|||||||
dot-case: 3.0.4
|
dot-case: 3.0.4
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.2: {}
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
source-map-support@0.5.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9817,11 +9781,11 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@8.9.0: {}
|
undici-types@8.9.0: {}
|
||||||
|
|
||||||
undici@6.28.1: {}
|
undici@6.29.1: {}
|
||||||
|
|
||||||
undici@7.29.1: {}
|
undici@7.30.1: {}
|
||||||
|
|
||||||
undici@8.11.2: {}
|
undici@8.12.3: {}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
||||||
|
|
||||||
@@ -10022,7 +9986,7 @@ snapshots:
|
|||||||
'@wdio/utils': 10.1.0(supports-color@7.2.0)(yauzl@2.10.0)
|
'@wdio/utils': 10.1.0(supports-color@7.2.0)(yauzl@2.10.0)
|
||||||
deepmerge-ts: 8.0.2
|
deepmerge-ts: 8.0.2
|
||||||
https-proxy-agent: 9.1.0(supports-color@7.2.0)
|
https-proxy-agent: 9.1.0(supports-color@7.2.0)
|
||||||
undici: 8.11.2
|
undici: 8.12.3
|
||||||
ws: 8.22.1
|
ws: 8.22.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
|
|||||||
@@ -113,6 +113,27 @@
|
|||||||
"ghcr.io/devcontainers/features/rust"
|
"ghcr.io/devcontainers/features/rust"
|
||||||
],
|
],
|
||||||
"groupName": "Rust toolchain"
|
"groupName": "Rust toolchain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Update the Specta contract toolchain together",
|
||||||
|
"matchManagers": ["cargo"],
|
||||||
|
"matchPackageNames": ["specta", "tauri-specta", "specta-typescript"],
|
||||||
|
"groupName": "specta",
|
||||||
|
"groupSlug": "specta",
|
||||||
|
"prCreation": "immediate",
|
||||||
|
"automerge": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Keep Specta aligned with the supported tauri-specta release",
|
||||||
|
"matchManagers": ["cargo"],
|
||||||
|
"matchPackageNames": ["specta", "tauri-specta"],
|
||||||
|
"allowedVersions": "/^2\\.0\\.0-rc\\.26$/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Keep the TypeScript exporter compatible with Specta rc.26",
|
||||||
|
"matchManagers": ["cargo"],
|
||||||
|
"matchPackageNames": ["specta-typescript"],
|
||||||
|
"allowedVersions": "/^0\\.0\\.13$/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"customManagers": [
|
"customManagers": [
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import path from 'node:path'
|
|||||||
import zlib from 'node:zlib'
|
import zlib from 'node:zlib'
|
||||||
|
|
||||||
import AdmZip from 'adm-zip'
|
import AdmZip from 'adm-zip'
|
||||||
import { glob } from 'glob'
|
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent'
|
import { HttpsProxyAgent } from 'https-proxy-agent'
|
||||||
import { extract } from 'tar'
|
import { extract } from 'tar'
|
||||||
|
|
||||||
@@ -519,7 +518,7 @@ const resolveServicePermission = async () => {
|
|||||||
let hasChanges = false
|
let hasChanges = false
|
||||||
|
|
||||||
for (const f of serviceExecutables) {
|
for (const f of serviceExecutables) {
|
||||||
const files = glob.sync(path.join(SERVICE_DIR, f))
|
const files = fs.globSync(path.join(SERVICE_DIR, f))
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
const currentHash = await calculateFileHash(filePath)
|
const currentHash = await calculateFileHash(filePath)
|
||||||
|
|||||||
+50
-17
@@ -32,7 +32,9 @@ function is_valid_ip() {
|
|||||||
|
|
||||||
# Prints one value of a SystemConfiguration dictionary, or nothing when the key is absent.
|
# Prints one value of a SystemConfiguration dictionary, or nothing when the key is absent.
|
||||||
function sc_value() {
|
function sc_value() {
|
||||||
printf 'show %s\nquit\n' "$1" | scutil | sed -n "s/^ *$2 : //p"
|
local dictionary
|
||||||
|
dictionary=$(printf 'show %s\nquit\n' "$1" | scutil) || return 1
|
||||||
|
sed -n "s/^ *$2 : //p" <<<"$dictionary"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prints the service's DNS servers, or "empty" when none is set; fails on any other answer.
|
# Prints the service's DNS servers, or "empty" when none is set; fails on any other answer.
|
||||||
@@ -50,31 +52,62 @@ function dns_of() {
|
|||||||
echo "$dns"
|
echo "$dns"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $# -lt 1 ] && echo "Usage: $0 <IP address>" && exit 1
|
[ $# -lt 1 ] && echo "Usage: $0 <newline-separated DNS servers> [legacy state directory]" >&2 && exit 1
|
||||||
! is_valid_ip "$1" && echo "$1 is not a valid IP address." && exit 1
|
dns_servers=()
|
||||||
|
while read -r server; do
|
||||||
|
! is_valid_ip "$server" && echo "$server is not a valid IP address." >&2 && exit 1
|
||||||
|
dns_servers+=("$server")
|
||||||
|
done <<<"$1"
|
||||||
|
|
||||||
# Backups are keyed by service ID, which survives renaming the service and renumbering its interface.
|
# Backups are keyed by service ID, which survives renaming the service and renumbering its interface.
|
||||||
service_id=$(sc_value State:/Network/Global/IPv4 PrimaryService)
|
service_id=$(sc_value State:/Network/Global/IPv4 PrimaryService) || exit 1
|
||||||
hardware_port=$(sc_value "Setup:/Network/Service/$service_id" UserDefinedName)
|
hardware_port=$(sc_value "Setup:/Network/Service/$service_id" UserDefinedName) || exit 1
|
||||||
[ -z "$hardware_port" ] && echo "No primary network service." && exit 1
|
[ -z "$hardware_port" ] && echo "No primary network service; waiting for the network." >&2 && exit 75
|
||||||
|
|
||||||
original_dns=$(dns_of "$hardware_port") || { echo "Cannot read DNS servers of $hardware_port." >&2; exit 1; }
|
original_dns=$(dns_of "$hardware_port") || { echo "Cannot read DNS servers of $hardware_port." >&2; exit 1; }
|
||||||
|
|
||||||
backup=".original_dns/$service_id"
|
backup=".original_dns/$service_id"
|
||||||
mkdir -p .original_dns || exit 1
|
mkdir -p .original_dns || exit 1
|
||||||
# Older releases kept a single backup for whichever service was primary.
|
# Key old single-slot backups before import so a later network switch cannot reassign them.
|
||||||
if [ -f .original_dns.txt ]; then
|
for state_dir in "$PWD" "${2:-$PWD}"; do
|
||||||
if [ -f "$backup" ]; then
|
if [ -f "$state_dir/.original_dns.txt" ]; then
|
||||||
rm -f .original_dns.txt || exit 1
|
mkdir -p "$state_dir/.original_dns" || exit 1
|
||||||
else
|
if [ -f "$state_dir/.original_dns/$service_id" ]; then
|
||||||
mv .original_dns.txt "$backup" || exit 1
|
rm -f "$state_dir/.original_dns.txt" || exit 1
|
||||||
|
else
|
||||||
|
mv "$state_dir/.original_dns.txt" "$state_dir/.original_dns/$service_id" || exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
if [ ! -f "$backup" ] && [ -n "$2" ] && [ -f "$2/$backup" ]; then
|
||||||
|
cp "$2/$backup" ".original_dns/.$service_id" || exit 1
|
||||||
|
mv ".original_dns/.$service_id" "$backup" || exit 1
|
||||||
|
fi
|
||||||
|
backup_header='# clash-verge-dns-managed'
|
||||||
|
function save_backup() {
|
||||||
|
printf '%s\n%s\n' "$backup_header" "$1" >".original_dns/.$service_id" &&
|
||||||
|
mv ".original_dns/.$service_id" "$backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
saved_dns=
|
||||||
|
legacy_backup=false
|
||||||
|
if [ -f "$backup" ]; then
|
||||||
|
saved_dns=$(cat "$backup") || exit 1
|
||||||
|
case "$saved_dns" in
|
||||||
|
"$backup_header"$'\n'*) saved_dns=${saved_dns#*$'\n'} ;;
|
||||||
|
*) legacy_backup=true ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
# The placeholder is ours, not the user's: keep the backup taken before it was applied.
|
|
||||||
if [ "$original_dns" != "$1" ]; then
|
if [ "$original_dns" != "$1" ]; then
|
||||||
echo "$original_dns" >"$backup" || exit 1
|
# Only an unmarked historical backup owns the old single-address placeholder.
|
||||||
networksetup -setdnsservers "$hardware_port" "$1"
|
if [ "$original_dns" != "114.114.114.114" ] || [ "$legacy_backup" != true ]; then
|
||||||
|
save_backup "$original_dns" || exit 1
|
||||||
|
legacy_backup=false
|
||||||
|
fi
|
||||||
|
networksetup -setdnsservers "$hardware_port" "${dns_servers[@]}"
|
||||||
[ "$(dns_of "$hardware_port")" = "$1" ] || { echo "Failed to set DNS for $hardware_port." >&2; exit 1; }
|
[ "$(dns_of "$hardware_port")" = "$1" ] || { echo "Failed to set DNS for $hardware_port." >&2; exit 1; }
|
||||||
elif [ ! -f "$backup" ]; then
|
fi
|
||||||
echo "empty" >"$backup" || exit 1
|
# Keep legacy ownership until the new list is verified, including a retry after a failed marker write.
|
||||||
|
if [ "$legacy_backup" = true ]; then
|
||||||
|
save_backup "$saved_dns" || exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
+64
-35
@@ -1,9 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
[ $# -lt 1 ] && echo "Usage: $0 <placeholder IP address>" && exit 1
|
[ $# -lt 1 ] && echo "Usage: $0 <newline-separated DNS servers> [legacy state directory]" >&2 && exit 1
|
||||||
|
|
||||||
# Prints one value of a SystemConfiguration dictionary, or nothing when the key is absent.
|
# Prints one value of a SystemConfiguration dictionary, or nothing when the key is absent.
|
||||||
function sc_value() {
|
function sc_value() {
|
||||||
printf 'show %s\nquit\n' "$1" | scutil | sed -n "s/^ *$2 : //p"
|
local dictionary
|
||||||
|
dictionary=$(printf 'show %s\nquit\n' "$1" | scutil) || return 1
|
||||||
|
sed -n "s/^ *$2 : //p" <<<"$dictionary"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prints the service's DNS servers, or "empty" when none is set; fails on any other answer.
|
# Prints the service's DNS servers, or "empty" when none is set; fails on any other answer.
|
||||||
@@ -21,42 +23,69 @@ function dns_of() {
|
|||||||
echo "$dns"
|
echo "$dns"
|
||||||
}
|
}
|
||||||
|
|
||||||
result=0
|
backup_header='# clash-verge-dns-managed'
|
||||||
# Older releases kept a single backup for whichever service was primary.
|
completed_backups=()
|
||||||
if [ -f .original_dns.txt ]; then
|
|
||||||
service_id=$(sc_value State:/Network/Global/IPv4 PrimaryService)
|
|
||||||
if [ -z "$service_id" ] || ! mkdir -p .original_dns; then
|
|
||||||
result=1
|
|
||||||
elif [ -f ".original_dns/$service_id" ]; then
|
|
||||||
rm -f .original_dns.txt || result=1
|
|
||||||
elif ! mv .original_dns.txt ".original_dns/$service_id"; then
|
|
||||||
result=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
for backup in .original_dns/*; do
|
function restore_dns() {
|
||||||
[ -f "$backup" ] || continue
|
local result=0
|
||||||
hardware_port=$(sc_value "Setup:/Network/Service/${backup##*/}" UserDefinedName)
|
# Older releases kept a single backup for whichever service was primary.
|
||||||
# Kept for a service that is gone: there is nothing to write it back to.
|
if [ -f .original_dns.txt ]; then
|
||||||
[ -n "$hardware_port" ] || continue
|
service_id=$(sc_value State:/Network/Global/IPv4 PrimaryService)
|
||||||
if ! current_dns=$(dns_of "$hardware_port"); then
|
if [ -z "$service_id" ] || ! mkdir -p .original_dns; then
|
||||||
echo "Cannot read DNS servers of $hardware_port." >&2
|
result=1
|
||||||
result=1
|
elif [ -f ".original_dns/$service_id" ]; then
|
||||||
continue
|
rm -f .original_dns.txt || result=1
|
||||||
|
elif ! mv .original_dns.txt ".original_dns/$service_id"; then
|
||||||
|
result=1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
# A service no longer on the placeholder was changed by someone else, and that change stands.
|
|
||||||
if [ "$current_dns" = "$1" ]; then
|
for backup in .original_dns/*; do
|
||||||
original_dns=$(cat "$backup") || { result=1; continue; }
|
[ -f "$backup" ] || continue
|
||||||
# Older releases could back up the placeholder itself.
|
# Current backups take precedence even after their DNS has already been restored.
|
||||||
[ "$original_dns" = "$1" ] && original_dns=empty
|
if [ "$PWD" != "$state_dir" ] && [ -f "$state_dir/$backup" ]; then
|
||||||
networksetup -setdnsservers "$hardware_port" $original_dns
|
completed_backups+=("$PWD/$backup")
|
||||||
if [ "$(dns_of "$hardware_port")" != "$original_dns" ]; then
|
continue
|
||||||
echo "Failed to restore DNS for $hardware_port." >&2
|
fi
|
||||||
|
hardware_port=$(sc_value "Setup:/Network/Service/${backup##*/}" UserDefinedName) || { result=1; continue; }
|
||||||
|
# Kept for a service that is gone: there is nothing to write it back to.
|
||||||
|
[ -n "$hardware_port" ] || continue
|
||||||
|
if ! current_dns=$(dns_of "$hardware_port"); then
|
||||||
|
echo "Cannot read DNS servers of $hardware_port." >&2
|
||||||
result=1
|
result=1
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
fi
|
original_dns=$(cat "$backup") || { result=1; continue; }
|
||||||
rm -f "$backup" || result=1
|
legacy_backup=false
|
||||||
done
|
case "$original_dns" in
|
||||||
rmdir .original_dns 2>/dev/null
|
"$backup_header"$'\n'*) original_dns=${original_dns#*$'\n'} ;;
|
||||||
|
*) legacy_backup=true ;;
|
||||||
|
esac
|
||||||
|
# A service no longer on its recorded placeholder was changed by someone else.
|
||||||
|
if [ "$current_dns" = "$1" ] || { [ "$legacy_backup" = true ] && [ "$current_dns" = "114.114.114.114" ]; }; then
|
||||||
|
networksetup -setdnsservers "$hardware_port" $original_dns
|
||||||
|
if [ "$(dns_of "$hardware_port")" != "$original_dns" ]; then
|
||||||
|
echo "Failed to restore DNS for $hardware_port." >&2
|
||||||
|
result=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
completed_backups+=("$PWD/$backup")
|
||||||
|
done
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
result=0
|
||||||
|
state_dir=$PWD
|
||||||
|
restore_dns "$1" || result=1
|
||||||
|
if [ -n "$2" ] && [ "$2" != "$PWD" ]; then
|
||||||
|
{ cd "$2" && restore_dns "$1"; } || result=1
|
||||||
|
fi
|
||||||
|
# Remove superseded legacy backups first; a cleanup failure must leave current authority intact.
|
||||||
|
if [ "$result" -eq 0 ]; then
|
||||||
|
for ((index=${#completed_backups[@]}-1; index>=0; index--)); do
|
||||||
|
rm -f "${completed_backups[index]}" || { result=1; break; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
rmdir "$state_dir/.original_dns" "${2:-$state_dir}/.original_dns" 2>/dev/null
|
||||||
exit $result
|
exit $result
|
||||||
|
|||||||
+15
-20
@@ -29,7 +29,7 @@ perf-harness = ["dep:tauri-plugin-wdio-webdriver"]
|
|||||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.7.3", features = [] }
|
tauri-build = { version = "2.7.4", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clash-verge-draft = { workspace = true }
|
clash-verge-draft = { workspace = true }
|
||||||
@@ -55,11 +55,10 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_yaml_ng = { workspace = true }
|
serde_yaml_ng = { workspace = true }
|
||||||
smartstring = { workspace = true, features = ["serde"] }
|
smartstring = { workspace = true, features = ["serde"] }
|
||||||
bitflags = { workspace = true }
|
|
||||||
socket2 = { workspace = true }
|
socket2 = { workspace = true }
|
||||||
warp = { version = "0.4.3", features = ["server"] }
|
warp = { version = "0.4.3", features = ["server"] }
|
||||||
open = "5.4.4"
|
open = "5.4.5"
|
||||||
rfd = { version = "0.16.0", default-features = false, features = ["common-controls-v6"] }
|
rfd = { version = "0.17.2", default-features = false, features = ["common-controls-v6"] }
|
||||||
dirs = "7.0.0"
|
dirs = "7.0.0"
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
nanoid = "0.5"
|
nanoid = "0.5"
|
||||||
@@ -67,19 +66,17 @@ chrono = { workspace = true }
|
|||||||
boa_engine = "0.22.0"
|
boa_engine = "0.22.0"
|
||||||
percent-encoding = "2.3.2"
|
percent-encoding = "2.3.2"
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
regex = { workspace = true }
|
|
||||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs.git", branch = "main", features = [
|
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs.git", branch = "main", features = [
|
||||||
"guard",
|
"guard",
|
||||||
] }
|
] }
|
||||||
network-interface = { version = "2.0.5", features = ["serde"] }
|
network-interface = { version = "2.0.5", features = ["serde"] }
|
||||||
tauri-plugin-shell = "2.5.0"
|
tauri-plugin-shell = "2.6.0"
|
||||||
tauri-plugin-opener = "2.7.2"
|
tauri-plugin-opener = "2.7.2"
|
||||||
tauri-plugin-dialog = "2.8.3"
|
tauri-plugin-dialog = "2.8.4"
|
||||||
tauri-plugin-fs = "2.7.1"
|
tauri-plugin-fs = "2.7.1"
|
||||||
tauri-plugin-process = "2.4.0"
|
tauri-plugin-deep-link = "2.6.3"
|
||||||
tauri-plugin-deep-link = "2.6.2"
|
|
||||||
tauri-plugin-window-state = "2.5.0"
|
tauri-plugin-window-state = "2.5.0"
|
||||||
zip = "9.1.3"
|
zip = "9.3.0"
|
||||||
flate2 = "1.1.10"
|
flate2 = "1.1.10"
|
||||||
reqwest_dav = "0.3.3"
|
reqwest_dav = "0.3.3"
|
||||||
aes-gcm = { version = "0.11.1" }
|
aes-gcm = { version = "0.11.1" }
|
||||||
@@ -89,27 +86,26 @@ getrandom = "0.4.3"
|
|||||||
futures = "0.3.34"
|
futures = "0.3.34"
|
||||||
gethostname = "1.1.0"
|
gethostname = "1.1.0"
|
||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
tauri-plugin-notification = "2.7.0"
|
tauri-plugin-notification = "2.8.0"
|
||||||
tokio-stream = "0.1.19"
|
|
||||||
backon = { version = "1.6.0", features = ["tokio-sleep"] }
|
backon = { version = "1.6.0", features = ["tokio-sleep"] }
|
||||||
tauri-plugin-http = "2.8.3"
|
tauri-plugin-http = "2.8.3"
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tauri-plugin-devtools = { version = "2.2.2", optional = true }
|
tauri-plugin-devtools = { version = "2.2.3", optional = true }
|
||||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", rev = "7a03d16f4d39b1bb885b7babe66b9e153e34b615" }
|
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", rev = "7a03d16f4d39b1bb885b7babe66b9e153e34b615" }
|
||||||
async-trait = "0.1.92"
|
async-trait = "0.1.92"
|
||||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.7", version = "2.7.7", features = [
|
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.7", version = "2.7.7", features = [
|
||||||
"client",
|
"client",
|
||||||
] }
|
] }
|
||||||
arc-swap = "1.9.2"
|
arc-swap = "1.9.3"
|
||||||
tokio-rustls = "0.26.6"
|
tokio-rustls = "0.26.6"
|
||||||
rustls = { version = "0.23.46", features = ["ring"] }
|
rustls = { version = "0.23.46", features = ["ring"] }
|
||||||
webpki-roots = "1.0.9"
|
webpki-roots = "1.0.11"
|
||||||
dark-light = "3.0.0"
|
dark-light = "3.0.0"
|
||||||
bytes = "1.12.1"
|
bytes = "1.12.1"
|
||||||
tokio-util = { version = "0.7.20", features = ["time"] }
|
tokio-util = { version = "0.7.20", features = ["time"] }
|
||||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function", "serde_json"] }
|
specta = { version = "=2.0.0-rc.26", features = ["derive", "function", "serde_json"] }
|
||||||
tauri-specta = { version = "=2.0.0-rc.25", features = ["derive", "typescript"] }
|
tauri-specta = { version = "=2.0.0-rc.26", features = ["derive", "typescript"] }
|
||||||
specta-typescript = "=0.0.12"
|
specta-typescript = "=0.0.13"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2.191"
|
libc = "0.2.191"
|
||||||
@@ -119,7 +115,7 @@ libc = "0.2.191"
|
|||||||
clash-verge-signal = { workspace = true }
|
clash-verge-signal = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
tauri-plugin-wdio-webdriver = { version = "=1.5.0", optional = true }
|
tauri-plugin-wdio-webdriver = { version = "=1.5.1", optional = true }
|
||||||
objc2 = "0.6.5"
|
objc2 = "0.6.5"
|
||||||
objc2-foundation = { version = "0.3.2", features = [
|
objc2-foundation = { version = "0.3.2", features = [
|
||||||
"NSString",
|
"NSString",
|
||||||
@@ -156,7 +152,6 @@ tauri-plugin-global-shortcut = "2.4.1"
|
|||||||
tauri-plugin-updater = "2.14.1"
|
tauri-plugin-updater = "2.14.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true }
|
|
||||||
tokio = { workspace = true, features = ["test-util"] }
|
tokio = { workspace = true, features = ["test-util"] }
|
||||||
warp = { version = "0.4.3", features = ["test"] }
|
warp = { version = "0.4.3", features = ["test"] }
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,6 @@
|
|||||||
"global-shortcut:allow-register-all",
|
"global-shortcut:allow-register-all",
|
||||||
"global-shortcut:allow-unregister",
|
"global-shortcut:allow-unregister",
|
||||||
"global-shortcut:allow-unregister-all",
|
"global-shortcut:allow-unregister-all",
|
||||||
"process:allow-exit",
|
|
||||||
"clipboard-manager:allow-read-text",
|
"clipboard-manager:allow-read-text",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
"dialog:default"
|
"dialog:default"
|
||||||
|
|||||||
@@ -614,6 +614,53 @@ async fn select_node_with_timeout(group_name: &String, node: &String) -> Result<
|
|||||||
.with_context(|| format!("failed to select node [{node}] for group [{group_name}]"))
|
.with_context(|| format!("failed to select node [{node}] for group [{group_name}]"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mihomo lists these first in GLOBAL, so an unpicked GLOBAL starts on DIRECT.
|
||||||
|
const GLOBAL_BUILTINS: [&str; 2] = ["DIRECT", "REJECT"];
|
||||||
|
|
||||||
|
/// GLOBAL's first visible group, else its first node, while GLOBAL is still on a built-in.
|
||||||
|
fn global_exit_target(proxies: &Proxies) -> Option<&str> {
|
||||||
|
let global = proxies.proxies.get("GLOBAL")?;
|
||||||
|
if !global.now.as_deref().is_some_and(|now| GLOBAL_BUILTINS.contains(&now)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let members = global.all.as_deref()?;
|
||||||
|
let is_visible_group = |name: &str| {
|
||||||
|
proxies.proxies.get(name).is_some_and(|proxy| {
|
||||||
|
proxy.hidden != Some(true)
|
||||||
|
&& matches!(
|
||||||
|
proxy.proxy_type,
|
||||||
|
ProxyType::Selector | ProxyType::URLTest | ProxyType::Fallback | ProxyType::LoadBalance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
members
|
||||||
|
.iter()
|
||||||
|
.find(|name| is_visible_group(name))
|
||||||
|
.or_else(|| members.iter().find(|name| !GLOBAL_BUILTINS.contains(&name.as_str())))
|
||||||
|
.map(|name| name.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves GLOBAL off a built-in unless the profile records any GLOBAL choice.
|
||||||
|
pub(crate) async fn leave_default_global_exit() -> Result<()> {
|
||||||
|
let profiles = Config::profiles().await.data_arc();
|
||||||
|
let has_record = profiles
|
||||||
|
.current
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|current| profiles.get_item(current).ok())
|
||||||
|
.and_then(|item| item.selected.as_deref())
|
||||||
|
.is_some_and(|selected| selected.iter().any(|item| item.name.as_deref() == Some("GLOBAL")));
|
||||||
|
if has_record {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let snapshot = fetch_proxies_with_timeout().await?;
|
||||||
|
let Some(target) = global_exit_target(&snapshot) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
select_node_with_timeout(&String::from("GLOBAL"), &String::from(target)).await?;
|
||||||
|
logging!(info, Type::Config, "moved GLOBAL off its built-in default to {target}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn remaining_activations(
|
fn remaining_activations(
|
||||||
activations: &[(String, String)],
|
activations: &[(String, String)],
|
||||||
completed: &HashMap<String, String>,
|
completed: &HashMap<String, String>,
|
||||||
@@ -1019,6 +1066,12 @@ fn activate_selected_nodes_with(repair: SelectionRepair) -> tokio::sync::oneshot
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if Config::clash().await.latest_arc().get_mode().as_deref() == Some("global")
|
||||||
|
&& let Err(err) = leave_default_global_exit().await
|
||||||
|
{
|
||||||
|
logging!(warn, Type::Config, "failed to move GLOBAL off its default: {err:#}");
|
||||||
|
}
|
||||||
|
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
if is_activation_current(generation) {
|
if is_activation_current(generation) {
|
||||||
announce(Refresh::Clash);
|
announce(Refresh::Clash);
|
||||||
|
|||||||
@@ -559,12 +559,13 @@ impl CoreManager {
|
|||||||
})?;
|
})?;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
crate::utils::resolve::dns::sync_public_dns().await;
|
crate::utils::resolve::dns::sync_public_dns().await;
|
||||||
// At login the app usually beats the network. Leave the write to the network watcher
|
// Only automatic restores can wait for the watcher; explicit requests must report failure.
|
||||||
// rather than fail — only if the watcher is actually live; a user toggling while
|
|
||||||
// offline still fails fast.
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if crate::core::network_watch::is_armed()
|
let defer_until_network = crate::core::network_watch::is_armed()
|
||||||
&& !crate::utils::resolve::is_resolve_done()
|
&& crate::core::notification::what_was_asked()
|
||||||
|
== crate::core::notification::FailedOperation::SystemProxyRestore;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
if defer_until_network
|
||||||
&& Config::verge()
|
&& Config::verge()
|
||||||
.await
|
.await
|
||||||
.latest_arc()
|
.latest_arc()
|
||||||
@@ -579,7 +580,20 @@ impl CoreManager {
|
|||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
proxy_control::apply().await?;
|
proxy_control::apply().await?;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
if let Err(error) = proxy_control::apply().await {
|
||||||
|
if defer_until_network && proxy_control::is_missing_network_service_failure(&error) {
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Core,
|
||||||
|
"network service disappeared; the system proxy waits for its return"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
if !expectation.is_valid(
|
if !expectation.is_valid(
|
||||||
self.get_running_mode().as_ref(),
|
self.get_running_mode().as_ref(),
|
||||||
self.current_core_readiness_generation(),
|
self.current_core_readiness_generation(),
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ async fn reapply() {
|
|||||||
.enable_system_proxy
|
.enable_system_proxy
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
|
crate::utils::resolve::dns::sync_public_dns().await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !proxy_control::has_network_service().await {
|
if !proxy_control::has_network_service().await {
|
||||||
|
|||||||
@@ -180,6 +180,14 @@ pub const fn is_missing_network_service(_error: &sysproxy::Error) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_missing_network_service_failure(error: &anyhow::Error) -> bool {
|
||||||
|
!SystemProxyStateUnknown::is_in(error)
|
||||||
|
&& SysproxyFailure::from_chain(error).is_none()
|
||||||
|
&& error
|
||||||
|
.downcast_ref::<sysproxy::Error>()
|
||||||
|
.is_some_and(is_missing_network_service)
|
||||||
|
}
|
||||||
|
|
||||||
/// Distinguish a misplaced sidecar from a missing privileged service.
|
/// Distinguish a misplaced sidecar from a missing privileged service.
|
||||||
async fn classify_local_apply_failure(error: anyhow::Error) -> anyhow::Error {
|
async fn classify_local_apply_failure(error: anyhow::Error) -> anyhow::Error {
|
||||||
if SysproxyFailure::from_chain(&error).is_some() || !was_refused_locally(&error) {
|
if SysproxyFailure::from_chain(&error).is_some() || !was_refused_locally(&error) {
|
||||||
@@ -359,6 +367,13 @@ fn service_apply_result(requested: &MacosProxyConfig, outcome: ProxyApplyOutcome
|
|||||||
ProxyApplyOutcome::Applied | ProxyApplyOutcome::NotRequested => Ok(()),
|
ProxyApplyOutcome::Applied | ProxyApplyOutcome::NotRequested => Ok(()),
|
||||||
// Direct fallback satisfies a disable request.
|
// Direct fallback satisfies a disable request.
|
||||||
ProxyApplyOutcome::DirectFallback { .. } if matches!(requested, MacosProxyConfig::Disabled) => Ok(()),
|
ProxyApplyOutcome::DirectFallback { .. } if matches!(requested, MacosProxyConfig::Disabled) => Ok(()),
|
||||||
|
// Older helpers encode a failed service lookup as fallback even when no OS write occurred.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
ProxyApplyOutcome::DirectFallback { message }
|
||||||
|
if message == sysproxy::Error::NoActiveNetworkService.to_string() =>
|
||||||
|
{
|
||||||
|
Err(sysproxy::Error::NoActiveNetworkService.into())
|
||||||
|
}
|
||||||
ProxyApplyOutcome::DirectFallback { message } => {
|
ProxyApplyOutcome::DirectFallback { message } => {
|
||||||
Err(SysproxyFailure::DirectFallback { detail: message }.into())
|
Err(SysproxyFailure::DirectFallback { detail: message }.into())
|
||||||
}
|
}
|
||||||
@@ -609,6 +624,10 @@ pub async fn refresh_guard() -> Result<()> {
|
|||||||
logging!(debug, Type::Core, "proxy guard superseded (generation {generation})");
|
logging!(debug, Type::Core, "proxy guard superseded (generation {generation})");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Err(error) if is_missing_network_service_failure(&error) => {
|
||||||
|
consecutive_failures = 0;
|
||||||
|
logging!(debug, Type::Core, "system proxy guard waits for a network service");
|
||||||
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
consecutive_failures += 1;
|
consecutive_failures += 1;
|
||||||
logging!(
|
logging!(
|
||||||
@@ -1110,10 +1129,11 @@ mod tests {
|
|||||||
.err()
|
.err()
|
||||||
.unwrap_or_else(|| anyhow::anyhow!("an enable that never reached the OS must fail"));
|
.unwrap_or_else(|| anyhow::anyhow!("an enable that never reached the OS must fail"));
|
||||||
|
|
||||||
assert_eq!(
|
assert!(super::is_missing_network_service_failure(&error));
|
||||||
SysproxyFailure::from_chain(&error).map(SysproxyFailure::code),
|
assert!(SysproxyFailure::from_chain(&error).is_none());
|
||||||
Some("SYSPROXY_DIRECT_FALLBACK")
|
assert!(!super::is_missing_network_service_failure(
|
||||||
);
|
&error.context(SystemProxyStateUnknown)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -1970,7 +1970,7 @@ impl ServiceManager {
|
|||||||
|
|
||||||
async fn run_operation(&self, operation: impl Future<Output = Result<()>>) -> Result<()> {
|
async fn run_operation(&self, operation: impl Future<Output = Result<()>>) -> Result<()> {
|
||||||
run_operation_and_then(&RUN_STATE, operation, || async {
|
run_operation_and_then(&RUN_STATE, operation, || async {
|
||||||
if let Err(error) = Tray::global().update_menu().await {
|
if let Err(error) = Tray::global().update_part().await {
|
||||||
logging!(
|
logging!(
|
||||||
warn,
|
warn,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clash_verge_logging::{Type, logging, logging_error};
|
use clash_verge_logging::{Type, logging, logging_error};
|
||||||
|
use futures::StreamExt as _;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -21,7 +22,6 @@ use tokio::{
|
|||||||
sync::mpsc,
|
sync::mpsc,
|
||||||
time::{sleep, timeout},
|
time::{sleep, timeout},
|
||||||
};
|
};
|
||||||
use tokio_stream::StreamExt as _;
|
|
||||||
use tokio_util::time::{DelayQueue, delay_queue::Key};
|
use tokio_util::time::{DelayQueue, delay_queue::Key};
|
||||||
|
|
||||||
enum TimerCommand {
|
enum TimerCommand {
|
||||||
@@ -501,6 +501,7 @@ impl Timer {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{TaskSchedule, Timer, run_timer_profile_update_transition};
|
use super::{TaskSchedule, Timer, run_timer_profile_update_transition};
|
||||||
use crate::config::{PrfItem, PrfOption};
|
use crate::config::{PrfItem, PrfOption};
|
||||||
|
use futures::StreamExt as _;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -513,7 +514,6 @@ mod tests {
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::sync::Barrier;
|
use tokio::sync::Barrier;
|
||||||
use tokio_stream::StreamExt as _;
|
|
||||||
use tokio_util::time::DelayQueue;
|
use tokio_util::time::DelayQueue;
|
||||||
|
|
||||||
struct CancellationProbe {
|
struct CancellationProbe {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use nanoid::nanoid;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
fmt,
|
fmt,
|
||||||
hash::{DefaultHasher, Hash as _, Hasher as _},
|
hash::{DefaultHasher, Hash as _, Hasher as _},
|
||||||
|
path::Path,
|
||||||
sync::LazyLock,
|
sync::LazyLock,
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
};
|
};
|
||||||
@@ -329,7 +332,7 @@ impl CoreConfigValidator {
|
|||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(warn, Type::Validate, "无法确定文件类型: {}, 错误: {}", config_path, err);
|
logging!(warn, Type::Validate, "无法确定文件类型: {}, 错误: {}", config_path, err);
|
||||||
return Self::validate_config_internal_outcome(config_path).await;
|
return Self::validate_profile_yaml_outcome(config_path).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -344,7 +347,33 @@ impl CoreConfigValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logging!(info, Type::Validate, "使用Clash内核验证配置文件: {}", config_path);
|
logging!(info, Type::Validate, "使用Clash内核验证配置文件: {}", config_path);
|
||||||
Self::validate_config_internal_outcome(config_path).await
|
Self::validate_profile_yaml_outcome(config_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The user file is saved as written, so the core checks a copy carrying the tolerated short-ids.
|
||||||
|
async fn validate_profile_yaml_outcome(config_path: &str) -> Result<ValidationOutcome> {
|
||||||
|
let Some(tolerant) = Self::short_id_tolerant_text(config_path).await else {
|
||||||
|
return Self::validate_config_internal_outcome(config_path).await;
|
||||||
|
};
|
||||||
|
let Ok(app_dir) = dirs::app_home_dir() else {
|
||||||
|
return Self::validate_config_internal_outcome(config_path).await;
|
||||||
|
};
|
||||||
|
let check_path = app_dir.join(format!("short-id-check-{}.yaml", nanoid!()));
|
||||||
|
defer! {
|
||||||
|
let _ = std::fs::remove_file(&check_path);
|
||||||
|
}
|
||||||
|
if help::save_yaml_str(&check_path, &tolerant).await.is_err() {
|
||||||
|
return Self::validate_config_internal_outcome(config_path).await;
|
||||||
|
}
|
||||||
|
Self::validate_config_internal_outcome(dirs::path_to_str(&check_path)?).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn short_id_tolerant_text(config_path: &str) -> Option<std::string::String> {
|
||||||
|
let yaml = fs::read_to_string(config_path).await.ok()?;
|
||||||
|
match help::short_id_tolerant(&yaml, Path::new(config_path)) {
|
||||||
|
Ok((Cow::Owned(tolerant), _)) => Some(tolerant),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn validate_config_internal_outcome(config_path: &str) -> Result<ValidationOutcome> {
|
async fn validate_config_internal_outcome(config_path: &str) -> Result<ValidationOutcome> {
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ use crate::{
|
|||||||
config::PrfItem,
|
config::PrfItem,
|
||||||
utils::{dirs, help},
|
utils::{dirs, help},
|
||||||
};
|
};
|
||||||
|
use clash_verge_logging::{Type, logging};
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
|
use std::{borrow::Cow, path::Path};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -48,7 +50,8 @@ impl AsyncChainItemFrom for Option<ChainItem> {
|
|||||||
data: ChainType::Merge(help::read_mapping(&path).await.ok()?),
|
data: ChainType::Merge(help::read_mapping(&path).await.ok()?),
|
||||||
}),
|
}),
|
||||||
"rules" | "proxies" | "groups" => {
|
"rules" | "proxies" | "groups" => {
|
||||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
let yaml = fs::read_to_string(&path).await.ok()?;
|
||||||
|
let seq_map = parse_seq_map(&yaml, &path)?;
|
||||||
let data = match itype {
|
let data = match itype {
|
||||||
"rules" => ChainType::Rules(seq_map),
|
"rules" => ChainType::Rules(seq_map),
|
||||||
"proxies" => ChainType::Proxies(seq_map),
|
"proxies" => ChainType::Proxies(seq_map),
|
||||||
@@ -60,3 +63,51 @@ impl AsyncChainItemFrom for Option<ChainItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Typed parsing reads `delete: [1234]` as strings where a `Value` round trip would reject it.
|
||||||
|
fn parse_seq_map(yaml: &str, path: &Path) -> Option<SeqMap> {
|
||||||
|
let original = serde_yaml_ng::from_str::<SeqMap>(yaml).ok()?;
|
||||||
|
let Ok((Cow::Owned(tolerant), _)) = help::short_id_tolerant(yaml, path) else {
|
||||||
|
return Some(original);
|
||||||
|
};
|
||||||
|
match serde_yaml_ng::from_str::<SeqMap>(&tolerant) {
|
||||||
|
Ok(seq_map) => Some(seq_map),
|
||||||
|
Err(_) => {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
"kept numeric short-id in {}: quoted text does not load",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
Some(original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::expect_used, reason = "tests assert by panicking")]
|
||||||
|
mod tests {
|
||||||
|
use super::parse_seq_map;
|
||||||
|
use serde_yaml_ng::Value;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seq_short_id_keeps_typed_parse() {
|
||||||
|
for (input, delete) in [
|
||||||
|
("prepend: []\nappend: []\ndelete: [1234]\n", "1234"),
|
||||||
|
("prepend: []\nappend: []\ndelete: [true]\n", "true"),
|
||||||
|
] {
|
||||||
|
let seq_map = parse_seq_map(input, Path::new("b8.yaml")).expect("seq file should load");
|
||||||
|
assert_eq!(seq_map.delete, [delete]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = "prepend:\n - name: r\n reality-opts:\n short-id: 1234e123\nappend: []\ndelete: []\n";
|
||||||
|
let seq_map = parse_seq_map(input, Path::new("seq.yaml")).expect("seq file should load");
|
||||||
|
let short_id = seq_map
|
||||||
|
.prepend
|
||||||
|
.first()
|
||||||
|
.and_then(|proxy| proxy.get("reality-opts"))
|
||||||
|
.and_then(|opts| opts.get("short-id"));
|
||||||
|
assert_eq!(short_id, Some(&Value::String("1234e123".into())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ pub async fn change_clash_mode(mode: String) -> Result<(), String> {
|
|||||||
return Err(err.to_string().into());
|
return Err(err.to_string().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mode.as_str() == "global"
|
||||||
|
&& let Err(err) = crate::config::profiles::leave_default_global_exit().await
|
||||||
|
{
|
||||||
|
logging!(warn, Type::Core, "failed to move GLOBAL off its default: {err:#}");
|
||||||
|
}
|
||||||
|
|
||||||
let clash = Config::clash().await;
|
let clash = Config::clash().await;
|
||||||
let save_result = {
|
let save_result = {
|
||||||
let _config_write = Config::lock_config_write().await;
|
let _config_write = Config::lock_config_write().await;
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ mod app_init {
|
|||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_process::init())
|
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
|||||||
+285
-2
@@ -7,6 +7,8 @@ use scopeguard::{ScopeGuard, guard};
|
|||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use serde_yaml_ng::{Mapping, Value};
|
use serde_yaml_ng::{Mapping, Value};
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
@@ -45,8 +47,8 @@ pub async fn read_mapping(path: &Path) -> Result<Mapping> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_mapping(yaml_str: String, path: PathBuf) -> Result<Mapping> {
|
fn parse_mapping(yaml_str: String, path: PathBuf) -> Result<Mapping> {
|
||||||
match serde_yaml_ng::from_str::<serde_yaml_ng::Value>(&yaml_str) {
|
match short_id_tolerant(&yaml_str, &path) {
|
||||||
Ok(mut val) => {
|
Ok((_, mut val)) => {
|
||||||
val.apply_merge()
|
val.apply_merge()
|
||||||
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
|
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
|
||||||
|
|
||||||
@@ -66,6 +68,150 @@ fn parse_mapping(yaml_str: String, path: PathBuf) -> Result<Mapping> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SHORT_ID_KEY: &str = "short-id:";
|
||||||
|
|
||||||
|
/// A float like `1234e123` loses its hex text, so quote short-ids before parsing and keep that only if nothing else changed.
|
||||||
|
pub(crate) fn short_id_tolerant<'a>(yaml: &'a str, path: &Path) -> serde_yaml_ng::Result<(Cow<'a, str>, Value)> {
|
||||||
|
let original = serde_yaml_ng::from_str::<Value>(yaml)?;
|
||||||
|
if !has_numeric_short_id(&original) {
|
||||||
|
return Ok((Cow::Borrowed(yaml), original));
|
||||||
|
}
|
||||||
|
let quoted = quote_plain_short_ids(yaml);
|
||||||
|
if matches!(quoted, Cow::Borrowed(_)) {
|
||||||
|
return Ok((quoted, original));
|
||||||
|
}
|
||||||
|
|
||||||
|
match serde_yaml_ng::from_str::<Value>("ed) {
|
||||||
|
Ok(tolerant) if same_except_short_id_text(&original, &tolerant) => Ok((quoted, tolerant)),
|
||||||
|
_ => {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
"kept numeric short-id in {}: quoting it would change other values",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
Ok((Cow::Borrowed(yaml), original))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_numeric_short_id(value: &Value) -> bool {
|
||||||
|
match value {
|
||||||
|
Value::Mapping(map) => map
|
||||||
|
.iter()
|
||||||
|
.any(|(key, value)| (is_short_id_key(key) && value.is_number()) || has_numeric_short_id(value)),
|
||||||
|
Value::Sequence(seq) => seq.iter().any(has_numeric_short_id),
|
||||||
|
Value::Tagged(tagged) => has_numeric_short_id(&tagged.value),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same_except_short_id_text(original: &Value, tolerant: &Value) -> bool {
|
||||||
|
match (original, tolerant) {
|
||||||
|
(Value::Mapping(original), Value::Mapping(tolerant)) => {
|
||||||
|
original.len() == tolerant.len()
|
||||||
|
&& original
|
||||||
|
.iter()
|
||||||
|
.zip(tolerant.iter())
|
||||||
|
.all(|((key, old), (new_key, new))| {
|
||||||
|
key == new_key && (same_except_short_id_text(old, new) || is_short_id_text(key, old, new))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Value::Sequence(original), Value::Sequence(tolerant)) => {
|
||||||
|
original.len() == tolerant.len()
|
||||||
|
&& original
|
||||||
|
.iter()
|
||||||
|
.zip(tolerant.iter())
|
||||||
|
.all(|(old, new)| same_except_short_id_text(old, new))
|
||||||
|
}
|
||||||
|
(Value::Tagged(original), Value::Tagged(tolerant)) => {
|
||||||
|
original.tag == tolerant.tag && same_except_short_id_text(&original.value, &tolerant.value)
|
||||||
|
}
|
||||||
|
_ => original == tolerant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_short_id_text(key: &Value, old: &Value, new: &Value) -> bool {
|
||||||
|
is_short_id_key(key)
|
||||||
|
&& old.is_number()
|
||||||
|
&& matches!(new, Value::String(text) if serde_yaml_ng::from_str::<Value>(text).ok().as_ref() == Some(old))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_short_id_key(key: &Value) -> bool {
|
||||||
|
key.as_str() == Some("short-id")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quote_plain_short_ids(yaml: &str) -> Cow<'_, str> {
|
||||||
|
let mut quoted = String::new();
|
||||||
|
let mut copied = 0;
|
||||||
|
for (key, _) in yaml.match_indices(SHORT_ID_KEY) {
|
||||||
|
let Some(value) = plain_short_id_at(yaml, key) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let (Some(head), Some(id)) = (yaml.get(copied..value.start), yaml.get(value.clone())) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
quoted.push_str(head);
|
||||||
|
quoted.push('"');
|
||||||
|
quoted.push_str(id);
|
||||||
|
quoted.push('"');
|
||||||
|
copied = value.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if copied == 0 {
|
||||||
|
return Cow::Borrowed(yaml);
|
||||||
|
}
|
||||||
|
quoted.push_str(yaml.get(copied..).unwrap_or_default());
|
||||||
|
Cow::Owned(quoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plain_short_id_at(yaml: &str, key: usize) -> Option<Range<usize>> {
|
||||||
|
let bytes = yaml.as_bytes();
|
||||||
|
let before = bytes.get(..key)?;
|
||||||
|
let flow = matches!(
|
||||||
|
before.iter().rev().find(|byte| !byte.is_ascii_whitespace()),
|
||||||
|
Some(b'{' | b',')
|
||||||
|
);
|
||||||
|
let line_start = before
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.take_while(|&&byte| byte != b'\n')
|
||||||
|
.all(|&byte| is_blank(byte));
|
||||||
|
if !flow && !line_start {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = key + SHORT_ID_KEY.len();
|
||||||
|
let gap = bytes.get(start..)?.iter().take_while(|&&byte| is_blank(byte)).count();
|
||||||
|
if gap == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let start = start + gap;
|
||||||
|
let len = bytes
|
||||||
|
.get(start..)?
|
||||||
|
.iter()
|
||||||
|
.take_while(|byte| byte.is_ascii_hexdigit())
|
||||||
|
.count();
|
||||||
|
if len == 0 || !ends_plain_short_id(bytes.get(start + len..)?, flow) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(start..start + len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ends_plain_short_id(rest: &[u8], flow: bool) -> bool {
|
||||||
|
let blanks = rest.iter().take_while(|&&byte| is_blank(byte)).count();
|
||||||
|
match rest.get(blanks..) {
|
||||||
|
Some([] | [b'\n', ..] | [b'\r', b'\n', ..]) => true,
|
||||||
|
Some([b'#', ..]) => blanks > 0,
|
||||||
|
Some([b',' | b'}', ..]) => flow,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn is_blank(byte: u8) -> bool {
|
||||||
|
matches!(byte, b' ' | b'\t')
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn save_yaml<T: Serialize + Sync>(path: &Path, data: &T, prefix: Option<&str>) -> Result<()> {
|
pub async fn save_yaml<T: Serialize + Sync>(path: &Path, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||||
let data_str = with_encryption(|| async { serde_yaml_ng::to_string(data) }).await?;
|
let data_str = with_encryption(|| async { serde_yaml_ng::to_string(data) }).await?;
|
||||||
|
|
||||||
@@ -319,3 +465,140 @@ pub fn snapshot_path(original_path: &Path) -> Result<PathBuf> {
|
|||||||
|
|
||||||
Ok(temp_path)
|
Ok(temp_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::expect_used, reason = "tests assert by panicking")]
|
||||||
|
mod tests {
|
||||||
|
use super::{has_numeric_short_id, parse_mapping, quote_plain_short_ids, short_id_tolerant};
|
||||||
|
use serde_yaml_ng::Value;
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quotes_plain_short_ids() {
|
||||||
|
let cases = [
|
||||||
|
("short-id: 1234e123", "short-id: \"1234e123\""),
|
||||||
|
("short-id: 0e5", "short-id: \"0e5\""),
|
||||||
|
("short-id: 12345678", "short-id: \"12345678\""),
|
||||||
|
("short-id: 6ba85179", "short-id: \"6ba85179\""),
|
||||||
|
("short-id: \"1234e123\"", "short-id: \"1234e123\""),
|
||||||
|
("short-id: '1234e123'", "short-id: '1234e123'"),
|
||||||
|
("short-id: \"\"", "short-id: \"\""),
|
||||||
|
("short-id:\npublic-key: k", "short-id:\npublic-key: k"),
|
||||||
|
("short-id: null", "short-id: null"),
|
||||||
|
("short-id: ~", "short-id: ~"),
|
||||||
|
("short-id: 1234e123 # note", "short-id: \"1234e123\" # note"),
|
||||||
|
("# short-id: 1234e123", "# short-id: 1234e123"),
|
||||||
|
(
|
||||||
|
"reality-opts: {public-key: k, short-id: 1234e123}",
|
||||||
|
"reality-opts: {public-key: k, short-id: \"1234e123\"}",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"reality-opts: {short-id: 1234e123, public-key: k}",
|
||||||
|
"reality-opts: {short-id: \"1234e123\", public-key: k}",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{\n public-key: k,\n short-id: 1234e123\n}",
|
||||||
|
"{\n public-key: k,\n short-id: \"1234e123\"\n}",
|
||||||
|
),
|
||||||
|
("short-id: &sid 1234e123", "short-id: &sid 1234e123"),
|
||||||
|
("short-id: !!str 1234e123", "short-id: !!str 1234e123"),
|
||||||
|
("short-id: abc,def", "short-id: abc,def"),
|
||||||
|
("short-id: 1234e123\r\n", "short-id: \"1234e123\"\r\n"),
|
||||||
|
("my-short-id: 1234e123", "my-short-id: 1234e123"),
|
||||||
|
("xshort-id: 1", "xshort-id: 1"),
|
||||||
|
("short-id:1234e123", "short-id:1234e123"),
|
||||||
|
("short-id: 1.5e3", "short-id: 1.5e3"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected) in cases {
|
||||||
|
let output = quote_plain_short_ids(input);
|
||||||
|
assert_eq!(output, expected, "input: {input:?}");
|
||||||
|
assert_eq!(
|
||||||
|
matches!(output, Cow::Borrowed(_)),
|
||||||
|
input == expected,
|
||||||
|
"input: {input:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(quote_plain_short_ids(&output), expected, "rerun: {input:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_short_id_survives_profile_parse() {
|
||||||
|
let profile = "\
|
||||||
|
proxies:
|
||||||
|
- name: r
|
||||||
|
type: vless
|
||||||
|
reality-opts:
|
||||||
|
public-key: SFv3pZQdZ1fpnNg9yI2RwXKqAOz4mZHRbT2Wq4kz8Ws
|
||||||
|
short-id: 1234e123
|
||||||
|
";
|
||||||
|
let mapping = parse_mapping(profile.into(), PathBuf::from("profile.yaml")).expect("profile should parse");
|
||||||
|
let short_id = mapping
|
||||||
|
.get("proxies")
|
||||||
|
.and_then(|proxies| proxies.get(0))
|
||||||
|
.and_then(|proxy| proxy.get("reality-opts"))
|
||||||
|
.and_then(|opts| opts.get("short-id"));
|
||||||
|
assert_eq!(short_id, Some(&Value::String("1234e123".into())));
|
||||||
|
|
||||||
|
let output = serde_yaml_ng::to_string(&mapping).expect("mapping should serialize");
|
||||||
|
assert!(output.contains("short-id: '1234e123'"), "{output}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_id_rewrite_falls_back_to_original_parse() {
|
||||||
|
// (case, input, has a numeric short-id today)
|
||||||
|
let cases = [
|
||||||
|
("B2", "proxies:\n - name: \"node, short-id: 1234e123}\"\n", false),
|
||||||
|
(
|
||||||
|
"B3",
|
||||||
|
"proxies:\n - name: \"node, short-id: 1234e123}\"\n - name: r\n reality-opts:\n short-id: 1234e123\n",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"B4",
|
||||||
|
"payload: |\n short-id: 1234e123\nreality-opts:\n short-id: 1234e123\n",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
("B5", "reality-opts:\n short-id: 1234e123\n more\n", false),
|
||||||
|
("B6", "reality-opts: {public-key: k, # c\n short-id: 1234e123}\n", true),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (case, input, numeric) in cases {
|
||||||
|
let today = serde_yaml_ng::from_str::<Value>(input).expect("input should parse");
|
||||||
|
assert_eq!(has_numeric_short_id(&today), numeric, "{case}");
|
||||||
|
let (text, value) = short_id_tolerant(input, Path::new("b.yaml")).expect("input should parse");
|
||||||
|
assert!(matches!(text, Cow::Borrowed(_)), "{case}");
|
||||||
|
assert_eq!(value, today, "{case}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_id_rewrite_keeps_every_numeric_short_id() {
|
||||||
|
let input = "\
|
||||||
|
proxies:
|
||||||
|
- name: a
|
||||||
|
reality-opts:
|
||||||
|
short-id: 1234e123
|
||||||
|
- {name: b, reality-opts: {short-id: 0e5}}
|
||||||
|
";
|
||||||
|
let (text, value) = short_id_tolerant(input, Path::new("b7.yaml")).expect("input should parse");
|
||||||
|
assert!(matches!(text, Cow::Owned(_)));
|
||||||
|
let short_ids: Vec<_> = value
|
||||||
|
.get("proxies")
|
||||||
|
.and_then(Value::as_sequence)
|
||||||
|
.expect("proxies should be a sequence")
|
||||||
|
.iter()
|
||||||
|
.map(|proxy| proxy.get("reality-opts").and_then(|opts| opts.get("short-id")).cloned())
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
short_ids,
|
||||||
|
[
|
||||||
|
Some(Value::String("1234e123".into())),
|
||||||
|
Some(Value::String("0e5".into()))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ use serde_yaml_ng::{Mapping, Value};
|
|||||||
use std::{
|
use std::{
|
||||||
io::Read as _,
|
io::Read as _,
|
||||||
os::unix::process::CommandExt as _,
|
os::unix::process::CommandExt as _,
|
||||||
path::{Path, PathBuf},
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
const DNS_STATE_FILE: &str = ".original_dns.txt";
|
|
||||||
const DNS_BACKUP_DIR: &str = ".original_dns";
|
|
||||||
// Any address routed into TUN works, since dns-hijack answers it; the value only matters if left behind.
|
// Any address routed into TUN works, since dns-hijack answers it; the value only matters if left behind.
|
||||||
const PLACEHOLDER_DNS: &str = "114.114.114.114";
|
const PLACEHOLDER_DNS: &str = "114.114.114.114\n223.5.5.5\n1.1.1.1";
|
||||||
// Scripts run under lifecycle_lock, and networksetup waits for other preference writers without a limit.
|
// Scripts run under lifecycle_lock, and networksetup waits for other preference writers without a limit.
|
||||||
pub(crate) const DNS_SCRIPT_TIMEOUT: Duration = Duration::from_secs(5);
|
pub(crate) const DNS_SCRIPT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
@@ -26,14 +24,6 @@ fn dns_state_dir() -> anyhow::Result<PathBuf> {
|
|||||||
Ok(dir)
|
Ok(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_dns_state_dir(resource_dir: &Path, state_dir: PathBuf) -> PathBuf {
|
|
||||||
if resource_dir.join(DNS_STATE_FILE).exists() || resource_dir.join(DNS_BACKUP_DIR).exists() {
|
|
||||||
resource_dir.to_path_buf()
|
|
||||||
} else {
|
|
||||||
state_dir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Without the placeholder, a LAN resolver is reached outside TUN and fake-ip is bypassed.
|
// Without the placeholder, a LAN resolver is reached outside TUN and fake-ip is bypassed.
|
||||||
fn needs_public_dns(config: &Mapping) -> bool {
|
fn needs_public_dns(config: &Mapping) -> bool {
|
||||||
let section = |name: &str| config.get(name).and_then(Value::as_mapping);
|
let section = |name: &str| config.get(name).and_then(Value::as_mapping);
|
||||||
@@ -79,25 +69,19 @@ pub async fn restore_public_dns() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
async fn set_public_dns() {
|
async fn set_public_dns() {
|
||||||
logging!(debug, Type::Config, "try to set system dns");
|
logging!(debug, Type::Config, "try to set system dns");
|
||||||
let result = async {
|
let result = run_dns_script("set_dns.sh", true).await;
|
||||||
let script = crate::utils::dirs::app_resources_dir()?.join("set_dns.sh");
|
|
||||||
run_dns_script(&script, dns_state_dir()?).await
|
|
||||||
}
|
|
||||||
.await;
|
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => logging!(info, Type::Config, "set system dns successfully"),
|
Ok(DnsScriptOutcome::Complete) => logging!(info, Type::Config, "set system dns successfully"),
|
||||||
|
Ok(DnsScriptOutcome::Deferred) => {
|
||||||
|
logging!(debug, Type::Config, "system DNS waits for a primary network service")
|
||||||
|
}
|
||||||
Err(err) => logging!(error, Type::Config, "set system dns failed: {err:#}"),
|
Err(err) => logging!(error, Type::Config, "set system dns failed: {err:#}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unset_public_dns() -> anyhow::Result<()> {
|
async fn unset_public_dns() -> anyhow::Result<()> {
|
||||||
logging!(debug, Type::Config, "try to unset system dns");
|
logging!(debug, Type::Config, "try to unset system dns");
|
||||||
let result = async {
|
let result = run_dns_script("unset_dns.sh", false).await.map(|_| ());
|
||||||
let resource_dir = crate::utils::dirs::app_resources_dir()?;
|
|
||||||
let state_dir = restore_dns_state_dir(&resource_dir, dns_state_dir()?);
|
|
||||||
run_dns_script(&resource_dir.join("unset_dns.sh"), state_dir).await
|
|
||||||
}
|
|
||||||
.await;
|
|
||||||
match &result {
|
match &result {
|
||||||
Ok(()) => logging!(info, Type::Config, "unset system dns successfully"),
|
Ok(()) => logging!(info, Type::Config, "unset system dns successfully"),
|
||||||
Err(err) => logging!(error, Type::Config, "unset system dns failed: {err:#}"),
|
Err(err) => logging!(error, Type::Config, "unset system dns failed: {err:#}"),
|
||||||
@@ -105,12 +89,21 @@ async fn unset_public_dns() -> anyhow::Result<()> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DnsScriptOutcome {
|
||||||
|
Complete,
|
||||||
|
Deferred,
|
||||||
|
}
|
||||||
|
|
||||||
/// On timeout the script's whole process group is killed, so no networksetup it started writes late.
|
/// On timeout the script's whole process group is killed, so no networksetup it started writes late.
|
||||||
async fn run_dns_script(script: &Path, dir: PathBuf) -> anyhow::Result<()> {
|
async fn run_dns_script(script_name: &str, allow_deferred: bool) -> anyhow::Result<DnsScriptOutcome> {
|
||||||
|
let resource_dir = crate::utils::dirs::app_resources_dir()?;
|
||||||
|
let script = resource_dir.join(script_name);
|
||||||
|
let dir = dns_state_dir()?;
|
||||||
ensure!(script.exists(), "DNS script not found: {}", script.display());
|
ensure!(script.exists(), "DNS script not found: {}", script.display());
|
||||||
let mut child = Command::new("bash")
|
let mut child = Command::new("bash")
|
||||||
.arg(script)
|
.arg(&script)
|
||||||
.arg(PLACEHOLDER_DNS)
|
.arg(PLACEHOLDER_DNS)
|
||||||
|
.arg(resource_dir)
|
||||||
.current_dir(dir)
|
.current_dir(dir)
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
@@ -134,41 +127,18 @@ async fn run_dns_script(script: &Path, dir: PathBuf) -> anyhow::Result<()> {
|
|||||||
if let Some(mut pipe) = child.stderr.take() {
|
if let Some(mut pipe) = child.stderr.take() {
|
||||||
pipe.read_to_string(&mut stderr)?;
|
pipe.read_to_string(&mut stderr)?;
|
||||||
}
|
}
|
||||||
|
// EX_TEMPFAIL is reserved by set_dns.sh for a network service that is not ready.
|
||||||
|
if allow_deferred && status.code() == Some(75) {
|
||||||
|
return Ok(DnsScriptOutcome::Deferred);
|
||||||
|
}
|
||||||
ensure!(
|
ensure!(
|
||||||
status.success(),
|
status.success(),
|
||||||
"exit code {}: {}",
|
"exit code {}: {}",
|
||||||
status.code().unwrap_or(-1),
|
status.code().unwrap_or(-1),
|
||||||
stderr.trim()
|
stderr.trim()
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(DnsScriptOutcome::Complete)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.context("DNS script task failed")?
|
.context("DNS script task failed")?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[allow(clippy::expect_used)]
|
|
||||||
fn restore_dns_state_dir_defaults_to_app_data_but_honors_legacy_file() {
|
|
||||||
use super::restore_dns_state_dir;
|
|
||||||
|
|
||||||
let root = std::env::temp_dir().join(format!("clash-verge-dns-{}", nanoid::nanoid!()));
|
|
||||||
let resource_dir = root.join("resources");
|
|
||||||
let state_dir = root.join("app-data");
|
|
||||||
std::fs::create_dir_all(&resource_dir).expect("create test resource directory");
|
|
||||||
|
|
||||||
assert_eq!(restore_dns_state_dir(&resource_dir, state_dir.clone()), state_dir);
|
|
||||||
|
|
||||||
std::fs::write(resource_dir.join(".original_dns.txt"), "empty").expect("create legacy DNS state");
|
|
||||||
assert_eq!(restore_dns_state_dir(&resource_dir, state_dir.clone()), resource_dir);
|
|
||||||
|
|
||||||
// A migrated legacy backup whose restore failed must still be retried there.
|
|
||||||
std::fs::remove_file(resource_dir.join(".original_dns.txt")).expect("remove legacy DNS state");
|
|
||||||
std::fs::create_dir(resource_dir.join(".original_dns")).expect("create migrated DNS state");
|
|
||||||
assert_eq!(restore_dns_state_dir(&resource_dir, state_dir), resource_dir);
|
|
||||||
|
|
||||||
std::fs::remove_dir_all(root).expect("remove test directory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,9 +33,9 @@
|
|||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
|
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json",
|
||||||
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
|
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
|
||||||
"https://gh-proxy.org/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
|
"https://gh-proxy.org/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json"
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json"
|
|
||||||
],
|
],
|
||||||
"windows": {
|
"windows": {
|
||||||
"installMode": "passive"
|
"installMode": "passive"
|
||||||
|
|||||||
@@ -26,8 +26,9 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
|
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json",
|
||||||
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
"https://gh-proxy.org/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json"
|
||||||
],
|
],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,9 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
|
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json",
|
||||||
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
"https://gh-proxy.org/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json"
|
||||||
],
|
],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,9 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
|
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json",
|
||||||
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
"https://update.hwdns.net/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
"https://gh-proxy.org/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json"
|
||||||
],
|
],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,21 +464,6 @@ export const ConnectionTable = (props: Props) => {
|
|||||||
]
|
]
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setColumnOrder((prevValue) => {
|
|
||||||
const baseFields = baseColumns.map((col) => col.field)
|
|
||||||
const prev = Array.isArray(prevValue) ? prevValue : []
|
|
||||||
const reconciled = reconcileColumnOrder(prev, baseFields)
|
|
||||||
if (
|
|
||||||
reconciled.length === prev.length &&
|
|
||||||
reconciled.every((field, i) => field === prev[i])
|
|
||||||
) {
|
|
||||||
return prevValue
|
|
||||||
}
|
|
||||||
return reconciled
|
|
||||||
})
|
|
||||||
}, [baseColumns, setColumnOrder])
|
|
||||||
|
|
||||||
const orderedColumns = useMemo(() => {
|
const orderedColumns = useMemo(() => {
|
||||||
const baseFields = baseColumns.map((column) => column.field)
|
const baseFields = baseColumns.map((column) => column.field)
|
||||||
const reconciledOrder = reconcileColumnOrder(columnOrder, baseFields)
|
const reconciledOrder = reconcileColumnOrder(columnOrder, baseFields)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DeveloperBoardOutlined } from '@mui/icons-material'
|
import { DeveloperBoardOutlined, RestartAltRounded } from '@mui/icons-material'
|
||||||
import { Divider, Stack, Typography } from '@mui/material'
|
import { Divider, IconButton, Stack, Tooltip, Typography } from '@mui/material'
|
||||||
import { useMemo } from 'react'
|
import { useLockFn } from 'ahooks'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { useClash } from '@/hooks/use-clash'
|
import { useClash } from '@/hooks/use-clash'
|
||||||
@@ -11,6 +12,8 @@ import {
|
|||||||
useRulesData,
|
useRulesData,
|
||||||
useSystemData,
|
useSystemData,
|
||||||
} from '@/providers/app-data-context'
|
} from '@/providers/app-data-context'
|
||||||
|
import { restartCore } from '@/services/cmds'
|
||||||
|
import { mutate } from '@/services/mutate'
|
||||||
|
|
||||||
import { EnhancedCard } from './enhanced-card'
|
import { EnhancedCard } from './enhanced-card'
|
||||||
|
|
||||||
@@ -30,6 +33,23 @@ export const ClashInfoCard = () => {
|
|||||||
const { rules } = useRulesData()
|
const { rules } = useRulesData()
|
||||||
const uptime = useAppUptime()
|
const uptime = useAppUptime()
|
||||||
const { systemProxyAddress } = useSystemData()
|
const { systemProxyAddress } = useSystemData()
|
||||||
|
const [restarting, setRestarting] = useState(false)
|
||||||
|
|
||||||
|
const onRestart = useLockFn(async () => {
|
||||||
|
setRestarting(true)
|
||||||
|
try {
|
||||||
|
await mutate(() => restartCore(), {
|
||||||
|
id: 'restart-core',
|
||||||
|
successNotice: t(
|
||||||
|
'settings.feedback.notifications.clash.restartSuccess',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// the funnel already notified
|
||||||
|
} finally {
|
||||||
|
setRestarting(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 使用useMemo缓存格式化后的uptime,避免频繁计算
|
// 使用useMemo缓存格式化后的uptime,避免频繁计算
|
||||||
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime])
|
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime])
|
||||||
@@ -101,7 +121,20 @@ export const ClashInfoCard = () => {
|
|||||||
title={t('home.components.clashInfo.title')}
|
title={t('home.components.clashInfo.title')}
|
||||||
icon={<DeveloperBoardOutlined />}
|
icon={<DeveloperBoardOutlined />}
|
||||||
iconColor="warning"
|
iconColor="warning"
|
||||||
action={null}
|
action={
|
||||||
|
<Tooltip title={t('shared.actions.restart')}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
aria-label={t('shared.actions.restart')}
|
||||||
|
loading={restarting}
|
||||||
|
onClick={onRestart}
|
||||||
|
>
|
||||||
|
<RestartAltRounded />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{cardContent}
|
{cardContent}
|
||||||
</EnhancedCard>
|
</EnhancedCard>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @eslint-react/set-state-in-effect */
|
|
||||||
import {
|
import {
|
||||||
AccessTimeRounded,
|
AccessTimeRounded,
|
||||||
ArrowDropDown,
|
ArrowDropDown,
|
||||||
@@ -31,7 +30,15 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useEffectEvent,
|
||||||
|
useId,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
@@ -570,20 +577,22 @@ export const CurrentProxyCard = () => {
|
|||||||
return profileValue
|
return profileValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profileKey !== baseKey) {
|
return profileKey !== baseKey ? localStorage.getItem(baseKey) : null
|
||||||
const legacyValue = localStorage.getItem(baseKey)
|
|
||||||
if (legacyValue != null) {
|
|
||||||
localStorage.removeItem(baseKey)
|
|
||||||
localStorage.setItem(profileKey, legacyValue)
|
|
||||||
return legacyValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
[getProfileStorageKey],
|
[getProfileStorageKey],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Hand the pre-profile-scoping group key to the first profile that shows the card.
|
||||||
|
useEffect(() => {
|
||||||
|
const profileKey = getProfileStorageKey(STORAGE_KEY_GROUP)
|
||||||
|
if (profileKey === STORAGE_KEY_GROUP) return
|
||||||
|
if (localStorage.getItem(profileKey) != null) return
|
||||||
|
const legacyValue = localStorage.getItem(STORAGE_KEY_GROUP)
|
||||||
|
if (legacyValue == null) return
|
||||||
|
localStorage.removeItem(STORAGE_KEY_GROUP)
|
||||||
|
localStorage.setItem(profileKey, legacyValue)
|
||||||
|
}, [getProfileStorageKey])
|
||||||
|
|
||||||
const writeProfileScopedItem = useCallback(
|
const writeProfileScopedItem = useCallback(
|
||||||
(baseKey: string, value: string) => {
|
(baseKey: string, value: string) => {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
@@ -615,25 +624,17 @@ export const CurrentProxyCard = () => {
|
|||||||
return savedSortType ? (Number(savedSortType) as ProxySortType) : 0
|
return savedSortType ? (Number(savedSortType) as ProxySortType) : 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const [selectedGroupName, setSelectedGroupName] = useState('')
|
const [lastGroupName, setLastGroupName] = useState('')
|
||||||
const [openSelect, setOpenSelect] = useState<OpenSelect>(null)
|
const [openSelect, setOpenSelect] = useState<OpenSelect>(null)
|
||||||
|
const [openSelectMode, setOpenSelectMode] = useState(mode)
|
||||||
useEffect(() => {
|
if (openSelectMode !== mode) {
|
||||||
|
setOpenSelectMode(mode)
|
||||||
setOpenSelect(null)
|
setOpenSelect(null)
|
||||||
}, [mode])
|
}
|
||||||
|
|
||||||
const delayButtonRef = useRef<HTMLButtonElement>(null)
|
const delayButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
const delays = useGroupDelays(selectedGroupName || null)
|
|
||||||
|
|
||||||
const autoCheckInProgressRef = useRef(false)
|
const autoCheckInProgressRef = useRef(false)
|
||||||
const latestTimeoutRef = useRef<number>(
|
|
||||||
verge?.default_latency_timeout || 10000,
|
|
||||||
)
|
|
||||||
const latestProxyMemberRef = useRef<ResolvedProxyMember | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
latestTimeoutRef.current = verge?.default_latency_timeout || 10000
|
|
||||||
}, [verge?.default_latency_timeout])
|
|
||||||
|
|
||||||
const selectableGroups = useMemo(() => {
|
const selectableGroups = useMemo(() => {
|
||||||
if (!proxyView) return []
|
if (!proxyView) return []
|
||||||
@@ -644,6 +645,39 @@ export const CurrentProxyCard = () => {
|
|||||||
)
|
)
|
||||||
}, [proxyView])
|
}, [proxyView])
|
||||||
|
|
||||||
|
const selectedGroupName = useMemo(() => {
|
||||||
|
if (isDirectMode) return 'DIRECT'
|
||||||
|
if (!proxyView) return lastGroupName
|
||||||
|
if (isGlobalMode) return proxyView.global?.name ?? 'GLOBAL'
|
||||||
|
|
||||||
|
const hasGroup = (name: string | null) =>
|
||||||
|
!!name && selectableGroups.some((group) => group.name === name)
|
||||||
|
if (hasGroup(lastGroupName)) return lastGroupName
|
||||||
|
const savedGroup = readProfileScopedItem(STORAGE_KEY_GROUP)
|
||||||
|
if (hasGroup(savedGroup)) return savedGroup!
|
||||||
|
|
||||||
|
const primaryKeywords = ['auto', 'select', 'proxy', '节点选择', '自动选择']
|
||||||
|
const primaryGroup =
|
||||||
|
selectableGroups.find((group) =>
|
||||||
|
primaryKeywords.some((keyword) =>
|
||||||
|
group.name.toLowerCase().includes(keyword.toLowerCase()),
|
||||||
|
),
|
||||||
|
) ?? selectableGroups[0]
|
||||||
|
return primaryGroup?.name ?? ''
|
||||||
|
}, [
|
||||||
|
isDirectMode,
|
||||||
|
isGlobalMode,
|
||||||
|
lastGroupName,
|
||||||
|
proxyView,
|
||||||
|
readProfileScopedItem,
|
||||||
|
selectableGroups,
|
||||||
|
])
|
||||||
|
// Keep the shown group, auto picks included, until it leaves the profile.
|
||||||
|
if (proxyView && lastGroupName !== selectedGroupName) {
|
||||||
|
setLastGroupName(selectedGroupName)
|
||||||
|
}
|
||||||
|
const delays = useGroupDelays(selectedGroupName || null)
|
||||||
|
|
||||||
const selectedGroup = useMemo<ProxyGroupView | null>(() => {
|
const selectedGroup = useMemo<ProxyGroupView | null>(() => {
|
||||||
if (!proxyView || isDirectMode) return null
|
if (!proxyView || isDirectMode) return null
|
||||||
if (isGlobalMode) return proxyView.global
|
if (isGlobalMode) return proxyView.global
|
||||||
@@ -680,46 +714,6 @@ export const CurrentProxyCard = () => {
|
|||||||
[optionsForGroup, selectedGroup],
|
[optionsForGroup, selectedGroup],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!proxyView) return
|
|
||||||
if (isDirectMode) {
|
|
||||||
setSelectedGroupName('DIRECT')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (isGlobalMode) {
|
|
||||||
setSelectedGroupName(proxyView.global?.name ?? 'GLOBAL')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedGroup = readProfileScopedItem(STORAGE_KEY_GROUP)
|
|
||||||
const primaryKeywords = ['auto', 'select', 'proxy', '节点选择', '自动选择']
|
|
||||||
const primaryGroup =
|
|
||||||
selectableGroups.find((group) =>
|
|
||||||
primaryKeywords.some((keyword) =>
|
|
||||||
group.name.toLowerCase().includes(keyword.toLowerCase()),
|
|
||||||
),
|
|
||||||
) ?? selectableGroups[0]
|
|
||||||
const nextGroup = selectableGroups.some(
|
|
||||||
({ name }) => name === selectedGroupName,
|
|
||||||
)
|
|
||||||
? selectedGroupName
|
|
||||||
: selectableGroups.some(({ name }) => name === savedGroup)
|
|
||||||
? savedGroup!
|
|
||||||
: (primaryGroup?.name ?? '')
|
|
||||||
if (nextGroup !== selectedGroupName) {
|
|
||||||
setSelectedGroupName(nextGroup)
|
|
||||||
if (nextGroup) writeProfileScopedItem(STORAGE_KEY_GROUP, nextGroup)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
isDirectMode,
|
|
||||||
isGlobalMode,
|
|
||||||
proxyView,
|
|
||||||
readProfileScopedItem,
|
|
||||||
selectableGroups,
|
|
||||||
selectedGroupName,
|
|
||||||
writeProfileScopedItem,
|
|
||||||
])
|
|
||||||
|
|
||||||
const currentOption = useMemo(() => {
|
const currentOption = useMemo(() => {
|
||||||
if (!proxyView) return undefined
|
if (!proxyView) return undefined
|
||||||
if (isDirectMode) {
|
if (isDirectMode) {
|
||||||
@@ -743,12 +737,10 @@ export const CurrentProxyCard = () => {
|
|||||||
: undefined
|
: undefined
|
||||||
}, [isDirectMode, proxyView, selectedGroup])
|
}, [isDirectMode, proxyView, selectedGroup])
|
||||||
|
|
||||||
latestProxyMemberRef.current = currentOption?.member ?? null
|
|
||||||
|
|
||||||
const handleGroupChange = useCallback(
|
const handleGroupChange = useCallback(
|
||||||
(newGroupName: string) => {
|
(newGroupName: string) => {
|
||||||
if (isGlobalMode || isDirectMode) return
|
if (isGlobalMode || isDirectMode) return
|
||||||
setSelectedGroupName(newGroupName)
|
setLastGroupName(newGroupName)
|
||||||
writeProfileScopedItem(STORAGE_KEY_GROUP, newGroupName)
|
writeProfileScopedItem(STORAGE_KEY_GROUP, newGroupName)
|
||||||
},
|
},
|
||||||
[isDirectMode, isGlobalMode, writeProfileScopedItem],
|
[isDirectMode, isGlobalMode, writeProfileScopedItem],
|
||||||
@@ -793,7 +785,7 @@ export const CurrentProxyCard = () => {
|
|||||||
color: 'text.secondary',
|
color: 'text.secondary',
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkCurrentProxyDelay = useCallback(async () => {
|
const checkCurrentProxyDelay = useEffectEvent(async () => {
|
||||||
if (autoCheckInProgressRef.current) return
|
if (autoCheckInProgressRef.current) return
|
||||||
if (isDirectMode) return
|
if (isDirectMode) return
|
||||||
|
|
||||||
@@ -802,7 +794,7 @@ export const CurrentProxyCard = () => {
|
|||||||
|
|
||||||
if (!groupName || !proxyName) return
|
if (!groupName || !proxyName) return
|
||||||
|
|
||||||
const proxyMember = latestProxyMemberRef.current
|
const proxyMember = currentOption?.member
|
||||||
if (!proxyMember || !isInteractableMember(proxyMember)) {
|
if (!proxyMember || !isInteractableMember(proxyMember)) {
|
||||||
debugLog(
|
debugLog(
|
||||||
`[CurrentProxyCard] 自动延迟检测跳过,组: ${groupName}, 节点: ${proxyName} 未找到`,
|
`[CurrentProxyCard] 自动延迟检测跳过,组: ${groupName}, 节点: ${proxyName} 未找到`,
|
||||||
@@ -812,7 +804,7 @@ export const CurrentProxyCard = () => {
|
|||||||
|
|
||||||
autoCheckInProgressRef.current = true
|
autoCheckInProgressRef.current = true
|
||||||
|
|
||||||
const timeout = latestTimeoutRef.current || 10000
|
const timeout = verge?.default_latency_timeout || 10000
|
||||||
|
|
||||||
try {
|
try {
|
||||||
debugLog(
|
debugLog(
|
||||||
@@ -828,7 +820,7 @@ export const CurrentProxyCard = () => {
|
|||||||
autoCheckInProgressRef.current = false
|
autoCheckInProgressRef.current = false
|
||||||
refreshProxy()
|
refreshProxy()
|
||||||
}
|
}
|
||||||
}, [isDirectMode, refreshProxy, selectedGroupName, selectedProxyName])
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDirectMode) return
|
if (isDirectMode) return
|
||||||
@@ -858,7 +850,6 @@ export const CurrentProxyCard = () => {
|
|||||||
if (intervalTimer) clearTimeout(intervalTimer)
|
if (intervalTimer) clearTimeout(intervalTimer)
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
checkCurrentProxyDelay,
|
|
||||||
autoDelayIntervalMs,
|
autoDelayIntervalMs,
|
||||||
isDirectMode,
|
isDirectMode,
|
||||||
selectedGroupName,
|
selectedGroupName,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useEffectEvent,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
useReducer,
|
useReducer,
|
||||||
@@ -120,7 +121,6 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
const drawFrameRef = useRef<number | undefined>(undefined)
|
const drawFrameRef = useRef<number | undefined>(undefined)
|
||||||
const hoverFrameRef = useRef<number | undefined>(undefined)
|
const hoverFrameRef = useRef<number | undefined>(undefined)
|
||||||
const mouseMoveFrameRef = useRef<number | undefined>(undefined)
|
const mouseMoveFrameRef = useRef<number | undefined>(undefined)
|
||||||
const scheduleDrawGraphRef = useRef<() => void>(() => {})
|
|
||||||
const pendingMousePositionRef = useRef<{
|
const pendingMousePositionRef = useRef<{
|
||||||
clientX: number
|
clientX: number
|
||||||
clientY: number
|
clientY: number
|
||||||
@@ -189,11 +189,11 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
}
|
}
|
||||||
}, [displayData])
|
}, [displayData])
|
||||||
|
|
||||||
const handleFocusStateChange = useCallback((focused: boolean) => {
|
const handleFocusStateChange = useEffectEvent((focused: boolean) => {
|
||||||
isWindowFocusedRef.current = focused
|
isWindowFocusedRef.current = focused
|
||||||
|
|
||||||
scheduleDrawGraphRef.current()
|
scheduleDrawGraph()
|
||||||
}, [])
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||||
@@ -217,7 +217,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
window.removeEventListener('blur', handleBlur)
|
window.removeEventListener('blur', handleBlur)
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||||
}
|
}
|
||||||
}, [handleFocusStateChange])
|
}, [])
|
||||||
|
|
||||||
const calculateY = useCallback(
|
const calculateY = useCallback(
|
||||||
(
|
(
|
||||||
@@ -906,25 +906,21 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
scheduleDrawGraph()
|
scheduleDrawGraph()
|
||||||
}, [scheduleDrawGraph])
|
}, [scheduleDrawGraph])
|
||||||
|
|
||||||
useEffect(() => {
|
const redrawGraph = useEffectEvent(() => scheduleDrawGraph())
|
||||||
scheduleDrawGraphRef.current = scheduleDrawGraph
|
|
||||||
}, [scheduleDrawGraph])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current
|
const canvas = canvasRef.current
|
||||||
if (!canvas || typeof window === 'undefined') return
|
if (!canvas || typeof window === 'undefined') return
|
||||||
|
|
||||||
if (typeof ResizeObserver === 'undefined') {
|
if (typeof ResizeObserver === 'undefined') {
|
||||||
const handleResize = () => scheduleDrawGraphRef.current()
|
const handleResize = () => redrawGraph()
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(() =>
|
const resizeObserver = new ResizeObserver(() => redrawGraph())
|
||||||
scheduleDrawGraphRef.current(),
|
|
||||||
)
|
|
||||||
resizeObserver.observe(canvas)
|
resizeObserver.observe(canvas)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import { Typography, Stack, Divider, Chip, IconButton } from '@mui/material'
|
import { Typography, Stack, Divider, Chip, IconButton } from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
@@ -22,10 +22,23 @@ import {
|
|||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { getSystemInfo } from '@/services/cmds'
|
import { getSystemInfo } from '@/services/cmds'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
import { useQuery } from '@/services/query-client'
|
||||||
import { version as appVersion } from '@root/package.json'
|
import { version as appVersion } from '@root/package.json'
|
||||||
|
|
||||||
import { EnhancedCard } from './enhanced-card'
|
import { EnhancedCard } from './enhanced-card'
|
||||||
|
|
||||||
|
const getOsInfo = async () => {
|
||||||
|
const info = await getSystemInfo()
|
||||||
|
const sysName = info.system_name
|
||||||
|
let sysVersion = info.system_version
|
||||||
|
|
||||||
|
if (sysName && sysVersion.toLowerCase().startsWith(sysName.toLowerCase())) {
|
||||||
|
sysVersion = sysVersion.substring(sysName.length).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${sysName} ${sysVersion}`
|
||||||
|
}
|
||||||
|
|
||||||
export const SystemInfoCard = () => {
|
export const SystemInfoCard = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { verge, patchVerge } = useVerge()
|
const { verge, patchVerge } = useVerge()
|
||||||
@@ -36,31 +49,16 @@ export const SystemInfoCard = () => {
|
|||||||
|
|
||||||
const { checkUpdate: triggerCheckUpdate, lastCheckUpdate } = useUpdate(true)
|
const { checkUpdate: triggerCheckUpdate, lastCheckUpdate } = useUpdate(true)
|
||||||
|
|
||||||
const [osInfo, setOsInfo] = useState('')
|
const { data: osInfo = '' } = useQuery({
|
||||||
|
queryKey: ['getSystemInfo'],
|
||||||
|
queryFn: getOsInfo,
|
||||||
|
})
|
||||||
|
|
||||||
const lastCheckUpdateText = useMemo(
|
const lastCheckUpdateText = useMemo(
|
||||||
() => (lastCheckUpdate ? new Date(lastCheckUpdate).toLocaleString() : '-'),
|
() => (lastCheckUpdate ? new Date(lastCheckUpdate).toLocaleString() : '-'),
|
||||||
[lastCheckUpdate],
|
[lastCheckUpdate],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getSystemInfo()
|
|
||||||
.then((info) => {
|
|
||||||
const sysName = info.system_name
|
|
||||||
let sysVersion = info.system_version
|
|
||||||
|
|
||||||
if (
|
|
||||||
sysName &&
|
|
||||||
sysVersion.toLowerCase().startsWith(sysName.toLowerCase())
|
|
||||||
) {
|
|
||||||
sysVersion = sysVersion.substring(sysName.length).trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
setOsInfo(`${sysName} ${sysVersion}`)
|
|
||||||
})
|
|
||||||
.catch(console.error)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!verge?.auto_check_update) return
|
if (!verge?.auto_check_update) return
|
||||||
if (readLastCheckTime() !== null) return
|
if (readLastCheckTime() !== null) return
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useEffectEvent,
|
||||||
useReducer,
|
useReducer,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@@ -113,7 +114,6 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
|||||||
const setProfileLoading = useSetProfileLoading()
|
const setProfileLoading = useSetProfileLoading()
|
||||||
|
|
||||||
const [showNextUpdate, setShowNextUpdate] = useState(false)
|
const [showNextUpdate, setShowNextUpdate] = useState(false)
|
||||||
const showNextUpdateRef = useRef(false)
|
|
||||||
const [nextUpdateTime, setNextUpdateTime] = useState('')
|
const [nextUpdateTime, setNextUpdateTime] = useState('')
|
||||||
const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
@@ -203,10 +203,6 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
|||||||
setShowNextUpdate(!showNextUpdate)
|
setShowNextUpdate(!showNextUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
showNextUpdateRef.current = showNextUpdate
|
|
||||||
}, [showNextUpdate])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showNextUpdate) {
|
if (showNextUpdate) {
|
||||||
fetchNextUpdateTime()
|
fetchNextUpdateTime()
|
||||||
@@ -218,8 +214,10 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
|||||||
updated,
|
updated,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const isNextUpdateShown = useEffectEvent(() => showNextUpdate)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timerUpdateRevision === 0 || !showNextUpdateRef.current) return
|
if (timerUpdateRevision === 0 || !isNextUpdateShown()) return
|
||||||
|
|
||||||
if (refreshTimeoutRef.current !== undefined) {
|
if (refreshTimeoutRef.current !== undefined) {
|
||||||
clearTimeout(refreshTimeoutRef.current)
|
clearTimeout(refreshTimeoutRef.current)
|
||||||
@@ -236,7 +234,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
|||||||
}, [fetchNextUpdateTime, timerUpdateRevision])
|
}, [fetchNextUpdateTime, timerUpdateRevision])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (completedUpdateRevision === 0 || !showNextUpdateRef.current) return
|
if (completedUpdateRevision === 0 || !isNextUpdateShown()) return
|
||||||
fetchNextUpdateTime()
|
fetchNextUpdateTime()
|
||||||
}, [completedUpdateRevision, fetchNextUpdateTime])
|
}, [completedUpdateRevision, fetchNextUpdateTime])
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import { useEffect, useImperativeHandle, useRef, useState } from 'react'
|
import { useImperativeHandle, useRef, useState } from 'react'
|
||||||
import { Controller, useForm } from 'react-hook-form'
|
import { Controller, useForm } from 'react-hook-form'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@@ -77,17 +77,6 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const selfProxy = watch('option.self_proxy')
|
|
||||||
const withProxy = watch('option.with_proxy')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selfProxy) setValue('option.with_proxy', false)
|
|
||||||
}, [selfProxy, setValue])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (withProxy) setValue('option.self_proxy', false)
|
|
||||||
}, [setValue, withProxy])
|
|
||||||
|
|
||||||
const handleOk = useLockFn(
|
const handleOk = useLockFn(
|
||||||
handleSubmit(async (form) => {
|
handleSubmit(async (form) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -419,7 +408,15 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
|||||||
<InputLabel>
|
<InputLabel>
|
||||||
{t('profiles.modals.profileForm.fields.useSystemProxy')}
|
{t('profiles.modals.profileForm.fields.useSystemProxy')}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Switch checked={field.value!} {...field} color="primary" />
|
<Switch
|
||||||
|
checked={field.value!}
|
||||||
|
{...field}
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
field.onChange(checked)
|
||||||
|
if (checked) setValue('option.self_proxy', false)
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
</StyledBox>
|
</StyledBox>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -432,7 +429,15 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
|||||||
<InputLabel>
|
<InputLabel>
|
||||||
{t('profiles.modals.profileForm.fields.useClashProxy')}
|
{t('profiles.modals.profileForm.fields.useClashProxy')}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Switch checked={field.value!} {...field} color="primary" />
|
<Switch
|
||||||
|
checked={field.value!}
|
||||||
|
{...field}
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
field.onChange(checked)
|
||||||
|
if (checked) setValue('option.with_proxy', false)
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
</StyledBox>
|
</StyledBox>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import * as yaml from 'js-yaml'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getRecord,
|
getRecord,
|
||||||
rebindNode,
|
rebindNode,
|
||||||
@@ -34,3 +36,31 @@ export const rebindProxyChainItems = (
|
|||||||
delay: record?.history.at(-1)?.delay,
|
delay: record?.history.at(-1)?.delay,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
interface ParsedChainConfig {
|
||||||
|
proxies?: Array<{
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
[key: string]: any
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseChainConfigItems = (configData: string): ProxyChainItem[] => {
|
||||||
|
try {
|
||||||
|
// JSON is valid YAML, so one parser covers both persisted formats.
|
||||||
|
const parsedConfig = yaml.load(configData) as ParsedChainConfig | null
|
||||||
|
const timestamp = Date.now()
|
||||||
|
|
||||||
|
return (
|
||||||
|
parsedConfig?.proxies?.map((proxy, index) => ({
|
||||||
|
id: `${proxy.name}_${timestamp}_${index}`,
|
||||||
|
name: proxy.name,
|
||||||
|
type: proxy.type,
|
||||||
|
delay: undefined,
|
||||||
|
})) || []
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to process chain config data:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
keyframes,
|
keyframes,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import {
|
import {
|
||||||
type Ref,
|
type Ref,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -60,18 +59,9 @@ const chainPointerSensor = PointerSensor.configure({
|
|||||||
|
|
||||||
type RuntimeConfigWithProxySequence = IConfigData & { proxies?: unknown }
|
type RuntimeConfigWithProxySequence = IConfigData & { proxies?: unknown }
|
||||||
|
|
||||||
interface ParsedChainConfig {
|
|
||||||
proxies?: Array<{
|
|
||||||
name: string
|
|
||||||
type: string
|
|
||||||
[key: string]: any
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProxyChainProps {
|
interface ProxyChainProps {
|
||||||
proxyChain: ProxyChainItem[]
|
proxyChain: ProxyChainItem[]
|
||||||
onUpdateChain: (chain: ProxyChainItem[]) => void
|
onUpdateChain: (chain: ProxyChainItem[]) => void
|
||||||
chainConfigData?: string | null
|
|
||||||
onMarkUnsavedChanges?: () => void
|
onMarkUnsavedChanges?: () => void
|
||||||
mode?: string
|
mode?: string
|
||||||
selectedGroup?: string | null
|
selectedGroup?: string | null
|
||||||
@@ -98,21 +88,6 @@ const arrowFadeIn = keyframes`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const toChainItems = (
|
|
||||||
parsedConfig: ParsedChainConfig | null | undefined,
|
|
||||||
): ProxyChainItem[] => {
|
|
||||||
const timestamp = Date.now()
|
|
||||||
|
|
||||||
return (
|
|
||||||
parsedConfig?.proxies?.map((proxy, index) => ({
|
|
||||||
id: `${proxy.name}_${timestamp}_${index}`,
|
|
||||||
name: proxy.name,
|
|
||||||
type: proxy.type,
|
|
||||||
delay: undefined,
|
|
||||||
})) || []
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChainCardProps {
|
interface ChainCardProps {
|
||||||
proxy: ProxyChainItem
|
proxy: ProxyChainItem
|
||||||
index: number
|
index: number
|
||||||
@@ -328,7 +303,6 @@ const SortableProxyChainItem = ({
|
|||||||
export const ProxyChain = ({
|
export const ProxyChain = ({
|
||||||
proxyChain,
|
proxyChain,
|
||||||
onUpdateChain,
|
onUpdateChain,
|
||||||
chainConfigData,
|
|
||||||
onMarkUnsavedChanges,
|
onMarkUnsavedChanges,
|
||||||
mode,
|
mode,
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
@@ -580,23 +554,6 @@ export const ProxyChain = ({
|
|||||||
recordSelection,
|
recordSelection,
|
||||||
])
|
])
|
||||||
|
|
||||||
// 处理链式代理配置数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (chainConfigData) {
|
|
||||||
try {
|
|
||||||
// JSON is valid YAML, so one parser covers both persisted formats.
|
|
||||||
const parsedConfig = yaml.load(chainConfigData) as ParsedChainConfig
|
|
||||||
const chainItems = toChainItems(parsedConfig)
|
|
||||||
|
|
||||||
if (chainItems.length > 0) {
|
|
||||||
onUpdateChain(chainItems)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to process chain config data:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [chainConfigData, onUpdateChain])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
elevation={1}
|
elevation={1}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ interface GroupSelectMenuProps {
|
|||||||
|
|
||||||
interface ProxyGroupsChainProps {
|
interface ProxyGroupsChainProps {
|
||||||
mode: string
|
mode: string
|
||||||
chainConfigData?: string | null
|
chainConfigItems?: ProxyChainItem[] | null
|
||||||
onExitChainMode: () => void
|
onExitChainMode: () => void
|
||||||
availableGroups: any[]
|
availableGroups: any[]
|
||||||
activeSelectedGroup: string | null
|
activeSelectedGroup: string | null
|
||||||
@@ -305,7 +305,7 @@ export function ProxyGroupsChain(props: ProxyGroupsChainProps) {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
mode,
|
mode,
|
||||||
chainConfigData,
|
chainConfigItems,
|
||||||
onExitChainMode,
|
onExitChainMode,
|
||||||
availableGroups,
|
availableGroups,
|
||||||
activeSelectedGroup,
|
activeSelectedGroup,
|
||||||
@@ -338,6 +338,16 @@ export function ProxyGroupsChain(props: ProxyGroupsChainProps) {
|
|||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Each fetched runtime chain replaces the stored one once.
|
||||||
|
const configItems = chainConfigItems ?? null
|
||||||
|
const [appliedConfigItems, setAppliedConfigItems] = useState<
|
||||||
|
ProxyChainItem[] | null
|
||||||
|
>(null)
|
||||||
|
if (configItems !== appliedConfigItems) {
|
||||||
|
setAppliedConfigItems(configItems)
|
||||||
|
if (configItems?.length) setProxyChain(configItems)
|
||||||
|
}
|
||||||
|
|
||||||
const candidateNodes = useMemo(
|
const candidateNodes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
renderList.flatMap((item) => {
|
renderList.flatMap((item) => {
|
||||||
@@ -520,7 +530,6 @@ export function ProxyGroupsChain(props: ProxyGroupsChainProps) {
|
|||||||
<ProxyChain
|
<ProxyChain
|
||||||
proxyChain={currentProxyChain}
|
proxyChain={currentProxyChain}
|
||||||
onUpdateChain={setProxyChain}
|
onUpdateChain={setProxyChain}
|
||||||
chainConfigData={chainConfigData}
|
|
||||||
mode={mode}
|
mode={mode}
|
||||||
selectedGroup={activeSelectedGroup}
|
selectedGroup={activeSelectedGroup}
|
||||||
onExitChainMode={onExitChainMode}
|
onExitChainMode={onExitChainMode}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from '@/types/proxy-view'
|
} from '@/types/proxy-view'
|
||||||
import { debugLog } from '@/utils/debug'
|
import { debugLog } from '@/utils/debug'
|
||||||
|
|
||||||
|
import type { ProxyChainItem } from './proxy-chain-model'
|
||||||
import { ProxyEmptyState } from './proxy-empty-state'
|
import { ProxyEmptyState } from './proxy-empty-state'
|
||||||
import {
|
import {
|
||||||
resolveEmptyListReason,
|
resolveEmptyListReason,
|
||||||
@@ -68,7 +69,7 @@ function useStableCallback<T extends (...args: any[]) => any>(fn: T): T {
|
|||||||
interface Props {
|
interface Props {
|
||||||
mode: string
|
mode: string
|
||||||
isChainMode?: boolean
|
isChainMode?: boolean
|
||||||
chainConfigData?: string | null
|
chainConfigItems?: ProxyChainItem[] | null
|
||||||
onExitChainMode: () => void
|
onExitChainMode: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +184,7 @@ function useProxyRenderState(
|
|||||||
renderList,
|
renderList,
|
||||||
onProxies,
|
onProxies,
|
||||||
onHeadState,
|
onHeadState,
|
||||||
|
setHeadState: setListHeadState,
|
||||||
handleCheckAll,
|
handleCheckAll,
|
||||||
saveScrollPosition,
|
saveScrollPosition,
|
||||||
getScrollPosition,
|
getScrollPosition,
|
||||||
@@ -191,10 +193,10 @@ function useProxyRenderState(
|
|||||||
|
|
||||||
function ChainProxyGroups(props: {
|
function ChainProxyGroups(props: {
|
||||||
mode: string
|
mode: string
|
||||||
chainConfigData?: string | null
|
chainConfigItems?: ProxyChainItem[] | null
|
||||||
onExitChainMode: () => void
|
onExitChainMode: () => void
|
||||||
}) {
|
}) {
|
||||||
const { mode, chainConfigData, onExitChainMode } = props
|
const { mode, chainConfigItems, onExitChainMode } = props
|
||||||
const { proxyView } = useProxiesData()
|
const { proxyView } = useProxiesData()
|
||||||
const [selectedGroup, setSelectedGroup] = useState<string | null>(null)
|
const [selectedGroup, setSelectedGroup] = useState<string | null>(null)
|
||||||
|
|
||||||
@@ -348,7 +350,7 @@ function ChainProxyGroups(props: {
|
|||||||
<Suspense fallback={<BaseLoading />}>
|
<Suspense fallback={<BaseLoading />}>
|
||||||
<ProxyGroupsChain
|
<ProxyGroupsChain
|
||||||
mode={mode}
|
mode={mode}
|
||||||
chainConfigData={chainConfigData}
|
chainConfigItems={chainConfigItems}
|
||||||
onExitChainMode={onExitChainMode}
|
onExitChainMode={onExitChainMode}
|
||||||
availableGroups={availableGroups}
|
availableGroups={availableGroups}
|
||||||
activeSelectedGroup={activeSelectedGroup}
|
activeSelectedGroup={activeSelectedGroup}
|
||||||
@@ -377,12 +379,17 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
renderList,
|
renderList,
|
||||||
onProxies,
|
onProxies,
|
||||||
onHeadState,
|
onHeadState,
|
||||||
|
setHeadState,
|
||||||
handleCheckAll,
|
handleCheckAll,
|
||||||
getScrollPosition,
|
getScrollPosition,
|
||||||
saveScrollPosition,
|
saveScrollPosition,
|
||||||
} = useProxyRenderState(mode, false, null)
|
} = useProxyRenderState(mode, false, null)
|
||||||
const emptyList = useEmptyRenderList()
|
const emptyList = useEmptyRenderList()
|
||||||
const { onDragEnd: onHeaderDragEnd } = useProxyGroupHeaderLayout()
|
const { onDragEnd: onHeaderDragEnd } = useProxyGroupHeaderLayout()
|
||||||
|
const { proxyView } = useProxiesData()
|
||||||
|
const globalGroupName = proxyView?.global?.name
|
||||||
|
// The revealed group sits below GLOBAL's rows, so a click must bring it into view.
|
||||||
|
const pendingRevealRef = useRef<string | null>(null)
|
||||||
const renderFirstRef = useRef(true)
|
const renderFirstRef = useRef(true)
|
||||||
// Do not persist intermediate positions produced while restoring virtual scroll.
|
// Do not persist intermediate positions produced while restoring virtual scroll.
|
||||||
const isRestoringRef = useRef(false)
|
const isRestoringRef = useRef(false)
|
||||||
@@ -477,9 +484,17 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
if (!['Selector', 'URLTest', 'Fallback'].includes(group.type)) return
|
if (!['Selector', 'URLTest', 'Fallback'].includes(group.type)) return
|
||||||
if (!isInteractableMember(member)) return
|
if (!isInteractableMember(member)) return
|
||||||
|
|
||||||
|
if (group.name === globalGroupName) {
|
||||||
|
pendingRevealRef.current =
|
||||||
|
member.kind === 'group' ? member.ref.name : null
|
||||||
|
// The selection refetches the view, so skip onHeadState's refetch.
|
||||||
|
if (member.kind === 'group') {
|
||||||
|
setHeadState(member.ref.name, { open: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
select(group.name, member.ref.name, group.now!, group.fixed!)
|
select(group.name, member.ref.name, group.now!, group.fixed!)
|
||||||
},
|
},
|
||||||
[select],
|
[globalGroupName, select, setHeadState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleLocation = useStableCallback((group: ProxyGroupView) => {
|
const handleLocation = useStableCallback((group: ProxyGroupView) => {
|
||||||
@@ -525,6 +540,17 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
return Array.from(new Set(names))
|
return Array.from(new Set(names))
|
||||||
}, [renderList])
|
}, [renderList])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mode !== 'global') {
|
||||||
|
pendingRevealRef.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const groupName = pendingRevealRef.current
|
||||||
|
if (!groupName || !proxyGroupNames.includes(groupName)) return
|
||||||
|
pendingRevealRef.current = null
|
||||||
|
handleGroupLocationByName(groupName)
|
||||||
|
}, [handleGroupLocationByName, mode, proxyGroupNames])
|
||||||
|
|
||||||
const handleGroupToggle = useCallback(
|
const handleGroupToggle = useCallback(
|
||||||
async (group: ProxyGroupView) => {
|
async (group: ProxyGroupView) => {
|
||||||
const index = renderList.findIndex(
|
const index = renderList.findIndex(
|
||||||
@@ -608,7 +634,7 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
/>
|
/>
|
||||||
</DragDropProvider>
|
</DragDropProvider>
|
||||||
|
|
||||||
{(mode === 'rule' || mode === 'global') && (
|
{mode === 'rule' && (
|
||||||
<ProxyGroupNavigator
|
<ProxyGroupNavigator
|
||||||
proxyGroupNames={proxyGroupNames}
|
proxyGroupNames={proxyGroupNames}
|
||||||
onGroupLocation={handleGroupLocationByName}
|
onGroupLocation={handleGroupLocationByName}
|
||||||
@@ -621,7 +647,7 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ProxyGroups = (props: Props) => {
|
export const ProxyGroups = (props: Props) => {
|
||||||
const { mode, isChainMode = false, chainConfigData, onExitChainMode } = props
|
const { mode, isChainMode = false, chainConfigItems, onExitChainMode } = props
|
||||||
const { profiles, isLoading: isProfilesLoading } = useProfiles()
|
const { profiles, isLoading: isProfilesLoading } = useProfiles()
|
||||||
const { isProxyViewPending } = useProxiesData()
|
const { isProxyViewPending } = useProxiesData()
|
||||||
const { isRunningModePending } = useSystemData()
|
const { isRunningModePending } = useSystemData()
|
||||||
@@ -645,7 +671,7 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
return isChainMode ? (
|
return isChainMode ? (
|
||||||
<ChainProxyGroups
|
<ChainProxyGroups
|
||||||
mode={mode}
|
mode={mode}
|
||||||
chainConfigData={chainConfigData}
|
chainConfigItems={chainConfigItems}
|
||||||
onExitChainMode={onExitChainMode}
|
onExitChainMode={onExitChainMode}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useVerge } from '@/hooks/use-verge'
|
|||||||
import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context'
|
import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context'
|
||||||
import delayManager, { type DelaySnapshot } from '@/services/delay'
|
import delayManager, { type DelaySnapshot } from '@/services/delay'
|
||||||
import {
|
import {
|
||||||
|
findCurrentGroupMember,
|
||||||
isInteractableMember,
|
isInteractableMember,
|
||||||
resolveMember,
|
resolveMember,
|
||||||
selectGlobalChainNodes,
|
selectGlobalChainNodes,
|
||||||
@@ -209,8 +210,10 @@ export const useRenderList = (
|
|||||||
const renderGroups = useMemo(() => {
|
const renderGroups = useMemo(() => {
|
||||||
if (!proxyView) return []
|
if (!proxyView) return []
|
||||||
if (mode === 'rule') return proxyView.groups
|
if (mode === 'rule') return proxyView.groups
|
||||||
const global = proxyView.global ? [proxyView.global] : []
|
const { global } = proxyView
|
||||||
return mode === 'global' ? [...global, ...proxyView.groups] : global
|
if (!global) return []
|
||||||
|
const current = findCurrentGroupMember(proxyView, global)?.member
|
||||||
|
return current?.kind === 'group' ? [global, current.group] : [global]
|
||||||
}, [mode, proxyView])
|
}, [mode, proxyView])
|
||||||
|
|
||||||
// Every group this list draws, so a test settling in any of them re-sorts that group.
|
// Every group this list draws, so a test settling in any of them re-sorts that group.
|
||||||
@@ -260,10 +263,10 @@ export const useRenderList = (
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const useRule = mode === 'rule' || mode === 'global'
|
|
||||||
const cache = groupCacheRef.current
|
const cache = groupCacheRef.current
|
||||||
|
|
||||||
const retList = renderGroups.flatMap((group) => {
|
const retList = renderGroups.flatMap((group) => {
|
||||||
|
const isFlat = group === proxyView.global
|
||||||
const headState = headStates[group.name] || DEFAULT_STATE
|
const headState = headStates[group.name] || DEFAULT_STATE
|
||||||
const cached = cache.get(group.name)
|
const cached = cache.get(group.name)
|
||||||
if (
|
if (
|
||||||
@@ -279,17 +282,19 @@ export const useRenderList = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ret: IRenderItem[] = [
|
const ret: IRenderItem[] = [
|
||||||
{
|
isFlat
|
||||||
type: 0,
|
? { type: 1, key: `head-${group.name}`, group, headState }
|
||||||
key: group.name,
|
: {
|
||||||
group,
|
type: 0,
|
||||||
headState,
|
key: group.name,
|
||||||
icon: group.icon!,
|
group,
|
||||||
testUrl: group.testUrl!,
|
headState,
|
||||||
},
|
icon: group.icon!,
|
||||||
|
testUrl: group.testUrl!,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if (headState.open || !useRule) {
|
if (headState.open || isFlat) {
|
||||||
const occurrences = filterSort(
|
const occurrences = filterSort(
|
||||||
resolveOccurrences(proxyView, group),
|
resolveOccurrences(proxyView, group),
|
||||||
group.name,
|
group.name,
|
||||||
@@ -302,9 +307,6 @@ export const useRenderList = (
|
|||||||
useRegularExpression: headState.filterUseRegularExpression,
|
useRegularExpression: headState.filterUseRegularExpression,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (!useRule) {
|
|
||||||
ret.push({ type: 1, key: `head-${group.name}`, group, headState })
|
|
||||||
}
|
|
||||||
if (occurrences.length === 0) {
|
if (occurrences.length === 0) {
|
||||||
ret.push({ type: 3, key: `empty-${group.name}`, group, headState })
|
ret.push({ type: 3, key: `empty-${group.name}`, group, headState })
|
||||||
} else if (col > 1) {
|
} else if (col > 1) {
|
||||||
@@ -343,9 +345,8 @@ export const useRenderList = (
|
|||||||
return ret
|
return ret
|
||||||
})
|
})
|
||||||
|
|
||||||
const filtered = !useRule
|
const filtered =
|
||||||
? retList.slice(1)
|
mode === 'rule' ? retList.filter((item) => !item.group.hidden) : retList
|
||||||
: retList.filter((item) => !item.group.hidden)
|
|
||||||
if (
|
if (
|
||||||
prevListRef.current.length === filtered.length &&
|
prevListRef.current.length === filtered.length &&
|
||||||
filtered.every((item, index) => item === prevListRef.current[index])
|
filtered.every((item, index) => item === prevListRef.current[index])
|
||||||
|
|||||||
@@ -133,7 +133,11 @@ export function AutoBackupSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ListItem divider disableGutters>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.backup.auto.scheduleLabel"
|
||||||
|
divider
|
||||||
|
disableGutters
|
||||||
|
>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
@@ -152,7 +156,11 @@ export function AutoBackupSettings() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem divider disableGutters>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.backup.auto.intervalLabel"
|
||||||
|
divider
|
||||||
|
disableGutters
|
||||||
|
>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={2}
|
spacing={2}
|
||||||
@@ -194,7 +202,11 @@ export function AutoBackupSettings() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem divider disableGutters>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.backup.auto.changeLabel"
|
||||||
|
divider
|
||||||
|
disableGutters
|
||||||
|
>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ export const BackupConfigViewer = memo(
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-setting-target="settings.modals.backup.fields.webdavUrl"
|
||||||
fullWidth
|
fullWidth
|
||||||
label={t('settings.modals.backup.fields.webdavUrl')}
|
label={t('settings.modals.backup.fields.webdavUrl')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -187,6 +188,7 @@ export const BackupConfigViewer = memo(
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 6 }}>
|
<Grid size={{ xs: 6 }}>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-setting-target="settings.modals.backup.fields.username"
|
||||||
label={t('settings.modals.backup.fields.username')}
|
label={t('settings.modals.backup.fields.username')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -199,6 +201,7 @@ export const BackupConfigViewer = memo(
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 6 }}>
|
<Grid size={{ xs: 6 }}>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-setting-target="webdav-password"
|
||||||
label={t('shared.labels.password')}
|
label={t('shared.labels.password')}
|
||||||
type="password"
|
type="password"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -230,6 +233,7 @@ export const BackupConfigViewer = memo(
|
|||||||
color={'primary'}
|
color={'primary'}
|
||||||
sx={{ height: '100%' }}
|
sx={{ height: '100%' }}
|
||||||
type="button"
|
type="button"
|
||||||
|
data-setting-target="webdav-save"
|
||||||
onClick={handleSubmit(save)}
|
onClick={handleSubmit(save)}
|
||||||
>
|
>
|
||||||
{t('shared.actions.save')}
|
{t('shared.actions.save')}
|
||||||
@@ -239,6 +243,7 @@ export const BackupConfigViewer = memo(
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="success"
|
color="success"
|
||||||
|
data-setting-target="webdav-backup"
|
||||||
onClick={handleBackup}
|
onClick={handleBackup}
|
||||||
type="button"
|
type="button"
|
||||||
size="large"
|
size="large"
|
||||||
@@ -247,6 +252,7 @@ export const BackupConfigViewer = memo(
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
data-setting-target="webdav-refresh"
|
||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
type="button"
|
type="button"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -146,7 +146,12 @@ export const BackupHistoryViewer = ({
|
|||||||
setRows([])
|
setRows([])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!isLocal && webdavStatus === 'failed' && !options?.force) {
|
// Read the status here: as a dependency, the write below re-runs the fetch.
|
||||||
|
if (
|
||||||
|
!isLocal &&
|
||||||
|
getWebdavStatus(webdavSignature) === 'failed' &&
|
||||||
|
!options?.force
|
||||||
|
) {
|
||||||
setRows([])
|
setRows([])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -180,7 +185,7 @@ export const BackupHistoryViewer = ({
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[buildRow, isLocal, open, shouldSkipWebDav, webdavSignature, webdavStatus],
|
[buildRow, isLocal, open, shouldSkipWebDav, webdavSignature],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -322,7 +327,13 @@ export const BackupHistoryViewer = ({
|
|||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<Box sx={{ position: 'relative', minHeight: 320 }}>
|
<Box
|
||||||
|
data-settings-dialog="backup-history"
|
||||||
|
data-search-ready={!loading}
|
||||||
|
data-setting-target="backup-history"
|
||||||
|
tabIndex={-1}
|
||||||
|
sx={{ position: 'relative', minHeight: 320 }}
|
||||||
|
>
|
||||||
<BaseLoadingOverlay isLoading={isBusy} />
|
<BaseLoadingOverlay isLoading={isBusy} />
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Stack
|
<Stack
|
||||||
@@ -407,6 +418,7 @@ export const BackupHistoryViewer = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
|
data-setting-target="backup-history-export"
|
||||||
onClick={() => handleExport(row.filename)}
|
onClick={() => handleExport(row.filename)}
|
||||||
>
|
>
|
||||||
<DownloadRounded fontSize="small" />
|
<DownloadRounded fontSize="small" />
|
||||||
@@ -415,6 +427,7 @@ export const BackupHistoryViewer = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
|
data-setting-target="backup-history-delete"
|
||||||
onClick={() => handleDelete(row.filename)}
|
onClick={() => handleDelete(row.filename)}
|
||||||
>
|
>
|
||||||
<DeleteOutlined fontSize="small" />
|
<DeleteOutlined fontSize="small" />
|
||||||
@@ -422,6 +435,7 @@ export const BackupHistoryViewer = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
|
data-setting-target="backup-history-restore"
|
||||||
onClick={() => handleRestore(row.filename)}
|
onClick={() => handleRestore(row.filename)}
|
||||||
>
|
>
|
||||||
<RestoreRounded fontSize="small" />
|
<RestoreRounded fontSize="small" />
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import type { ReactNode, Ref } from 'react'
|
import type { ReactNode, Ref } from 'react'
|
||||||
import { useCallback, useImperativeHandle, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef } from '@/components/base'
|
import { BaseDialog, DialogRef } from '@/components/base'
|
||||||
@@ -23,6 +23,8 @@ import { mutate } from '@/services/mutate'
|
|||||||
import { errorDetail, showNotice } from '@/services/notice-service'
|
import { errorDetail, showNotice } from '@/services/notice-service'
|
||||||
import { buildWebdavSignature, setWebdavStatus } from '@/services/webdav-status'
|
import { buildWebdavSignature, setWebdavStatus } from '@/services/webdav-status'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
import { AutoBackupSettings } from './auto-backup-settings'
|
import { AutoBackupSettings } from './auto-backup-settings'
|
||||||
import { BackupHistoryViewer } from './backup-history-viewer'
|
import { BackupHistoryViewer } from './backup-history-viewer'
|
||||||
import { BackupWebdavDialog } from './backup-webdav-dialog'
|
import { BackupWebdavDialog } from './backup-webdav-dialog'
|
||||||
@@ -39,9 +41,18 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
const [historySource, setHistorySource] = useState<BackupSource>('local')
|
const [historySource, setHistorySource] = useState<BackupSource>('local')
|
||||||
const [historyPage, setHistoryPage] = useState(0)
|
const [historyPage, setHistoryPage] = useState(0)
|
||||||
const [webdavDialogOpen, setWebdavDialogOpen] = useState(false)
|
const [webdavDialogOpen, setWebdavDialogOpen] = useState(false)
|
||||||
|
const searchParentRef = useRef<'webdav' | 'history' | null>(null)
|
||||||
|
const closeSearchDetail = (detail: 'webdav' | 'history') => {
|
||||||
|
if (detail === 'webdav') setWebdavDialogOpen(false)
|
||||||
|
else setHistoryOpen(false)
|
||||||
|
if (searchParentRef.current === detail) {
|
||||||
|
searchParentRef.current = null
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
const webdavSignature = buildWebdavSignature(verge)
|
const webdavSignature = buildWebdavSignature(verge)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'backup-viewer', open, () => ({
|
||||||
open: () => setOpen(true),
|
open: () => setOpen(true),
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}))
|
}))
|
||||||
@@ -52,6 +63,23 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
setHistoryOpen(true)
|
setHistoryOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useSettingDialog(undefined, 'backup-webdav', webdavDialogOpen, () => ({
|
||||||
|
open: () => {
|
||||||
|
if (!open) searchParentRef.current = 'webdav'
|
||||||
|
setOpen(true)
|
||||||
|
setWebdavDialogOpen(true)
|
||||||
|
},
|
||||||
|
close: () => setWebdavDialogOpen(false),
|
||||||
|
}))
|
||||||
|
useSettingDialog(undefined, 'backup-history', historyOpen, () => ({
|
||||||
|
open: () => {
|
||||||
|
if (!open) searchParentRef.current = 'history'
|
||||||
|
setOpen(true)
|
||||||
|
openHistory('local')
|
||||||
|
},
|
||||||
|
close: () => setHistoryOpen(false),
|
||||||
|
}))
|
||||||
|
|
||||||
const handleBackup = useLockFn(async (target: BackupSource) => {
|
const handleBackup = useLockFn(async (target: BackupSource) => {
|
||||||
try {
|
try {
|
||||||
setBusyAction(target)
|
setBusyAction(target)
|
||||||
@@ -129,6 +157,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="backup-viewer" />
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Stack
|
<Stack
|
||||||
spacing={1}
|
spacing={1}
|
||||||
@@ -166,6 +195,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
description: t('settings.modals.backup.manual.local'),
|
description: t('settings.modals.backup.manual.local'),
|
||||||
actions: [
|
actions: [
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.modals.backup.actions.backup"
|
||||||
key="backup"
|
key="backup"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -176,6 +206,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
{t('settings.modals.backup.actions.backup')}
|
{t('settings.modals.backup.actions.backup')}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.modals.backup.actions.viewHistory"
|
||||||
key="history"
|
key="history"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -185,6 +216,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
{t('settings.modals.backup.actions.viewHistory')}
|
{t('settings.modals.backup.actions.viewHistory')}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.modals.backup.actions.importBackup"
|
||||||
key="import"
|
key="import"
|
||||||
variant="text"
|
variant="text"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -263,11 +295,11 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
page={historyPage}
|
page={historyPage}
|
||||||
onSourceChange={setHistorySource}
|
onSourceChange={setHistorySource}
|
||||||
onPageChange={setHistoryPage}
|
onPageChange={setHistoryPage}
|
||||||
onClose={() => setHistoryOpen(false)}
|
onClose={() => closeSearchDetail('history')}
|
||||||
/>
|
/>
|
||||||
<BackupWebdavDialog
|
<BackupWebdavDialog
|
||||||
open={webdavDialogOpen}
|
open={webdavDialogOpen}
|
||||||
onClose={() => setWebdavDialogOpen(false)}
|
onClose={() => closeSearchDetail('webdav')}
|
||||||
onBackupSuccess={() => openHistory('webdav')}
|
onBackupSuccess={() => openHistory('webdav')}
|
||||||
setBusy={setWebdavBusy}
|
setBusy={setWebdavBusy}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -76,7 +76,13 @@ export const BackupWebdavDialog = ({
|
|||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<Box sx={{ position: 'relative' }}>
|
<Box
|
||||||
|
data-settings-dialog="backup-webdav"
|
||||||
|
data-search-ready={!loading}
|
||||||
|
data-setting-target="webdav-state"
|
||||||
|
tabIndex={-1}
|
||||||
|
sx={{ position: 'relative' }}
|
||||||
|
>
|
||||||
<BaseLoadingOverlay isLoading={loading} />
|
<BaseLoadingOverlay isLoading={loading} />
|
||||||
<BackupConfigViewer
|
<BackupConfigViewer
|
||||||
setLoading={handleLoading}
|
setLoading={handleLoading}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import { useImperativeHandle, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { closeAllConnections } from 'tauri-plugin-mihomo-api'
|
import { closeAllConnections } from 'tauri-plugin-mihomo-api'
|
||||||
|
|
||||||
@@ -24,6 +24,8 @@ import { changeClashCore, restartCore, upgradeClashCore } from '@/services/cmds'
|
|||||||
import { mutate } from '@/services/mutate'
|
import { mutate } from '@/services/mutate'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
const VALID_CORE = [
|
const VALID_CORE = [
|
||||||
{
|
{
|
||||||
name: 'Mihomo',
|
name: 'Mihomo',
|
||||||
@@ -49,7 +51,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
const [restarting, setRestarting] = useState(false)
|
const [restarting, setRestarting] = useState(false)
|
||||||
const [changingCore, setChangingCore] = useState<string | null>(null)
|
const [changingCore, setChangingCore] = useState<string | null>(null)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'clash-core-viewer', open, () => ({
|
||||||
open: () => setOpen(true),
|
open: () => setOpen(true),
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}))
|
}))
|
||||||
@@ -139,6 +141,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
loading={upgrading}
|
loading={upgrading}
|
||||||
disabled={restarting || changingCore !== null}
|
disabled={restarting || changingCore !== null}
|
||||||
sx={{ marginRight: '8px' }}
|
sx={{ marginRight: '8px' }}
|
||||||
|
data-setting-target="core-upgrade"
|
||||||
onClick={onUpgrade}
|
onClick={onUpgrade}
|
||||||
>
|
>
|
||||||
{t('shared.actions.upgrade')}
|
{t('shared.actions.upgrade')}
|
||||||
@@ -150,6 +153,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
loadingPosition="start"
|
loadingPosition="start"
|
||||||
loading={restarting}
|
loading={restarting}
|
||||||
disabled={upgrading}
|
disabled={upgrading}
|
||||||
|
data-setting-target="core-restart"
|
||||||
onClick={onRestart}
|
onClick={onRestart}
|
||||||
>
|
>
|
||||||
{t('shared.actions.restart')}
|
{t('shared.actions.restart')}
|
||||||
@@ -170,7 +174,12 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
<List component="nav">
|
<List
|
||||||
|
component="nav"
|
||||||
|
data-settings-dialog="clash-core-viewer"
|
||||||
|
data-setting-target="core-variants"
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
{VALID_CORE.map((each) => (
|
{VALID_CORE.map((each) => (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
key={each.core}
|
key={each.core}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn, useRequest } from 'ahooks'
|
import { useLockFn, useRequest } from 'ahooks'
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, Switch } from '@/components/base'
|
import { BaseDialog, Switch } from '@/components/base'
|
||||||
@@ -22,6 +22,8 @@ import { showNotice } from '@/services/notice-service'
|
|||||||
import type { TranslationKey } from '@/types/generated/i18n-keys'
|
import type { TranslationKey } from '@/types/generated/i18n-keys'
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
const OS = getSystem()
|
const OS = getSystem()
|
||||||
|
|
||||||
interface ClashPortViewerRef {
|
interface ClashPortViewerRef {
|
||||||
@@ -87,6 +89,7 @@ const readPortSettings = (
|
|||||||
})
|
})
|
||||||
|
|
||||||
interface PortRowProps {
|
interface PortRowProps {
|
||||||
|
searchTarget: string
|
||||||
label: string
|
label: string
|
||||||
secondary?: string
|
secondary?: string
|
||||||
port: number
|
port: number
|
||||||
@@ -97,6 +100,7 @@ interface PortRowProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PortRow = ({
|
const PortRow = ({
|
||||||
|
searchTarget,
|
||||||
label,
|
label,
|
||||||
secondary,
|
secondary,
|
||||||
port,
|
port,
|
||||||
@@ -107,7 +111,10 @@ const PortRow = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem sx={{ padding: '4px 0', minHeight: 36 }}>
|
<ListItem
|
||||||
|
data-setting-target={searchTarget}
|
||||||
|
sx={{ padding: '4px 0', minHeight: 36 }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={label}
|
primary={label}
|
||||||
secondary={secondary}
|
secondary={secondary}
|
||||||
@@ -129,6 +136,7 @@ const PortRow = ({
|
|||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
data-setting-target="port-random"
|
||||||
onClick={() => onPortChange(generateRandomPort())}
|
onClick={() => onPortChange(generateRandomPort())}
|
||||||
title={t('settings.modals.clashPort.actions.random')}
|
title={t('settings.modals.clashPort.actions.random')}
|
||||||
disabled={!enabled}
|
disabled={!enabled}
|
||||||
@@ -192,7 +200,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'clash-port-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setPorts(readPortSettings(verge, configuredMixedPort))
|
setPorts(readPortSettings(verge, configuredMixedPort))
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -245,9 +253,11 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="clash-port-viewer" />
|
||||||
<List sx={{ width: '100%' }}>
|
<List sx={{ width: '100%' }}>
|
||||||
<PortRow
|
<PortRow
|
||||||
label={t('settings.modals.clashPort.fields.mixed')}
|
label={t('settings.modals.clashPort.fields.mixed')}
|
||||||
|
searchTarget="settings.modals.clashPort.fields.mixed"
|
||||||
secondary={
|
secondary={
|
||||||
displayedMixedPort !== configuredMixedPort
|
displayedMixedPort !== configuredMixedPort
|
||||||
? t('settings.modals.clashPort.messages.runningPort', {
|
? t('settings.modals.clashPort.messages.runningPort', {
|
||||||
@@ -265,6 +275,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
|
|||||||
<PortRow
|
<PortRow
|
||||||
key={key}
|
key={key}
|
||||||
label={t(label)}
|
label={t(label)}
|
||||||
|
searchTarget={label}
|
||||||
port={ports[key].port}
|
port={ports[key].port}
|
||||||
enabled={ports[key].enabled}
|
enabled={ports[key].enabled}
|
||||||
onPortChange={(port) => updateListener(key, { port })}
|
onPortChange={(port) => updateListener(key, { port })}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { useImperativeHandle, useState, type Ref } from 'react'
|
import { useState, type Ref } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch } from '@/components/base'
|
import { BaseDialog, DialogRef, Switch } from '@/components/base'
|
||||||
@@ -20,6 +20,8 @@ import { useClashInfo } from '@/hooks/use-clash'
|
|||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
@@ -35,7 +37,7 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 对话框打开时初始化配置
|
// 对话框打开时初始化配置
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'controller-viewer', open, () => ({
|
||||||
open: async () => {
|
open: async () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
setController(clashInfo?.server || '')
|
setController(clashInfo?.server || '')
|
||||||
@@ -124,8 +126,10 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="controller-viewer" />
|
||||||
<List>
|
<List>
|
||||||
<ListItem
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.externalController.fields.enable"
|
||||||
sx={{
|
sx={{
|
||||||
padding: '5px 2px',
|
padding: '5px 2px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -144,6 +148,7 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.externalController.fields.address"
|
||||||
sx={{
|
sx={{
|
||||||
padding: '5px 2px',
|
padding: '5px 2px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -184,6 +189,7 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.externalController.fields.secret"
|
||||||
sx={{
|
sx={{
|
||||||
padding: '5px 2px',
|
padding: '5px 2px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -16,14 +16,7 @@ import {
|
|||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import {
|
import { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useReducer,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +40,8 @@ import { useThemeMode } from '@/services/states'
|
|||||||
import type { MonacoEditorInstance } from '@/types/monaco'
|
import type { MonacoEditorInstance } from '@/types/monaco'
|
||||||
import { MONACO_FONT_FAMILY } from '@/utils/font-family'
|
import { MONACO_FONT_FAMILY } from '@/utils/font-family'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
const Item = styled(ListItem)(() => ({
|
const Item = styled(ListItem)(() => ({
|
||||||
padding: '5px 2px',
|
padding: '5px 2px',
|
||||||
'& textarea': {
|
'& textarea': {
|
||||||
@@ -190,6 +185,110 @@ const DEFAULT_DNS_CONFIG = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DnsFormValues = {
|
||||||
|
enable: boolean
|
||||||
|
listen: string
|
||||||
|
enhancedMode: 'fake-ip' | 'redir-host'
|
||||||
|
fakeIpRange: string
|
||||||
|
fakeIpRange6: string
|
||||||
|
fakeIpFilterMode: 'blacklist' | 'whitelist'
|
||||||
|
preferH3: boolean
|
||||||
|
respectRules: boolean
|
||||||
|
useHosts: boolean
|
||||||
|
useSystemHosts: boolean
|
||||||
|
ipv6: boolean
|
||||||
|
fakeIpFilter: string
|
||||||
|
nameserver: string
|
||||||
|
fallback: string
|
||||||
|
defaultNameserver: string
|
||||||
|
proxyServerNameserver: string
|
||||||
|
directNameserver: string
|
||||||
|
directNameserverFollowPolicy: boolean
|
||||||
|
fallbackGeoip: boolean
|
||||||
|
fallbackGeoipCode: string
|
||||||
|
fallbackIpcidr: string
|
||||||
|
fallbackDomain: string
|
||||||
|
nameserverPolicy: string
|
||||||
|
hosts: string // hosts设置,独立于dns
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_FORM_VALUES: DnsFormValues = {
|
||||||
|
enable: DEFAULT_DNS_CONFIG.enable,
|
||||||
|
listen: DEFAULT_DNS_CONFIG.listen,
|
||||||
|
enhancedMode: DEFAULT_DNS_CONFIG['enhanced-mode'],
|
||||||
|
fakeIpRange: DEFAULT_DNS_CONFIG['fake-ip-range'],
|
||||||
|
fakeIpRange6: DEFAULT_DNS_CONFIG['fake-ip-range6'],
|
||||||
|
fakeIpFilterMode: DEFAULT_DNS_CONFIG['fake-ip-filter-mode'],
|
||||||
|
preferH3: DEFAULT_DNS_CONFIG['prefer-h3'],
|
||||||
|
respectRules: DEFAULT_DNS_CONFIG['respect-rules'],
|
||||||
|
useHosts: DEFAULT_DNS_CONFIG['use-hosts'],
|
||||||
|
useSystemHosts: DEFAULT_DNS_CONFIG['use-system-hosts'],
|
||||||
|
ipv6: DEFAULT_DNS_CONFIG.ipv6,
|
||||||
|
fakeIpFilter: DEFAULT_DNS_CONFIG['fake-ip-filter'].join(', '),
|
||||||
|
defaultNameserver: DEFAULT_DNS_CONFIG['default-nameserver'].join(', '),
|
||||||
|
nameserver: DEFAULT_DNS_CONFIG.nameserver.join(', '),
|
||||||
|
fallback: DEFAULT_DNS_CONFIG.fallback.join(', '),
|
||||||
|
proxyServerNameserver:
|
||||||
|
DEFAULT_DNS_CONFIG['proxy-server-nameserver']?.join(', ') || '',
|
||||||
|
directNameserver: DEFAULT_DNS_CONFIG['direct-nameserver']?.join(', ') || '',
|
||||||
|
directNameserverFollowPolicy:
|
||||||
|
DEFAULT_DNS_CONFIG['direct-nameserver-follow-policy'] || false,
|
||||||
|
fallbackGeoip: DEFAULT_DNS_CONFIG['fallback-filter'].geoip,
|
||||||
|
fallbackGeoipCode: DEFAULT_DNS_CONFIG['fallback-filter']['geoip-code'],
|
||||||
|
fallbackIpcidr:
|
||||||
|
DEFAULT_DNS_CONFIG['fallback-filter'].ipcidr?.join(', ') || '',
|
||||||
|
fallbackDomain:
|
||||||
|
DEFAULT_DNS_CONFIG['fallback-filter'].domain?.join(', ') || '',
|
||||||
|
nameserverPolicy: '',
|
||||||
|
hosts: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDnsConfig(values: DnsFormValues) {
|
||||||
|
const dnsConfig: any = {
|
||||||
|
enable: values.enable,
|
||||||
|
listen: values.listen,
|
||||||
|
'enhanced-mode': values.enhancedMode,
|
||||||
|
'fake-ip-range': values.fakeIpRange,
|
||||||
|
'fake-ip-range6': values.fakeIpRange6,
|
||||||
|
'fake-ip-filter-mode': values.fakeIpFilterMode,
|
||||||
|
'prefer-h3': values.preferH3,
|
||||||
|
'respect-rules': values.respectRules,
|
||||||
|
'use-hosts': values.useHosts,
|
||||||
|
'use-system-hosts': values.useSystemHosts,
|
||||||
|
ipv6: values.ipv6,
|
||||||
|
'fake-ip-filter': parseList(values.fakeIpFilter),
|
||||||
|
'default-nameserver': parseList(values.defaultNameserver),
|
||||||
|
nameserver: parseList(values.nameserver),
|
||||||
|
'nameserver-policy': parseNameserverPolicy(values.nameserverPolicy),
|
||||||
|
'direct-nameserver-follow-policy': values.directNameserverFollowPolicy,
|
||||||
|
'fallback-filter': {
|
||||||
|
geoip: values.fallbackGeoip,
|
||||||
|
'geoip-code': values.fallbackGeoipCode,
|
||||||
|
ipcidr: parseList(values.fallbackIpcidr),
|
||||||
|
domain: parseList(values.fallbackDomain),
|
||||||
|
},
|
||||||
|
|
||||||
|
fallback: parseList(values.fallback),
|
||||||
|
'proxy-server-nameserver': parseList(values.proxyServerNameserver),
|
||||||
|
'direct-nameserver': parseList(values.directNameserver),
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDnsYaml(values: DnsFormValues) {
|
||||||
|
const config: Record<string, any> = {}
|
||||||
|
|
||||||
|
const dnsConfig = buildDnsConfig(values)
|
||||||
|
if (Object.keys(dnsConfig).length > 0) {
|
||||||
|
config.dns = dnsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
config.hosts = parseHosts(values.hosts)
|
||||||
|
|
||||||
|
return yaml.dump(config, { forceQuotes: true })
|
||||||
|
}
|
||||||
|
|
||||||
export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { mutateClash } = useClash()
|
const { mutateClash } = useClash()
|
||||||
@@ -203,64 +302,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
const themeMode = useThemeMode()
|
const themeMode = useThemeMode()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
const [initializing, setInitializing] = useState(false)
|
||||||
const [visualization, setVisualization] = useState(true)
|
const [visualization, setVisualization] = useState(true)
|
||||||
const skipYamlSyncRef = useRef(false)
|
|
||||||
const editorRef = useRef<MonacoEditorInstance | null>(null)
|
const editorRef = useRef<MonacoEditorInstance | null>(null)
|
||||||
const [values, setValues] = useState<{
|
const [values, setValues] = useState<DnsFormValues>(DEFAULT_FORM_VALUES)
|
||||||
enable: boolean
|
|
||||||
listen: string
|
|
||||||
enhancedMode: 'fake-ip' | 'redir-host'
|
|
||||||
fakeIpRange: string
|
|
||||||
fakeIpRange6: string
|
|
||||||
fakeIpFilterMode: 'blacklist' | 'whitelist'
|
|
||||||
preferH3: boolean
|
|
||||||
respectRules: boolean
|
|
||||||
useHosts: boolean
|
|
||||||
useSystemHosts: boolean
|
|
||||||
ipv6: boolean
|
|
||||||
fakeIpFilter: string
|
|
||||||
nameserver: string
|
|
||||||
fallback: string
|
|
||||||
defaultNameserver: string
|
|
||||||
proxyServerNameserver: string
|
|
||||||
directNameserver: string
|
|
||||||
directNameserverFollowPolicy: boolean
|
|
||||||
fallbackGeoip: boolean
|
|
||||||
fallbackGeoipCode: string
|
|
||||||
fallbackIpcidr: string
|
|
||||||
fallbackDomain: string
|
|
||||||
nameserverPolicy: string
|
|
||||||
hosts: string // hosts设置,独立于dns
|
|
||||||
}>({
|
|
||||||
enable: DEFAULT_DNS_CONFIG.enable,
|
|
||||||
listen: DEFAULT_DNS_CONFIG.listen,
|
|
||||||
enhancedMode: DEFAULT_DNS_CONFIG['enhanced-mode'],
|
|
||||||
fakeIpRange: DEFAULT_DNS_CONFIG['fake-ip-range'],
|
|
||||||
fakeIpRange6: DEFAULT_DNS_CONFIG['fake-ip-range6'],
|
|
||||||
fakeIpFilterMode: DEFAULT_DNS_CONFIG['fake-ip-filter-mode'],
|
|
||||||
preferH3: DEFAULT_DNS_CONFIG['prefer-h3'],
|
|
||||||
respectRules: DEFAULT_DNS_CONFIG['respect-rules'],
|
|
||||||
useHosts: DEFAULT_DNS_CONFIG['use-hosts'],
|
|
||||||
useSystemHosts: DEFAULT_DNS_CONFIG['use-system-hosts'],
|
|
||||||
ipv6: DEFAULT_DNS_CONFIG.ipv6,
|
|
||||||
fakeIpFilter: DEFAULT_DNS_CONFIG['fake-ip-filter'].join(', '),
|
|
||||||
defaultNameserver: DEFAULT_DNS_CONFIG['default-nameserver'].join(', '),
|
|
||||||
nameserver: DEFAULT_DNS_CONFIG.nameserver.join(', '),
|
|
||||||
fallback: DEFAULT_DNS_CONFIG.fallback.join(', '),
|
|
||||||
proxyServerNameserver:
|
|
||||||
DEFAULT_DNS_CONFIG['proxy-server-nameserver']?.join(', ') || '',
|
|
||||||
directNameserver: DEFAULT_DNS_CONFIG['direct-nameserver']?.join(', ') || '',
|
|
||||||
directNameserverFollowPolicy:
|
|
||||||
DEFAULT_DNS_CONFIG['direct-nameserver-follow-policy'] || false,
|
|
||||||
fallbackGeoip: DEFAULT_DNS_CONFIG['fallback-filter'].geoip,
|
|
||||||
fallbackGeoipCode: DEFAULT_DNS_CONFIG['fallback-filter']['geoip-code'],
|
|
||||||
fallbackIpcidr:
|
|
||||||
DEFAULT_DNS_CONFIG['fallback-filter'].ipcidr?.join(', ') || '',
|
|
||||||
fallbackDomain:
|
|
||||||
DEFAULT_DNS_CONFIG['fallback-filter'].domain?.join(', ') || '',
|
|
||||||
nameserverPolicy: '',
|
|
||||||
hosts: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
// Prevent YAML-to-form sync before configuration loads.
|
// Prevent YAML-to-form sync before configuration loads.
|
||||||
const [yamlContent, setYamlContent] = useReducer(
|
const [yamlContent, setYamlContent] = useReducer(
|
||||||
@@ -269,7 +314,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const updateValuesFromConfig = useCallback((config: any) => {
|
const updateValuesFromConfig = useCallback((config: any) => {
|
||||||
if (!config) return
|
if (!config) return undefined
|
||||||
|
|
||||||
const dnsConfig = config.dns || {}
|
const dnsConfig = config.dns || {}
|
||||||
const hostsConfig = config.hosts || {}
|
const hostsConfig = config.hosts || {}
|
||||||
@@ -289,7 +334,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
? fakeIpFilterMode
|
? fakeIpFilterMode
|
||||||
: DEFAULT_DNS_CONFIG['fake-ip-filter-mode']
|
: DEFAULT_DNS_CONFIG['fake-ip-filter-mode']
|
||||||
|
|
||||||
setValues({
|
const next: DnsFormValues = {
|
||||||
enable: dnsConfig.enable ?? DEFAULT_DNS_CONFIG.enable,
|
enable: dnsConfig.enable ?? DEFAULT_DNS_CONFIG.enable,
|
||||||
listen: dnsConfig.listen ?? DEFAULT_DNS_CONFIG.listen,
|
listen: dnsConfig.listen ?? DEFAULT_DNS_CONFIG.listen,
|
||||||
enhancedMode: validEnhancedMode,
|
enhancedMode: validEnhancedMode,
|
||||||
@@ -341,127 +386,27 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
nameserverPolicy:
|
nameserverPolicy:
|
||||||
formatNameserverPolicy(dnsConfig['nameserver-policy']) || '',
|
formatNameserverPolicy(dnsConfig['nameserver-policy']) || '',
|
||||||
hosts: formatHosts(hostsConfig) || '',
|
hosts: formatHosts(hostsConfig) || '',
|
||||||
})
|
}
|
||||||
|
setValues(next)
|
||||||
|
return next
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const generateDnsConfig = useCallback(() => {
|
|
||||||
const dnsConfig: any = {
|
|
||||||
enable: values.enable,
|
|
||||||
listen: values.listen,
|
|
||||||
'enhanced-mode': values.enhancedMode,
|
|
||||||
'fake-ip-range': values.fakeIpRange,
|
|
||||||
'fake-ip-range6': values.fakeIpRange6,
|
|
||||||
'fake-ip-filter-mode': values.fakeIpFilterMode,
|
|
||||||
'prefer-h3': values.preferH3,
|
|
||||||
'respect-rules': values.respectRules,
|
|
||||||
'use-hosts': values.useHosts,
|
|
||||||
'use-system-hosts': values.useSystemHosts,
|
|
||||||
ipv6: values.ipv6,
|
|
||||||
'fake-ip-filter': parseList(values.fakeIpFilter),
|
|
||||||
'default-nameserver': parseList(values.defaultNameserver),
|
|
||||||
nameserver: parseList(values.nameserver),
|
|
||||||
'nameserver-policy': parseNameserverPolicy(values.nameserverPolicy),
|
|
||||||
'direct-nameserver-follow-policy': values.directNameserverFollowPolicy,
|
|
||||||
'fallback-filter': {
|
|
||||||
geoip: values.fallbackGeoip,
|
|
||||||
'geoip-code': values.fallbackGeoipCode,
|
|
||||||
ipcidr: parseList(values.fallbackIpcidr),
|
|
||||||
domain: parseList(values.fallbackDomain),
|
|
||||||
},
|
|
||||||
|
|
||||||
fallback: parseList(values.fallback),
|
|
||||||
'proxy-server-nameserver': parseList(values.proxyServerNameserver),
|
|
||||||
'direct-nameserver': parseList(values.directNameserver),
|
|
||||||
}
|
|
||||||
|
|
||||||
return dnsConfig
|
|
||||||
}, [values])
|
|
||||||
|
|
||||||
const updateYamlFromValues = useCallback(() => {
|
|
||||||
const config: Record<string, any> = {}
|
|
||||||
|
|
||||||
const dnsConfig = generateDnsConfig()
|
|
||||||
if (Object.keys(dnsConfig).length > 0) {
|
|
||||||
config.dns = dnsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
config.hosts = parseHosts(values.hosts)
|
|
||||||
|
|
||||||
setYamlContent(yaml.dump(config, { forceQuotes: true }))
|
|
||||||
}, [generateDnsConfig, values.hosts])
|
|
||||||
|
|
||||||
const resetToDefaults = useCallback(() => {
|
const resetToDefaults = useCallback(() => {
|
||||||
setValues({
|
setValues(DEFAULT_FORM_VALUES)
|
||||||
enable: DEFAULT_DNS_CONFIG.enable,
|
setYamlContent(buildDnsYaml(DEFAULT_FORM_VALUES))
|
||||||
listen: DEFAULT_DNS_CONFIG.listen,
|
}, [])
|
||||||
enhancedMode: DEFAULT_DNS_CONFIG['enhanced-mode'],
|
|
||||||
fakeIpRange: DEFAULT_DNS_CONFIG['fake-ip-range'],
|
|
||||||
fakeIpRange6: DEFAULT_DNS_CONFIG['fake-ip-range6'],
|
|
||||||
fakeIpFilterMode: DEFAULT_DNS_CONFIG['fake-ip-filter-mode'],
|
|
||||||
preferH3: DEFAULT_DNS_CONFIG['prefer-h3'],
|
|
||||||
respectRules: DEFAULT_DNS_CONFIG['respect-rules'],
|
|
||||||
useHosts: DEFAULT_DNS_CONFIG['use-hosts'],
|
|
||||||
useSystemHosts: DEFAULT_DNS_CONFIG['use-system-hosts'],
|
|
||||||
ipv6: DEFAULT_DNS_CONFIG.ipv6,
|
|
||||||
fakeIpFilter: DEFAULT_DNS_CONFIG['fake-ip-filter'].join(', '),
|
|
||||||
defaultNameserver: DEFAULT_DNS_CONFIG['default-nameserver'].join(', '),
|
|
||||||
nameserver: DEFAULT_DNS_CONFIG.nameserver.join(', '),
|
|
||||||
fallback: DEFAULT_DNS_CONFIG.fallback.join(', '),
|
|
||||||
proxyServerNameserver:
|
|
||||||
DEFAULT_DNS_CONFIG['proxy-server-nameserver']?.join(', ') || '',
|
|
||||||
directNameserver:
|
|
||||||
DEFAULT_DNS_CONFIG['direct-nameserver']?.join(', ') || '',
|
|
||||||
directNameserverFollowPolicy:
|
|
||||||
DEFAULT_DNS_CONFIG['direct-nameserver-follow-policy'] || false,
|
|
||||||
fallbackGeoip: DEFAULT_DNS_CONFIG['fallback-filter'].geoip,
|
|
||||||
fallbackGeoipCode: DEFAULT_DNS_CONFIG['fallback-filter']['geoip-code'],
|
|
||||||
fallbackIpcidr:
|
|
||||||
DEFAULT_DNS_CONFIG['fallback-filter'].ipcidr?.join(', ') || '',
|
|
||||||
fallbackDomain:
|
|
||||||
DEFAULT_DNS_CONFIG['fallback-filter'].domain?.join(', ') || '',
|
|
||||||
nameserverPolicy: '',
|
|
||||||
hosts: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
updateYamlFromValues()
|
|
||||||
}, [updateYamlFromValues])
|
|
||||||
|
|
||||||
const updateValuesFromYaml = useCallback(() => {
|
const updateValuesFromYaml = useCallback(() => {
|
||||||
try {
|
try {
|
||||||
const parsedYaml = yaml.load(yamlContent) as any
|
const parsedYaml = yaml.load(yamlContent) as any
|
||||||
if (!parsedYaml || parsedYaml.isInitValue) return
|
if (!parsedYaml || parsedYaml.isInitValue) return
|
||||||
|
|
||||||
skipYamlSyncRef.current = true
|
|
||||||
updateValuesFromConfig(parsedYaml)
|
updateValuesFromConfig(parsedYaml)
|
||||||
} catch {
|
} catch {
|
||||||
showNotice.error('settings.modals.dns.errors.invalidYaml')
|
showNotice.error('settings.modals.dns.errors.invalidYaml')
|
||||||
}
|
}
|
||||||
}, [yamlContent, updateValuesFromConfig])
|
}, [yamlContent, updateValuesFromConfig])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (skipYamlSyncRef.current) {
|
|
||||||
skipYamlSyncRef.current = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateYamlFromValues()
|
|
||||||
}, [updateYamlFromValues])
|
|
||||||
|
|
||||||
const latestUpdateValuesFromYamlRef = useRef(updateValuesFromYaml)
|
|
||||||
const latestUpdateYamlFromValuesRef = useRef(updateYamlFromValues)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
latestUpdateValuesFromYamlRef.current = updateValuesFromYaml
|
|
||||||
latestUpdateYamlFromValuesRef.current = updateYamlFromValues
|
|
||||||
}, [updateValuesFromYaml, updateYamlFromValues])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visualization) {
|
|
||||||
latestUpdateValuesFromYamlRef.current()
|
|
||||||
} else {
|
|
||||||
latestUpdateYamlFromValuesRef.current()
|
|
||||||
}
|
|
||||||
}, [visualization])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
editorRef.current?.dispose()
|
editorRef.current?.dispose()
|
||||||
@@ -470,34 +415,33 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const initDnsConfig = useCallback(async () => {
|
const initDnsConfig = useCallback(async () => {
|
||||||
|
setInitializing(true)
|
||||||
try {
|
try {
|
||||||
const dnsConfig = await getDnsConfigContent()
|
const dnsConfig = await getDnsConfigContent()
|
||||||
|
|
||||||
if (dnsConfig !== null) {
|
if (dnsConfig !== null) {
|
||||||
const config = yaml.load(dnsConfig) as any
|
const config = yaml.load(dnsConfig) as any
|
||||||
|
|
||||||
updateValuesFromConfig(config)
|
const loaded = updateValuesFromConfig(config)
|
||||||
setYamlContent(dnsConfig)
|
setYamlContent(loaded ? buildDnsYaml(loaded) : dnsConfig)
|
||||||
} else {
|
} else {
|
||||||
resetToDefaults()
|
resetToDefaults()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to initialize DNS config', err)
|
console.error('Failed to initialize DNS config', err)
|
||||||
resetToDefaults()
|
resetToDefaults()
|
||||||
|
} finally {
|
||||||
|
setInitializing(false)
|
||||||
}
|
}
|
||||||
}, [resetToDefaults, updateValuesFromConfig])
|
}, [resetToDefaults, updateValuesFromConfig])
|
||||||
|
|
||||||
useImperativeHandle(
|
useSettingDialog(ref, 'dns-viewer', open, () => ({
|
||||||
ref,
|
open: () => {
|
||||||
() => ({
|
setOpen(true)
|
||||||
open: () => {
|
void initDnsConfig()
|
||||||
setOpen(true)
|
},
|
||||||
void initDnsConfig()
|
close: () => setOpen(false),
|
||||||
},
|
}))
|
||||||
close: () => setOpen(false),
|
|
||||||
}),
|
|
||||||
[initDnsConfig],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
const onSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -506,7 +450,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
if (visualization) {
|
if (visualization) {
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
const dnsConfig = generateDnsConfig()
|
const dnsConfig = buildDnsConfig(values)
|
||||||
if (Object.keys(dnsConfig).length > 0) {
|
if (Object.keys(dnsConfig).length > 0) {
|
||||||
config.dns = dnsConfig
|
config.dns = dnsConfig
|
||||||
}
|
}
|
||||||
@@ -607,6 +551,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
{t('settings.modals.dns.dialog.title')}
|
{t('settings.modals.dns.dialog.title')}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="dns-viewer-reset"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
color="warning"
|
color="warning"
|
||||||
@@ -618,8 +563,14 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
|
data-setting-target="dns-mode"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVisualization((prev) => !prev)
|
if (visualization) {
|
||||||
|
setYamlContent(buildDnsYaml(values))
|
||||||
|
} else {
|
||||||
|
updateValuesFromYaml()
|
||||||
|
}
|
||||||
|
setVisualization(!visualization)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{visualization
|
{visualization
|
||||||
@@ -642,6 +593,15 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
data-settings-dialog="dns-viewer"
|
||||||
|
data-search-ready={!initializing}
|
||||||
|
/>
|
||||||
|
{initializing && (
|
||||||
|
<Typography role="status" data-settings-loading tabIndex={-1}>
|
||||||
|
{t('shared.statuses.loading')}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Alert severity="info" sx={{ mb: 2 }}>
|
<Alert severity="info" sx={{ mb: 2 }}>
|
||||||
{t('settings.modals.dns.dialog.profileScope')}
|
{t('settings.modals.dns.dialog.profileScope')}
|
||||||
</Alert>
|
</Alert>
|
||||||
@@ -662,7 +622,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
{t('settings.modals.dns.sections.general')}
|
{t('settings.modals.dns.sections.general')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.enable">
|
||||||
<ListItemText primary={t('settings.modals.dns.fields.enable')} />
|
<ListItemText primary={t('settings.modals.dns.fields.enable')} />
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
@@ -671,7 +631,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.listen">
|
||||||
<ListItemText primary={t('settings.modals.dns.fields.listen')} />
|
<ListItemText primary={t('settings.modals.dns.fields.listen')} />
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
@@ -684,7 +644,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.enhancedMode">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.enhancedMode')}
|
primary={t('settings.modals.dns.fields.enhancedMode')}
|
||||||
/>
|
/>
|
||||||
@@ -699,7 +659,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.fakeIpRange">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fakeIpRange')}
|
primary={t('settings.modals.dns.fields.fakeIpRange')}
|
||||||
/>
|
/>
|
||||||
@@ -714,7 +674,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.fakeIpRange6">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fakeIpRange6')}
|
primary={t('settings.modals.dns.fields.fakeIpRange6')}
|
||||||
/>
|
/>
|
||||||
@@ -729,7 +689,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.fakeIpFilterMode">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fakeIpFilterMode')}
|
primary={t('settings.modals.dns.fields.fakeIpFilterMode')}
|
||||||
/>
|
/>
|
||||||
@@ -744,7 +704,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.ipv6.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.ipv6.label')}
|
primary={t('settings.modals.dns.fields.ipv6.label')}
|
||||||
secondary={t('settings.modals.dns.fields.ipv6.description')}
|
secondary={t('settings.modals.dns.fields.ipv6.description')}
|
||||||
@@ -756,7 +716,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.preferH3.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.preferH3.label')}
|
primary={t('settings.modals.dns.fields.preferH3.label')}
|
||||||
secondary={t('settings.modals.dns.fields.preferH3.description')}
|
secondary={t('settings.modals.dns.fields.preferH3.description')}
|
||||||
@@ -768,7 +728,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.respectRules.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.respectRules.label')}
|
primary={t('settings.modals.dns.fields.respectRules.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -782,7 +742,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.useHosts.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.useHosts.label')}
|
primary={t('settings.modals.dns.fields.useHosts.label')}
|
||||||
secondary={t('settings.modals.dns.fields.useHosts.description')}
|
secondary={t('settings.modals.dns.fields.useHosts.description')}
|
||||||
@@ -794,7 +754,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.useSystemHosts.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.useSystemHosts.label')}
|
primary={t('settings.modals.dns.fields.useSystemHosts.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -808,7 +768,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.directPolicy.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.directPolicy.label')}
|
primary={t('settings.modals.dns.fields.directPolicy.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -822,7 +782,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.defaultNameserver.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.defaultNameserver.label')}
|
primary={t('settings.modals.dns.fields.defaultNameserver.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -842,7 +805,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.nameserver.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.nameserver.label')}
|
primary={t('settings.modals.dns.fields.nameserver.label')}
|
||||||
secondary={t('settings.modals.dns.fields.nameserver.description')}
|
secondary={t('settings.modals.dns.fields.nameserver.description')}
|
||||||
@@ -860,7 +826,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.fallback.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fallback.label')}
|
primary={t('settings.modals.dns.fields.fallback.label')}
|
||||||
secondary={t('settings.modals.dns.fields.fallback.description')}
|
secondary={t('settings.modals.dns.fields.fallback.description')}
|
||||||
@@ -878,7 +847,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.proxy.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.proxy.label')}
|
primary={t('settings.modals.dns.fields.proxy.label')}
|
||||||
secondary={t('settings.modals.dns.fields.proxy.description')}
|
secondary={t('settings.modals.dns.fields.proxy.description')}
|
||||||
@@ -896,7 +868,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.directNameserver.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.directNameserver.label')}
|
primary={t('settings.modals.dns.fields.directNameserver.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -916,7 +891,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.fakeIpFilter.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fakeIpFilter.label')}
|
primary={t('settings.modals.dns.fields.fakeIpFilter.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -936,7 +914,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.nameserverPolicy.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.nameserverPolicy.label')}
|
primary={t('settings.modals.dns.fields.nameserverPolicy.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -963,7 +944,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
{t('settings.modals.dns.sections.fallbackFilter')}
|
{t('settings.modals.dns.sections.fallbackFilter')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.geoipFiltering.label">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.geoipFiltering.label')}
|
primary={t('settings.modals.dns.fields.geoipFiltering.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -977,7 +958,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.modals.dns.fields.geoipCode">
|
||||||
<ListItemText primary={t('settings.modals.dns.fields.geoipCode')} />
|
<ListItemText primary={t('settings.modals.dns.fields.geoipCode')} />
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
@@ -990,7 +971,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.fallbackIpCidr.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fallbackIpCidr.label')}
|
primary={t('settings.modals.dns.fields.fallbackIpCidr.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -1010,7 +994,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.fallbackDomain.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.fallbackDomain.label')}
|
primary={t('settings.modals.dns.fields.fallbackDomain.label')}
|
||||||
secondary={t(
|
secondary={t(
|
||||||
@@ -1037,7 +1024,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
{t('settings.modals.dns.sections.hosts')}
|
{t('settings.modals.dns.sections.hosts')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Item
|
||||||
|
data-setting-target="settings.modals.dns.fields.hosts.label"
|
||||||
|
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.dns.fields.hosts.label')}
|
primary={t('settings.modals.dns.fields.hosts.label')}
|
||||||
secondary={t('settings.modals.dns.fields.hosts.description')}
|
secondary={t('settings.modals.dns.fields.hosts.description')}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Delete as DeleteIcon } from '@mui/icons-material'
|
import { Delete as DeleteIcon } from '@mui/icons-material'
|
||||||
import { Box, Button, Divider, List, ListItem, TextField } from '@mui/material'
|
import { Box, Button, Divider, List, ListItem, TextField } from '@mui/material'
|
||||||
import { useLockFn, useRequest } from 'ahooks'
|
import { useLockFn, useRequest } from 'ahooks'
|
||||||
import { forwardRef, useImperativeHandle, useRef, useState } from 'react'
|
import { forwardRef, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, Switch } from '@/components/base'
|
import { BaseDialog, Switch } from '@/components/base'
|
||||||
@@ -10,6 +10,8 @@ import { restartCore } from '@/services/cmds'
|
|||||||
import { mutate } from '@/services/mutate'
|
import { mutate } from '@/services/mutate'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
// Development origins must never be persisted into production configuration.
|
// Development origins must never be persisted into production configuration.
|
||||||
const DEV_URLS = [
|
const DEV_URLS = [
|
||||||
'tauri://localhost',
|
'tauri://localhost',
|
||||||
@@ -154,7 +156,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'external-controller-cors', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
const cors = clash?.['external-controller-cors']
|
const cors = clash?.['external-controller-cors']
|
||||||
const origins = cors?.['allow-origins'] ?? []
|
const origins = cors?.['allow-origins'] ?? []
|
||||||
@@ -186,8 +188,12 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={handleSave}
|
onOk={handleSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="external-controller-cors" />
|
||||||
<List sx={{ width: '90%', padding: 2 }}>
|
<List sx={{ width: '90%', padding: 2 }}>
|
||||||
<ListItem sx={{ padding: '8px 0' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.externalCors.fields.allowPrivateNetwork"
|
||||||
|
sx={{ padding: '8px 0' }}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -214,7 +220,10 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
|
|||||||
|
|
||||||
<Divider sx={{ my: 2 }} />
|
<Divider sx={{ my: 2 }} />
|
||||||
|
|
||||||
<ListItem sx={{ padding: '8px 0' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.externalCors.fields.allowedOrigins"
|
||||||
|
sx={{ padding: '8px 0' }}
|
||||||
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<div style={{ marginBottom: 8, fontWeight: 'bold' }}>
|
<div style={{ marginBottom: 8, fontWeight: 'bold' }}>
|
||||||
{t('settings.sections.externalCors.fields.allowedOrigins')}
|
{t('settings.sections.externalCors.fields.allowedOrigins')}
|
||||||
@@ -252,6 +261,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.sections.externalCors.actions.add"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleAddOrigin}
|
onClick={handleAddOrigin}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { styled, Typography } from '@mui/material'
|
import { styled, Typography } from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch } from '@/components/base'
|
import { BaseDialog, DialogRef, Switch } from '@/components/base'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
import { HotkeyInput } from './hotkey-input'
|
import { HotkeyInput } from './hotkey-input'
|
||||||
|
|
||||||
const ItemWrapper = styled('div')`
|
const ItemWrapper = styled('div')`
|
||||||
@@ -51,7 +53,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
verge?.enable_global_hotkey ?? true,
|
verge?.enable_global_hotkey ?? true,
|
||||||
)
|
)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'hotkey-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
|
|
||||||
@@ -111,7 +113,11 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
<ItemWrapper style={{ marginBottom: 16 }}>
|
<span data-settings-dialog="hotkey-viewer" />
|
||||||
|
<ItemWrapper
|
||||||
|
data-setting-target="settings.modals.hotkey.toggles.enableGlobal"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
<Typography>
|
<Typography>
|
||||||
{t('settings.modals.hotkey.toggles.enableGlobal')}
|
{t('settings.modals.hotkey.toggles.enableGlobal')}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -123,7 +129,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
</ItemWrapper>
|
</ItemWrapper>
|
||||||
|
|
||||||
{HOTKEY_FUNC.map((func) => (
|
{HOTKEY_FUNC.map((func) => (
|
||||||
<ItemWrapper key={func}>
|
<ItemWrapper key={func} data-setting-target={HOTKEY_FUNC_LABELS[func]}>
|
||||||
<Typography>{t(HOTKEY_FUNC_LABELS[func])}</Typography>
|
<Typography>{t(HOTKEY_FUNC_LABELS[func])}</Typography>
|
||||||
<HotkeyInput
|
<HotkeyInput
|
||||||
value={hotkeyMap[func] ?? []}
|
value={hotkeyMap[func] ?? []}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { convertFileSrc } from '@tauri-apps/api/core'
|
|||||||
import { join } from '@tauri-apps/api/path'
|
import { join } from '@tauri-apps/api/path'
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
||||||
import { exists } from '@tauri-apps/plugin-fs'
|
import { exists } from '@tauri-apps/plugin-fs'
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||||
@@ -26,6 +26,8 @@ import { mutate } from '@/services/mutate'
|
|||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
import { GuardState } from './guard-state'
|
import { GuardState } from './guard-state'
|
||||||
|
|
||||||
const OS = getSystem()
|
const OS = getSystem()
|
||||||
@@ -97,7 +99,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'layout-viewer', open, () => ({
|
||||||
open: () => setOpen(true),
|
open: () => setOpen(true),
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}))
|
}))
|
||||||
@@ -120,8 +122,9 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="layout-viewer" />
|
||||||
<List>
|
<List>
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.preferSystemTitlebar">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.preferSystemTitlebar',
|
'settings.components.verge.layout.fields.preferSystemTitlebar',
|
||||||
@@ -140,7 +143,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.trafficGraph">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.layout.fields.trafficGraph')}
|
primary={t('settings.components.verge.layout.fields.trafficGraph')}
|
||||||
/>
|
/>
|
||||||
@@ -156,7 +159,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.memoryUsage">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.layout.fields.memoryUsage')}
|
primary={t('settings.components.verge.layout.fields.memoryUsage')}
|
||||||
/>
|
/>
|
||||||
@@ -172,7 +175,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.proxyGroupIcon">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.proxyGroupIcon',
|
'settings.components.verge.layout.fields.proxyGroupIcon',
|
||||||
@@ -190,7 +193,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
|
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
|
||||||
@@ -212,7 +215,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.toastPosition">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.layout.fields.toastPosition')}
|
primary={t('settings.components.verge.layout.fields.toastPosition')}
|
||||||
/>
|
/>
|
||||||
@@ -248,7 +251,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.hoverNavigator">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
@@ -276,7 +279,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.hoverNavigatorDelay">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
@@ -335,7 +338,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.navIcon">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.layout.fields.navIcon')}
|
primary={t('settings.components.verge.layout.fields.navIcon')}
|
||||||
/>
|
/>
|
||||||
@@ -360,7 +363,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.collapseNavBar">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.collapseNavBar',
|
'settings.components.verge.layout.fields.collapseNavBar',
|
||||||
@@ -379,7 +382,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
{OS === 'macos' && (
|
{OS === 'macos' && (
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.trayIcon">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.layout.fields.trayIcon')}
|
primary={t('settings.components.verge.layout.fields.trayIcon')}
|
||||||
/>
|
/>
|
||||||
@@ -407,7 +410,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</Item>
|
</Item>
|
||||||
)}
|
)}
|
||||||
{OS === 'macos' && (
|
{OS === 'macos' && (
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.enableTraySpeed">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.enableTraySpeed',
|
'settings.components.verge.layout.fields.enableTraySpeed',
|
||||||
@@ -445,7 +448,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
)} */}
|
)} */}
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.proxyGroupsDisplayMode">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.proxyGroupsDisplayMode',
|
'settings.components.verge.layout.fields.proxyGroupsDisplayMode',
|
||||||
@@ -481,7 +484,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.showOutboundModesInline">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.showOutboundModesInline',
|
'settings.components.verge.layout.fields.showOutboundModesInline',
|
||||||
@@ -499,7 +502,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.commonTrayIcon">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.commonTrayIcon',
|
'settings.components.verge.layout.fields.commonTrayIcon',
|
||||||
@@ -512,6 +515,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
onGuard={(e) => patchVerge({ common_tray_icon: e })}
|
onGuard={(e) => patchVerge({ common_tray_icon: e })}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.components.verge.basic.actions.browse"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
startIcon={
|
startIcon={
|
||||||
@@ -554,7 +558,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.systemProxyTrayIcon">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.components.verge.layout.fields.systemProxyTrayIcon',
|
'settings.components.verge.layout.fields.systemProxyTrayIcon',
|
||||||
@@ -609,7 +613,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.layout.fields.tunTrayIcon">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.layout.fields.tunTrayIcon')}
|
primary={t('settings.components.verge.layout.fields.tunTrayIcon')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import { useImperativeHandle, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||||
@@ -17,6 +17,8 @@ import { entry_lightweight_mode } from '@/services/cmds'
|
|||||||
import { mutate } from '@/services/mutate'
|
import { mutate } from '@/services/mutate'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { verge, patchVerge } = useVerge()
|
const { verge, patchVerge } = useVerge()
|
||||||
@@ -27,7 +29,7 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
autoEnterLiteModeDelay: 10, // 默认10分钟
|
autoEnterLiteModeDelay: 10, // 默认10分钟
|
||||||
})
|
})
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'lite-mode-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
setValues({
|
setValues({
|
||||||
@@ -61,8 +63,12 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="lite-mode-viewer" />
|
||||||
<List>
|
<List>
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.liteMode.actions.enterNow"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.liteMode.actions.enterNow')}
|
primary={t('settings.modals.liteMode.actions.enterNow')}
|
||||||
/>
|
/>
|
||||||
@@ -84,7 +90,10 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.liteMode.toggles.autoEnter"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.liteMode.toggles.autoEnter')}
|
primary={t('settings.modals.liteMode.toggles.autoEnter')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -105,7 +114,10 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
|
|
||||||
{values.autoEnterLiteMode && (
|
{values.autoEnterLiteMode && (
|
||||||
<>
|
<>
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.liteMode.fields.delay"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.liteMode.fields.delay')}
|
primary={t('settings.modals.liteMode.fields.delay')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
const buildInitialValues = (verge?: IVergeConfig | null) => ({
|
const buildInitialValues = (verge?: IVergeConfig | null) => ({
|
||||||
appLogLevel: verge?.app_log_level ?? 'warn',
|
appLogLevel: verge?.app_log_level ?? 'warn',
|
||||||
appLogMaxSize: verge?.app_log_max_size ?? 128,
|
appLogMaxSize: verge?.app_log_max_size ?? 128,
|
||||||
@@ -38,7 +40,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [values, setValues] = useState(() => buildInitialValues(null))
|
const [values, setValues] = useState(() => buildInitialValues(null))
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'misc-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
setValues(buildInitialValues(verge))
|
setValues(buildInitialValues(verge))
|
||||||
@@ -80,8 +82,12 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="misc-viewer" />
|
||||||
<List>
|
<List>
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.appLogLevel"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.appLogLevel')}
|
primary={t('settings.modals.misc.fields.appLogLevel')}
|
||||||
/>
|
/>
|
||||||
@@ -104,7 +110,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.appLogMaxSize"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.appLogMaxSize')}
|
primary={t('settings.modals.misc.fields.appLogMaxSize')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -136,7 +145,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.appLogMaxCount"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.appLogMaxCount')}
|
primary={t('settings.modals.misc.fields.appLogMaxCount')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -168,7 +180,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.autoCloseConnections"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.autoCloseConnections')}
|
primary={t('settings.modals.misc.fields.autoCloseConnections')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -187,7 +202,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.autoCheckUpdate"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.autoCheckUpdate')}
|
primary={t('settings.modals.misc.fields.autoCheckUpdate')}
|
||||||
/>
|
/>
|
||||||
@@ -200,7 +218,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.enableBuiltinEnhanced"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.enableBuiltinEnhanced')}
|
primary={t('settings.modals.misc.fields.enableBuiltinEnhanced')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -219,7 +240,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.proxyLayoutColumns"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.proxyLayoutColumns')}
|
primary={t('settings.modals.misc.fields.proxyLayoutColumns')}
|
||||||
/>
|
/>
|
||||||
@@ -245,7 +269,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.autoLogClean"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.autoLogClean')}
|
primary={t('settings.modals.misc.fields.autoLogClean')}
|
||||||
/>
|
/>
|
||||||
@@ -298,7 +325,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.autoDelayDetection"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.autoDelayDetection')}
|
primary={t('settings.modals.misc.fields.autoDelayDetection')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -317,7 +347,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.autoDelayDetectionInterval"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.modals.misc.fields.autoDelayDetectionInterval',
|
'settings.modals.misc.fields.autoDelayDetectionInterval',
|
||||||
@@ -355,7 +388,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.defaultLatencyTest"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.defaultLatencyTest')}
|
primary={t('settings.modals.misc.fields.defaultLatencyTest')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -379,7 +415,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.misc.fields.defaultLatencyTimeout"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.misc.fields.defaultLatencyTimeout')}
|
primary={t('settings.modals.misc.fields.defaultLatencyTimeout')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ import { ContentCopyRounded } from '@mui/icons-material'
|
|||||||
import { alpha, Box, Button, CircularProgress, IconButton } from '@mui/material'
|
import { alpha, Box, Button, CircularProgress, IconButton } from '@mui/material'
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import { useImperativeHandle, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
|
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
|
||||||
import { useNetworkInterfaces } from '@/hooks/use-network'
|
import { useNetworkInterfaces } from '@/hooks/use-network'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [isV4, setIsV4] = useState(true)
|
const [isV4, setIsV4] = useState(true)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'network-interface-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
},
|
},
|
||||||
@@ -51,41 +53,49 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
{loading && isEmpty ? (
|
<div
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
data-settings-dialog="network-interface-viewer"
|
||||||
<CircularProgress size={24} />
|
data-setting-target="network-interface-viewer"
|
||||||
</Box>
|
tabIndex={-1}
|
||||||
) : isEmpty ? (
|
>
|
||||||
<Box sx={{ minHeight: 160 }}>
|
{loading && isEmpty ? (
|
||||||
<BaseEmpty />
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||||
</Box>
|
<CircularProgress size={24} />
|
||||||
) : (
|
|
||||||
networkInterfaces.map((item) => (
|
|
||||||
<Box key={item.name}>
|
|
||||||
<h4>{item.name}</h4>
|
|
||||||
<Box>
|
|
||||||
{item.addr.map((address) => {
|
|
||||||
const ip = getAddressIp(address)
|
|
||||||
return (
|
|
||||||
ip && (
|
|
||||||
<AddressDisplay
|
|
||||||
key={ip}
|
|
||||||
label={t(
|
|
||||||
'settings.modals.networkInterface.fields.ipAddress',
|
|
||||||
)}
|
|
||||||
content={ip}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
<AddressDisplay
|
|
||||||
label={t('settings.modals.networkInterface.fields.macAddress')}
|
|
||||||
content={item.mac_addr ?? ''}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
))
|
) : isEmpty ? (
|
||||||
)}
|
<Box sx={{ minHeight: 160 }}>
|
||||||
|
<BaseEmpty />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
networkInterfaces.map((item) => (
|
||||||
|
<Box key={item.name}>
|
||||||
|
<h4>{item.name}</h4>
|
||||||
|
<Box>
|
||||||
|
{item.addr.map((address) => {
|
||||||
|
const ip = getAddressIp(address)
|
||||||
|
return (
|
||||||
|
ip && (
|
||||||
|
<AddressDisplay
|
||||||
|
key={ip}
|
||||||
|
label={t(
|
||||||
|
'settings.modals.networkInterface.fields.ipAddress',
|
||||||
|
)}
|
||||||
|
content={ip}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<AddressDisplay
|
||||||
|
label={t(
|
||||||
|
'settings.modals.networkInterface.fields.macAddress',
|
||||||
|
)}
|
||||||
|
content={item.mac_addr ?? ''}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import React, { ReactNode, useState } from 'react'
|
|||||||
import isAsyncFunction from '@/utils/is-async-function'
|
import isAsyncFunction from '@/utils/is-async-function'
|
||||||
|
|
||||||
interface ItemProps {
|
interface ItemProps {
|
||||||
|
'data-setting-target'?: string
|
||||||
label: ReactNode
|
label: ReactNode
|
||||||
extra?: ReactNode
|
extra?: ReactNode
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
@@ -21,6 +22,7 @@ interface ItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SettingItem: React.FC<ItemProps> = ({
|
export const SettingItem: React.FC<ItemProps> = ({
|
||||||
|
'data-setting-target': searchTarget,
|
||||||
label,
|
label,
|
||||||
extra,
|
extra,
|
||||||
children,
|
children,
|
||||||
@@ -49,7 +51,7 @@ export const SettingItem: React.FC<ItemProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return clickable ? (
|
return clickable ? (
|
||||||
<ListItem disablePadding>
|
<ListItem data-setting-target={searchTarget} disablePadding>
|
||||||
<ListItemButton onClick={handleClick} disabled={isLoading}>
|
<ListItemButton onClick={handleClick} disabled={isLoading}>
|
||||||
<ListItemText primary={primary} secondary={secondary} />
|
<ListItemText primary={primary} secondary={secondary} />
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -60,7 +62,7 @@ export const SettingItem: React.FC<ItemProps> = ({
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
) : (
|
) : (
|
||||||
<ListItem sx={{ pt: '5px', pb: '5px' }}>
|
<ListItem data-setting-target={searchTarget} sx={{ pt: '5px', pb: '5px' }}>
|
||||||
<ListItemText primary={primary} secondary={secondary} />
|
<ListItemText primary={primary} secondary={secondary} />
|
||||||
{children}
|
{children}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@@ -13,13 +13,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import {
|
import { forwardRef, useMemo, useState } from 'react'
|
||||||
forwardRef,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -41,9 +35,12 @@ import {
|
|||||||
getSystemHostname,
|
getSystemHostname,
|
||||||
} from '@/services/cmds'
|
} from '@/services/cmds'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
import { useQuery } from '@/services/query-client'
|
||||||
import { debugLog } from '@/utils/debug'
|
import { debugLog } from '@/utils/debug'
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
||||||
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
|
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
|
||||||
}`
|
}`
|
||||||
@@ -128,13 +125,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
} = verge ?? {}
|
} = verge ?? {}
|
||||||
|
|
||||||
const [value, setValue] = useState(() => buildInitialValues(null))
|
const [value, setValue] = useState(() => buildInitialValues(null))
|
||||||
const [embeddedServerPort, setEmbeddedServerPort] = useState<number | null>(
|
const { data: embeddedServerPort } = useQuery({
|
||||||
null,
|
queryKey: ['getEmbeddedServerPort'],
|
||||||
)
|
queryFn: getEmbeddedServerPort,
|
||||||
|
})
|
||||||
useEffect(() => {
|
|
||||||
getEmbeddedServerPort().then(setEmbeddedServerPort).catch(console.error)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const separator = useMemo(() => (isWindows ? ';' : ','), [isWindows])
|
const separator = useMemo(() => (isWindows ? ';' : ','), [isWindows])
|
||||||
|
|
||||||
@@ -191,7 +185,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
setPacEditorSavedValue(nextPac)
|
setPacEditorSavedValue(nextPac)
|
||||||
})
|
})
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'sysproxy-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
setValue(buildInitialValues(verge))
|
setValue(buildInitialValues(verge))
|
||||||
@@ -363,41 +357,49 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
loading={saving}
|
loading={saving}
|
||||||
disableOk={saving}
|
disableOk={saving}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="sysproxy-viewer" />
|
||||||
<List>
|
<List>
|
||||||
<BaseFieldset
|
<Box data-setting-target="sysproxy-status" tabIndex={-1}>
|
||||||
label={t('settings.modals.sysproxy.fieldsets.currentStatus')}
|
<BaseFieldset
|
||||||
padding="15px 10px"
|
label={t('settings.modals.sysproxy.fieldsets.currentStatus')}
|
||||||
>
|
padding="15px 10px"
|
||||||
<FlexBox>
|
>
|
||||||
<Typography className="label">
|
|
||||||
{t('settings.modals.sysproxy.fields.enableStatus')}
|
|
||||||
</Typography>
|
|
||||||
<Typography className="value">
|
|
||||||
{isProxyReallyEnabled
|
|
||||||
? t('shared.statuses.enabled')
|
|
||||||
: t('shared.statuses.disabled')}
|
|
||||||
</Typography>
|
|
||||||
</FlexBox>
|
|
||||||
{!value.pac && (
|
|
||||||
<FlexBox>
|
<FlexBox>
|
||||||
<Typography className="label">
|
<Typography className="label">
|
||||||
{t('settings.modals.sysproxy.fields.serverAddr')}
|
{t('settings.modals.sysproxy.fields.enableStatus')}
|
||||||
</Typography>
|
|
||||||
<Typography className="value">{getSystemProxyAddress}</Typography>
|
|
||||||
</FlexBox>
|
|
||||||
)}
|
|
||||||
{value.pac && (
|
|
||||||
<FlexBox>
|
|
||||||
<Typography className="label">
|
|
||||||
{t('settings.modals.sysproxy.fields.pacUrl')}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography className="value">
|
<Typography className="value">
|
||||||
{getCurrentPacUrl || '-'}
|
{isProxyReallyEnabled
|
||||||
|
? t('shared.statuses.enabled')
|
||||||
|
: t('shared.statuses.disabled')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
)}
|
{!value.pac && (
|
||||||
</BaseFieldset>
|
<FlexBox>
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<Typography className="label">
|
||||||
|
{t('settings.modals.sysproxy.fields.serverAddr')}
|
||||||
|
</Typography>
|
||||||
|
<Typography className="value">
|
||||||
|
{getSystemProxyAddress}
|
||||||
|
</Typography>
|
||||||
|
</FlexBox>
|
||||||
|
)}
|
||||||
|
{value.pac && (
|
||||||
|
<FlexBox>
|
||||||
|
<Typography className="label">
|
||||||
|
{t('settings.modals.sysproxy.fields.pacUrl')}
|
||||||
|
</Typography>
|
||||||
|
<Typography className="value">
|
||||||
|
{getCurrentPacUrl || '-'}
|
||||||
|
</Typography>
|
||||||
|
</FlexBox>
|
||||||
|
)}
|
||||||
|
</BaseFieldset>
|
||||||
|
</Box>
|
||||||
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.proxyHost"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.proxyHost')}
|
primary={t('settings.modals.sysproxy.fields.proxyHost')}
|
||||||
/>
|
/>
|
||||||
@@ -424,7 +426,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.usePacMode"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.usePacMode')}
|
primary={t('settings.modals.sysproxy.fields.usePacMode')}
|
||||||
/>
|
/>
|
||||||
@@ -436,7 +441,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.proxyGuard"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.proxyGuard')}
|
primary={t('settings.modals.sysproxy.fields.proxyGuard')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -454,7 +462,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.guardDuration"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.guardDuration')}
|
primary={t('settings.modals.sysproxy.fields.guardDuration')}
|
||||||
/>
|
/>
|
||||||
@@ -477,7 +488,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{!value.pac && (
|
{!value.pac && (
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.alwaysUseDefaultBypass"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.modals.sysproxy.fields.alwaysUseDefaultBypass',
|
'settings.modals.sysproxy.fields.alwaysUseDefaultBypass',
|
||||||
@@ -504,7 +518,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!value.pac && (
|
{!value.pac && (
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.enableBypassCheck"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.enableBypassCheck')}
|
primary={t('settings.modals.sysproxy.fields.enableBypassCheck')}
|
||||||
/>
|
/>
|
||||||
@@ -536,7 +553,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
setValue((v) => ({ ...v, bypass: nextValue }))
|
setValue((v) => ({ ...v, bypass: nextValue }))
|
||||||
}}
|
}}
|
||||||
renderHeader={(modeToggle) => (
|
renderHeader={(modeToggle) => (
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.proxyBypass"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.proxyBypass')}
|
primary={t('settings.modals.sysproxy.fields.proxyBypass')}
|
||||||
/>
|
/>
|
||||||
@@ -564,12 +584,16 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{value.pac && (
|
{value.pac && (
|
||||||
<ListItem sx={{ padding: '5px 2px', alignItems: 'start' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.sysproxy.fields.pacScriptContent"
|
||||||
|
sx={{ padding: '5px 2px', alignItems: 'start' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.sysproxy.fields.pacScriptContent')}
|
primary={t('settings.modals.sysproxy.fields.pacScriptContent')}
|
||||||
sx={{ padding: '3px 0' }}
|
sx={{ padding: '3px 0' }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.modals.sysproxy.actions.editPac"
|
||||||
startIcon={<EditRounded />}
|
startIcon={<EditRounded />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={openPacEditor}
|
onClick={openPacEditor}
|
||||||
|
|||||||
@@ -9,13 +9,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import {
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef } from '@/components/base'
|
import { BaseDialog, DialogRef } from '@/components/base'
|
||||||
@@ -24,6 +18,8 @@ import { useVerge } from '@/hooks/use-verge'
|
|||||||
import { defaultDarkTheme, defaultTheme } from '@/pages/_theme'
|
import { defaultDarkTheme, defaultTheme } from '@/pages/_theme'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
||||||
const { ref } = props
|
const { ref } = props
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -41,7 +37,7 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
|||||||
themeRef.current = theme
|
themeRef.current = theme
|
||||||
}, [theme])
|
}, [theme])
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'theme-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
setTheme({ ...theme_setting })
|
setTheme({ ...theme_setting })
|
||||||
@@ -128,7 +124,7 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
|||||||
const renderItem = (labelKey: string, key: ThemeKey) => {
|
const renderItem = (labelKey: string, key: ThemeKey) => {
|
||||||
const label = t(labelKey)
|
const label = t(labelKey)
|
||||||
return (
|
return (
|
||||||
<Item key={key}>
|
<Item key={key} data-setting-target={labelKey}>
|
||||||
<ListItemText primary={label} />
|
<ListItemText primary={label} />
|
||||||
<Round sx={{ background: theme[key] || dt[key] }} />
|
<Round sx={{ background: theme[key] || dt[key] }} />
|
||||||
<TextField
|
<TextField
|
||||||
@@ -153,10 +149,11 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="theme-viewer" />
|
||||||
<List sx={{ pt: 0 }}>
|
<List sx={{ pt: 0 }}>
|
||||||
{fieldDefinitions.map((field) => renderItem(field.labelKey, field.key))}
|
{fieldDefinitions.map((field) => renderItem(field.labelKey, field.key))}
|
||||||
|
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.theme.fields.fontFamily">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.theme.fields.fontFamily')}
|
primary={t('settings.components.verge.theme.fields.fontFamily')}
|
||||||
/>
|
/>
|
||||||
@@ -167,11 +164,12 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
|||||||
onKeyDown={(e) => e.key === 'Enter' && onSave()}
|
onKeyDown={(e) => e.key === 'Enter' && onSave()}
|
||||||
/>
|
/>
|
||||||
</Item>
|
</Item>
|
||||||
<Item>
|
<Item data-setting-target="settings.components.verge.theme.fields.cssInjection">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.components.verge.theme.fields.cssInjection')}
|
primary={t('settings.components.verge.theme.fields.cssInjection')}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.components.verge.theme.actions.editCss"
|
||||||
startIcon={<EditRounded />}
|
startIcon={<EditRounded />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={openCssEditor}
|
onClick={openCssEditor}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import { useImperativeHandle, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +26,8 @@ import { showNotice } from '@/services/notice-service'
|
|||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
import { areValidIpCidrs } from '@/utils/network'
|
import { areValidIpCidrs } from '@/utils/network'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
import { DnsModeSwitch } from './dns-mode-switch'
|
import { DnsModeSwitch } from './dns-mode-switch'
|
||||||
import { StackModeSwitch } from './stack-mode-switch'
|
import { StackModeSwitch } from './stack-mode-switch'
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
? t('settings.modals.tun.messages.invalidRouteExcludeAddress')
|
? t('settings.modals.tun.messages.invalidRouteExcludeAddress')
|
||||||
: t('settings.modals.tun.messages.routeExcludeAddressHint')
|
: t('settings.modals.tun.messages.routeExcludeAddressHint')
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'tun-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
const nextAutoRoute = clash?.tun['auto-route'] ?? true
|
const nextAutoRoute = clash?.tun['auto-route'] ?? true
|
||||||
@@ -152,6 +154,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 1 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 1 }}>
|
||||||
<Typography variant="h6">{t('settings.modals.tun.title')}</Typography>
|
<Typography variant="h6">{t('settings.modals.tun.title')}</Typography>
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="tun-viewer-reset"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -204,8 +207,12 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={onSave}
|
onOk={onSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="tun-viewer" />
|
||||||
<List>
|
<List>
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.stack"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText primary={t('settings.modals.tun.fields.stack')} />
|
<ListItemText primary={t('settings.modals.tun.fields.stack')} />
|
||||||
<StackModeSwitch
|
<StackModeSwitch
|
||||||
value={values.stack}
|
value={values.stack}
|
||||||
@@ -218,7 +225,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.dnsMode"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.tun.fields.dnsMode')}
|
primary={t('settings.modals.tun.fields.dnsMode')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -237,7 +247,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.device"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText primary={t('settings.modals.tun.fields.device')} />
|
<ListItemText primary={t('settings.modals.tun.fields.device')} />
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
@@ -254,7 +267,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.autoRoute"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText primary={t('settings.modals.tun.fields.autoRoute')} />
|
<ListItemText primary={t('settings.modals.tun.fields.autoRoute')} />
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
@@ -270,7 +286,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{OS === 'linux' && (
|
{OS === 'linux' && (
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.autoRedirect"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.tun.fields.autoRedirect')}
|
primary={t('settings.modals.tun.fields.autoRedirect')}
|
||||||
sx={{ maxWidth: 'fit-content' }}
|
sx={{ maxWidth: 'fit-content' }}
|
||||||
@@ -294,7 +313,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.strictRoute"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText primary={t('settings.modals.tun.fields.strictRoute')} />
|
<ListItemText primary={t('settings.modals.tun.fields.strictRoute')} />
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
@@ -303,7 +325,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.autoDetectInterface"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.tun.fields.autoDetectInterface')}
|
primary={t('settings.modals.tun.fields.autoDetectInterface')}
|
||||||
/>
|
/>
|
||||||
@@ -316,7 +341,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.dnsHijack"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText primary={t('settings.modals.tun.fields.dnsHijack')} />
|
<ListItemText primary={t('settings.modals.tun.fields.dnsHijack')} />
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
@@ -333,7 +361,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.mtu"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText primary={t('settings.modals.tun.fields.mtu')} />
|
<ListItemText primary={t('settings.modals.tun.fields.mtu')} />
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
@@ -365,7 +396,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
setValues((v) => ({ ...v, routeExcludeAddress: nextValue }))
|
setValues((v) => ({ ...v, routeExcludeAddress: nextValue }))
|
||||||
}
|
}
|
||||||
renderHeader={(modeToggle) => (
|
renderHeader={(modeToggle) => (
|
||||||
<ListItem sx={{ padding: '5px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.modals.tun.fields.routeExcludeAddress"
|
||||||
|
sx={{ padding: '5px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('settings.modals.tun.fields.routeExcludeAddress')}
|
primary={t('settings.modals.tun.fields.routeExcludeAddress')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @eslint-react/set-state-in-effect */
|
|
||||||
import { Delete, ExpandLess, ExpandMore } from '@mui/icons-material'
|
import { Delete, ExpandLess, ExpandMore } from '@mui/icons-material'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -12,14 +11,7 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import {
|
import { forwardRef, useMemo, useRef, useState } from 'react'
|
||||||
forwardRef,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog } from '@/components/base'
|
import { BaseDialog } from '@/components/base'
|
||||||
@@ -42,6 +34,8 @@ import {
|
|||||||
normalizeListenHost,
|
normalizeListenHost,
|
||||||
} from '@/utils/network'
|
} from '@/utils/network'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
interface TunnelsViewerRef {
|
interface TunnelsViewerRef {
|
||||||
open: () => void
|
open: () => void
|
||||||
close: () => void
|
close: () => void
|
||||||
@@ -95,7 +89,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
const [values, setValues] = useState<TunnelFormValues>(buildInitialValues)
|
const [values, setValues] = useState<TunnelFormValues>(buildInitialValues)
|
||||||
const [draftTunnels, setDraftTunnels] = useState<TunnelEntry[]>([])
|
const [draftTunnels, setDraftTunnels] = useState<TunnelEntry[]>([])
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'tunnels-viewer', open, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setValues(buildInitialValues())
|
setValues(buildInitialValues())
|
||||||
setDraftTunnels(() => clash?.tunnels ?? [])
|
setDraftTunnels(() => clash?.tunnels ?? [])
|
||||||
@@ -157,29 +151,20 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
const selectedProxyToken = selectedProxyOption
|
const selectedProxyToken = selectedProxyOption
|
||||||
? proxyOptionToken(selectedProxyOption)
|
? proxyOptionToken(selectedProxyOption)
|
||||||
: ''
|
: ''
|
||||||
const selectedGroupExists =
|
// A group or member missing from the proxy view reads as the default.
|
||||||
!values.group || proxyGroups.some(({ name }) => name === values.group)
|
const effectiveGroup = proxyGroups.some(({ name }) => name === values.group)
|
||||||
|
? values.group
|
||||||
|
: ''
|
||||||
|
const effectiveProxy = selectedProxyOption ? values.proxy : null
|
||||||
const latestProxyStateRef = useRef({
|
const latestProxyStateRef = useRef({
|
||||||
binding: values.proxy,
|
binding: effectiveProxy,
|
||||||
options: proxyOptions,
|
options: proxyOptions,
|
||||||
})
|
})
|
||||||
latestProxyStateRef.current = {
|
latestProxyStateRef.current = {
|
||||||
binding: values.proxy,
|
binding: effectiveProxy,
|
||||||
options: proxyOptions,
|
options: proxyOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedGroupExists && (!values.proxy || selectedProxyOption)) return
|
|
||||||
setValues((current) => {
|
|
||||||
if (current.group !== values.group || current.proxy !== values.proxy) {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
return selectedGroupExists
|
|
||||||
? { ...current, proxy: null }
|
|
||||||
: { ...current, group: '', proxy: null }
|
|
||||||
})
|
|
||||||
}, [selectedGroupExists, selectedProxyOption, values.group, values.proxy])
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
await patchClash({ tunnels: draftTunnels })
|
await patchClash({ tunnels: draftTunnels })
|
||||||
@@ -312,10 +297,14 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
}}
|
}}
|
||||||
onOk={handleSave}
|
onOk={handleSave}
|
||||||
>
|
>
|
||||||
|
<span data-settings-dialog="tunnels-viewer" />
|
||||||
<List>
|
<List>
|
||||||
{draftTunnels.length > 0 && (
|
{draftTunnels.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<ListItem sx={{ padding: '4px 0', opacity: 0.6 }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.existing"
|
||||||
|
sx={{ padding: '4px 0', opacity: 0.6 }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.sections.clash.form.fields.tunnels.existing',
|
'settings.sections.clash.form.fields.tunnels.existing',
|
||||||
@@ -325,6 +314,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
<List component="nav">
|
<List component="nav">
|
||||||
{tunnelEntries.map((item) => (
|
{tunnelEntries.map((item) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.default"
|
||||||
key={`${item.key}`}
|
key={`${item.key}`}
|
||||||
sx={{ padding: '4px 0' }}
|
sx={{ padding: '4px 0' }}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
@@ -353,6 +343,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
)}
|
)}
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
sx={{ padding: '4px 0', opacity: 0.8 }}
|
sx={{ padding: '4px 0', opacity: 0.8 }}
|
||||||
|
data-setting-target="tunnels-expand"
|
||||||
onClick={() => setExpanded((v) => !v)}
|
onClick={() => setExpanded((v) => !v)}
|
||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@@ -363,7 +354,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
{expanded ? <ExpandLess /> : <ExpandMore />}
|
{expanded ? <ExpandLess /> : <ExpandMore />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<ListItem sx={{ padding: '8px 0' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.protocols"
|
||||||
|
sx={{ padding: '8px 0' }}
|
||||||
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
{/* 输入框区域 */}
|
{/* 输入框区域 */}
|
||||||
{/* 协议 */}
|
{/* 协议 */}
|
||||||
@@ -391,7 +385,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* 本地监听地址 */}
|
{/* 本地监听地址 */}
|
||||||
<ListItem sx={{ padding: '6px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.localAddr"
|
||||||
|
sx={{ padding: '6px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.sections.clash.form.fields.tunnels.localAddr',
|
'settings.sections.clash.form.fields.tunnels.localAddr',
|
||||||
@@ -410,7 +407,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* 本地监听端口 */}
|
{/* 本地监听端口 */}
|
||||||
<ListItem sx={{ padding: '6px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.localPort"
|
||||||
|
sx={{ padding: '6px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.sections.clash.form.fields.tunnels.localPort',
|
'settings.sections.clash.form.fields.tunnels.localPort',
|
||||||
@@ -430,7 +430,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* 目标服务器地址 */}
|
{/* 目标服务器地址 */}
|
||||||
<ListItem sx={{ padding: '6px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.targetAddr"
|
||||||
|
sx={{ padding: '6px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.sections.clash.form.fields.tunnels.targetAddr',
|
'settings.sections.clash.form.fields.tunnels.targetAddr',
|
||||||
@@ -449,7 +452,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* 目标服务器端口 */}
|
{/* 目标服务器端口 */}
|
||||||
<ListItem sx={{ padding: '6px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.targetPort"
|
||||||
|
sx={{ padding: '6px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={t(
|
||||||
'settings.sections.clash.form.fields.tunnels.targetPort',
|
'settings.sections.clash.form.fields.tunnels.targetPort',
|
||||||
@@ -469,7 +475,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* 代理组 */}
|
{/* 代理组 */}
|
||||||
<ListItem sx={{ padding: '6px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.proxyGroup"
|
||||||
|
sx={{ padding: '6px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<>
|
<>
|
||||||
@@ -490,7 +499,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
<Select
|
<Select
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ width: 200, '> div': { py: '7.5px' } }}
|
sx={{ width: 200, '> div': { py: '7.5px' } }}
|
||||||
value={values.group}
|
value={effectiveGroup}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const nextGroup = e.target.value as string
|
const nextGroup = e.target.value as string
|
||||||
@@ -531,7 +540,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* 代理节点 */}
|
{/* 代理节点 */}
|
||||||
<ListItem sx={{ padding: '6px 2px' }}>
|
<ListItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.proxyNode"
|
||||||
|
sx={{ padding: '6px 2px' }}
|
||||||
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<>
|
<>
|
||||||
@@ -569,7 +581,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
: null,
|
: null,
|
||||||
}))
|
}))
|
||||||
}}
|
}}
|
||||||
disabled={!values.group} // 没选组就禁用
|
disabled={!effectiveGroup} // 没选组就禁用
|
||||||
>
|
>
|
||||||
<MenuItem value="">
|
<MenuItem value="">
|
||||||
{t('settings.sections.clash.form.fields.tunnels.default')}
|
{t('settings.sections.clash.form.fields.tunnels.default')}
|
||||||
@@ -592,6 +604,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
|||||||
|
|
||||||
{/* 添加按钮 */}
|
{/* 添加按钮 */}
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.actions.add"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export const WebUIItem = (props: Props) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
data-setting-target="web-ui-open"
|
||||||
title={t('settings.modals.webUI.actions.openUrl')}
|
title={t('settings.modals.webUI.actions.openUrl')}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={() => onOpenUrl?.(value)}
|
onClick={() => onOpenUrl?.(value)}
|
||||||
@@ -139,6 +140,7 @@ export const WebUIItem = (props: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
data-setting-target="web-ui-edit"
|
||||||
title={t('shared.actions.edit')}
|
title={t('shared.actions.edit')}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -150,6 +152,7 @@ export const WebUIItem = (props: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
data-setting-target="web-ui-delete"
|
||||||
title={t('shared.actions.delete')}
|
title={t('shared.actions.delete')}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Box, Button, Typography } from '@mui/material'
|
import { Box, Button, Typography } from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import type { Ref } from 'react'
|
import type { Ref } from 'react'
|
||||||
import { useImperativeHandle, useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
|
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
|
||||||
@@ -10,6 +10,8 @@ import { useVerge } from '@/hooks/use-verge'
|
|||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
import { openExternalUrl } from '@/utils/open-external-url'
|
import { openExternalUrl } from '@/utils/open-external-url'
|
||||||
|
|
||||||
|
import { useSettingDialog } from '../settings-search-context'
|
||||||
|
|
||||||
import { WebUIItem } from './web-ui-item'
|
import { WebUIItem } from './web-ui-item'
|
||||||
|
|
||||||
const DEFAULT_WEB_UI_LIST = [
|
const DEFAULT_WEB_UI_LIST = [
|
||||||
@@ -27,7 +29,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useSettingDialog(ref, 'web-ui-viewer', open, () => ({
|
||||||
open: () => setOpen(true),
|
open: () => setOpen(true),
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}))
|
}))
|
||||||
@@ -114,6 +116,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
disabled={editing}
|
disabled={editing}
|
||||||
|
data-setting-target="web-ui-new"
|
||||||
onClick={() => setEditing(true)}
|
onClick={() => setEditing(true)}
|
||||||
>
|
>
|
||||||
{t('shared.actions.new')}
|
{t('shared.actions.new')}
|
||||||
@@ -132,36 +135,42 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
{!editing && webUIList.length === 0 && (
|
<div
|
||||||
<BaseEmpty
|
data-settings-dialog="web-ui-viewer"
|
||||||
extra={
|
data-setting-target="web-ui-viewer"
|
||||||
<Typography sx={{ mt: 2, fontSize: '12px' }}>
|
tabIndex={-1}
|
||||||
{t('settings.modals.webUI.messages.placeholderInstruction')}
|
>
|
||||||
</Typography>
|
{!editing && webUIList.length === 0 && (
|
||||||
}
|
<BaseEmpty
|
||||||
/>
|
extra={
|
||||||
)}
|
<Typography sx={{ mt: 2, fontSize: '12px' }}>
|
||||||
|
{t('settings.modals.webUI.messages.placeholderInstruction')}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{webUIEntries.map(({ item, index, key }) => (
|
{webUIEntries.map(({ item, index, key }) => (
|
||||||
<WebUIItem
|
<WebUIItem
|
||||||
key={key}
|
key={key}
|
||||||
value={item}
|
value={item}
|
||||||
onChange={(v) => handleChange(index, v)}
|
onChange={(v) => handleChange(index, v)}
|
||||||
onDelete={() => handleDelete(index)}
|
onDelete={() => handleDelete(index)}
|
||||||
onOpenUrl={handleOpenUrl}
|
onOpenUrl={handleOpenUrl}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{editing && (
|
{editing && (
|
||||||
<WebUIItem
|
<WebUIItem
|
||||||
value=""
|
value=""
|
||||||
onlyEdit
|
onlyEdit
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
handleAdd(v || '')
|
handleAdd(v || '')
|
||||||
}}
|
}}
|
||||||
onCancel={() => setEditing(false)}
|
onCancel={() => setEditing(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
<HeaderConfiguration ref={corsRef} />
|
<HeaderConfiguration ref={corsRef} />
|
||||||
<TunnelsViewer ref={tunnelRef} />
|
<TunnelsViewer ref={tunnelRef} />
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.allowLan"
|
||||||
label={t('settings.sections.clash.form.fields.allowLan')}
|
label={t('settings.sections.clash.form.fields.allowLan')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -181,6 +182,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.dnsOverwrite"
|
||||||
label={t('settings.sections.clash.form.fields.dnsOverwrite')}
|
label={t('settings.sections.clash.form.fields.dnsOverwrite')}
|
||||||
extra={
|
extra={
|
||||||
<>
|
<>
|
||||||
@@ -203,7 +205,10 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem label={t('settings.sections.clash.form.fields.ipv6')}>
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.ipv6"
|
||||||
|
label={t('settings.sections.clash.form.fields.ipv6')}
|
||||||
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={ipv6 ?? false}
|
value={ipv6 ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
@@ -217,6 +222,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.unifiedDelay"
|
||||||
label={t('settings.sections.clash.form.fields.unifiedDelay')}
|
label={t('settings.sections.clash.form.fields.unifiedDelay')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -238,6 +244,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.logLevel"
|
||||||
label={t('settings.sections.clash.form.fields.logLevel')}
|
label={t('settings.sections.clash.form.fields.logLevel')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -279,7 +286,10 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem label={t('settings.sections.clash.form.fields.portConfig')}>
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.portConfig"
|
||||||
|
label={t('settings.sections.clash.form.fields.portConfig')}
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
@@ -294,6 +304,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.external"
|
||||||
label={t('settings.sections.clash.form.fields.external')}
|
label={t('settings.sections.clash.form.fields.external')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -311,11 +322,13 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.webUI"
|
||||||
onClick={() => webRef.current?.open()}
|
onClick={() => webRef.current?.open()}
|
||||||
label={t('settings.sections.clash.form.fields.webUI')}
|
label={t('settings.sections.clash.form.fields.webUI')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.clashCore"
|
||||||
label={t('settings.sections.clash.form.fields.clashCore')}
|
label={t('settings.sections.clash.form.fields.clashCore')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -329,6 +342,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
|
|
||||||
{isWIN && (
|
{isWIN && (
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.openUwpTool"
|
||||||
onClick={invoke_uwp_tool}
|
onClick={invoke_uwp_tool}
|
||||||
label={t('settings.sections.clash.form.fields.openUwpTool')}
|
label={t('settings.sections.clash.form.fields.openUwpTool')}
|
||||||
extra={
|
extra={
|
||||||
@@ -341,11 +355,13 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.updateGeoData"
|
||||||
onClick={onUpdateGeo}
|
onClick={onUpdateGeo}
|
||||||
label={t('settings.sections.clash.form.fields.updateGeoData')}
|
label={t('settings.sections.clash.form.fields.updateGeoData')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.clash.form.fields.tunnels.title"
|
||||||
label={t('settings.sections.clash.form.fields.tunnels.title')}
|
label={t('settings.sections.clash.form.fields.tunnels.title')}
|
||||||
onClick={() => tunnelRef.current?.open()}
|
onClick={() => tunnelRef.current?.open()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Box } from '@mui/material'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@@ -29,17 +30,24 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingList title={t('settings.sections.system.title')}>
|
<SettingList title={t('settings.sections.system.title')}>
|
||||||
<ProxyControlSwitches
|
<Box data-setting-target="settings.sections.system.toggles.tunMode">
|
||||||
label={t('settings.sections.system.toggles.tunMode')}
|
<ProxyControlSwitches
|
||||||
onError={onError}
|
label={t('settings.sections.system.toggles.tunMode')}
|
||||||
/>
|
onError={onError}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<ProxyControlSwitches
|
<Box data-setting-target="settings.sections.system.toggles.systemProxy">
|
||||||
label={t('settings.sections.system.toggles.systemProxy')}
|
<ProxyControlSwitches
|
||||||
onError={onError}
|
label={t('settings.sections.system.toggles.systemProxy')}
|
||||||
/>
|
onError={onError}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<SettingItem label={t('settings.sections.system.fields.autoLaunch')}>
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.system.fields.autoLaunch"
|
||||||
|
label={t('settings.sections.system.fields.autoLaunch')}
|
||||||
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_auto_launch ?? false}
|
value={enable_auto_launch ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
@@ -64,6 +72,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.sections.system.fields.silentStart"
|
||||||
label={t('settings.sections.system.fields.silentStart')}
|
label={t('settings.sections.system.fields.silentStart')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
<LiteModeViewer ref={liteModeRef} />
|
<LiteModeViewer ref={liteModeRef} />
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.backupSetting"
|
||||||
onClick={() => backupRef.current?.open()}
|
onClick={() => backupRef.current?.open()}
|
||||||
label={t('settings.components.verge.advanced.fields.backupSetting')}
|
label={t('settings.components.verge.advanced.fields.backupSetting')}
|
||||||
extra={
|
extra={
|
||||||
@@ -101,11 +102,13 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.runtimeConfig"
|
||||||
onClick={() => configRef.current?.open()}
|
onClick={() => configRef.current?.open()}
|
||||||
label={t('settings.components.verge.advanced.fields.runtimeConfig')}
|
label={t('settings.components.verge.advanced.fields.runtimeConfig')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.openConfDir"
|
||||||
onClick={openAppDir}
|
onClick={openAppDir}
|
||||||
label={t('settings.components.verge.advanced.fields.openConfDir')}
|
label={t('settings.components.verge.advanced.fields.openConfDir')}
|
||||||
extra={
|
extra={
|
||||||
@@ -117,21 +120,25 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.openCoreDir"
|
||||||
onClick={openCoreDir}
|
onClick={openCoreDir}
|
||||||
label={t('settings.components.verge.advanced.fields.openCoreDir')}
|
label={t('settings.components.verge.advanced.fields.openCoreDir')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.openLogsDir"
|
||||||
onClick={openLogsDir}
|
onClick={openLogsDir}
|
||||||
label={t('settings.components.verge.advanced.fields.openLogsDir')}
|
label={t('settings.components.verge.advanced.fields.openLogsDir')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.checkUpdates"
|
||||||
onClick={onCheckUpdate}
|
onClick={onCheckUpdate}
|
||||||
label={t('settings.components.verge.advanced.fields.checkUpdates')}
|
label={t('settings.components.verge.advanced.fields.checkUpdates')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.openDevTools"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void mutate(() => openDevTools(), {
|
void mutate(() => openDevTools(), {
|
||||||
id: 'open-devtools',
|
id: 'open-devtools',
|
||||||
@@ -142,6 +149,7 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.liteModeSettings"
|
||||||
label={t('settings.components.verge.advanced.fields.liteModeSettings')}
|
label={t('settings.components.verge.advanced.fields.liteModeSettings')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -153,6 +161,7 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.exit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void mutate(() => exitApp(), {
|
void mutate(() => exitApp(), {
|
||||||
id: 'exit-app',
|
id: 'exit-app',
|
||||||
@@ -163,6 +172,7 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.exportDiagnostics"
|
||||||
label={t('settings.components.verge.advanced.fields.exportDiagnostics')}
|
label={t('settings.components.verge.advanced.fields.exportDiagnostics')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
@@ -173,6 +183,7 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
|||||||
></SettingItem>
|
></SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.advanced.fields.vergeVersion"
|
||||||
label={t('settings.components.verge.advanced.fields.vergeVersion')}
|
label={t('settings.components.verge.advanced.fields.vergeVersion')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
<UpdateViewer ref={updateRef} />
|
<UpdateViewer ref={updateRef} />
|
||||||
<BackupViewer ref={backupRef} />
|
<BackupViewer ref={backupRef} />
|
||||||
|
|
||||||
<SettingItem label={t('settings.components.verge.basic.fields.language')}>
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.language"
|
||||||
|
label={t('settings.components.verge.basic.fields.language')}
|
||||||
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={language ?? 'en'}
|
value={language ?? 'en'}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
@@ -119,6 +122,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.themeMode"
|
||||||
label={t('settings.components.verge.basic.fields.themeMode')}
|
label={t('settings.components.verge.basic.fields.themeMode')}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
@@ -133,6 +137,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
|
|
||||||
{OS !== 'linux' && (
|
{OS !== 'linux' && (
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.trayClickEvent"
|
||||||
label={t('settings.components.verge.basic.fields.trayClickEvent')}
|
label={t('settings.components.verge.basic.fields.trayClickEvent')}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
@@ -166,6 +171,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.copyEnvType"
|
||||||
label={t('settings.components.verge.basic.fields.copyEnvType')}
|
label={t('settings.components.verge.basic.fields.copyEnvType')}
|
||||||
extra={
|
extra={
|
||||||
<TooltipIcon icon={ContentCopyRounded} onClick={onCopyClashEnv} />
|
<TooltipIcon icon={ContentCopyRounded} onClick={onCopyClashEnv} />
|
||||||
@@ -189,6 +195,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.startPage"
|
||||||
label={t('settings.components.verge.basic.fields.startPage')}
|
label={t('settings.components.verge.basic.fields.startPage')}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
@@ -211,6 +218,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.startupScript"
|
||||||
label={t('settings.components.verge.basic.fields.startupScript')}
|
label={t('settings.components.verge.basic.fields.startupScript')}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
@@ -228,6 +236,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
endAdornment={
|
endAdornment={
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
data-setting-target="settings.components.verge.basic.actions.browse"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const selected = await open({
|
const selected = await open({
|
||||||
directory: false,
|
directory: false,
|
||||||
@@ -264,21 +273,25 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.themeSetting"
|
||||||
onClick={() => themeRef.current?.open()}
|
onClick={() => themeRef.current?.open()}
|
||||||
label={t('settings.components.verge.basic.fields.themeSetting')}
|
label={t('settings.components.verge.basic.fields.themeSetting')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.layoutSetting"
|
||||||
onClick={() => layoutRef.current?.open()}
|
onClick={() => layoutRef.current?.open()}
|
||||||
label={t('settings.components.verge.basic.fields.layoutSetting')}
|
label={t('settings.components.verge.basic.fields.layoutSetting')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.misc"
|
||||||
onClick={() => miscRef.current?.open()}
|
onClick={() => miscRef.current?.open()}
|
||||||
label={t('settings.components.verge.basic.fields.misc')}
|
label={t('settings.components.verge.basic.fields.misc')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
|
data-setting-target="settings.components.verge.basic.fields.hotkeySetting"
|
||||||
onClick={() => hotkeyRef.current?.open()}
|
onClick={() => hotkeyRef.current?.open()}
|
||||||
label={t('settings.components.verge.basic.fields.hotkeySetting')}
|
label={t('settings.components.verge.basic.fields.hotkeySetting')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,645 @@
|
|||||||
|
import type { TranslationKey } from '@/types/generated/i18n-keys'
|
||||||
|
|
||||||
|
export interface SettingEntry {
|
||||||
|
id: string
|
||||||
|
name: TranslationKey
|
||||||
|
aliases: string
|
||||||
|
section: TranslationKey
|
||||||
|
path: TranslationKey
|
||||||
|
prerequisite?: string
|
||||||
|
target?: string
|
||||||
|
fallback?: string
|
||||||
|
dialog?: string
|
||||||
|
platforms?: readonly string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupDefaults = Pick<SettingEntry, 'section' | 'path'> &
|
||||||
|
Partial<
|
||||||
|
Pick<SettingEntry, 'dialog' | 'fallback' | 'prerequisite' | 'aliases'>
|
||||||
|
>
|
||||||
|
type EntryOptions = Partial<Omit<SettingEntry, 'name'>>
|
||||||
|
type GroupEntries = Partial<Record<TranslationKey, string | EntryOptions>>
|
||||||
|
|
||||||
|
function group(defaults: GroupDefaults, entries: GroupEntries): SettingEntry[] {
|
||||||
|
return (
|
||||||
|
Object.entries(entries) as [TranslationKey, string | EntryOptions][]
|
||||||
|
).map(([name, value]) => {
|
||||||
|
const options = typeof value === 'string' ? { aliases: value } : value
|
||||||
|
return {
|
||||||
|
...defaults,
|
||||||
|
...options,
|
||||||
|
id: options.id ?? name,
|
||||||
|
name,
|
||||||
|
aliases: [options.aliases, defaults.aliases].filter(Boolean).join(' '),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const system = {
|
||||||
|
section: 'settings.sections.system.title',
|
||||||
|
path: 'settings.sections.system.title',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const tun = {
|
||||||
|
section: 'settings.sections.system.title',
|
||||||
|
path: 'settings.modals.tun.title',
|
||||||
|
dialog: 'tun-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const sysproxy = {
|
||||||
|
section: 'settings.sections.system.title',
|
||||||
|
path: 'settings.modals.sysproxy.title',
|
||||||
|
dialog: 'sysproxy-viewer',
|
||||||
|
fallback: 'settings.modals.sysproxy.fields.usePacMode',
|
||||||
|
prerequisite: 'sysproxy-status',
|
||||||
|
aliases: '代理 PAC bypass 绕过',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const clash = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.clash.title',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const networkInterface = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.clash.title',
|
||||||
|
dialog: 'network-interface-viewer',
|
||||||
|
aliases: 'network interface viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const dns = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.modals.dns.dialog.title',
|
||||||
|
dialog: 'dns-viewer',
|
||||||
|
fallback: 'dns-mode',
|
||||||
|
aliases: 'DNS 域名解析 DNS 域名解析',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const dnsReset = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.modals.dns.dialog.title',
|
||||||
|
dialog: 'dns-viewer',
|
||||||
|
aliases: 'dns viewer reset DNS 域名解析',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const clashPort = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.modals.clashPort.title',
|
||||||
|
dialog: 'clash-port-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const controller = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.externalController.title',
|
||||||
|
dialog: 'controller-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const externalControllerCors = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.externalCors.title',
|
||||||
|
dialog: 'external-controller-cors',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const webUi = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.clash.title',
|
||||||
|
dialog: 'web-ui-viewer',
|
||||||
|
aliases: 'web ui viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const webUiActions = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.modals.webUI.title',
|
||||||
|
dialog: 'web-ui-viewer',
|
||||||
|
fallback: 'web-ui-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const clashCore = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.clash.form.fields.clashCore',
|
||||||
|
dialog: 'clash-core-viewer',
|
||||||
|
fallback: 'core-variants',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const coreVariants = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.clash.title',
|
||||||
|
dialog: 'clash-core-viewer',
|
||||||
|
aliases: 'core variants',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const tunnels = {
|
||||||
|
section: 'settings.sections.clash.title',
|
||||||
|
path: 'settings.sections.clash.form.fields.tunnels.title',
|
||||||
|
dialog: 'tunnels-viewer',
|
||||||
|
fallback: 'tunnels-expand',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const basic = {
|
||||||
|
section: 'settings.components.verge.basic.title',
|
||||||
|
path: 'settings.components.verge.basic.title',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
section: 'settings.components.verge.basic.title',
|
||||||
|
path: 'settings.components.verge.theme.title',
|
||||||
|
dialog: 'theme-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const layout = {
|
||||||
|
section: 'settings.components.verge.basic.title',
|
||||||
|
path: 'settings.components.verge.layout.title',
|
||||||
|
dialog: 'layout-viewer',
|
||||||
|
aliases: 'browse layout 布局 托盘',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const layoutFields = {
|
||||||
|
section: 'settings.components.verge.basic.title',
|
||||||
|
path: 'settings.components.verge.basic.fields.layoutSetting',
|
||||||
|
dialog: 'layout-viewer',
|
||||||
|
aliases: 'layout 布局 托盘',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const misc = {
|
||||||
|
section: 'settings.components.verge.basic.title',
|
||||||
|
path: 'settings.modals.misc.title',
|
||||||
|
dialog: 'misc-viewer',
|
||||||
|
aliases: 'misc 杂项',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const hotkey = {
|
||||||
|
section: 'settings.components.verge.basic.title',
|
||||||
|
path: 'settings.modals.hotkey.title',
|
||||||
|
dialog: 'hotkey-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const advanced = {
|
||||||
|
section: 'settings.components.verge.advanced.title',
|
||||||
|
path: 'settings.components.verge.advanced.title',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const backup = {
|
||||||
|
section: 'settings.components.verge.advanced.title',
|
||||||
|
path: 'settings.modals.backup.title',
|
||||||
|
dialog: 'backup-viewer',
|
||||||
|
aliases: 'backup 备份',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const backupHistory = {
|
||||||
|
section: 'settings.components.verge.advanced.title',
|
||||||
|
path: 'settings.modals.backup.title',
|
||||||
|
dialog: 'backup-history',
|
||||||
|
fallback: 'backup-history',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const backupWebdav = {
|
||||||
|
section: 'settings.components.verge.advanced.title',
|
||||||
|
path: 'settings.modals.backup.webdav.title',
|
||||||
|
dialog: 'backup-webdav',
|
||||||
|
fallback: 'webdav-state',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const liteMode = {
|
||||||
|
section: 'settings.components.verge.advanced.title',
|
||||||
|
path: 'settings.modals.liteMode.title',
|
||||||
|
dialog: 'lite-mode-viewer',
|
||||||
|
} satisfies GroupDefaults
|
||||||
|
|
||||||
|
const settingsCatalog: readonly SettingEntry[] = [
|
||||||
|
...group(system, {
|
||||||
|
'settings.sections.system.toggles.tunMode': 'TUN 虚拟网卡 vpn',
|
||||||
|
'settings.sections.proxyControl.actions.uninstallService': {
|
||||||
|
id: 'service-uninstall',
|
||||||
|
fallback: 'settings.sections.system.toggles.tunMode',
|
||||||
|
aliases: 'service uninstall',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(tun, {
|
||||||
|
'settings.modals.tun.fields.stack': 'stack TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.dnsMode': 'dns mode TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.device': 'device TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.autoRoute': 'auto route TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.autoRedirect': {
|
||||||
|
aliases: 'auto redirect TUN 虚拟网卡',
|
||||||
|
prerequisite: 'settings.modals.tun.fields.autoRoute',
|
||||||
|
platforms: ['linux'],
|
||||||
|
},
|
||||||
|
'settings.modals.tun.fields.strictRoute': 'strict route TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.autoDetectInterface':
|
||||||
|
'auto detect interface TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.dnsHijack': 'dns hijack TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.mtu': 'mtu TUN 虚拟网卡',
|
||||||
|
'settings.modals.tun.fields.routeExcludeAddress': {
|
||||||
|
aliases: 'route exclude address TUN 虚拟网卡',
|
||||||
|
prerequisite: 'settings.modals.tun.fields.autoRoute',
|
||||||
|
},
|
||||||
|
'shared.actions.resetToDefault': {
|
||||||
|
id: 'tun-viewer-reset',
|
||||||
|
aliases: 'tun viewer reset',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(system, {
|
||||||
|
'settings.sections.system.toggles.systemProxy': 'system proxy 系统代理',
|
||||||
|
}),
|
||||||
|
...group(sysproxy, {
|
||||||
|
'settings.modals.sysproxy.fields.proxyHost': 'proxy host',
|
||||||
|
'settings.modals.sysproxy.fields.usePacMode': 'use pac mode',
|
||||||
|
'settings.modals.sysproxy.fields.proxyGuard': 'proxy guard',
|
||||||
|
'settings.modals.sysproxy.fields.guardDuration': 'guard duration',
|
||||||
|
'settings.modals.sysproxy.fields.alwaysUseDefaultBypass':
|
||||||
|
'always use default bypass',
|
||||||
|
'settings.modals.sysproxy.fields.enableBypassCheck': 'enable bypass check',
|
||||||
|
'settings.modals.sysproxy.fields.proxyBypass': 'proxy bypass',
|
||||||
|
'settings.modals.sysproxy.fields.pacScriptContent': 'pac script content',
|
||||||
|
'settings.modals.sysproxy.actions.editPac': 'edit pac',
|
||||||
|
}),
|
||||||
|
...group(system, {
|
||||||
|
'settings.sections.system.fields.autoLaunch': 'auto launch ',
|
||||||
|
'settings.sections.system.fields.silentStart': 'silent start ',
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.allowLan': 'allow lan ',
|
||||||
|
}),
|
||||||
|
...group(networkInterface, {
|
||||||
|
'settings.modals.networkInterface.title': {
|
||||||
|
id: 'network-interface-viewer',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.dnsOverwrite': 'dns overwrite ',
|
||||||
|
}),
|
||||||
|
...group(dns, {
|
||||||
|
'settings.modals.dns.fields.enable': 'enable',
|
||||||
|
'settings.modals.dns.fields.listen': 'listen',
|
||||||
|
'settings.modals.dns.fields.enhancedMode': 'enhancedMode',
|
||||||
|
'settings.modals.dns.fields.fakeIpRange': 'fakeIpRange',
|
||||||
|
'settings.modals.dns.fields.fakeIpRange6': 'fakeIpRange6',
|
||||||
|
'settings.modals.dns.fields.fakeIpFilterMode': 'fakeIpFilterMode',
|
||||||
|
'settings.modals.dns.fields.ipv6.label': 'label',
|
||||||
|
'settings.modals.dns.fields.preferH3.label': 'label',
|
||||||
|
'settings.modals.dns.fields.respectRules.label': 'label',
|
||||||
|
'settings.modals.dns.fields.useHosts.label': 'label',
|
||||||
|
'settings.modals.dns.fields.useSystemHosts.label': 'label',
|
||||||
|
'settings.modals.dns.fields.directPolicy.label': 'label',
|
||||||
|
'settings.modals.dns.fields.defaultNameserver.label': 'label',
|
||||||
|
'settings.modals.dns.fields.nameserver.label': 'label',
|
||||||
|
'settings.modals.dns.fields.fallback.label': 'label',
|
||||||
|
'settings.modals.dns.fields.proxy.label': 'label',
|
||||||
|
'settings.modals.dns.fields.directNameserver.label': 'label',
|
||||||
|
'settings.modals.dns.fields.fakeIpFilter.label': 'label',
|
||||||
|
'settings.modals.dns.fields.nameserverPolicy.label': 'label',
|
||||||
|
'settings.modals.dns.fields.geoipFiltering.label': 'label',
|
||||||
|
'settings.modals.dns.fields.geoipCode': 'geoipCode',
|
||||||
|
'settings.modals.dns.fields.fallbackIpCidr.label': 'label',
|
||||||
|
'settings.modals.dns.fields.fallbackDomain.label': 'label',
|
||||||
|
'settings.modals.dns.fields.hosts.label': 'label',
|
||||||
|
}),
|
||||||
|
...group(dnsReset, {
|
||||||
|
'shared.actions.resetToDefault': {
|
||||||
|
id: 'dns-viewer-reset',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.ipv6': 'ipv6 ',
|
||||||
|
'settings.sections.clash.form.fields.unifiedDelay': 'unified delay ',
|
||||||
|
'settings.sections.clash.form.fields.logLevel': 'log level ',
|
||||||
|
'settings.sections.clash.form.fields.portConfig': 'port config ',
|
||||||
|
}),
|
||||||
|
...group(clashPort, {
|
||||||
|
'settings.modals.clashPort.fields.socks': 'clash port viewer',
|
||||||
|
'settings.modals.clashPort.fields.http': 'clash port viewer',
|
||||||
|
'settings.modals.clashPort.fields.redir': {
|
||||||
|
aliases: 'clash port viewer',
|
||||||
|
platforms: ['macos', 'linux'],
|
||||||
|
},
|
||||||
|
'settings.modals.clashPort.fields.tproxy': {
|
||||||
|
aliases: 'clash port viewer',
|
||||||
|
platforms: ['linux'],
|
||||||
|
},
|
||||||
|
'settings.modals.clashPort.fields.mixed': 'clash port viewer',
|
||||||
|
'settings.modals.clashPort.actions.random': {
|
||||||
|
id: 'port-random',
|
||||||
|
aliases: 'port random',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.external': 'external ',
|
||||||
|
}),
|
||||||
|
...group(controller, {
|
||||||
|
'settings.sections.externalController.fields.enable': 'enable ',
|
||||||
|
'settings.sections.externalController.fields.address': {
|
||||||
|
aliases: 'address ',
|
||||||
|
prerequisite: 'settings.sections.externalController.fields.enable',
|
||||||
|
},
|
||||||
|
'settings.sections.externalController.fields.secret': {
|
||||||
|
aliases: 'secret ',
|
||||||
|
prerequisite: 'settings.sections.externalController.fields.enable',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(externalControllerCors, {
|
||||||
|
'settings.sections.externalCors.fields.allowPrivateNetwork':
|
||||||
|
'allow private network ',
|
||||||
|
'settings.sections.externalCors.fields.allowedOrigins': 'allowed origins ',
|
||||||
|
'settings.sections.externalCors.actions.add': 'add ',
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.webUI': 'web ui ',
|
||||||
|
}),
|
||||||
|
...group(webUi, {
|
||||||
|
'settings.modals.webUI.title': {
|
||||||
|
id: 'web-ui-viewer',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(webUiActions, {
|
||||||
|
'shared.actions.new': {
|
||||||
|
id: 'web-ui-new',
|
||||||
|
aliases: 'web ui new',
|
||||||
|
},
|
||||||
|
'shared.actions.edit': {
|
||||||
|
id: 'web-ui-edit',
|
||||||
|
aliases: 'web ui edit',
|
||||||
|
},
|
||||||
|
'shared.actions.delete': {
|
||||||
|
id: 'web-ui-delete',
|
||||||
|
aliases: 'web ui delete',
|
||||||
|
},
|
||||||
|
'settings.modals.webUI.actions.openUrl': {
|
||||||
|
id: 'web-ui-open',
|
||||||
|
aliases: 'web ui open',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.clashCore': 'clash core ',
|
||||||
|
}),
|
||||||
|
...group(clashCore, {
|
||||||
|
'shared.actions.upgrade': {
|
||||||
|
id: 'core-upgrade',
|
||||||
|
aliases: 'core upgrade',
|
||||||
|
},
|
||||||
|
'shared.actions.restart': {
|
||||||
|
id: 'core-restart',
|
||||||
|
aliases: 'core restart',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(coreVariants, {
|
||||||
|
'settings.sections.clash.form.fields.clashCore': {
|
||||||
|
id: 'core-variants',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(clash, {
|
||||||
|
'settings.sections.clash.form.fields.openUwpTool': {
|
||||||
|
aliases: 'open uwp tool ',
|
||||||
|
platforms: ['windows'],
|
||||||
|
},
|
||||||
|
'settings.sections.clash.form.fields.updateGeoData': 'update geo data ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.title': 'title ',
|
||||||
|
}),
|
||||||
|
...group(tunnels, {
|
||||||
|
'settings.sections.clash.form.fields.tunnels.existing': 'existing ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.default': 'default ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.protocols': 'protocols ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.localAddr': 'local addr ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.localPort': 'local port ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.targetAddr': 'target addr ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.targetPort': 'target port ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.proxyGroup': 'proxy group ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.proxyNode': 'proxy node ',
|
||||||
|
'settings.sections.clash.form.fields.tunnels.actions.add': 'add ',
|
||||||
|
}),
|
||||||
|
...group(basic, {
|
||||||
|
'settings.components.verge.basic.fields.language': 'language ',
|
||||||
|
'settings.components.verge.basic.fields.themeMode': 'theme mode ',
|
||||||
|
'settings.components.verge.basic.fields.trayClickEvent': {
|
||||||
|
aliases: 'tray click event ',
|
||||||
|
platforms: ['windows', 'macos'],
|
||||||
|
},
|
||||||
|
'settings.components.verge.basic.fields.copyEnvType': 'copy env type ',
|
||||||
|
'settings.components.verge.basic.fields.startPage': 'start page ',
|
||||||
|
'settings.components.verge.basic.fields.startupScript': 'startup script ',
|
||||||
|
'settings.components.verge.basic.fields.themeSetting': 'theme setting ',
|
||||||
|
}),
|
||||||
|
...group(theme, {
|
||||||
|
'settings.components.verge.theme.actions.editCss':
|
||||||
|
'edit css theme 主题 外观',
|
||||||
|
'settings.components.verge.theme.fields.fontFamily':
|
||||||
|
'fontFamily theme 主题 外观',
|
||||||
|
'settings.components.verge.theme.fields.cssInjection':
|
||||||
|
'cssInjection theme 主题 外观',
|
||||||
|
'settings.components.verge.theme.fields.primaryColor': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.secondaryColor': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.primaryText': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.secondaryText': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.infoColor': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.warningColor': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.errorColor': 'theme viewer',
|
||||||
|
'settings.components.verge.theme.fields.successColor': 'theme viewer',
|
||||||
|
}),
|
||||||
|
...group(basic, {
|
||||||
|
'settings.components.verge.basic.fields.layoutSetting': 'layout setting ',
|
||||||
|
}),
|
||||||
|
...group(layout, {
|
||||||
|
'settings.components.verge.basic.actions.browse': '',
|
||||||
|
}),
|
||||||
|
...group(layoutFields, {
|
||||||
|
'settings.components.verge.layout.fields.preferSystemTitlebar':
|
||||||
|
'preferSystemTitlebar',
|
||||||
|
'settings.components.verge.layout.fields.trafficGraph': 'trafficGraph',
|
||||||
|
'settings.components.verge.layout.fields.memoryUsage': 'memoryUsage',
|
||||||
|
'settings.components.verge.layout.fields.proxyGroupIcon': 'proxyGroupIcon',
|
||||||
|
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur':
|
||||||
|
'pauseRenderTrafficStatsOnBlur',
|
||||||
|
'settings.components.verge.layout.fields.toastPosition': 'toastPosition',
|
||||||
|
'settings.components.verge.layout.fields.hoverNavigator': 'hoverNavigator',
|
||||||
|
'settings.components.verge.layout.fields.hoverNavigatorDelay':
|
||||||
|
'hoverNavigatorDelay',
|
||||||
|
'settings.components.verge.layout.fields.navIcon': 'navIcon',
|
||||||
|
'settings.components.verge.layout.fields.collapseNavBar': 'collapseNavBar',
|
||||||
|
'settings.components.verge.layout.fields.trayIcon': {
|
||||||
|
aliases: 'trayIcon',
|
||||||
|
platforms: ['macos'],
|
||||||
|
},
|
||||||
|
'settings.components.verge.layout.fields.enableTraySpeed': {
|
||||||
|
aliases: 'enableTraySpeed',
|
||||||
|
platforms: ['macos'],
|
||||||
|
},
|
||||||
|
'settings.components.verge.layout.fields.proxyGroupsDisplayMode':
|
||||||
|
'proxyGroupsDisplayMode',
|
||||||
|
'settings.components.verge.layout.fields.showOutboundModesInline':
|
||||||
|
'showOutboundModesInline',
|
||||||
|
'settings.components.verge.layout.fields.commonTrayIcon': 'commonTrayIcon',
|
||||||
|
'settings.components.verge.layout.fields.systemProxyTrayIcon':
|
||||||
|
'systemProxyTrayIcon',
|
||||||
|
'settings.components.verge.layout.fields.tunTrayIcon': 'tunTrayIcon',
|
||||||
|
}),
|
||||||
|
...group(basic, {
|
||||||
|
'settings.components.verge.basic.fields.misc': 'misc ',
|
||||||
|
}),
|
||||||
|
...group(misc, {
|
||||||
|
'settings.modals.misc.fields.appLogLevel': 'app log level',
|
||||||
|
'settings.modals.misc.fields.appLogMaxSize': 'app log max size',
|
||||||
|
'settings.modals.misc.fields.appLogMaxCount': 'app log max count',
|
||||||
|
'settings.modals.misc.fields.autoCloseConnections':
|
||||||
|
'auto close connections',
|
||||||
|
'settings.modals.misc.fields.autoCheckUpdate': 'auto check update',
|
||||||
|
'settings.modals.misc.fields.enableBuiltinEnhanced':
|
||||||
|
'enable builtin enhanced',
|
||||||
|
'settings.modals.misc.fields.proxyLayoutColumns': 'proxy layout columns',
|
||||||
|
'settings.modals.misc.fields.autoLogClean': 'auto log clean',
|
||||||
|
'settings.modals.misc.fields.autoDelayDetection': 'auto delay detection',
|
||||||
|
'settings.modals.misc.fields.autoDelayDetectionInterval': {
|
||||||
|
aliases: 'auto delay detection interval',
|
||||||
|
prerequisite: 'settings.modals.misc.fields.autoDelayDetection',
|
||||||
|
},
|
||||||
|
'settings.modals.misc.fields.defaultLatencyTest': 'default latency test',
|
||||||
|
'settings.modals.misc.fields.defaultLatencyTimeout':
|
||||||
|
'default latency timeout',
|
||||||
|
}),
|
||||||
|
...group(basic, {
|
||||||
|
'settings.components.verge.basic.fields.hotkeySetting': 'hotkey setting ',
|
||||||
|
}),
|
||||||
|
...group(hotkey, {
|
||||||
|
'settings.modals.hotkey.functions.openOrCloseDashboard': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.rule': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.global': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.direct': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.toggleSystemProxy': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.toggleTunMode': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.entryLightweightMode': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.functions.reactivateProfiles': 'hotkey viewer',
|
||||||
|
'settings.modals.hotkey.toggles.enableGlobal':
|
||||||
|
'settings.modals.hotkey.toggles.enableGlobal',
|
||||||
|
}),
|
||||||
|
...group(advanced, {
|
||||||
|
'settings.components.verge.advanced.fields.backupSetting':
|
||||||
|
'backup setting ',
|
||||||
|
}),
|
||||||
|
...group(backup, {
|
||||||
|
'settings.modals.backup.actions.backup': 'backup',
|
||||||
|
}),
|
||||||
|
...group(backupHistory, {
|
||||||
|
'settings.modals.backup.actions.viewHistory': {
|
||||||
|
aliases: 'view history backup 备份',
|
||||||
|
target: 'backup-history',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(backup, {
|
||||||
|
'settings.modals.backup.actions.importBackup': 'import backup',
|
||||||
|
}),
|
||||||
|
...group(backupWebdav, {
|
||||||
|
'settings.modals.backup.fields.webdavUrl': 'webdavUrl WebDAV 云备份',
|
||||||
|
'settings.modals.backup.fields.username': 'username WebDAV 云备份',
|
||||||
|
}),
|
||||||
|
...group(backup, {
|
||||||
|
'settings.modals.backup.auto.scheduleLabel': 'scheduleLabel',
|
||||||
|
'settings.modals.backup.auto.intervalLabel': {
|
||||||
|
aliases: 'intervalLabel',
|
||||||
|
prerequisite: 'settings.modals.backup.auto.scheduleLabel',
|
||||||
|
},
|
||||||
|
'settings.modals.backup.auto.changeLabel': 'changeLabel',
|
||||||
|
}),
|
||||||
|
...group(backupHistory, {
|
||||||
|
'settings.modals.backup.history.title': {
|
||||||
|
aliases: '备份 历史 恢复 删除 restore delete history',
|
||||||
|
target: 'backup-history',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(backupWebdav, {
|
||||||
|
'shared.labels.password': {
|
||||||
|
id: 'webdav-password',
|
||||||
|
aliases: 'webdav password',
|
||||||
|
},
|
||||||
|
'shared.actions.save': {
|
||||||
|
id: 'webdav-save',
|
||||||
|
aliases: 'webdav save',
|
||||||
|
},
|
||||||
|
'settings.modals.backup.actions.backup': {
|
||||||
|
id: 'webdav-backup',
|
||||||
|
aliases: 'webdav backup',
|
||||||
|
},
|
||||||
|
'shared.actions.refresh': {
|
||||||
|
id: 'webdav-refresh',
|
||||||
|
aliases: 'webdav refresh',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(backupHistory, {
|
||||||
|
'settings.modals.backup.actions.deleteBackup': {
|
||||||
|
id: 'backup-history-delete',
|
||||||
|
aliases: 'backup history delete',
|
||||||
|
path: 'settings.modals.backup.history.title',
|
||||||
|
},
|
||||||
|
'settings.modals.backup.actions.restoreBackup': {
|
||||||
|
id: 'backup-history-restore',
|
||||||
|
aliases: 'backup history restore',
|
||||||
|
path: 'settings.modals.backup.history.title',
|
||||||
|
},
|
||||||
|
'settings.modals.backup.actions.exportBackup': {
|
||||||
|
id: 'backup-history-export',
|
||||||
|
aliases: 'backup history export',
|
||||||
|
path: 'settings.modals.backup.history.title',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(advanced, {
|
||||||
|
'settings.components.verge.advanced.fields.runtimeConfig':
|
||||||
|
'runtime config ',
|
||||||
|
'settings.components.verge.advanced.fields.openConfDir': 'open conf dir ',
|
||||||
|
'settings.components.verge.advanced.fields.openCoreDir': 'open core dir ',
|
||||||
|
'settings.components.verge.advanced.fields.openLogsDir': 'open logs dir ',
|
||||||
|
'settings.components.verge.advanced.fields.checkUpdates': 'check updates ',
|
||||||
|
'settings.components.verge.advanced.fields.openDevTools': 'open dev tools ',
|
||||||
|
'settings.components.verge.advanced.fields.liteModeSettings':
|
||||||
|
'lite mode settings ',
|
||||||
|
}),
|
||||||
|
...group(liteMode, {
|
||||||
|
'settings.modals.liteMode.actions.enterNow': 'enter now ',
|
||||||
|
'settings.modals.liteMode.toggles.autoEnter': 'auto enter ',
|
||||||
|
'settings.modals.liteMode.fields.delay': {
|
||||||
|
fallback: 'settings.modals.liteMode.toggles.autoEnter',
|
||||||
|
aliases: 'delay ',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...group(advanced, {
|
||||||
|
'settings.components.verge.advanced.fields.exit': 'exit ',
|
||||||
|
'settings.components.verge.advanced.fields.exportDiagnostics':
|
||||||
|
'export diagnostics ',
|
||||||
|
'settings.components.verge.advanced.fields.vergeVersion': 'verge version ',
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export function searchSettings(
|
||||||
|
query: string,
|
||||||
|
platform: string,
|
||||||
|
translate: (key: TranslationKey) => string,
|
||||||
|
english: (key: TranslationKey) => string,
|
||||||
|
) {
|
||||||
|
const words = query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean)
|
||||||
|
if (!words.length) return []
|
||||||
|
return settingsCatalog
|
||||||
|
.filter((e) => !e.platforms || e.platforms.includes(platform))
|
||||||
|
.map((entry, order) => {
|
||||||
|
const names = (
|
||||||
|
translate(entry.name) +
|
||||||
|
' ' +
|
||||||
|
english(entry.name)
|
||||||
|
).toLocaleLowerCase()
|
||||||
|
const other = (
|
||||||
|
translate(entry.section) +
|
||||||
|
' ' +
|
||||||
|
english(entry.section) +
|
||||||
|
' ' +
|
||||||
|
entry.aliases +
|
||||||
|
' ' +
|
||||||
|
translate(entry.path) +
|
||||||
|
' ' +
|
||||||
|
english(entry.path)
|
||||||
|
).toLocaleLowerCase()
|
||||||
|
const score = words.every(
|
||||||
|
(word) => names.includes(word) || other.includes(word),
|
||||||
|
)
|
||||||
|
? words.reduce((sum, word) => sum + (names.includes(word) ? 1 : 0), 0)
|
||||||
|
: -1
|
||||||
|
return { entry, order, score }
|
||||||
|
})
|
||||||
|
.filter((e) => e.score >= 0)
|
||||||
|
.sort((a, b) => b.score - a.score || a.order - b.order)
|
||||||
|
.map((e) => e.entry)
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
use,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
type Ref,
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
|
import type { DialogRef } from '@/components/base'
|
||||||
|
|
||||||
|
export interface SearchDialog {
|
||||||
|
id: string
|
||||||
|
open: () => void
|
||||||
|
isOpen: boolean
|
||||||
|
}
|
||||||
|
export const SettingsSearchContext = createContext<Set<SearchDialog> | null>(
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
|
||||||
|
export function useSettingDialog(
|
||||||
|
ref: Ref<DialogRef> | undefined,
|
||||||
|
id: string,
|
||||||
|
open: boolean,
|
||||||
|
create: () => DialogRef,
|
||||||
|
) {
|
||||||
|
useImperativeHandle(ref, create)
|
||||||
|
const registry = use(SettingsSearchContext)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!registry) return
|
||||||
|
const handle = {
|
||||||
|
id,
|
||||||
|
isOpen: open,
|
||||||
|
open: () => {
|
||||||
|
if (!open) create().open()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
registry.add(handle)
|
||||||
|
return () => {
|
||||||
|
registry.delete(handle)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
import { CloseRounded } from '@mui/icons-material'
|
||||||
|
import {
|
||||||
|
GlobalStyles,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material'
|
||||||
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
import getSystem from '@/utils/get-system'
|
||||||
|
|
||||||
|
import { searchSettings, type SettingEntry } from './settings-catalog'
|
||||||
|
import {
|
||||||
|
SettingsSearchContext,
|
||||||
|
type SearchDialog,
|
||||||
|
} from './settings-search-context'
|
||||||
|
|
||||||
|
export function SettingsSearch({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: (
|
||||||
|
header: ReactNode,
|
||||||
|
results: ReactNode,
|
||||||
|
hidden: boolean,
|
||||||
|
) => ReactNode
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [registry] = useState(() => new Set<SearchDialog>())
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
const [selected, setSelected] = useState(0)
|
||||||
|
const [showPage, setShowPage] = useState(false)
|
||||||
|
const rootRef = useRef<HTMLDivElement>(null)
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const originalScrollRef = useRef(0)
|
||||||
|
const resultScrollRef = useRef(0)
|
||||||
|
const cancelLocateRef = useRef<() => void>(() => {})
|
||||||
|
const results = searchSettings(
|
||||||
|
query,
|
||||||
|
getSystem(),
|
||||||
|
(key) => t(key),
|
||||||
|
(key) => t(key, { lng: 'en' }),
|
||||||
|
)
|
||||||
|
const searching = !!query.trim()
|
||||||
|
const scroller = () =>
|
||||||
|
rootRef.current?.querySelector('.base-container > section')
|
||||||
|
const restoreResults = () => {
|
||||||
|
cancelLocateRef.current()
|
||||||
|
setShowPage(false)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
scroller()?.scrollTo({ top: resultScrollRef.current })
|
||||||
|
inputRef.current?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const changeQuery = (value: string) => {
|
||||||
|
cancelLocateRef.current()
|
||||||
|
if (!searching && value.trim())
|
||||||
|
originalScrollRef.current = scroller()?.scrollTop ?? 0
|
||||||
|
setQuery(value)
|
||||||
|
setSelected(0)
|
||||||
|
setShowPage(false)
|
||||||
|
resultScrollRef.current = 0
|
||||||
|
requestAnimationFrame(() =>
|
||||||
|
scroller()?.scrollTo({
|
||||||
|
top: value.trim() ? 0 : originalScrollRef.current,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const locate = (entry: SettingEntry) => {
|
||||||
|
cancelLocateRef.current()
|
||||||
|
if (!showPage) resultScrollRef.current = scroller()?.scrollTop ?? 0
|
||||||
|
if (entry.dialog) {
|
||||||
|
const handles = [...registry].filter(
|
||||||
|
(handle) => handle.id === entry.dialog,
|
||||||
|
)
|
||||||
|
;(handles.find((handle) => handle.isOpen) ?? handles[0])?.open()
|
||||||
|
} else setShowPage(true)
|
||||||
|
let highlighted: HTMLElement | null = null
|
||||||
|
let stopped = false
|
||||||
|
let sawScope = false
|
||||||
|
let temporaryTabIndex = false
|
||||||
|
const cleanup = () => {
|
||||||
|
stopped = true
|
||||||
|
observer.disconnect()
|
||||||
|
clearTimeout(timeout)
|
||||||
|
highlighted?.removeAttribute('data-search-highlight')
|
||||||
|
if (temporaryTabIndex) highlighted?.removeAttribute('tabindex')
|
||||||
|
}
|
||||||
|
const find = (timedOut = false) => {
|
||||||
|
if (stopped) return
|
||||||
|
const marker = entry.dialog
|
||||||
|
? [
|
||||||
|
...document.querySelectorAll(
|
||||||
|
`[data-settings-dialog="${entry.dialog}"]`,
|
||||||
|
),
|
||||||
|
].find(
|
||||||
|
(marker) =>
|
||||||
|
marker.closest('[role="dialog"]')?.getClientRects().length,
|
||||||
|
)
|
||||||
|
: rootRef.current
|
||||||
|
const scope = entry.dialog ? marker?.closest('[role="dialog"]') : marker
|
||||||
|
if (!scope) {
|
||||||
|
if (sawScope) {
|
||||||
|
cleanup()
|
||||||
|
inputRef.current?.focus()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (highlighted) return
|
||||||
|
sawScope = true
|
||||||
|
const pending = marker?.getAttribute('data-search-ready') === 'false'
|
||||||
|
if (pending && !timedOut) return
|
||||||
|
let target = pending
|
||||||
|
? (scope.querySelector<HTMLElement>('[data-settings-loading]') ??
|
||||||
|
(marker instanceof HTMLElement ? marker : null))
|
||||||
|
: scope.querySelector<HTMLElement>(
|
||||||
|
`[data-setting-target="${entry.target ?? entry.id}"]`,
|
||||||
|
)
|
||||||
|
const fields = target
|
||||||
|
? [
|
||||||
|
...target.querySelectorAll<HTMLElement>(
|
||||||
|
'input,textarea,select,[role="combobox"]',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
const disabled =
|
||||||
|
target?.matches(':disabled,[aria-disabled="true"]') ||
|
||||||
|
(fields.length > 0 &&
|
||||||
|
fields.every((field) =>
|
||||||
|
field.matches(':disabled,[aria-disabled="true"]'),
|
||||||
|
))
|
||||||
|
let fallback = pending
|
||||||
|
if (!target || !target.getClientRects().length || disabled) {
|
||||||
|
const fallbackId = disabled
|
||||||
|
? (entry.prerequisite ?? entry.fallback)
|
||||||
|
: entry.fallback
|
||||||
|
if (fallbackId) {
|
||||||
|
target = scope.querySelector<HTMLElement>(
|
||||||
|
`[data-setting-target="${fallbackId}"]`,
|
||||||
|
)
|
||||||
|
fallback = !disabled || !entry.prerequisite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!target || !target.getClientRects().length) return
|
||||||
|
highlighted = target
|
||||||
|
target.setAttribute('data-search-highlight', 'true')
|
||||||
|
target.scrollIntoView({ block: 'center' })
|
||||||
|
const targetFocusable = target.matches(
|
||||||
|
'button,input,textarea,select,a[href],[tabindex]',
|
||||||
|
)
|
||||||
|
const control =
|
||||||
|
!fallback && !targetFocusable
|
||||||
|
? [
|
||||||
|
...target.querySelectorAll<HTMLElement>(
|
||||||
|
'input:not(:disabled),textarea:not(:disabled),button:not(:disabled),[role="combobox"],a[href]',
|
||||||
|
),
|
||||||
|
].find(
|
||||||
|
(element) =>
|
||||||
|
element.getClientRects().length &&
|
||||||
|
element.getAttribute('aria-disabled') !== 'true',
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
temporaryTabIndex = !control && !targetFocusable
|
||||||
|
if (temporaryTabIndex) target.tabIndex = -1
|
||||||
|
;(control ?? target).focus({ preventScroll: true })
|
||||||
|
}
|
||||||
|
const observer = new MutationObserver(() => find())
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['style', 'class', 'data-search-ready', 'disabled'],
|
||||||
|
})
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!highlighted) {
|
||||||
|
find(true)
|
||||||
|
if (!highlighted) cleanup()
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
cancelLocateRef.current = cleanup
|
||||||
|
requestAnimationFrame(() => find())
|
||||||
|
}
|
||||||
|
useEffect(() => () => cancelLocateRef.current(), [])
|
||||||
|
const moveSelection = (
|
||||||
|
from: number,
|
||||||
|
direction: number,
|
||||||
|
focusResult = false,
|
||||||
|
) => {
|
||||||
|
const next = (from + direction + results.length) % results.length
|
||||||
|
setSelected(next)
|
||||||
|
const target = rootRef.current?.querySelector<HTMLElement>(
|
||||||
|
`[data-search-result="${next}"]`,
|
||||||
|
)
|
||||||
|
if (focusResult) target?.focus()
|
||||||
|
target?.scrollIntoView({ block: 'nearest' })
|
||||||
|
}
|
||||||
|
const header = (
|
||||||
|
<Box
|
||||||
|
sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
inputRef={inputRef}
|
||||||
|
size="small"
|
||||||
|
label={t('settings.search.placeholder')}
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => changeQuery(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault()
|
||||||
|
if (showPage) restoreResults()
|
||||||
|
else changeQuery('')
|
||||||
|
}
|
||||||
|
if (e.key === 'Enter' && results[selected]) {
|
||||||
|
e.preventDefault()
|
||||||
|
locate(results[selected])
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(e.key === 'ArrowDown' || e.key === 'ArrowUp') &&
|
||||||
|
results.length
|
||||||
|
) {
|
||||||
|
e.preventDefault()
|
||||||
|
moveSelection(selected, e.key === 'ArrowDown' ? 1 : -1)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{searching && (
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('settings.search.clear')}
|
||||||
|
onClick={() => changeQuery('')}
|
||||||
|
>
|
||||||
|
<CloseRounded />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
{showPage && (
|
||||||
|
<Button onClick={restoreResults}>{t('settings.search.back')}</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
const resultView =
|
||||||
|
searching && !showPage ? (
|
||||||
|
<List aria-label={t('settings.search.results')}>
|
||||||
|
{results.length ? (
|
||||||
|
results.map((entry, index) => (
|
||||||
|
<ListItemButton
|
||||||
|
key={entry.id}
|
||||||
|
data-search-result={index}
|
||||||
|
selected={selected === index}
|
||||||
|
onFocus={() => setSelected(index)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelected(index)
|
||||||
|
locate(entry)
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault()
|
||||||
|
moveSelection(index, e.key === 'ArrowDown' ? 1 : -1, true)
|
||||||
|
}
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault()
|
||||||
|
inputRef.current?.focus()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemText
|
||||||
|
primary={t(entry.name)}
|
||||||
|
secondary={`${t(entry.section)} / ${t(entry.path)}`}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Typography sx={{ p: 2 }}>{t('settings.search.empty')}</Typography>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
) : null
|
||||||
|
return (
|
||||||
|
<SettingsSearchContext value={registry}>
|
||||||
|
<GlobalStyles
|
||||||
|
styles={{
|
||||||
|
'[data-search-highlight]': {
|
||||||
|
outline: '2px solid #6b70e8',
|
||||||
|
outlineOffset: -2,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
ref={rootRef}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (
|
||||||
|
e.key === 'Escape' &&
|
||||||
|
showPage &&
|
||||||
|
!(e.target as HTMLElement).closest('[role="dialog"]')
|
||||||
|
) {
|
||||||
|
e.stopPropagation()
|
||||||
|
restoreResults()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
'& .base-page > header': {
|
||||||
|
flex: '0 0 auto',
|
||||||
|
minHeight: 58,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 1,
|
||||||
|
py: 1,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children(header, resultView, searching && !showPage)}
|
||||||
|
</Box>
|
||||||
|
</SettingsSearchContext>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -220,6 +220,7 @@ const ProxyControlSwitches = ({
|
|||||||
)}
|
)}
|
||||||
icon={DeleteForeverRounded}
|
icon={DeleteForeverRounded}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
data-setting-target="service-uninstall"
|
||||||
onClick={onUninstallService}
|
onClick={onUninstallService}
|
||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import { BaseDialog } from '@/components/base'
|
import { BaseDialog } from '@/components/base'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
|
import type {
|
||||||
|
IVergeTestItem_Deserialize,
|
||||||
|
IVergeTestItem_Serialize,
|
||||||
|
} from '@/services/bindings'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -27,7 +31,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>(
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const { verge, patchVerge } = useVerge()
|
const { verge, patchVerge } = useVerge()
|
||||||
const testList = verge?.test_list ?? []
|
const testList = verge?.test_list ?? []
|
||||||
const { control, ...formIns } = useForm<IVergeTestItem>({
|
const { control, ...formIns } = useForm<IVergeTestItem_Deserialize>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
@@ -37,11 +41,16 @@ export const TestViewer = forwardRef<TestViewerRef, Props>(
|
|||||||
|
|
||||||
const patchTestList = async (
|
const patchTestList = async (
|
||||||
uid: string,
|
uid: string,
|
||||||
patch: Partial<IVergeTestItem>,
|
patch: Partial<IVergeTestItem_Deserialize>,
|
||||||
) => {
|
) => {
|
||||||
const newList = testList.map((x) => {
|
const newList = testList.map((x) => {
|
||||||
if (x.uid === uid) {
|
if (x.uid === uid) {
|
||||||
return { ...x, ...patch }
|
return {
|
||||||
|
uid: x.uid,
|
||||||
|
name: patch.name === undefined ? x.name : patch.name,
|
||||||
|
icon: patch.icon === undefined ? x.icon : patch.icon,
|
||||||
|
url: patch.url === undefined ? x.url : patch.url,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return x
|
return x
|
||||||
})
|
})
|
||||||
@@ -77,7 +86,12 @@ export const TestViewer = forwardRef<TestViewerRef, Props>(
|
|||||||
|
|
||||||
if (openType === 'new') {
|
if (openType === 'new') {
|
||||||
const uid = nanoid()
|
const uid = nanoid()
|
||||||
const item = { ...form, uid }
|
const item: IVergeTestItem_Serialize = {
|
||||||
|
uid,
|
||||||
|
name: form.name ?? null,
|
||||||
|
icon: form.icon ?? null,
|
||||||
|
url: form.url ?? null,
|
||||||
|
}
|
||||||
const newList = [...testList, item]
|
const newList = [...testList, item]
|
||||||
await patchVerge({ test_list: newList })
|
await patchVerge({ test_list: newList })
|
||||||
onChange(uid)
|
onChange(uid)
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "جاري تثبيت الخدمة...",
|
"installing": "جاري تثبيت الخدمة...",
|
||||||
"uninstalling": "Uninstalling Service..."
|
"uninstalling": "Uninstalling Service..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "البحث في الإعدادات",
|
||||||
|
"clear": "مسح البحث",
|
||||||
|
"back": "العودة إلى النتائج",
|
||||||
|
"results": "نتائج البحث",
|
||||||
|
"empty": "لم يتم العثور على إعدادات"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Service wird installiert...",
|
"installing": "Service wird installiert...",
|
||||||
"uninstalling": "Service wird deinstalliert..."
|
"uninstalling": "Service wird deinstalliert..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Einstellungen suchen",
|
||||||
|
"clear": "Suche löschen",
|
||||||
|
"back": "Zurück zu Ergebnissen",
|
||||||
|
"results": "Suchergebnisse",
|
||||||
|
"empty": "Keine Einstellungen gefunden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Installing Service...",
|
"installing": "Installing Service...",
|
||||||
"uninstalling": "Uninstalling Service..."
|
"uninstalling": "Uninstalling Service..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search settings",
|
||||||
|
"clear": "Clear search",
|
||||||
|
"back": "Back to results",
|
||||||
|
"results": "Search results",
|
||||||
|
"empty": "No settings found"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Instalando servicio...",
|
"installing": "Instalando servicio...",
|
||||||
"uninstalling": "Desinstalando servicio..."
|
"uninstalling": "Desinstalando servicio..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Buscar ajustes",
|
||||||
|
"clear": "Borrar búsqueda",
|
||||||
|
"back": "Volver a resultados",
|
||||||
|
"results": "Resultados de búsqueda",
|
||||||
|
"empty": "No se encontraron ajustes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "در حال نصب سرویس...",
|
"installing": "در حال نصب سرویس...",
|
||||||
"uninstalling": "در حال حذف سرویس..."
|
"uninstalling": "در حال حذف سرویس..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "جستجوی تنظیمات",
|
||||||
|
"clear": "پاک کردن جستجو",
|
||||||
|
"back": "بازگشت به نتایج",
|
||||||
|
"results": "نتایج جستجو",
|
||||||
|
"empty": "تنظیماتی یافت نشد"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Memasang Layanan...",
|
"installing": "Memasang Layanan...",
|
||||||
"uninstalling": "Uninstalling Service..."
|
"uninstalling": "Uninstalling Service..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Cari pengaturan",
|
||||||
|
"clear": "Hapus pencarian",
|
||||||
|
"back": "Kembali ke hasil",
|
||||||
|
"results": "Hasil pencarian",
|
||||||
|
"empty": "Pengaturan tidak ditemukan"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "サービスをインストール中...",
|
"installing": "サービスをインストール中...",
|
||||||
"uninstalling": "サービスをアンインストール中..."
|
"uninstalling": "サービスをアンインストール中..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "設定を検索",
|
||||||
|
"clear": "検索をクリア",
|
||||||
|
"back": "結果に戻る",
|
||||||
|
"results": "検索結果",
|
||||||
|
"empty": "設定が見つかりません"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "서비스 설치 중...",
|
"installing": "서비스 설치 중...",
|
||||||
"uninstalling": "서비스 제거 중..."
|
"uninstalling": "서비스 제거 중..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "설정 검색",
|
||||||
|
"clear": "검색 지우기",
|
||||||
|
"back": "결과로 돌아가기",
|
||||||
|
"results": "검색 결과",
|
||||||
|
"empty": "설정을 찾을 수 없습니다"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Instalando o Serviço...",
|
"installing": "Instalando o Serviço...",
|
||||||
"uninstalling": "Desinstalando o Serviço..."
|
"uninstalling": "Desinstalando o Serviço..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Pesquisar configurações",
|
||||||
|
"clear": "Limpar pesquisa",
|
||||||
|
"back": "Voltar aos resultados",
|
||||||
|
"results": "Resultados da pesquisa",
|
||||||
|
"empty": "Nenhuma configuração encontrada"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Установка службы...",
|
"installing": "Установка службы...",
|
||||||
"uninstalling": "Удаление службы..."
|
"uninstalling": "Удаление службы..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Поиск настроек",
|
||||||
|
"clear": "Очистить поиск",
|
||||||
|
"back": "Вернуться к результатам",
|
||||||
|
"results": "Результаты поиска",
|
||||||
|
"empty": "Настройки не найдены"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Hizmet Kuruluyor...",
|
"installing": "Hizmet Kuruluyor...",
|
||||||
"uninstalling": "Hizmet Kaldırılıyor..."
|
"uninstalling": "Hizmet Kaldırılıyor..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Ayarları ara",
|
||||||
|
"clear": "Aramayı temizle",
|
||||||
|
"back": "Sonuçlara dön",
|
||||||
|
"results": "Arama sonuçları",
|
||||||
|
"empty": "Ayar bulunamadı"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "Хезмәт урнаштырыла...",
|
"installing": "Хезмәт урнаштырыла...",
|
||||||
"uninstalling": "Uninstalling Service..."
|
"uninstalling": "Uninstalling Service..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Көйләүләрне эзләү",
|
||||||
|
"clear": "Эзләүне чистарту",
|
||||||
|
"back": "Нәтиҗәләргә кайту",
|
||||||
|
"results": "Эзләү нәтиҗәләре",
|
||||||
|
"empty": "Көйләүләр табылмады"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "安装服务中...",
|
"installing": "安装服务中...",
|
||||||
"uninstalling": "卸载服务中..."
|
"uninstalling": "卸载服务中..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "搜索设置",
|
||||||
|
"clear": "清空搜索",
|
||||||
|
"back": "返回结果",
|
||||||
|
"results": "搜索结果",
|
||||||
|
"empty": "未找到设置"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,5 +714,12 @@
|
|||||||
"installing": "安裝服務中...",
|
"installing": "安裝服務中...",
|
||||||
"uninstalling": "服務解除安裝中..."
|
"uninstalling": "服務解除安裝中..."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "搜尋設定",
|
||||||
|
"clear": "清除搜尋",
|
||||||
|
"back": "返回結果",
|
||||||
|
"results": "搜尋結果",
|
||||||
|
"empty": "找不到設定"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { arrayMove } from '@dnd-kit/helpers'
|
import { arrayMove } from '@dnd-kit/helpers'
|
||||||
import type { DragEndEvent } from '@dnd-kit/react'
|
import type { DragEndEvent } from '@dnd-kit/react'
|
||||||
import { isSortable } from '@dnd-kit/react/sortable'
|
import { isSortable } from '@dnd-kit/react/sortable'
|
||||||
import { useCallback, useEffect, useMemo, useReducer } from 'react'
|
import { useCallback, useMemo, useReducer, useState } from 'react'
|
||||||
|
|
||||||
type MenuOrderAction = { type: 'sync'; payload: string[] }
|
type MenuOrderAction = { type: 'sync'; payload: string[] }
|
||||||
|
|
||||||
@@ -79,9 +79,11 @@ export const useNavMenuOrder = <T extends { path: string }>({
|
|||||||
configMenuOrder,
|
configMenuOrder,
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
const [syncedConfigOrder, setSyncedConfigOrder] = useState(configMenuOrder)
|
||||||
|
if (!areOrdersEqual(syncedConfigOrder, configMenuOrder)) {
|
||||||
|
setSyncedConfigOrder(configMenuOrder)
|
||||||
dispatchMenuOrder({ type: 'sync', payload: configMenuOrder })
|
dispatchMenuOrder({ type: 'sync', payload: configMenuOrder })
|
||||||
}, [configMenuOrder])
|
}
|
||||||
|
|
||||||
const isDefaultOrder = useMemo(
|
const isDefaultOrder = useMemo(
|
||||||
() => areOrdersEqual(menuOrder, defaultOrder),
|
() => areOrdersEqual(menuOrder, defaultOrder),
|
||||||
|
|||||||
+17
-18
@@ -1,11 +1,15 @@
|
|||||||
import { LanOutlined, LanRounded, WarningRounded } from '@mui/icons-material'
|
import { LanOutlined, LanRounded, WarningRounded } from '@mui/icons-material'
|
||||||
import { Box, Button, ButtonGroup, Typography } from '@mui/material'
|
import { Box, Button, ButtonGroup, Typography } from '@mui/material'
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { useCallback, useEffect, useReducer, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, BasePage, TooltipIcon } from '@/components/base'
|
import { BaseDialog, BasePage, TooltipIcon } from '@/components/base'
|
||||||
import { ProviderButton } from '@/components/proxy/provider-button'
|
import { ProviderButton } from '@/components/proxy/provider-button'
|
||||||
|
import {
|
||||||
|
parseChainConfigItems,
|
||||||
|
type ProxyChainItem,
|
||||||
|
} from '@/components/proxy/proxy-chain-model'
|
||||||
import { ProxyGroups } from '@/components/proxy/proxy-groups'
|
import { ProxyGroups } from '@/components/proxy/proxy-groups'
|
||||||
import {
|
import {
|
||||||
useAppRefreshers,
|
useAppRefreshers,
|
||||||
@@ -40,18 +44,13 @@ const ProxyPage = () => {
|
|||||||
})
|
})
|
||||||
const [chainConfirmOpen, setChainConfirmOpen] = useState(false)
|
const [chainConfirmOpen, setChainConfirmOpen] = useState(false)
|
||||||
|
|
||||||
const [chainConfigData, dispatchChainConfigData] = useReducer(
|
const [chainConfigItems, setChainConfigItems] = useState<
|
||||||
(_: string | null, action: string | null) => action,
|
ProxyChainItem[] | null
|
||||||
null as string | null,
|
>(null)
|
||||||
)
|
|
||||||
|
|
||||||
const { clashConfig } = useClashConfigData()
|
const { clashConfig } = useClashConfigData()
|
||||||
const { refreshClashConfig } = useAppRefreshers()
|
const { refreshClashConfig } = useAppRefreshers()
|
||||||
|
|
||||||
const updateChainConfigData = useCallback((value: string | null) => {
|
|
||||||
dispatchChainConfigData(value)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const normalizedMode = clashConfig?.mode?.toLowerCase()
|
const normalizedMode = clashConfig?.mode?.toLowerCase()
|
||||||
const curMode = isMode(normalizedMode) ? normalizedMode : undefined
|
const curMode = isMode(normalizedMode) ? normalizedMode : undefined
|
||||||
const chainWarning = t('proxies.page.chain.warning')
|
const chainWarning = t('proxies.page.chain.warning')
|
||||||
@@ -71,6 +70,7 @@ const ProxyPage = () => {
|
|||||||
|
|
||||||
const applyChainMode = (enabled: boolean) => {
|
const applyChainMode = (enabled: boolean) => {
|
||||||
setIsChainMode(enabled)
|
setIsChainMode(enabled)
|
||||||
|
if (!enabled) setChainConfigItems(null)
|
||||||
localStorage.setItem('proxy-chain-mode-enabled', enabled.toString())
|
localStorage.setItem('proxy-chain-mode-enabled', enabled.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +99,7 @@ const ProxyPage = () => {
|
|||||||
|
|
||||||
// 当开启链式代理模式时,获取配置数据
|
// 当开启链式代理模式时,获取配置数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isChainMode) {
|
if (!isChainMode) return
|
||||||
updateChainConfigData(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
||||||
@@ -113,19 +110,21 @@ const ProxyPage = () => {
|
|||||||
if (!exitNode) {
|
if (!exitNode) {
|
||||||
console.error('No proxy chain exit node found in localStorage')
|
console.error('No proxy chain exit node found in localStorage')
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
updateChainConfigData('')
|
setChainConfigItems([])
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const configData = await getRuntimeProxyChainConfig(exitNode)
|
const configData = await getRuntimeProxyChainConfig(exitNode)
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
updateChainConfigData(configData || '')
|
setChainConfigItems(
|
||||||
|
configData ? parseChainConfigItems(configData) : [],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get runtime proxy chain config:', error)
|
console.error('Failed to get runtime proxy chain config:', error)
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
updateChainConfigData('')
|
setChainConfigItems([])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +134,7 @@ const ProxyPage = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [isChainMode, updateChainConfigData])
|
}, [isChainMode])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (normalizedMode && !isMode(normalizedMode)) {
|
if (normalizedMode && !isMode(normalizedMode)) {
|
||||||
@@ -204,7 +203,7 @@ const ProxyPage = () => {
|
|||||||
<ProxyGroups
|
<ProxyGroups
|
||||||
mode={curMode ?? 'rule'}
|
mode={curMode ?? 'rule'}
|
||||||
isChainMode={isChainMode}
|
isChainMode={isChainMode}
|
||||||
chainConfigData={chainConfigData}
|
chainConfigItems={chainConfigItems}
|
||||||
onExitChainMode={onExitChainMode}
|
onExitChainMode={onExitChainMode}
|
||||||
/>
|
/>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
|
|||||||
+95
-74
@@ -8,6 +8,7 @@ import SettingClash from '@/components/setting/setting-clash'
|
|||||||
import SettingSystem from '@/components/setting/setting-system'
|
import SettingSystem from '@/components/setting/setting-system'
|
||||||
import SettingVergeAdvanced from '@/components/setting/setting-verge-advanced'
|
import SettingVergeAdvanced from '@/components/setting/setting-verge-advanced'
|
||||||
import SettingVergeBasic from '@/components/setting/setting-verge-basic'
|
import SettingVergeBasic from '@/components/setting/setting-verge-basic'
|
||||||
|
import { SettingsSearch } from '@/components/setting/settings-search'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
import { useThemeMode } from '@/services/states'
|
import { useThemeMode } from '@/services/states'
|
||||||
import { openExternalUrl } from '@/utils/open-external-url'
|
import { openExternalUrl } from '@/utils/open-external-url'
|
||||||
@@ -39,82 +40,102 @@ const SettingPage = () => {
|
|||||||
const isDark = mode === 'light' ? false : true
|
const isDark = mode === 'light' ? false : true
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<SettingsSearch>
|
||||||
title={t('settings.page.title')}
|
{(searchHeader, results, hidden) => (
|
||||||
header={
|
<BasePage
|
||||||
<ButtonGroup
|
title={t('settings.page.title')}
|
||||||
variant="contained"
|
header={
|
||||||
aria-label={t('settings.page.actionsGroupLabel')}
|
<Box
|
||||||
>
|
sx={{
|
||||||
<IconButton
|
display: 'flex',
|
||||||
size="medium"
|
gap: 1,
|
||||||
color="inherit"
|
flexWrap: 'wrap',
|
||||||
title={t('settings.page.actions.manual')}
|
alignItems: 'center',
|
||||||
onClick={toGithubDoc}
|
}}
|
||||||
>
|
>
|
||||||
<HelpOutlineRounded fontSize="inherit" />
|
{searchHeader}
|
||||||
</IconButton>
|
<ButtonGroup
|
||||||
<IconButton
|
variant="contained"
|
||||||
size="medium"
|
aria-label={t('settings.page.actionsGroupLabel')}
|
||||||
color="inherit"
|
>
|
||||||
title={t('settings.page.actions.telegram')}
|
<IconButton
|
||||||
onClick={toTelegramChannel}
|
size="medium"
|
||||||
>
|
color="inherit"
|
||||||
<Telegram fontSize="inherit" />
|
title={t('settings.page.actions.manual')}
|
||||||
</IconButton>
|
onClick={toGithubDoc}
|
||||||
|
>
|
||||||
|
<HelpOutlineRounded fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="medium"
|
||||||
|
color="inherit"
|
||||||
|
title={t('settings.page.actions.telegram')}
|
||||||
|
onClick={toTelegramChannel}
|
||||||
|
>
|
||||||
|
<Telegram fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="medium"
|
size="medium"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
title={t('settings.page.actions.github')}
|
title={t('settings.page.actions.github')}
|
||||||
onClick={toGithubRepo}
|
onClick={toGithubRepo}
|
||||||
|
>
|
||||||
|
<GitHub fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{results}
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={1.5}
|
||||||
|
columns={{ xs: 6, sm: 6, md: 12 }}
|
||||||
|
sx={{ display: hidden ? 'none' : undefined }}
|
||||||
>
|
>
|
||||||
<GitHub fontSize="inherit" />
|
<Grid size={6}>
|
||||||
</IconButton>
|
<Box
|
||||||
</ButtonGroup>
|
sx={{
|
||||||
}
|
borderRadius: 2,
|
||||||
>
|
marginBottom: 1.5,
|
||||||
<Grid container spacing={1.5} columns={{ xs: 6, sm: 6, md: 12 }}>
|
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
||||||
<Grid size={6}>
|
}}
|
||||||
<Box
|
>
|
||||||
sx={{
|
<SettingSystem onError={onError} />
|
||||||
borderRadius: 2,
|
</Box>
|
||||||
marginBottom: 1.5,
|
<Box
|
||||||
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
sx={{
|
||||||
}}
|
borderRadius: 2,
|
||||||
>
|
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
||||||
<SettingSystem onError={onError} />
|
}}
|
||||||
</Box>
|
>
|
||||||
<Box
|
<SettingClash onError={onError} />
|
||||||
sx={{
|
</Box>
|
||||||
borderRadius: 2,
|
</Grid>
|
||||||
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
<Grid size={6}>
|
||||||
}}
|
<Box
|
||||||
>
|
sx={{
|
||||||
<SettingClash onError={onError} />
|
borderRadius: 2,
|
||||||
</Box>
|
marginBottom: 1.5,
|
||||||
</Grid>
|
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
||||||
<Grid size={6}>
|
}}
|
||||||
<Box
|
>
|
||||||
sx={{
|
<SettingVergeBasic onError={onError} />
|
||||||
borderRadius: 2,
|
</Box>
|
||||||
marginBottom: 1.5,
|
<Box
|
||||||
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
sx={{
|
||||||
}}
|
borderRadius: 2,
|
||||||
>
|
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
||||||
<SettingVergeBasic onError={onError} />
|
}}
|
||||||
</Box>
|
>
|
||||||
<Box
|
<SettingVergeAdvanced onError={onError} />
|
||||||
sx={{
|
</Box>
|
||||||
borderRadius: 2,
|
</Grid>
|
||||||
backgroundColor: isDark ? '#282a36' : '#ffffff',
|
</Grid>
|
||||||
}}
|
</BasePage>
|
||||||
>
|
)}
|
||||||
<SettingVergeAdvanced onError={onError} />
|
</SettingsSearch>
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</BasePage>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+261
-155
@@ -23,7 +23,10 @@ export const commands = {
|
|||||||
__TAURI_INVOKE<string[]>('get_network_interfaces'),
|
__TAURI_INVOKE<string[]>('get_network_interfaces'),
|
||||||
getSystemHostname: () => __TAURI_INVOKE<string>('get_system_hostname'),
|
getSystemHostname: () => __TAURI_INVOKE<string>('get_system_hostname'),
|
||||||
restartApp: () => __TAURI_INVOKE<null>('restart_app'),
|
restartApp: () => __TAURI_INVOKE<null>('restart_app'),
|
||||||
installUpdate: (version: string, onEvent: Channel<DownloadEvent>) =>
|
installUpdate: (
|
||||||
|
version: string,
|
||||||
|
onEvent: Channel<DownloadEvent_Deserialize>,
|
||||||
|
) =>
|
||||||
__TAURI_INVOKE<InstallUpdateOutcome>('install_update', {
|
__TAURI_INVOKE<InstallUpdateOutcome>('install_update', {
|
||||||
version,
|
version,
|
||||||
onEvent,
|
onEvent,
|
||||||
@@ -34,11 +37,12 @@ export const commands = {
|
|||||||
upgradeClashCore: (force: boolean) =>
|
upgradeClashCore: (force: boolean) =>
|
||||||
__TAURI_INVOKE<CoreUpgradeReport>('upgrade_clash_core', { force }),
|
__TAURI_INVOKE<CoreUpgradeReport>('upgrade_clash_core', { force }),
|
||||||
/** Returns one coherent core/service snapshot instead of independently refreshed state. */
|
/** Returns one coherent core/service snapshot instead of independently refreshed state. */
|
||||||
getRuntimeState: () => __TAURI_INVOKE<RunStateView>('get_runtime_state'),
|
getRuntimeState: () =>
|
||||||
|
__TAURI_INVOKE<RunStateView_Serialize>('get_runtime_state'),
|
||||||
getPendingFailures: () =>
|
getPendingFailures: () =>
|
||||||
__TAURI_INVOKE<PendingFailure[]>('get_pending_failures'),
|
__TAURI_INVOKE<PendingFailure[]>('get_pending_failures'),
|
||||||
getSidecarFailure: () =>
|
getSidecarFailure: () =>
|
||||||
__TAURI_INVOKE<SidecarFailureSnapshot>('get_sidecar_failure'),
|
__TAURI_INVOKE<SidecarFailureSnapshot_Serialize>('get_sidecar_failure'),
|
||||||
entryLightweightMode: () => __TAURI_INVOKE<null>('entry_lightweight_mode'),
|
entryLightweightMode: () => __TAURI_INVOKE<null>('entry_lightweight_mode'),
|
||||||
installService: () =>
|
installService: () =>
|
||||||
__TAURI_INVOKE<ServiceInstallOutcome>('install_service'),
|
__TAURI_INVOKE<ServiceInstallOutcome>('install_service'),
|
||||||
@@ -48,7 +52,7 @@ export const commands = {
|
|||||||
repairService: () => __TAURI_INVOKE<ServiceInstallOutcome>('repair_service'),
|
repairService: () => __TAURI_INVOKE<ServiceInstallOutcome>('repair_service'),
|
||||||
continueWithSidecar: () => __TAURI_INVOKE<null>('continue_with_sidecar'),
|
continueWithSidecar: () => __TAURI_INVOKE<null>('continue_with_sidecar'),
|
||||||
syncRuntimeProviders: () => __TAURI_INVOKE<void>('sync_runtime_providers'),
|
syncRuntimeProviders: () => __TAURI_INVOKE<void>('sync_runtime_providers'),
|
||||||
getClashInfo: () => __TAURI_INVOKE<ClashInfo>('get_clash_info'),
|
getClashInfo: () => __TAURI_INVOKE<ClashInfo_Serialize>('get_clash_info'),
|
||||||
patchClashConfig: (payload: YamlMapping) =>
|
patchClashConfig: (payload: YamlMapping) =>
|
||||||
__TAURI_INVOKE<null>('patch_clash_config', { payload }),
|
__TAURI_INVOKE<null>('patch_clash_config', { payload }),
|
||||||
patchClashMode: (payload: string) =>
|
patchClashMode: (payload: string) =>
|
||||||
@@ -56,16 +60,11 @@ export const commands = {
|
|||||||
/** Reads the saved mode without depending on strict mihomo `/configs` deserialization. */
|
/** Reads the saved mode without depending on strict mihomo `/configs` deserialization. */
|
||||||
getClashMode: () => __TAURI_INVOKE<string | null>('get_clash_mode'),
|
getClashMode: () => __TAURI_INVOKE<string | null>('get_clash_mode'),
|
||||||
changeClashCore: (clashCore: string) =>
|
changeClashCore: (clashCore: string) =>
|
||||||
__TAURI_INVOKE<{
|
__TAURI_INVOKE<CommandFailure_Serialize | null>('change_clash_core', {
|
||||||
/** Stable frontend identifier, when classified. */
|
clashCore,
|
||||||
code?: string | null
|
}),
|
||||||
/** Human-readable diagnostic detail. */
|
|
||||||
detail: string
|
|
||||||
/** Requested operation when known by the command. */
|
|
||||||
operation?: FailedOperation | null
|
|
||||||
} | null>('change_clash_core', { clashCore }),
|
|
||||||
getRuntimeConfig: () =>
|
getRuntimeConfig: () =>
|
||||||
__TAURI_INVOKE<{ [key in string]: JsonValue } | null>('get_runtime_config'),
|
__TAURI_INVOKE<YamlMapping | null>('get_runtime_config'),
|
||||||
getProxyView: () => __TAURI_INVOKE<ProxyViewV1_Serialize>('get_proxy_view'),
|
getProxyView: () => __TAURI_INVOKE<ProxyViewV1_Serialize>('get_proxy_view'),
|
||||||
getRuntimeYaml: () => __TAURI_INVOKE<string>('get_runtime_yaml'),
|
getRuntimeYaml: () => __TAURI_INVOKE<string>('get_runtime_yaml'),
|
||||||
getRuntimeLogs: () => __TAURI_INVOKE<RuntimeLogs>('get_runtime_logs'),
|
getRuntimeLogs: () => __TAURI_INVOKE<RuntimeLogs>('get_runtime_logs'),
|
||||||
@@ -73,7 +72,7 @@ export const commands = {
|
|||||||
__TAURI_INVOKE<string>('get_runtime_proxy_chain_config', {
|
__TAURI_INVOKE<string>('get_runtime_proxy_chain_config', {
|
||||||
proxyChainExitNode,
|
proxyChainExitNode,
|
||||||
}),
|
}),
|
||||||
updateProxyChainConfigInRuntime: (proxyChainConfig: JsonValue | null) =>
|
updateProxyChainConfigInRuntime: (proxyChainConfig: YamlValue | null) =>
|
||||||
__TAURI_INVOKE<ValidationOutcome>('update_proxy_chain_config_in_runtime', {
|
__TAURI_INVOKE<ValidationOutcome>('update_proxy_chain_config_in_runtime', {
|
||||||
proxyChainConfig,
|
proxyChainConfig,
|
||||||
}),
|
}),
|
||||||
@@ -103,18 +102,11 @@ export const commands = {
|
|||||||
takeDnsOverrideNotice: () =>
|
takeDnsOverrideNotice: () =>
|
||||||
__TAURI_INVOKE<boolean>('take_dns_override_notice'),
|
__TAURI_INVOKE<boolean>('take_dns_override_notice'),
|
||||||
takeServiceFallbackNotice: () =>
|
takeServiceFallbackNotice: () =>
|
||||||
__TAURI_INVOKE<
|
__TAURI_INVOKE<ServiceFallbackNotice | null>(
|
||||||
| { kind: 'unavailable' }
|
'take_service_fallback_notice',
|
||||||
| { kind: 'coreRejected'; reason: string }
|
),
|
||||||
| { kind: 'notAutoStarted' }
|
|
||||||
| null
|
|
||||||
>('take_service_fallback_notice'),
|
|
||||||
getCoreStartupError: () =>
|
getCoreStartupError: () =>
|
||||||
__TAURI_INVOKE<
|
__TAURI_INVOKE<CoreFailure | null>('get_core_startup_error'),
|
||||||
| { kind: 'startFailed'; detail: string }
|
|
||||||
| { kind: 'serviceCoreStopped'; detail: string }
|
|
||||||
| null
|
|
||||||
>('get_core_startup_error'),
|
|
||||||
takeServiceRepairNotice: () =>
|
takeServiceRepairNotice: () =>
|
||||||
__TAURI_INVOKE<boolean>('take_service_repair_notice'),
|
__TAURI_INVOKE<boolean>('take_service_repair_notice'),
|
||||||
takeServiceOwnerNotice: () =>
|
takeServiceOwnerNotice: () =>
|
||||||
@@ -140,10 +132,12 @@ export const commands = {
|
|||||||
openDevtools: () => __TAURI_INVOKE<void>('open_devtools'),
|
openDevtools: () => __TAURI_INVOKE<void>('open_devtools'),
|
||||||
exitApp: () => __TAURI_INVOKE<void>('exit_app'),
|
exitApp: () => __TAURI_INVOKE<void>('exit_app'),
|
||||||
getNetworkInterfacesInfo: () =>
|
getNetworkInterfacesInfo: () =>
|
||||||
__TAURI_INVOKE<NetworkInterfaceView[]>('get_network_interfaces_info'),
|
__TAURI_INVOKE<NetworkInterfaceView_Serialize[]>(
|
||||||
|
'get_network_interfaces_info',
|
||||||
|
),
|
||||||
getProfiles: () => __TAURI_INVOKE<IProfiles_Serialize>('get_profiles'),
|
getProfiles: () => __TAURI_INVOKE<IProfiles_Serialize>('get_profiles'),
|
||||||
enhanceProfiles: () => __TAURI_INVOKE<ValidationOutcome>('enhance_profiles'),
|
enhanceProfiles: () => __TAURI_INVOKE<ValidationOutcome>('enhance_profiles'),
|
||||||
patchProfilesConfig: (profiles: ProfileSelection) =>
|
patchProfilesConfig: (profiles: ProfileSelection_Deserialize) =>
|
||||||
__TAURI_INVOKE<ValidationOutcome>('patch_profiles_config', { profiles }),
|
__TAURI_INVOKE<ValidationOutcome>('patch_profiles_config', { profiles }),
|
||||||
viewProfile: (index: string) =>
|
viewProfile: (index: string) =>
|
||||||
__TAURI_INVOKE<null>('view_profile', { index }),
|
__TAURI_INVOKE<null>('view_profile', { index }),
|
||||||
@@ -151,52 +145,12 @@ export const commands = {
|
|||||||
__TAURI_INVOKE<null>('patch_profile', { index, profile }),
|
__TAURI_INVOKE<null>('patch_profile', { index, profile }),
|
||||||
createProfile: (item: ProfileCreate_Deserialize, fileData: string | null) =>
|
createProfile: (item: ProfileCreate_Deserialize, fileData: string | null) =>
|
||||||
__TAURI_INVOKE<null>('create_profile', { item, fileData }),
|
__TAURI_INVOKE<null>('create_profile', { item, fileData }),
|
||||||
importProfile: (
|
importProfile: (url: string, option: PrfOption_Deserialize | null) =>
|
||||||
url: string,
|
__TAURI_INVOKE<null>('import_profile', { url, option }),
|
||||||
option: {
|
|
||||||
user_agent?: string | null
|
|
||||||
with_proxy?: boolean | null
|
|
||||||
self_proxy?: boolean | null
|
|
||||||
update_interval?: number | null
|
|
||||||
/**
|
|
||||||
* HTTP request timeout in seconds
|
|
||||||
* default is 60 seconds
|
|
||||||
*/
|
|
||||||
timeout_seconds?: number | null
|
|
||||||
/** default is `false` */
|
|
||||||
danger_accept_invalid_certs?: boolean | null
|
|
||||||
allow_auto_update?: boolean | null
|
|
||||||
merge?: string | null
|
|
||||||
script?: string | null
|
|
||||||
rules?: string | null
|
|
||||||
proxies?: string | null
|
|
||||||
groups?: string | null
|
|
||||||
} | null,
|
|
||||||
) => __TAURI_INVOKE<null>('import_profile', { url, option }),
|
|
||||||
reorderProfile: (activeId: string, overId: string) =>
|
reorderProfile: (activeId: string, overId: string) =>
|
||||||
__TAURI_INVOKE<null>('reorder_profile', { activeId, overId }),
|
__TAURI_INVOKE<null>('reorder_profile', { activeId, overId }),
|
||||||
updateProfile: (
|
updateProfile: (index: string, option: PrfOption_Deserialize | null) =>
|
||||||
index: string,
|
__TAURI_INVOKE<null>('update_profile', { index, option }),
|
||||||
option: {
|
|
||||||
user_agent?: string | null
|
|
||||||
with_proxy?: boolean | null
|
|
||||||
self_proxy?: boolean | null
|
|
||||||
update_interval?: number | null
|
|
||||||
/**
|
|
||||||
* HTTP request timeout in seconds
|
|
||||||
* default is 60 seconds
|
|
||||||
*/
|
|
||||||
timeout_seconds?: number | null
|
|
||||||
/** default is `false` */
|
|
||||||
danger_accept_invalid_certs?: boolean | null
|
|
||||||
allow_auto_update?: boolean | null
|
|
||||||
merge?: string | null
|
|
||||||
script?: string | null
|
|
||||||
rules?: string | null
|
|
||||||
proxies?: string | null
|
|
||||||
groups?: string | null
|
|
||||||
} | null,
|
|
||||||
) => __TAURI_INVOKE<null>('update_profile', { index, option }),
|
|
||||||
deleteProfile: (index: string) =>
|
deleteProfile: (index: string) =>
|
||||||
__TAURI_INVOKE<null>('delete_profile', { index }),
|
__TAURI_INVOKE<null>('delete_profile', { index }),
|
||||||
readProfileFile: (index: string) =>
|
readProfileFile: (index: string) =>
|
||||||
@@ -224,11 +178,14 @@ export const commands = {
|
|||||||
__TAURI_INVOKE<null>('delete_webdav_backup', { filename }),
|
__TAURI_INVOKE<null>('delete_webdav_backup', { filename }),
|
||||||
restoreWebdavBackup: (filename: string) =>
|
restoreWebdavBackup: (filename: string) =>
|
||||||
__TAURI_INVOKE<null>('restore_webdav_backup', { filename }),
|
__TAURI_INVOKE<null>('restore_webdav_backup', { filename }),
|
||||||
getUnlockItems: () => __TAURI_INVOKE<UnlockItem[]>('get_unlock_items'),
|
getUnlockItems: () =>
|
||||||
checkMediaUnlock: (onComplete: Channel<UnlockItem>) =>
|
__TAURI_INVOKE<UnlockItem_Serialize[]>('get_unlock_items'),
|
||||||
__TAURI_INVOKE<UnlockItem[]>('check_media_unlock', { onComplete }),
|
checkMediaUnlock: (onComplete: Channel<UnlockItem_Deserialize>) =>
|
||||||
|
__TAURI_INVOKE<UnlockItem_Serialize[]>('check_media_unlock', {
|
||||||
|
onComplete,
|
||||||
|
}),
|
||||||
checkMediaUnlockItem: (name: string) =>
|
checkMediaUnlockItem: (name: string) =>
|
||||||
__TAURI_INVOKE<UnlockItem>('check_media_unlock_item', { name }),
|
__TAURI_INVOKE<UnlockItem_Serialize>('check_media_unlock_item', { name }),
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Types */
|
/* Types */
|
||||||
@@ -237,7 +194,20 @@ export type AutoProxy = {
|
|||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClashInfo = {
|
export type ClashInfo = ClashInfo_Serialize | ClashInfo_Deserialize
|
||||||
|
|
||||||
|
export type ClashInfo_Deserialize = {
|
||||||
|
/** clash core port */
|
||||||
|
mixed_port: number
|
||||||
|
socks_port: number
|
||||||
|
port: number
|
||||||
|
/** same as `external-controller` */
|
||||||
|
server: string
|
||||||
|
/** clash secret */
|
||||||
|
secret?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClashInfo_Serialize = {
|
||||||
/** clash core port */
|
/** clash core port */
|
||||||
mixed_port: number
|
mixed_port: number
|
||||||
socks_port: number
|
socks_port: number
|
||||||
@@ -255,16 +225,6 @@ export type CommandFailure =
|
|||||||
|
|
||||||
/** Structured command failure returned to the frontend. */
|
/** Structured command failure returned to the frontend. */
|
||||||
export type CommandFailure_Deserialize = {
|
export type CommandFailure_Deserialize = {
|
||||||
/** Stable frontend identifier, when classified. */
|
|
||||||
code: string | null
|
|
||||||
/** Human-readable diagnostic detail. */
|
|
||||||
detail: string
|
|
||||||
/** Requested operation when known by the command. */
|
|
||||||
operation: FailedOperation | null
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Structured command failure returned to the frontend. */
|
|
||||||
export type CommandFailure_Serialize = {
|
|
||||||
/** Stable frontend identifier, when classified. */
|
/** Stable frontend identifier, when classified. */
|
||||||
code?: string | null
|
code?: string | null
|
||||||
/** Human-readable diagnostic detail. */
|
/** Human-readable diagnostic detail. */
|
||||||
@@ -273,6 +233,16 @@ export type CommandFailure_Serialize = {
|
|||||||
operation?: FailedOperation | null
|
operation?: FailedOperation | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Structured command failure returned to the frontend. */
|
||||||
|
export type CommandFailure_Serialize = {
|
||||||
|
/** Stable frontend identifier, when classified. */
|
||||||
|
code?: string
|
||||||
|
/** Human-readable diagnostic detail. */
|
||||||
|
detail: string
|
||||||
|
/** Requested operation when known by the command. */
|
||||||
|
operation?: FailedOperation
|
||||||
|
}
|
||||||
|
|
||||||
/** Why the Core is not running, kept until a Core starts again. */
|
/** Why the Core is not running, kept until a Core starts again. */
|
||||||
export type CoreFailure =
|
export type CoreFailure =
|
||||||
| { kind: 'startFailed'; detail: string }
|
| { kind: 'startFailed'; detail: string }
|
||||||
@@ -293,7 +263,26 @@ export type DnsOverrideOutcome =
|
|||||||
| { status: 'confirmation_required'; source: string }
|
| { status: 'confirmation_required'; source: string }
|
||||||
|
|
||||||
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
||||||
export type DownloadEvent =
|
export type DownloadEvent = DownloadEvent_Serialize | DownloadEvent_Deserialize
|
||||||
|
|
||||||
|
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
||||||
|
export type DownloadEvent_Deserialize =
|
||||||
|
| {
|
||||||
|
event: 'Started'
|
||||||
|
data: {
|
||||||
|
contentLength?: number | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
event: 'Progress'
|
||||||
|
data: {
|
||||||
|
chunkLength: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| { event: 'Finished' }
|
||||||
|
|
||||||
|
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
||||||
|
export type DownloadEvent_Serialize =
|
||||||
| {
|
| {
|
||||||
event: 'Started'
|
event: 'Started'
|
||||||
data: {
|
data: {
|
||||||
@@ -333,14 +322,27 @@ export type IProfiles_Serialize = {
|
|||||||
/** ### `verge.yaml` schema */
|
/** ### `verge.yaml` schema */
|
||||||
export type IVerge = IVerge_Serialize | IVerge_Deserialize
|
export type IVerge = IVerge_Serialize | IVerge_Deserialize
|
||||||
|
|
||||||
export type IVergeTestItem = {
|
export type IVergeTestItem =
|
||||||
|
| IVergeTestItem_Serialize
|
||||||
|
| IVergeTestItem_Deserialize
|
||||||
|
|
||||||
|
export type IVergeTestItem_Deserialize = {
|
||||||
uid?: string | null
|
uid?: string | null
|
||||||
name?: string | null
|
name?: string | null
|
||||||
icon?: string | null
|
icon?: string | null
|
||||||
url?: string | null
|
url?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IVergeTheme = {
|
export type IVergeTestItem_Serialize = {
|
||||||
|
uid: string | null
|
||||||
|
name: string | null
|
||||||
|
icon: string | null
|
||||||
|
url: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IVergeTheme = IVergeTheme_Serialize | IVergeTheme_Deserialize
|
||||||
|
|
||||||
|
export type IVergeTheme_Deserialize = {
|
||||||
primary_color?: string | null
|
primary_color?: string | null
|
||||||
secondary_color?: string | null
|
secondary_color?: string | null
|
||||||
primary_text?: string | null
|
primary_text?: string | null
|
||||||
@@ -353,6 +355,19 @@ export type IVergeTheme = {
|
|||||||
css_injection?: string | null
|
css_injection?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IVergeTheme_Serialize = {
|
||||||
|
primary_color: string | null
|
||||||
|
secondary_color: string | null
|
||||||
|
primary_text: string | null
|
||||||
|
secondary_text: string | null
|
||||||
|
info_color: string | null
|
||||||
|
error_color: string | null
|
||||||
|
warning_color: string | null
|
||||||
|
success_color: string | null
|
||||||
|
font_family: string | null
|
||||||
|
css_injection: string | null
|
||||||
|
}
|
||||||
|
|
||||||
/** ### `verge.yaml` schema */
|
/** ### `verge.yaml` schema */
|
||||||
export type IVerge_Deserialize = {
|
export type IVerge_Deserialize = {
|
||||||
/** silent | error | warn | info | debug | trace */
|
/** silent | error | warn | info | debug | trace */
|
||||||
@@ -395,7 +410,7 @@ export type IVerge_Deserialize = {
|
|||||||
proxy_auto_config?: boolean | null
|
proxy_auto_config?: boolean | null
|
||||||
pac_file_content?: string | null
|
pac_file_content?: string | null
|
||||||
proxy_host?: string | null
|
proxy_host?: string | null
|
||||||
theme_setting?: IVergeTheme | null
|
theme_setting?: IVergeTheme_Deserialize | null
|
||||||
web_ui_list?: string[] | null
|
web_ui_list?: string[] | null
|
||||||
clash_core?: string | null
|
clash_core?: string | null
|
||||||
/** format: {func},{key} */
|
/** format: {func},{key} */
|
||||||
@@ -411,7 +426,7 @@ export type IVerge_Deserialize = {
|
|||||||
auto_delay_detection_interval_minutes?: number | null
|
auto_delay_detection_interval_minutes?: number | null
|
||||||
enable_builtin_enhanced?: boolean | null
|
enable_builtin_enhanced?: boolean | null
|
||||||
proxy_layout_column?: number | null
|
proxy_layout_column?: number | null
|
||||||
test_list?: IVergeTestItem[] | null
|
test_list?: IVergeTestItem_Deserialize[] | null
|
||||||
/** 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 */
|
/** 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 */
|
||||||
auto_log_clean?: number | null
|
auto_log_clean?: number | null
|
||||||
enable_auto_backup_schedule?: boolean | null
|
enable_auto_backup_schedule?: boolean | null
|
||||||
@@ -460,13 +475,13 @@ export type IVerge_Serialize = {
|
|||||||
traffic_graph: boolean | null
|
traffic_graph: boolean | null
|
||||||
/** show memory info (only for Mihomo) */
|
/** show memory info (only for Mihomo) */
|
||||||
enable_memory_usage: boolean | null
|
enable_memory_usage: boolean | null
|
||||||
enable_group_icon?: boolean | null
|
enable_group_icon?: boolean
|
||||||
pause_render_traffic_stats_on_blur: boolean | null
|
pause_render_traffic_stats_on_blur: boolean | null
|
||||||
common_tray_icon?: boolean | null
|
common_tray_icon?: boolean
|
||||||
menu_icon?: string | null
|
menu_icon?: string
|
||||||
menu_order?: string[] | null
|
menu_order?: string[]
|
||||||
proxy_group_tools_position?: string | null
|
proxy_group_tools_position?: string
|
||||||
notice_position?: string | null
|
notice_position?: string
|
||||||
collapse_navbar: boolean | null
|
collapse_navbar: boolean | null
|
||||||
sysproxy_tray_icon: boolean | null
|
sysproxy_tray_icon: boolean | null
|
||||||
tun_tray_icon: boolean | null
|
tun_tray_icon: boolean | null
|
||||||
@@ -485,11 +500,11 @@ export type IVerge_Serialize = {
|
|||||||
proxy_auto_config: boolean | null
|
proxy_auto_config: boolean | null
|
||||||
pac_file_content: string | null
|
pac_file_content: string | null
|
||||||
proxy_host: string | null
|
proxy_host: string | null
|
||||||
theme_setting: IVergeTheme | null
|
theme_setting: IVergeTheme_Serialize | null
|
||||||
web_ui_list: string[] | null
|
web_ui_list: string[] | null
|
||||||
clash_core?: string | null
|
clash_core?: string
|
||||||
/** format: {func},{key} */
|
/** format: {func},{key} */
|
||||||
hotkeys?: string[] | null
|
hotkeys?: string[]
|
||||||
enable_global_hotkey: boolean | null
|
enable_global_hotkey: boolean | null
|
||||||
home_cards: JsonValue | null
|
home_cards: JsonValue | null
|
||||||
auto_close_connection: boolean | null
|
auto_close_connection: boolean | null
|
||||||
@@ -501,7 +516,7 @@ export type IVerge_Serialize = {
|
|||||||
auto_delay_detection_interval_minutes: number | null
|
auto_delay_detection_interval_minutes: number | null
|
||||||
enable_builtin_enhanced: boolean | null
|
enable_builtin_enhanced: boolean | null
|
||||||
proxy_layout_column: number | null
|
proxy_layout_column: number | null
|
||||||
test_list: IVergeTestItem[] | null
|
test_list: IVergeTestItem_Serialize[] | null
|
||||||
/** 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 */
|
/** 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 */
|
||||||
auto_log_clean: number | null
|
auto_log_clean: number | null
|
||||||
enable_auto_backup_schedule: boolean | null
|
enable_auto_backup_schedule: boolean | null
|
||||||
@@ -513,10 +528,10 @@ export type IVerge_Serialize = {
|
|||||||
verge_socks_enabled: boolean | null
|
verge_socks_enabled: boolean | null
|
||||||
verge_port: number | null
|
verge_port: number | null
|
||||||
verge_http_enabled: boolean | null
|
verge_http_enabled: boolean | null
|
||||||
webdav_url?: string | null
|
webdav_url?: string
|
||||||
webdav_username?: string | null
|
webdav_username?: string
|
||||||
webdav_password?: string | null
|
webdav_password?: string
|
||||||
tray_proxy_groups_display_mode?: string | null
|
tray_proxy_groups_display_mode?: string
|
||||||
tray_inline_outbound_modes: boolean | null
|
tray_inline_outbound_modes: boolean | null
|
||||||
enable_auto_light_weight_mode: boolean | null
|
enable_auto_light_weight_mode: boolean | null
|
||||||
/** 自动进入轻量模式的延迟(分钟) */
|
/** 自动进入轻量模式的延迟(分钟) */
|
||||||
@@ -578,7 +593,17 @@ export type LocalBackupFile = {
|
|||||||
content_length: number
|
content_length: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NetworkInterfaceView = {
|
export type NetworkInterfaceView =
|
||||||
|
| NetworkInterfaceView_Serialize
|
||||||
|
| NetworkInterfaceView_Deserialize
|
||||||
|
|
||||||
|
export type NetworkInterfaceView_Deserialize = {
|
||||||
|
name: string
|
||||||
|
addr: InterfaceAddress[]
|
||||||
|
mac_addr?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NetworkInterfaceView_Serialize = {
|
||||||
name: string
|
name: string
|
||||||
addr: InterfaceAddress[]
|
addr: InterfaceAddress[]
|
||||||
mac_addr: string | null
|
mac_addr: string | null
|
||||||
@@ -637,7 +662,20 @@ export type PendingFailure = {
|
|||||||
sequence: number
|
sequence: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlatformVergeFields = {
|
export type PlatformVergeFields =
|
||||||
|
| PlatformVergeFields_Serialize
|
||||||
|
| PlatformVergeFields_Deserialize
|
||||||
|
|
||||||
|
export type PlatformVergeFields_Deserialize = {
|
||||||
|
tray_icon?: string | null
|
||||||
|
verge_redir_port?: number | null
|
||||||
|
verge_redir_enabled?: boolean | null
|
||||||
|
verge_tproxy_port?: number | null
|
||||||
|
verge_tproxy_enabled?: boolean | null
|
||||||
|
enable_tray_speed?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlatformVergeFields_Serialize = {
|
||||||
tray_icon?: string | null
|
tray_icon?: string | null
|
||||||
verge_redir_port?: number | null
|
verge_redir_port?: number | null
|
||||||
verge_redir_enabled?: boolean | null
|
verge_redir_enabled?: boolean | null
|
||||||
@@ -663,7 +701,7 @@ export type PrfItem_Deserialize = {
|
|||||||
file?: string | null
|
file?: string | null
|
||||||
desc?: string | null
|
desc?: string | null
|
||||||
url?: string | null
|
url?: string | null
|
||||||
selected?: PrfSelected[] | null
|
selected?: PrfSelected_Deserialize[] | null
|
||||||
extra?: PrfExtra | null
|
extra?: PrfExtra | null
|
||||||
updated?: number | null
|
updated?: number | null
|
||||||
option?: PrfOption_Deserialize | null
|
option?: PrfOption_Deserialize | null
|
||||||
@@ -676,13 +714,13 @@ export type PrfItem_Serialize = {
|
|||||||
type: string | null
|
type: string | null
|
||||||
name: string | null
|
name: string | null
|
||||||
file: string | null
|
file: string | null
|
||||||
desc?: string | null
|
desc?: string
|
||||||
url?: string | null
|
url?: string
|
||||||
selected?: PrfSelected[] | null
|
selected?: PrfSelected_Serialize[]
|
||||||
extra?: PrfExtra | null
|
extra?: PrfExtra
|
||||||
updated: number | null
|
updated: number | null
|
||||||
option?: PrfOption_Serialize | null
|
option?: PrfOption_Serialize
|
||||||
home?: string | null
|
home?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PrfOption = PrfOption_Serialize | PrfOption_Deserialize
|
export type PrfOption = PrfOption_Serialize | PrfOption_Deserialize
|
||||||
@@ -708,18 +746,18 @@ export type PrfOption_Deserialize = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PrfOption_Serialize = {
|
export type PrfOption_Serialize = {
|
||||||
user_agent?: string | null
|
user_agent?: string
|
||||||
with_proxy?: boolean | null
|
with_proxy?: boolean
|
||||||
self_proxy?: boolean | null
|
self_proxy?: boolean
|
||||||
update_interval?: number | null
|
update_interval?: number
|
||||||
/**
|
/**
|
||||||
* HTTP request timeout in seconds
|
* HTTP request timeout in seconds
|
||||||
* default is 60 seconds
|
* default is 60 seconds
|
||||||
*/
|
*/
|
||||||
timeout_seconds?: number | null
|
timeout_seconds?: number
|
||||||
/** default is `false` */
|
/** default is `false` */
|
||||||
danger_accept_invalid_certs?: boolean | null
|
danger_accept_invalid_certs?: boolean
|
||||||
allow_auto_update?: boolean | null
|
allow_auto_update?: boolean
|
||||||
merge: string | null
|
merge: string | null
|
||||||
script: string | null
|
script: string | null
|
||||||
rules: string | null
|
rules: string | null
|
||||||
@@ -727,11 +765,18 @@ export type PrfOption_Serialize = {
|
|||||||
groups: string | null
|
groups: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PrfSelected = {
|
export type PrfSelected = PrfSelected_Serialize | PrfSelected_Deserialize
|
||||||
|
|
||||||
|
export type PrfSelected_Deserialize = {
|
||||||
name?: string | null
|
name?: string | null
|
||||||
now?: string | null
|
now?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PrfSelected_Serialize = {
|
||||||
|
name: string | null
|
||||||
|
now: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export type ProfileCreate = ProfileCreate_Serialize | ProfileCreate_Deserialize
|
export type ProfileCreate = ProfileCreate_Serialize | ProfileCreate_Deserialize
|
||||||
|
|
||||||
export type ProfileCreate_Deserialize = {
|
export type ProfileCreate_Deserialize = {
|
||||||
@@ -761,7 +806,7 @@ export type ProfileDnsSettings_Deserialize = {
|
|||||||
|
|
||||||
export type ProfileDnsSettings_Serialize = {
|
export type ProfileDnsSettings_Serialize = {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
confirmation?: string | null
|
confirmation?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfilePatch = ProfilePatch_Serialize | ProfilePatch_Deserialize
|
export type ProfilePatch = ProfilePatch_Serialize | ProfilePatch_Deserialize
|
||||||
@@ -772,7 +817,7 @@ export type ProfilePatch_Deserialize = {
|
|||||||
desc?: string | null
|
desc?: string | null
|
||||||
file?: string | null
|
file?: string | null
|
||||||
url?: string | null
|
url?: string | null
|
||||||
selected?: PrfSelected[] | null
|
selected?: PrfSelected_Deserialize[] | null
|
||||||
extra?: PrfExtra | null
|
extra?: PrfExtra | null
|
||||||
updated?: number | null
|
updated?: number | null
|
||||||
option?: PrfOption_Deserialize | null
|
option?: PrfOption_Deserialize | null
|
||||||
@@ -784,16 +829,24 @@ export type ProfilePatch_Serialize = {
|
|||||||
desc: string | null
|
desc: string | null
|
||||||
file: string | null
|
file: string | null
|
||||||
url: string | null
|
url: string | null
|
||||||
selected: PrfSelected[] | null
|
selected: PrfSelected_Serialize[] | null
|
||||||
extra: PrfExtra | null
|
extra: PrfExtra | null
|
||||||
updated: number | null
|
updated: number | null
|
||||||
option: PrfOption_Serialize | null
|
option: PrfOption_Serialize | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileSelection = {
|
export type ProfileSelection =
|
||||||
|
| ProfileSelection_Serialize
|
||||||
|
| ProfileSelection_Deserialize
|
||||||
|
|
||||||
|
export type ProfileSelection_Deserialize = {
|
||||||
current?: string | null
|
current?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProfileSelection_Serialize = {
|
||||||
|
current: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export type ProfileUpdatePayload = {
|
export type ProfileUpdatePayload = {
|
||||||
uid: string
|
uid: string
|
||||||
}
|
}
|
||||||
@@ -813,11 +866,11 @@ export type ProxyGroupView =
|
|||||||
export type ProxyGroupView_Deserialize = {
|
export type ProxyGroupView_Deserialize = {
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
now: string | null
|
now?: string | null
|
||||||
fixed: string | null
|
fixed?: string | null
|
||||||
hidden: boolean | null
|
hidden?: boolean | null
|
||||||
icon: string | null
|
icon?: string | null
|
||||||
testUrl: string | null
|
testUrl?: string | null
|
||||||
history: DelayHistory[]
|
history: DelayHistory[]
|
||||||
members: ProxyMemberRef[]
|
members: ProxyMemberRef[]
|
||||||
} & ProxyCapabilities
|
} & ProxyCapabilities
|
||||||
@@ -825,11 +878,11 @@ export type ProxyGroupView_Deserialize = {
|
|||||||
export type ProxyGroupView_Serialize = {
|
export type ProxyGroupView_Serialize = {
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
now?: string | null
|
now?: string
|
||||||
fixed?: string | null
|
fixed?: string
|
||||||
hidden?: boolean | null
|
hidden?: boolean
|
||||||
icon?: string | null
|
icon?: string
|
||||||
testUrl?: string | null
|
testUrl?: string
|
||||||
history: DelayHistory[]
|
history: DelayHistory[]
|
||||||
members: ProxyMemberRef[]
|
members: ProxyMemberRef[]
|
||||||
} & ProxyCapabilities
|
} & ProxyCapabilities
|
||||||
@@ -855,10 +908,10 @@ export type ProxyNodeView_Deserialize = {
|
|||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
history: DelayHistory[]
|
history: DelayHistory[]
|
||||||
id: string | null
|
id?: string | null
|
||||||
hidden: boolean | null
|
hidden?: boolean | null
|
||||||
icon: string | null
|
icon?: string | null
|
||||||
testUrl: string | null
|
testUrl?: string | null
|
||||||
source: ProxyNodeSource
|
source: ProxyNodeSource
|
||||||
} & ProxyCapabilities
|
} & ProxyCapabilities
|
||||||
|
|
||||||
@@ -867,10 +920,10 @@ export type ProxyNodeView_Serialize = {
|
|||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
history: DelayHistory[]
|
history: DelayHistory[]
|
||||||
id?: string | null
|
id?: string
|
||||||
hidden?: boolean | null
|
hidden?: boolean
|
||||||
icon?: string | null
|
icon?: string
|
||||||
testUrl?: string | null
|
testUrl?: string
|
||||||
source: ProxyNodeSource
|
source: ProxyNodeSource
|
||||||
} & ProxyCapabilities
|
} & ProxyCapabilities
|
||||||
|
|
||||||
@@ -891,16 +944,16 @@ export type ProxyProviderView =
|
|||||||
export type ProxyProviderView_Deserialize = {
|
export type ProxyProviderView_Deserialize = {
|
||||||
name: string
|
name: string
|
||||||
vehicleType: ProxyProviderVehicleType
|
vehicleType: ProxyProviderVehicleType
|
||||||
updatedAt: string | null
|
updatedAt?: string | null
|
||||||
subscriptionInfo: ProxySubscriptionInfo | null
|
subscriptionInfo?: ProxySubscriptionInfo | null
|
||||||
proxyRecordIds: string[]
|
proxyRecordIds: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProxyProviderView_Serialize = {
|
export type ProxyProviderView_Serialize = {
|
||||||
name: string
|
name: string
|
||||||
vehicleType: ProxyProviderVehicleType
|
vehicleType: ProxyProviderVehicleType
|
||||||
updatedAt?: string | null
|
updatedAt?: string
|
||||||
subscriptionInfo?: ProxySubscriptionInfo | null
|
subscriptionInfo?: ProxySubscriptionInfo
|
||||||
proxyRecordIds: string[]
|
proxyRecordIds: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,8 +974,8 @@ export type ProxyViewV1_Deserialize = {
|
|||||||
schemaVersion: number
|
schemaVersion: number
|
||||||
orderSource: ProxyViewOrderSource
|
orderSource: ProxyViewOrderSource
|
||||||
providerState: ProxyViewProviderState
|
providerState: ProxyViewProviderState
|
||||||
global: ProxyGroupView_Deserialize | null
|
global?: ProxyGroupView_Deserialize | null
|
||||||
direct: string | null
|
direct?: string | null
|
||||||
groups: ProxyGroupView_Deserialize[]
|
groups: ProxyGroupView_Deserialize[]
|
||||||
records: { [key in string]: ProxyNodeView_Deserialize }
|
records: { [key in string]: ProxyNodeView_Deserialize }
|
||||||
standalone: string[]
|
standalone: string[]
|
||||||
@@ -941,7 +994,22 @@ export type ProxyViewV1_Serialize = {
|
|||||||
providers: ProxyProviderView_Serialize[]
|
providers: ProxyProviderView_Serialize[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RunStateView = {
|
export type RunStateView = RunStateView_Serialize | RunStateView_Deserialize
|
||||||
|
|
||||||
|
export type RunStateView_Deserialize = {
|
||||||
|
mode: RunningMode
|
||||||
|
service: ServiceHealthKind
|
||||||
|
serviceUnavailableReason?: string | null
|
||||||
|
pendingAction?: PendingAction | null
|
||||||
|
sidecarAllowed: boolean
|
||||||
|
isAdmin: boolean
|
||||||
|
opInFlight: boolean
|
||||||
|
serviceUsable: boolean
|
||||||
|
tunCapable: boolean
|
||||||
|
serviceNeedsAttention: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RunStateView_Serialize = {
|
||||||
mode: RunningMode
|
mode: RunningMode
|
||||||
service: ServiceHealthKind
|
service: ServiceHealthKind
|
||||||
serviceUnavailableReason: string | null
|
serviceUnavailableReason: string | null
|
||||||
@@ -980,7 +1048,18 @@ export type ServiceInstallOutcome =
|
|||||||
| { status: 'sidecar'; reason: string }
|
| { status: 'sidecar'; reason: string }
|
||||||
|
|
||||||
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
||||||
export type SidecarFailureSnapshot = {
|
export type SidecarFailureSnapshot =
|
||||||
|
| SidecarFailureSnapshot_Serialize
|
||||||
|
| SidecarFailureSnapshot_Deserialize
|
||||||
|
|
||||||
|
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
||||||
|
export type SidecarFailureSnapshot_Deserialize = {
|
||||||
|
revision: number
|
||||||
|
detail?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
||||||
|
export type SidecarFailureSnapshot_Serialize = {
|
||||||
revision: number
|
revision: number
|
||||||
detail: string | null
|
detail: string | null
|
||||||
}
|
}
|
||||||
@@ -1001,7 +1080,16 @@ export type ToggleableProxyPort = {
|
|||||||
port: number
|
port: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UnlockItem = {
|
export type UnlockItem = UnlockItem_Serialize | UnlockItem_Deserialize
|
||||||
|
|
||||||
|
export type UnlockItem_Deserialize = {
|
||||||
|
name: string
|
||||||
|
status: string
|
||||||
|
region?: string | null
|
||||||
|
check_time?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UnlockItem_Serialize = {
|
||||||
name: string
|
name: string
|
||||||
status: string
|
status: string
|
||||||
region: string | null
|
region: string | null
|
||||||
@@ -1027,7 +1115,11 @@ export type ValidationOutcome =
|
|||||||
|
|
||||||
export type ValidationSkipReason = 'exiting' | 'debounced'
|
export type ValidationSkipReason = 'exiting' | 'debounced'
|
||||||
|
|
||||||
export type VergeEventPayloads = {
|
export type VergeEventPayloads =
|
||||||
|
| VergeEventPayloads_Serialize
|
||||||
|
| VergeEventPayloads_Deserialize
|
||||||
|
|
||||||
|
export type VergeEventPayloads_Deserialize = {
|
||||||
'verge://refresh-clash-config': null
|
'verge://refresh-clash-config': null
|
||||||
'verge://refresh-verge-config': null
|
'verge://refresh-verge-config': null
|
||||||
'verge://refresh-profiles': null
|
'verge://refresh-profiles': null
|
||||||
@@ -1037,7 +1129,21 @@ export type VergeEventPayloads = {
|
|||||||
'verge://timer-updated': string
|
'verge://timer-updated': string
|
||||||
'profile-update-started': ProfileUpdatePayload
|
'profile-update-started': ProfileUpdatePayload
|
||||||
'profile-update-completed': ProfileUpdatePayload
|
'profile-update-completed': ProfileUpdatePayload
|
||||||
'verge://run-state-changed': RunStateView
|
'verge://run-state-changed': RunStateView_Deserialize
|
||||||
|
'verge://pending-failures-changed': null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VergeEventPayloads_Serialize = {
|
||||||
|
'verge://refresh-clash-config': null
|
||||||
|
'verge://refresh-verge-config': null
|
||||||
|
'verge://refresh-profiles': null
|
||||||
|
'verge://refresh-proxy-config': null
|
||||||
|
'verge://notice-message': [NoticeStatus, string]
|
||||||
|
'profile-changed': string
|
||||||
|
'verge://timer-updated': string
|
||||||
|
'profile-update-started': ProfileUpdatePayload
|
||||||
|
'profile-update-completed': ProfileUpdatePayload
|
||||||
|
'verge://run-state-changed': RunStateView_Serialize
|
||||||
'verge://pending-failures-changed': null
|
'verge://pending-failures-changed': null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import type {
|
|||||||
|
|
||||||
export type {
|
export type {
|
||||||
RunningMode,
|
RunningMode,
|
||||||
RunStateView as RunState,
|
RunStateView_Serialize as RunState,
|
||||||
FailedOperation,
|
FailedOperation,
|
||||||
PendingFailure,
|
PendingFailure,
|
||||||
SidecarFailureSnapshot,
|
SidecarFailureSnapshot,
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
export type { NoticeStatus, VergeEventPayloads } from './bindings'
|
export type {
|
||||||
|
NoticeStatus,
|
||||||
|
VergeEventPayloads_Serialize as VergeEventPayloads,
|
||||||
|
} from './bindings'
|
||||||
|
|||||||
@@ -827,6 +827,11 @@ export const translationKeys = [
|
|||||||
'settings.statuses.clash.restarting',
|
'settings.statuses.clash.restarting',
|
||||||
'settings.statuses.clashService.installing',
|
'settings.statuses.clashService.installing',
|
||||||
'settings.statuses.clashService.uninstalling',
|
'settings.statuses.clashService.uninstalling',
|
||||||
|
'settings.search.placeholder',
|
||||||
|
'settings.search.clear',
|
||||||
|
'settings.search.back',
|
||||||
|
'settings.search.results',
|
||||||
|
'settings.search.empty',
|
||||||
'shared.actions.cancel',
|
'shared.actions.cancel',
|
||||||
'shared.actions.close',
|
'shared.actions.close',
|
||||||
'shared.actions.confirm',
|
'shared.actions.confirm',
|
||||||
@@ -939,6 +944,7 @@ export const translationKeys = [
|
|||||||
'tests.statuses.test.yes',
|
'tests.statuses.test.yes',
|
||||||
'tests.statuses.test.no',
|
'tests.statuses.test.no',
|
||||||
'tests.statuses.test.failed',
|
'tests.statuses.test.failed',
|
||||||
|
'tests.statuses.test.risky',
|
||||||
'tests.statuses.test.completed',
|
'tests.statuses.test.completed',
|
||||||
'tests.statuses.test.disallowedIsp',
|
'tests.statuses.test.disallowedIsp',
|
||||||
'tests.statuses.test.originalsOnly',
|
'tests.statuses.test.originalsOnly',
|
||||||
|
|||||||
@@ -1251,6 +1251,13 @@ export interface TranslationResources {
|
|||||||
actionsGroupLabel: string
|
actionsGroupLabel: string
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
search: {
|
||||||
|
back: string
|
||||||
|
clear: string
|
||||||
|
empty: string
|
||||||
|
placeholder: string
|
||||||
|
results: string
|
||||||
|
}
|
||||||
sections: {
|
sections: {
|
||||||
appearance: {
|
appearance: {
|
||||||
dark: string
|
dark: string
|
||||||
@@ -1589,6 +1596,7 @@ export interface TranslationResources {
|
|||||||
noDisney: string
|
noDisney: string
|
||||||
originalsOnly: string
|
originalsOnly: string
|
||||||
pending: string
|
pending: string
|
||||||
|
risky: string
|
||||||
unsupportedRegion: string
|
unsupportedRegion: string
|
||||||
yes: string
|
yes: string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user