mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
Compare commits
8 Commits
bump-syspr
...
c57a962109
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c57a962109 | ||
|
|
36926df26c | ||
|
|
9d81a13c58 | ||
|
|
511fab9a9d | ||
|
|
88529af8c8 | ||
|
|
425096e8af | ||
|
|
8a4e2327c1 | ||
|
|
74b1687be9 |
@@ -1,51 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||
cd "$ROOT_DIR"
|
||||
if ! command -v "cargo-make" >/dev/null 2>&1; then
|
||||
echo "❌ cargo-make is required for pre-commit checks."
|
||||
cargo install --force cargo-make
|
||||
fi
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "❌ pnpm is required for pre-commit checks."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOCALE_DIFF="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src/locales/' || true)"
|
||||
if [ -n "$LOCALE_DIFF" ]; then
|
||||
echo "[pre-commit] Locale changes detected. Regenerating i18n types..."
|
||||
pnpm i18n:types
|
||||
if [ -d src/types/generated ]; then
|
||||
echo "[pre-commit] Staging regenerated i18n type artifacts..."
|
||||
git add src/types/generated
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[pre-commit] Running pnpm format before lint..."
|
||||
pnpm format
|
||||
|
||||
echo "[pre-commit] Running lint-staged for JS/TS files..."
|
||||
pnpm exec lint-staged
|
||||
|
||||
RUST_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src-tauri/.*\.rs$' || true)"
|
||||
if [ -n "$RUST_FILES" ]; then
|
||||
echo "[pre-commit] Formatting Rust changes with cargo fmt..."
|
||||
cargo fmt
|
||||
while IFS= read -r file; do
|
||||
[ -n "$file" ] && git add "$file"
|
||||
done <<< "$RUST_FILES"
|
||||
|
||||
echo "[pre-commit] Linting Rust changes with cargo clippy..."
|
||||
cargo clippy-all
|
||||
if ! command -v clash-verge-logging-check >/dev/null 2>&1; then
|
||||
echo "[pre-commit] Installing clash-verge-logging-check..."
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-logging-check.git
|
||||
fi
|
||||
clash-verge-logging-check
|
||||
fi
|
||||
|
||||
TS_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx)$' || true)"
|
||||
if [ -n "$TS_FILES" ]; then
|
||||
echo "[pre-commit] Running TypeScript type check..."
|
||||
pnpm typecheck
|
||||
fi
|
||||
|
||||
echo "[pre-commit] All checks completed successfully."
|
||||
cargo make pre-commit
|
||||
|
||||
@@ -1,36 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
remote_name="${1:-origin}"
|
||||
remote_url="${2:-unknown}"
|
||||
|
||||
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "❌ pnpm is required for pre-push checks."
|
||||
exit 1
|
||||
if ! command -v "cargo-make" >/dev/null 2>&1; then
|
||||
echo "❌ cargo-make is required for pre-push checks."
|
||||
cargo install --force cargo-make
|
||||
fi
|
||||
|
||||
echo "[pre-push] Preparing to push to '$remote_name' ($remote_url). Running full validation..."
|
||||
|
||||
echo "[pre-push] Checking Prettier formatting..."
|
||||
pnpm format:check
|
||||
|
||||
echo "[pre-push] Running ESLint..."
|
||||
pnpm lint
|
||||
|
||||
echo "[pre-push] Running TypeScript type checking..."
|
||||
pnpm typecheck
|
||||
|
||||
if command -v cargo >/dev/null 2>&1; then
|
||||
echo "[pre-push] Verifying Rust formatting..."
|
||||
cargo fmt --check
|
||||
|
||||
echo "[pre-push] Running cargo clippy..."
|
||||
cargo clippy-all
|
||||
else
|
||||
echo "[pre-push] ⚠️ cargo not found; skipping Rust checks."
|
||||
fi
|
||||
|
||||
echo "[pre-push] All checks passed."
|
||||
cargo make pre-push
|
||||
|
||||
174
Cargo.lock
generated
174
Cargo.lock
generated
@@ -156,7 +156,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
@@ -463,9 +463,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.15.3"
|
||||
version = "1.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86"
|
||||
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
@@ -473,9 +473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.36.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8"
|
||||
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -982,9 +982,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.53"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -1178,7 +1178,7 @@ dependencies = [
|
||||
"warp",
|
||||
"winapi",
|
||||
"winreg 0.55.0",
|
||||
"zip 7.1.0",
|
||||
"zip 7.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1233,8 +1233,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash_verge_service_ipc"
|
||||
version = "2.1.1"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#d9a3b701008a0b55ab21aaa8e5a60ce140dc90b9"
|
||||
version = "2.1.2"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#dc7238ef3a8d8b6b87e5e140a008f2e2d66ef262"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"compact_str",
|
||||
@@ -1276,7 +1276,7 @@ dependencies = [
|
||||
"block",
|
||||
"cocoa-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"objc",
|
||||
@@ -1302,7 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1509,6 +1509,19 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.2.0"
|
||||
@@ -2012,7 +2025,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2026,12 +2039,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.0"
|
||||
@@ -2276,7 +2283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3432,12 +3439,12 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
"windows-registry 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3765,7 +3772,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.2",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4497,7 +4504,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4513,9 +4520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
@@ -4855,9 +4862,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
@@ -4901,7 +4908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5449,9 +5456,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppmd-rust"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4"
|
||||
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
@@ -5542,9 +5549,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -5710,7 +5717,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -5748,16 +5755,16 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -6370,7 +6377,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6428,7 +6435,7 @@ dependencies = [
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7042,9 +7049,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
@@ -7244,16 +7251,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.37.2"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
|
||||
checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows 0.61.3",
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7315,21 +7322,20 @@ checksum = "c0e973b34477b7823833469eb0f5a3a60370fef7a453e02d751b59180d0a5a05"
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.34.5"
|
||||
source = "git+https://github.com/tauri-apps/tao#e196538f989894fcb92401fced54d7d3a65fc91a"
|
||||
source = "git+https://github.com/tauri-apps/tao#648161769c35ddd2bffd939f4d4d6a027325326c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"core-graphics 0.25.0",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
"dispatch2",
|
||||
"dlopen2",
|
||||
"dpi",
|
||||
"gdkwayland-sys",
|
||||
"gdkx11-sys",
|
||||
"gtk",
|
||||
"jni",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
@@ -7365,7 +7371,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tao-macros"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/tauri-apps/tao#e196538f989894fcb92401fced54d7d3a65fc91a"
|
||||
source = "git+https://github.com/tauri-apps/tao#648161769c35ddd2bffd939f4d4d6a027325326c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7587,7 +7593,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
"windows-registry 0.5.3",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
@@ -7943,7 +7949,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8102,9 +8108,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.45"
|
||||
version = "0.3.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
||||
checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -8120,15 +8126,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.25"
|
||||
version = "0.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
||||
checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -8191,7 +8197,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -8424,7 +8430,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -8716,6 +8722,12 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e43ffa54726cdc9ea78392023ffe9fe9cf9ac779e1c6fcb0d23f9862e3879d20"
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
@@ -8889,9 +8901,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
@@ -9327,7 +9339,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9507,6 +9519,17 @@ dependencies = [
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@@ -10083,9 +10106,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.13.1"
|
||||
version = "5.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b"
|
||||
checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
@@ -10118,9 +10141,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.13.1"
|
||||
version = "5.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37"
|
||||
checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.4.0",
|
||||
"proc-macro2",
|
||||
@@ -10144,18 +10167,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.33"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.33"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -10251,9 +10274,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "7.1.0"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9013f1222db8a6d680f13a7ccdc60a781199cd09c2fa4eff58e728bb181757fc"
|
||||
checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
@@ -10271,6 +10294,7 @@ dependencies = [
|
||||
"ppmd-rust",
|
||||
"sha1",
|
||||
"time",
|
||||
"typed-path",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
@@ -10284,9 +10308,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.15"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"
|
||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- 修复首次启动时代理信息刷新缓慢
|
||||
- 修复无网络时无限请求 IP 归属查询
|
||||
- 修复 WebDAV 页面重试逻辑
|
||||
- 修复 Linux 通过 GUI 安装服务模式权限不符合预期
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
86
Makefile.toml
Normal file
86
Makefile.toml
Normal file
@@ -0,0 +1,86 @@
|
||||
[config]
|
||||
skip_core_tasks = true
|
||||
skip_git_env_info = true
|
||||
skip_rust_env_info = true
|
||||
skip_crate_env_info = true
|
||||
|
||||
# --- Backend ---
|
||||
|
||||
[tasks.rust-format]
|
||||
install_crate = "rustfmt"
|
||||
command = "cargo"
|
||||
args = ["fmt", "--", "--emit=files"]
|
||||
|
||||
[tasks.rust-clippy]
|
||||
description = "Run cargo clippy to lint the code"
|
||||
command = "cargo"
|
||||
args = ["clippy", "--all-targets", "--all-features", "--", "-D", "warnings"]
|
||||
|
||||
# --- Frontend ---
|
||||
|
||||
[tasks.eslint]
|
||||
description = "Run ESLint to lint the code"
|
||||
command = "pnpm"
|
||||
args = ["lint"]
|
||||
[tasks.eslint.windows]
|
||||
command = "pnpm.cmd"
|
||||
|
||||
[tasks.typecheck]
|
||||
description = "Run type checks"
|
||||
command = "pnpm"
|
||||
args = ["typecheck"]
|
||||
[tasks.typecheck.windows]
|
||||
command = "pnpm.cmd"
|
||||
|
||||
[tasks.lint-staged]
|
||||
description = "Run lint-staged for staged files"
|
||||
command = "pnpm"
|
||||
args = ["exec", "lint-staged"]
|
||||
[tasks.lint-staged.windows]
|
||||
command = "pnpm.cmd"
|
||||
|
||||
# --- Jobs ---
|
||||
|
||||
# Rust format (for pre-commit)
|
||||
[tasks.rust-format-check]
|
||||
description = "Check Rust code formatting"
|
||||
dependencies = ["rust-format"]
|
||||
[tasks.rust-format-check.condition]
|
||||
files_modified.input = [
|
||||
"./src-tauri/**/*.rs",
|
||||
"./crates/**/*.rs",
|
||||
"**/Cargo.toml",
|
||||
]
|
||||
files_modified.output = ["./target/debug/*", "./target/release/*"]
|
||||
|
||||
# Rust lint (for pre-push)
|
||||
[tasks.rust-lint]
|
||||
description = "Run Rust linting"
|
||||
dependencies = ["rust-clippy"]
|
||||
[tasks.rust-lint.condition]
|
||||
files_modified.input = [
|
||||
"./src-tauri/**/*.rs",
|
||||
"./crates/**/*.rs",
|
||||
"**/Cargo.toml",
|
||||
]
|
||||
files_modified.output = ["./target/debug/*", "./target/release/*"]
|
||||
|
||||
# Frontend format (for pre-commit)
|
||||
[tasks.frontend-format]
|
||||
description = "Frontend format checks"
|
||||
dependencies = ["lint-staged"]
|
||||
|
||||
# Frontend lint (for pre-push)
|
||||
[tasks.frontend-lint]
|
||||
description = "Frontend linting and type checking"
|
||||
dependencies = ["eslint", "typecheck"]
|
||||
|
||||
# --- Git Hooks ---
|
||||
|
||||
[tasks.pre-commit]
|
||||
description = "Pre-commit checks: format only"
|
||||
dependencies = ["rust-format-check", "frontend-format"]
|
||||
|
||||
[tasks.pre-push]
|
||||
description = "Pre-push checks: lint and typecheck"
|
||||
dependencies = ["rust-lint", "frontend-lint"]
|
||||
@@ -8,7 +8,7 @@ rust-version = "1.91"
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
sysinfo = { version = "0.37.2", features = ["network", "system"] }
|
||||
sysinfo = { version = "0.38.0", features = ["network", "system"] }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc = "0.2.180"
|
||||
|
||||
24
package.json
24
package.json
@@ -55,12 +55,12 @@
|
||||
"@tauri-apps/plugin-shell": "2.3.4",
|
||||
"@tauri-apps/plugin-updater": "2.9.0",
|
||||
"ahooks": "^3.9.6",
|
||||
"axios": "^1.13.2",
|
||||
"axios": "^1.13.3",
|
||||
"dayjs": "1.11.19",
|
||||
"foxact": "^0.2.52",
|
||||
"i18next": "^25.7.4",
|
||||
"i18next": "^25.8.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lodash-es": "^4.17.22",
|
||||
"lodash-es": "^4.17.23",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-yaml": "^5.4.0",
|
||||
"nanoid": "^5.1.6",
|
||||
@@ -70,7 +70,7 @@
|
||||
"react-hook-form": "^7.71.1",
|
||||
"react-i18next": "16.5.3",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "^7.12.0",
|
||||
"react-router": "^7.13.0",
|
||||
"react-virtuoso": "^4.18.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"swr": "^2.3.8",
|
||||
@@ -78,14 +78,14 @@
|
||||
"types-pac": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^7.0.0",
|
||||
"@eslint-react/eslint-plugin": "^2.7.2",
|
||||
"@actions/github": "^8.0.0",
|
||||
"@eslint-react/eslint-plugin": "^2.7.4",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tauri-apps/cli": "2.9.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/react": "19.2.8",
|
||||
"@types/react": "19.2.9",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
"@vitejs/plugin-react-swc": "^4.2.2",
|
||||
@@ -103,18 +103,18 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"glob": "^13.0.0",
|
||||
"globals": "^17.0.0",
|
||||
"globals": "^17.1.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"husky": "^9.1.7",
|
||||
"jiti": "^2.6.1",
|
||||
"lint-staged": "^16.2.7",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^3.8.0",
|
||||
"sass": "^1.97.2",
|
||||
"tar": "^7.5.3",
|
||||
"prettier": "^3.8.1",
|
||||
"sass": "^1.97.3",
|
||||
"tar": "^7.5.6",
|
||||
"terser": "^5.46.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.53.0",
|
||||
"typescript-eslint": "^8.53.1",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-svgr": "^4.5.0"
|
||||
},
|
||||
|
||||
1370
pnpm-lock.yaml
generated
1370
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -81,7 +81,7 @@ tauri-plugin-fs = "2.4.5"
|
||||
tauri-plugin-process = "2.3.1"
|
||||
tauri-plugin-deep-link = "2.4.6"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
zip = "7.1.0"
|
||||
zip = "7.2.0"
|
||||
reqwest_dav = "0.3.1"
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
@@ -98,7 +98,7 @@ tauri-plugin-devtools = { version = "2.0.1" }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" }
|
||||
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
|
||||
async-trait = "0.1.89"
|
||||
clash_verge_service_ipc = { version = "2.1.1", features = [
|
||||
clash_verge_service_ipc = { version = "2.1.2", features = [
|
||||
"client",
|
||||
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
|
||||
arc-swap = "1.8.0"
|
||||
|
||||
@@ -117,7 +117,7 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
|
||||
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||
match profiles_reorder_safe(&active_id, &over_id).await {
|
||||
Ok(_) => {
|
||||
logging!(debug, Type::Cmd, "重新排序配置文件");
|
||||
logging!(info, Type::Cmd, "重新排序配置文件");
|
||||
Config::profiles().await.apply();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -60,13 +60,13 @@ pub struct PrfItem {
|
||||
pub file_data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PrfSelected {
|
||||
pub name: Option<String>,
|
||||
pub now: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
pub struct PrfExtra {
|
||||
pub upload: u64,
|
||||
pub download: u64,
|
||||
@@ -124,22 +124,25 @@ pub struct PrfOption {
|
||||
impl PrfOption {
|
||||
pub fn merge(one: Option<&Self>, other: Option<&Self>) -> Option<Self> {
|
||||
match (one, other) {
|
||||
(Some(a), Some(b)) => Some(Self {
|
||||
user_agent: b.user_agent.as_ref().or(a.user_agent.as_ref()).cloned(),
|
||||
with_proxy: b.with_proxy.or(a.with_proxy),
|
||||
self_proxy: b.self_proxy.or(a.self_proxy),
|
||||
danger_accept_invalid_certs: b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs),
|
||||
allow_auto_update: b.allow_auto_update.or(a.allow_auto_update),
|
||||
update_interval: b.update_interval.or(a.update_interval),
|
||||
merge: b.merge.as_ref().or(a.merge.as_ref()).cloned(),
|
||||
script: b.script.as_ref().or(a.script.as_ref()).cloned(),
|
||||
rules: b.rules.as_ref().or(a.rules.as_ref()).cloned(),
|
||||
proxies: b.proxies.as_ref().or(a.proxies.as_ref()).cloned(),
|
||||
groups: b.groups.as_ref().or(a.groups.as_ref()).cloned(),
|
||||
timeout_seconds: b.timeout_seconds.or(a.timeout_seconds),
|
||||
}),
|
||||
(Some(a), None) => Some(a.clone()),
|
||||
(None, Some(b)) => Some(b.clone()),
|
||||
(Some(a_ref), Some(b_ref)) => {
|
||||
let mut result = a_ref.clone();
|
||||
result.user_agent = b_ref.user_agent.clone().or(result.user_agent);
|
||||
result.with_proxy = b_ref.with_proxy.or(result.with_proxy);
|
||||
result.self_proxy = b_ref.self_proxy.or(result.self_proxy);
|
||||
result.danger_accept_invalid_certs =
|
||||
b_ref.danger_accept_invalid_certs.or(result.danger_accept_invalid_certs);
|
||||
result.allow_auto_update = b_ref.allow_auto_update.or(result.allow_auto_update);
|
||||
result.update_interval = b_ref.update_interval.or(result.update_interval);
|
||||
result.merge = b_ref.merge.clone().or(result.merge);
|
||||
result.script = b_ref.script.clone().or(result.script);
|
||||
result.rules = b_ref.rules.clone().or(result.rules);
|
||||
result.proxies = b_ref.proxies.clone().or(result.proxies);
|
||||
result.groups = b_ref.groups.clone().or(result.groups);
|
||||
result.timeout_seconds = b_ref.timeout_seconds.or(result.timeout_seconds);
|
||||
Some(result)
|
||||
}
|
||||
(Some(a_ref), None) => Some(a_ref.clone()),
|
||||
(None, Some(b_ref)) => Some(b_ref.clone()),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,12 @@ use crate::utils::{
|
||||
};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use once_cell::sync::OnceCell;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use smartstring::alias::String;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
use tokio::fs;
|
||||
|
||||
static PROFILE_FILE_RE: OnceCell<Regex> = OnceCell::new();
|
||||
|
||||
/// Define the `profiles.yaml` schema
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct IProfiles {
|
||||
@@ -41,15 +37,23 @@ pub struct CleanupResult {
|
||||
|
||||
macro_rules! patch {
|
||||
($lv: expr, $rv: expr, $key: tt) => {
|
||||
if let Some(ref val) = $rv.$key {
|
||||
if Some(val) != $lv.$key.as_ref() {
|
||||
$lv.$key = Some(val.to_owned());
|
||||
}
|
||||
if ($rv.$key).is_some() {
|
||||
$lv.$key = $rv.$key.to_owned();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl IProfiles {
|
||||
// Helper to find and remove an item by uid from the items vec, returning its file name (if any).
|
||||
fn take_item_file_by_uid(items: &mut Vec<PrfItem>, target_uid: Option<String>) -> Option<String> {
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid == target_uid {
|
||||
return items.remove(i).file;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn new() -> Self {
|
||||
let path = match dirs::profiles_path() {
|
||||
Ok(p) => p,
|
||||
@@ -59,22 +63,21 @@ impl IProfiles {
|
||||
}
|
||||
};
|
||||
|
||||
let mut profiles = match help::read_yaml::<Self>(&path).await {
|
||||
Ok(profiles) => profiles,
|
||||
match help::read_yaml::<Self>(&path).await {
|
||||
Ok(mut profiles) => {
|
||||
let items = profiles.items.get_or_insert_with(Vec::new);
|
||||
for item in items.iter_mut() {
|
||||
if item.uid.is_none() {
|
||||
item.uid = Some(help::get_uid("d").into());
|
||||
}
|
||||
}
|
||||
profiles
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Config, "{err}");
|
||||
return Self::default();
|
||||
}
|
||||
};
|
||||
|
||||
let items = profiles.items.get_or_insert_with(Vec::new);
|
||||
for item in items.iter_mut() {
|
||||
if item.uid.is_none() {
|
||||
item.uid = Some(help::get_uid("d").into());
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
profiles
|
||||
}
|
||||
|
||||
pub async fn save_file(&self) -> Result<()> {
|
||||
@@ -110,28 +113,38 @@ impl IProfiles {
|
||||
pub fn get_item(&self, uid: impl AsRef<str>) -> Result<&PrfItem> {
|
||||
let uid_str = uid.as_ref();
|
||||
|
||||
self.items
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("no profile items found"))?
|
||||
.iter()
|
||||
.find(|each| each.uid.as_ref().is_some_and(|uid_val| uid_val.as_str() == uid_str))
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to get the profile item \"uid:{}\"", uid_str))
|
||||
if let Some(items) = self.items.as_ref() {
|
||||
for each in items.iter() {
|
||||
if let Some(uid_val) = &each.uid
|
||||
&& uid_val.as_str() == uid_str
|
||||
{
|
||||
return Ok(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("failed to get the profile item \"uid:{}\"", uid_str);
|
||||
}
|
||||
|
||||
/// append new item
|
||||
/// if the file_data is some
|
||||
/// then should save the data to file
|
||||
pub async fn append_item(&mut self, item: &mut PrfItem) -> Result<()> {
|
||||
anyhow::ensure!(item.uid.is_some(), "the uid should not be null");
|
||||
let uid = &item.uid;
|
||||
if uid.is_none() {
|
||||
bail!("the uid should not be null");
|
||||
}
|
||||
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
anyhow::ensure!(item.file.is_some(), "the file should not be null");
|
||||
if item.file.is_none() {
|
||||
bail!("the file should not be null");
|
||||
}
|
||||
|
||||
let file = item
|
||||
.file
|
||||
.as_ref()
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("file field is required when file_data is provided"))?;
|
||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
|
||||
@@ -140,116 +153,111 @@ impl IProfiles {
|
||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||
}
|
||||
|
||||
if self.current.is_none()
|
||||
&& let Some(t) = item.itype.as_deref()
|
||||
&& (t == "remote" || t == "local")
|
||||
{
|
||||
self.current = item.uid.to_owned();
|
||||
if self.current.is_none() && (item.itype == Some("remote".into()) || item.itype == Some("local".into())) {
|
||||
self.current = uid.to_owned();
|
||||
}
|
||||
|
||||
self.items.get_or_insert_default().push(std::mem::take(item));
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
items.push(item.to_owned());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// reorder items
|
||||
pub async fn reorder(&mut self, active_id: &str, over_id: &str) -> Result<()> {
|
||||
if active_id == over_id {
|
||||
return Ok(());
|
||||
pub async fn reorder(&mut self, active_id: &String, over_id: &String) -> Result<()> {
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let mut old_index = None;
|
||||
let mut new_index = None;
|
||||
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid.as_ref() == Some(active_id) {
|
||||
old_index = Some(i);
|
||||
}
|
||||
if items[i].uid.as_ref() == Some(over_id) {
|
||||
new_index = Some(i);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(items) = self.items.as_mut() else {
|
||||
return Ok(());
|
||||
let (old_idx, new_idx) = match (old_index, new_index) {
|
||||
(Some(old), Some(new)) => (old, new),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let mut old_idx = None;
|
||||
let mut new_idx = None;
|
||||
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if let Some(uid) = item.uid.as_ref() {
|
||||
if uid == active_id {
|
||||
old_idx = Some(i);
|
||||
}
|
||||
if uid == over_id {
|
||||
new_idx = Some(i);
|
||||
}
|
||||
}
|
||||
if old_idx.is_some() && new_idx.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(old), Some(new)) = (old_idx, new_idx) {
|
||||
if old < new {
|
||||
items[old..=new].rotate_left(1);
|
||||
} else {
|
||||
items[new..=old].rotate_right(1);
|
||||
}
|
||||
|
||||
return self.save_file().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
let item = items.remove(old_idx);
|
||||
items.insert(new_idx, item);
|
||||
self.items = Some(items);
|
||||
self.save_file().await
|
||||
}
|
||||
|
||||
/// update the item value
|
||||
pub async fn patch_item(&mut self, uid: &String, item: &PrfItem) -> Result<()> {
|
||||
let items = self
|
||||
.items
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow::anyhow!("no profile items found"))?;
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
|
||||
let target = items.iter_mut().find(|each| each.uid.as_ref() == Some(uid));
|
||||
for each in items.iter_mut() {
|
||||
if each.uid.as_ref() == Some(uid) {
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
patch!(each, item, file);
|
||||
patch!(each, item, url);
|
||||
patch!(each, item, selected);
|
||||
patch!(each, item, extra);
|
||||
patch!(each, item, updated);
|
||||
patch!(each, item, option);
|
||||
|
||||
if let Some(each) = target {
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
patch!(each, item, file);
|
||||
patch!(each, item, url);
|
||||
patch!(each, item, selected);
|
||||
patch!(each, item, extra);
|
||||
patch!(each, item, updated);
|
||||
patch!(each, item, option);
|
||||
|
||||
return self.save_file().await;
|
||||
self.items = Some(items);
|
||||
return self.save_file().await;
|
||||
}
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
bail!("failed to find the profile item \"uid:{uid}\"")
|
||||
}
|
||||
|
||||
/// be used to update the remote item
|
||||
/// only patch `updated` `extra` `file_data`
|
||||
pub async fn update_item(&mut self, uid: &String, item: &mut PrfItem) -> Result<()> {
|
||||
let target = self
|
||||
.items
|
||||
.get_or_insert_default()
|
||||
.iter_mut()
|
||||
.find(|each| each.uid.as_ref() == Some(uid))
|
||||
.ok_or_else(|| anyhow::anyhow!("Item not found"))?;
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
target.extra = item.extra;
|
||||
target.updated = item.updated;
|
||||
target.home = std::mem::take(&mut item.home);
|
||||
target.option = PrfOption::merge(target.option.as_ref(), item.option.as_ref());
|
||||
// find the item
|
||||
let _ = self.get_item(uid)?;
|
||||
|
||||
let Some(file_data) = item.file_data.take() else {
|
||||
return self.save_file().await;
|
||||
};
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
let some_uid = Some(uid.clone());
|
||||
|
||||
let file = target
|
||||
.file
|
||||
.take()
|
||||
.or_else(|| item.file.take())
|
||||
.unwrap_or_else(|| format!("{}.yaml", uid).into());
|
||||
for each in items.iter_mut() {
|
||||
if each.uid == some_uid {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
each.home = item.home.to_owned();
|
||||
each.option = PrfOption::merge(each.option.as_ref(), item.option.as_ref());
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
let file = each.file.take();
|
||||
let file =
|
||||
file.unwrap_or_else(|| item.file.take().unwrap_or_else(|| format!("{}.yaml", &uid).into()));
|
||||
|
||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
// the file must exists
|
||||
each.file = Some(file.clone());
|
||||
|
||||
fs::write(&path, file_data.as_bytes())
|
||||
.await
|
||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
|
||||
target.file = Some(file);
|
||||
fs::write(&path, file_data.as_bytes())
|
||||
.await
|
||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.save_file().await
|
||||
}
|
||||
@@ -257,82 +265,68 @@ impl IProfiles {
|
||||
/// delete item
|
||||
/// if delete the current then return true
|
||||
pub async fn delete_item(&mut self, uid: &String) -> Result<bool> {
|
||||
let uids_to_remove: HashSet<String> = {
|
||||
let item = self.get_item(uid)?;
|
||||
let mut set = HashSet::new();
|
||||
set.insert(uid.clone());
|
||||
|
||||
if let Some(opt) = &item.option {
|
||||
if let Some(u) = &opt.merge {
|
||||
set.insert(u.clone());
|
||||
}
|
||||
if let Some(u) = &opt.script {
|
||||
set.insert(u.clone());
|
||||
}
|
||||
if let Some(u) = &opt.rules {
|
||||
set.insert(u.clone());
|
||||
}
|
||||
if let Some(u) = &opt.proxies {
|
||||
set.insert(u.clone());
|
||||
}
|
||||
if let Some(u) = &opt.groups {
|
||||
set.insert(u.clone());
|
||||
}
|
||||
}
|
||||
set
|
||||
};
|
||||
|
||||
let current = self.current.as_ref().unwrap_or(uid);
|
||||
let current = current.clone();
|
||||
let item = self.get_item(uid)?;
|
||||
let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
|
||||
let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
|
||||
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
|
||||
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
|
||||
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let mut deleted_files = Vec::new();
|
||||
|
||||
items.retain_mut(|item| {
|
||||
if let Some(item_uid) = item.uid.as_ref()
|
||||
&& uids_to_remove.contains(item_uid)
|
||||
{
|
||||
if let Some(file) = item.file.take() {
|
||||
deleted_files.push(file);
|
||||
// remove the main item (if exists) and delete its file
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.clone())) {
|
||||
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
|
||||
// remove related extension items (merge, script, rules, proxies, groups)
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, merge_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, script_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, rules_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, proxies_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, groups_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
// delete the original uid
|
||||
if current == *uid {
|
||||
self.current = None;
|
||||
for item in items.iter() {
|
||||
if item.itype == Some("remote".into()) || item.itype == Some("local".into()) {
|
||||
self.current = item.uid.clone();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
let is_deleting_current = self.current.as_ref() == Some(uid);
|
||||
if is_deleting_current {
|
||||
self.current = items
|
||||
.iter()
|
||||
.find(|i| i.itype.as_deref() == Some("remote") || i.itype.as_deref() == Some("local"))
|
||||
.and_then(|i| i.uid.clone());
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
|
||||
if let Ok(profile_dir) = dirs::app_profiles_dir() {
|
||||
for file in deleted_files {
|
||||
let _ = profile_dir.join(file.as_str()).remove_if_exists().await;
|
||||
}
|
||||
}
|
||||
|
||||
self.save_file().await?;
|
||||
Ok(is_deleting_current)
|
||||
Ok(current == *uid)
|
||||
}
|
||||
|
||||
/// 获取current指向的订阅内容
|
||||
pub async fn current_mapping(&self) -> Result<Mapping> {
|
||||
let (Some(current), Some(items)) = (self.current.as_ref(), self.items.as_ref()) else {
|
||||
return Ok(Mapping::new());
|
||||
};
|
||||
|
||||
let Some(target) = items.iter().find(|e| e.uid.as_ref() == Some(current)) else {
|
||||
bail!("failed to find the current profile \"uid:{current}\"");
|
||||
};
|
||||
|
||||
let file = target
|
||||
.file
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to get the file field"))?;
|
||||
let file_path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
help::read_mapping(&file_path).await
|
||||
match (self.current.as_ref(), self.items.as_ref()) {
|
||||
(Some(current), Some(items)) => {
|
||||
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
|
||||
let file_path = match item.file.as_ref() {
|
||||
Some(file) => dirs::app_profiles_dir()?.join(file.as_str()),
|
||||
None => bail!("failed to get the file field"),
|
||||
};
|
||||
return help::read_mapping(&file_path).await;
|
||||
}
|
||||
bail!("failed to find the current profile \"uid:{current}\"");
|
||||
}
|
||||
_ => Ok(Mapping::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断profile是否是current指向的
|
||||
@@ -342,32 +336,32 @@ impl IProfiles {
|
||||
|
||||
/// 获取所有的profiles(uid,名称, 是否为 current)
|
||||
pub fn profiles_preview(&self) -> Option<Vec<IProfilePreview<'_>>> {
|
||||
let items = self.items.as_ref()?;
|
||||
let current_uid = self.current.as_ref();
|
||||
|
||||
let previews = items
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
let uid = e.uid.as_ref()?;
|
||||
let name = e.name.as_ref()?;
|
||||
Some(IProfilePreview {
|
||||
uid,
|
||||
name,
|
||||
is_current: current_uid == Some(uid),
|
||||
self.items.as_ref().map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
if let (Some(uid), Some(name)) = (e.uid.as_ref(), e.name.as_ref()) {
|
||||
let is_current = self.is_current_profile_index(uid);
|
||||
let preview = IProfilePreview { uid, name, is_current };
|
||||
Some(preview)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(previews)
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// 通过 uid 获取名称
|
||||
pub fn get_name_by_uid(&self, uid: &str) -> Option<&String> {
|
||||
self.items
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.find(|item| item.uid.as_deref() == Some(uid))
|
||||
.and_then(|item| item.name.as_ref())
|
||||
pub fn get_name_by_uid(&self, uid: &String) -> Option<&String> {
|
||||
if let Some(items) = &self.items {
|
||||
for item in items {
|
||||
if item.uid.as_ref() == Some(uid) {
|
||||
return item.name.as_ref();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 以 app 中的 profile 列表为准,删除不再需要的文件
|
||||
@@ -454,39 +448,59 @@ impl IProfiles {
|
||||
|
||||
/// 获取所有 active profile 关联的文件名
|
||||
fn get_all_active_files(&self) -> HashSet<&str> {
|
||||
let mut active_files = HashSet::new();
|
||||
let items = match &self.items {
|
||||
Some(i) => i,
|
||||
None => return active_files,
|
||||
};
|
||||
let mut active_files: HashSet<&str> = HashSet::new();
|
||||
|
||||
let item_map: HashMap<Option<&str>, &PrfItem> = items.iter().map(|i| (i.uid.as_deref(), i)).collect();
|
||||
if let Some(items) = &self.items {
|
||||
for item in items {
|
||||
// 收集所有类型 profile 的文件
|
||||
if let Some(file) = &item.file {
|
||||
active_files.insert(file);
|
||||
}
|
||||
|
||||
for item in items {
|
||||
if let Some(f) = &item.file {
|
||||
active_files.insert(f.as_str());
|
||||
}
|
||||
|
||||
let Some(opt) = &item.option else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let related = [
|
||||
opt.merge.as_deref(),
|
||||
opt.script.as_deref(),
|
||||
opt.rules.as_deref(),
|
||||
opt.proxies.as_deref(),
|
||||
opt.groups.as_deref(),
|
||||
];
|
||||
|
||||
for r_uid in related.into_iter().flatten() {
|
||||
if let Some(r_item) = item_map.get(&Some(r_uid))
|
||||
&& let Some(f) = &r_item.file
|
||||
// 对于主 profile 类型(remote/local),还需要收集其关联的扩展文件
|
||||
if let Some(itype) = &item.itype
|
||||
&& (itype == "remote" || itype == "local")
|
||||
&& let Some(option) = &item.option
|
||||
{
|
||||
active_files.insert(f.as_str());
|
||||
// 收集关联的扩展文件
|
||||
if let Some(merge_uid) = &option.merge
|
||||
&& let Ok(merge_item) = self.get_item(merge_uid)
|
||||
&& let Some(file) = &merge_item.file
|
||||
{
|
||||
active_files.insert(file);
|
||||
}
|
||||
|
||||
if let Some(script_uid) = &option.script
|
||||
&& let Ok(script_item) = self.get_item(script_uid)
|
||||
&& let Some(file) = &script_item.file
|
||||
{
|
||||
active_files.insert(file);
|
||||
}
|
||||
|
||||
if let Some(rules_uid) = &option.rules
|
||||
&& let Ok(rules_item) = self.get_item(rules_uid)
|
||||
&& let Some(file) = &rules_item.file
|
||||
{
|
||||
active_files.insert(file);
|
||||
}
|
||||
|
||||
if let Some(proxies_uid) = &option.proxies
|
||||
&& let Ok(proxies_item) = self.get_item(proxies_uid)
|
||||
&& let Some(file) = &proxies_item.file
|
||||
{
|
||||
active_files.insert(file);
|
||||
}
|
||||
|
||||
if let Some(groups_uid) = &option.groups
|
||||
&& let Ok(groups_item) = self.get_item(groups_uid)
|
||||
&& let Some(file) = &groups_item.file
|
||||
{
|
||||
active_files.insert(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active_files
|
||||
}
|
||||
|
||||
@@ -501,10 +515,18 @@ impl IProfiles {
|
||||
// p12345678.yaml (proxies)
|
||||
// g12345678.yaml (groups)
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let re = PROFILE_FILE_RE
|
||||
.get_or_init(|| Regex::new(r"^(?:[RLmprg][a-zA-Z0-9_-]+\.yaml|s[a-zA-Z0-9_-]+\.js)$").unwrap());
|
||||
re.is_match(filename)
|
||||
let patterns = [
|
||||
r"^[RL][a-zA-Z0-9]+\.yaml$", // Remote/Local profiles
|
||||
r"^m[a-zA-Z0-9]+\.yaml$", // Merge files
|
||||
r"^s[a-zA-Z0-9]+\.js$", // Script files
|
||||
r"^[rpg][a-zA-Z0-9]+\.yaml$", // Rules/Proxies/Groups files
|
||||
];
|
||||
|
||||
patterns.iter().any(|pattern| {
|
||||
regex::Regex::new(pattern)
|
||||
.map(|re| re.is_match(filename))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ impl CoreManager {
|
||||
match event {
|
||||
tauri_plugin_shell::process::CommandEvent::Stdout(line)
|
||||
| tauri_plugin_shell::process::CommandEvent::Stderr(line) => {
|
||||
let message = CompactString::from(String::from_utf8_lossy(&line).as_ref());
|
||||
let message = CompactString::from(&*String::from_utf8_lossy(&line));
|
||||
Logger::global().writer_sidecar_log(Level::Error, &message);
|
||||
CLASH_LOGGER.append_log(message).await;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ pub struct TimerTask {
|
||||
pub last_run: i64, // Timestamp of last execution
|
||||
}
|
||||
|
||||
// TODO 一个 Timer 负责轻量, 一个 Timer 负责订阅更新。当前会生产 N(订阅数量) + 1 个定时任务
|
||||
pub struct Timer {
|
||||
/// cron manager
|
||||
pub delay_timer: Arc<RwLock<DelayTimer>>,
|
||||
|
||||
@@ -10,14 +10,13 @@ import { useLockFn } from "ahooks";
|
||||
import { useCallback, useEffect, useMemo, useReducer } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useServiceInstaller } from "@/hooks/use-service-installer";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useUpdate } from "@/hooks/use-update";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { getSystemInfo } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
import { version as appVersion } from "@root/package.json";
|
||||
|
||||
import { EnhancedCard } from "./enhanced-card";
|
||||
@@ -52,6 +51,18 @@ export const SystemInfoCard = () => {
|
||||
const { isAdminMode, isSidecarMode } = useSystemState();
|
||||
const { installServiceAndRestartCore } = useServiceInstaller();
|
||||
|
||||
// 自动检查更新逻辑
|
||||
const { checkUpdate: triggerCheckUpdate } = useUpdate(true, {
|
||||
onSuccess: () => {
|
||||
const now = Date.now();
|
||||
localStorage.setItem("last_check_update", now.toString());
|
||||
dispatchSystemState({
|
||||
type: "set-last-check-update",
|
||||
payload: new Date(now).toLocaleString(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 系统信息状态
|
||||
const [systemState, dispatchSystemState] = useReducer(systemStateReducer, {
|
||||
osInfo: "",
|
||||
@@ -109,7 +120,7 @@ export const SystemInfoCard = () => {
|
||||
|
||||
timeoutId = window.setTimeout(() => {
|
||||
if (verge?.auto_check_update) {
|
||||
checkUpdate().catch(console.error);
|
||||
triggerCheckUpdate().catch(console.error);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
@@ -118,26 +129,7 @@ export const SystemInfoCard = () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
}, [verge?.auto_check_update, dispatchSystemState]);
|
||||
|
||||
// 自动检查更新逻辑
|
||||
useSWR(
|
||||
verge?.auto_check_update ? "checkUpdate" : null,
|
||||
async () => {
|
||||
const now = Date.now();
|
||||
localStorage.setItem("last_check_update", now.toString());
|
||||
dispatchSystemState({
|
||||
type: "set-last-check-update",
|
||||
payload: new Date(now).toLocaleString(),
|
||||
});
|
||||
return await checkUpdate();
|
||||
},
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
refreshInterval: 24 * 60 * 60 * 1000, // 每天检查一次
|
||||
dedupingInterval: 60 * 60 * 1000, // 1小时内不重复检查
|
||||
},
|
||||
);
|
||||
}, [verge?.auto_check_update, dispatchSystemState, triggerCheckUpdate]);
|
||||
|
||||
// 导航到设置页面
|
||||
const goToSettings = useCallback(() => {
|
||||
@@ -164,7 +156,7 @@ export const SystemInfoCard = () => {
|
||||
// 检查更新
|
||||
const onCheckUpdate = useLockFn(async () => {
|
||||
try {
|
||||
const info = await checkUpdate();
|
||||
const info = await triggerCheckUpdate();
|
||||
if (!info?.available) {
|
||||
showNotice.success(
|
||||
"settings.components.verge.advanced.notifications.latestVersion",
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Button } from "@mui/material";
|
||||
import { useRef } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { DialogRef } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { checkUpdateSafe } from "@/services/update";
|
||||
import { useUpdate } from "@/hooks/use-update";
|
||||
|
||||
import { UpdateViewer } from "../setting/mods/update-viewer";
|
||||
|
||||
@@ -14,20 +12,9 @@ interface Props {
|
||||
|
||||
export const UpdateButton = (props: Props) => {
|
||||
const { className } = props;
|
||||
const { verge } = useVerge();
|
||||
const { auto_check_update } = verge || {};
|
||||
|
||||
const viewerRef = useRef<DialogRef>(null);
|
||||
|
||||
const { data: updateInfo } = useSWR(
|
||||
auto_check_update || auto_check_update === null ? "checkUpdate" : null,
|
||||
checkUpdateSafe,
|
||||
{
|
||||
errorRetryCount: 2,
|
||||
revalidateIfStale: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
},
|
||||
);
|
||||
const { updateInfo } = useUpdate();
|
||||
|
||||
if (!updateInfo?.available) return null;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useRuntimeConfig } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { getRuntimeConfig } from "@/services/cmds";
|
||||
import delayManager from "@/services/delay";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
|
||||
@@ -107,14 +106,7 @@ export const useRenderList = (
|
||||
const latencyTimeout = verge?.default_latency_timeout;
|
||||
|
||||
// 获取运行时配置用于链式代理模式
|
||||
const { data: runtimeConfig } = useSWR(
|
||||
isChainMode ? "getRuntimeConfig" : null,
|
||||
getRuntimeConfig,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: true,
|
||||
},
|
||||
);
|
||||
const { data: runtimeConfig } = useRuntimeConfig(!!isChainMode);
|
||||
|
||||
// 计算列数
|
||||
const col = useMemo(
|
||||
|
||||
@@ -4,10 +4,9 @@ import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||
import type { Ref } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
||||
import { useNetworkInterfaces } from "@/hooks/use-network";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
@@ -22,13 +21,7 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const { data: networkInterfaces } = useSWR(
|
||||
"clash-verge-rev-internal://network-interfaces",
|
||||
getNetworkInterfacesInfo,
|
||||
{
|
||||
fallbackData: [], // default data before fetch
|
||||
},
|
||||
);
|
||||
const { networkInterfaces } = useNetworkInterfaces();
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
|
||||
@@ -8,13 +8,12 @@ import { useImperativeHandle, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useUpdate } from "@/hooks/use-update";
|
||||
import { portableFlag } from "@/pages/_layout";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useSetUpdateState, useUpdateState } from "@/services/states";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
|
||||
export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -23,11 +22,7 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const updateState = useUpdateState();
|
||||
const setUpdateState = useSetUpdateState();
|
||||
|
||||
const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, {
|
||||
errorRetryCount: 2,
|
||||
revalidateIfStale: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
});
|
||||
const { updateInfo } = useUpdate();
|
||||
|
||||
const [downloaded, setDownloaded] = useState(0);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
@@ -51,11 +51,12 @@ const validatePorts = (patch: ClashInfoPatch) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useRuntimeConfig = (shouldFetch: boolean = true) => {
|
||||
return useSWR(shouldFetch ? "getRuntimeConfig" : null, getRuntimeConfig);
|
||||
};
|
||||
|
||||
export const useClash = () => {
|
||||
const { data: clash, mutate: mutateClash } = useSWR(
|
||||
"getRuntimeConfig",
|
||||
getRuntimeConfig,
|
||||
);
|
||||
const { data: clash, mutate: mutateClash } = useRuntimeConfig();
|
||||
|
||||
const { data: versionData, mutate: mutateVersion } = useSWR(
|
||||
"getVersion",
|
||||
|
||||
22
src/hooks/use-network.ts
Normal file
22
src/hooks/use-network.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import useSWR from "swr";
|
||||
|
||||
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
||||
|
||||
export const useNetworkInterfaces = () => {
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
"getNetworkInterfacesInfo",
|
||||
getNetworkInterfacesInfo,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
fallbackData: [],
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
networkInterfaces: data || [],
|
||||
loading: isLoading,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
};
|
||||
46
src/hooks/use-update.ts
Normal file
46
src/hooks/use-update.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import useSWR, { SWRConfiguration } from "swr";
|
||||
|
||||
import { checkUpdateSafe } from "@/services/update";
|
||||
|
||||
import { useVerge } from "./use-verge";
|
||||
|
||||
export interface UpdateInfo {
|
||||
version: string;
|
||||
body: string;
|
||||
date: string;
|
||||
available: boolean;
|
||||
downloadAndInstall: (onEvent?: any) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useUpdate = (
|
||||
enabled: boolean = true,
|
||||
options?: SWRConfiguration,
|
||||
) => {
|
||||
const { verge } = useVerge();
|
||||
const { auto_check_update } = verge || {};
|
||||
|
||||
// Determine if we should check for updates
|
||||
// If enabled is explicitly false, don't check
|
||||
// Otherwise, respect the auto_check_update setting (or default to true if null/undefined for manual triggers)
|
||||
const shouldCheck = enabled && auto_check_update !== false;
|
||||
|
||||
const {
|
||||
data: updateInfo,
|
||||
mutate: checkUpdate,
|
||||
isValidating,
|
||||
} = useSWR(shouldCheck ? "checkUpdate" : null, checkUpdateSafe, {
|
||||
errorRetryCount: 2,
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
refreshInterval: 24 * 60 * 60 * 1000, // 24 hours
|
||||
dedupingInterval: 60 * 60 * 1000, // 1 hour
|
||||
...options,
|
||||
});
|
||||
|
||||
return {
|
||||
updateInfo,
|
||||
checkUpdate,
|
||||
loading: isValidating,
|
||||
};
|
||||
};
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getRunningMode,
|
||||
getSystemProxy,
|
||||
} from "@/services/cmds";
|
||||
import { SWR_DEFAULTS, SWR_MIHOMO, SWR_REALTIME } from "@/services/config";
|
||||
import { SWR_DEFAULTS, SWR_MIHOMO } from "@/services/config";
|
||||
|
||||
import { AppDataContext, AppDataContextType } from "./app-data-context";
|
||||
|
||||
@@ -30,14 +30,7 @@ export const AppDataProvider = ({
|
||||
const { data: proxiesData, mutate: refreshProxy } = useSWR(
|
||||
"getProxies",
|
||||
calcuProxies,
|
||||
{
|
||||
...SWR_REALTIME,
|
||||
onError: (_) => {
|
||||
// FIXME when we intially start the app, and core is starting,
|
||||
// there will be error thrown by getProxies API.
|
||||
// We should handle this case properly later.
|
||||
},
|
||||
},
|
||||
SWR_MIHOMO,
|
||||
);
|
||||
|
||||
const { data: clashConfig, mutate: refreshClashConfig } = useSWR(
|
||||
|
||||
@@ -16,12 +16,6 @@ export const SWR_DEFAULTS = {
|
||||
dedupingInterval: 5000,
|
||||
} as const;
|
||||
|
||||
export const SWR_REALTIME = {
|
||||
...SWR_DEFAULTS,
|
||||
refreshInterval: 8000,
|
||||
dedupingInterval: 3000,
|
||||
} as const;
|
||||
|
||||
export const SWR_SLOW_POLL = {
|
||||
...SWR_DEFAULTS,
|
||||
refreshInterval: 60000,
|
||||
@@ -29,4 +23,6 @@ export const SWR_SLOW_POLL = {
|
||||
|
||||
export const SWR_MIHOMO = {
|
||||
...SWR_NOT_SMART,
|
||||
errorRetryInterval: 500,
|
||||
errorRetryCount: 15,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user