主界面修改及代理模块初始化
This commit is contained in:
Generated
+550
-13
@@ -207,6 +207,17 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "auto-launch"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471"
|
||||
dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"thiserror 1.0.69",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
@@ -460,8 +471,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
@@ -494,6 +507,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@@ -517,9 +540,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -530,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -701,13 +724,33 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||
dependencies = [
|
||||
"dirs-sys 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users 0.4.6",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -718,7 +761,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -845,7 +888,7 @@ dependencies = [
|
||||
"rustc_version",
|
||||
"toml 1.1.2+spec-1.1.0",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
"winreg 0.55.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -854,6 +897,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
@@ -982,6 +1034,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -989,7 +1050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
"foreign-types-shared 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1003,6 +1064,12 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
@@ -1393,6 +1460,25 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.14.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1488,6 +1574,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1498,6 +1585,37 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
@@ -1516,9 +1634,11 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1997,6 +2117,23 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@@ -2276,6 +2413,49 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -2609,6 +2789,17 @@ dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
@@ -2669,6 +2860,46 @@ version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.4"
|
||||
@@ -2703,6 +2934,20 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
@@ -2731,12 +2976,51 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -2746,6 +3030,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
@@ -2803,6 +3096,29 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.36.1"
|
||||
@@ -2927,6 +3243,18 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.21.0"
|
||||
@@ -2959,6 +3287,19 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serialize-to-javascript"
|
||||
version = "0.1.2"
|
||||
@@ -3135,6 +3476,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -3187,6 +3534,27 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@@ -3208,7 +3576,7 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"block2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dbus",
|
||||
@@ -3266,7 +3634,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"dunce",
|
||||
"embed_plist",
|
||||
"getrandom 0.3.4",
|
||||
@@ -3287,7 +3655,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.13.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -3316,7 +3684,7 @@ checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"glob",
|
||||
"heck 0.5.0",
|
||||
"json-patch",
|
||||
@@ -3386,6 +3754,20 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-autostart"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459383cebc193cdd03d1ba4acc40f2c408a7abce419d64bdcd2d745bc2886f70"
|
||||
dependencies = [
|
||||
"auto-launch",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.4"
|
||||
@@ -3534,11 +3916,17 @@ dependencies = [
|
||||
name = "thing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-autostart",
|
||||
"tauri-plugin-opener",
|
||||
"windows-sys 0.52.0",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3650,6 +4038,26 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
@@ -3866,7 +4274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2",
|
||||
@@ -3963,6 +4371,18 @@ version = "1.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -4006,6 +4426,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.1"
|
||||
@@ -4391,6 +4817,17 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -4436,6 +4873,24 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -4469,6 +4924,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -4509,6 +4979,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -4521,6 +4997,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -4533,6 +5015,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -4551,6 +5039,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -4563,6 +5057,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -4575,6 +5075,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -4587,6 +5093,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -4617,6 +5129,25 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.55.0"
|
||||
@@ -4649,7 +5180,7 @@ dependencies = [
|
||||
"block2",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"dom_query",
|
||||
"dpi",
|
||||
"dunce",
|
||||
@@ -4809,6 +5340,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
|
||||
@@ -22,4 +22,14 @@ tauri = { version = "2", features = ["tray-icon"] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
chrono = "0.4"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.52"
|
||||
windows-sys = { version = "0.52", features = ["Win32_Networking_WinInet", "Win32_Foundation"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -15,6 +15,7 @@
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-set-effects",
|
||||
"core:window:allow-set-background-color"
|
||||
"core:window:allow-set-background-color",
|
||||
"core:window:allow-set-theme"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"identifier": "desktop-capability",
|
||||
"platforms": [
|
||||
"macOS",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"autostart:default"
|
||||
]
|
||||
}
|
||||
+86
-4
@@ -1,21 +1,92 @@
|
||||
use tauri::Manager;
|
||||
|
||||
mod logger;
|
||||
mod mihomo_manager;
|
||||
mod process_manager;
|
||||
|
||||
use logger::{
|
||||
clear_logs, get_log_info, get_logs, log_message, LogManager,
|
||||
};
|
||||
use mihomo_manager::{
|
||||
proxy_activate_profile, proxy_clear_system_proxy, proxy_close_connection, proxy_delete_profile,
|
||||
proxy_get_connections, proxy_get_proxies, proxy_get_settings, proxy_get_system_proxy,
|
||||
proxy_import_profile, proxy_kernel_info, proxy_patch_configs, proxy_restart, proxy_save_settings,
|
||||
proxy_select_proxy, proxy_set_system_proxy, proxy_start, proxy_status, proxy_stop,
|
||||
proxy_test_delay, proxy_update_profile, proxy_version, MihomoManager,
|
||||
};
|
||||
use process_manager::{
|
||||
get_all_process_status, get_process_status, start_monitoring_thread, start_process,
|
||||
stop_all_processes, stop_process, ProcessManager,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn quit_app() {
|
||||
fn quit_app(state: tauri::State<'_, ProcessManager>) {
|
||||
// 退出前停止所有子进程
|
||||
state.stop_all();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_autostart::Builder::new().build())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet, quit_app])
|
||||
.manage(ProcessManager::new())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
quit_app,
|
||||
start_process,
|
||||
stop_process,
|
||||
get_process_status,
|
||||
get_all_process_status,
|
||||
stop_all_processes,
|
||||
log_message,
|
||||
get_logs,
|
||||
clear_logs,
|
||||
get_log_info,
|
||||
proxy_get_settings,
|
||||
proxy_save_settings,
|
||||
proxy_kernel_info,
|
||||
proxy_status,
|
||||
proxy_start,
|
||||
proxy_stop,
|
||||
proxy_restart,
|
||||
proxy_version,
|
||||
proxy_get_proxies,
|
||||
proxy_select_proxy,
|
||||
proxy_test_delay,
|
||||
proxy_get_connections,
|
||||
proxy_close_connection,
|
||||
proxy_patch_configs,
|
||||
proxy_import_profile,
|
||||
proxy_update_profile,
|
||||
proxy_delete_profile,
|
||||
proxy_activate_profile,
|
||||
proxy_set_system_proxy,
|
||||
proxy_clear_system_proxy,
|
||||
proxy_get_system_proxy
|
||||
])
|
||||
.setup(|app| {
|
||||
// 初始化日志系统,日志目录: {app_data_dir}/logs/
|
||||
let log_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("."))
|
||||
.join("logs");
|
||||
app.manage(LogManager::new(log_dir));
|
||||
|
||||
// 初始化 MihomoManager,数据目录: {app_data_dir}/proxy/
|
||||
let app_data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||
app.manage(MihomoManager::new(app_data_dir));
|
||||
|
||||
let open = tauri::menu::MenuItem::with_id(app, "open", "设置", true, None::<&str>)?;
|
||||
let quit = tauri::menu::MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
|
||||
let menu = tauri::menu::Menu::with_items(app, &[&open, &quit])?;
|
||||
@@ -32,12 +103,20 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
// 退出前停止所有子进程
|
||||
if let Some(pm) = app.try_state::<ProcessManager>() {
|
||||
pm.stop_all();
|
||||
}
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let tauri::tray::TrayIconEvent::Click { button: tauri::tray::MouseButton::Left, .. } = event {
|
||||
if let tauri::tray::TrayIconEvent::Click {
|
||||
button: tauri::tray::MouseButton::Left,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
window.show().ok();
|
||||
@@ -47,6 +126,9 @@ pub fn run() {
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
// 启动进程监控线程
|
||||
start_monitoring_thread(app.handle().clone());
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|window, event| {
|
||||
@@ -57,4 +139,4 @@ pub fn run() {
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
use chrono::Local;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// 日志级别
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// 单条日志记录
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LogEntry {
|
||||
pub timestamp: String,
|
||||
pub level: LogLevel,
|
||||
pub module: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// 日志文件信息
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LogInfo {
|
||||
pub log_dir: String,
|
||||
pub log_files: Vec<String>,
|
||||
pub total_size_bytes: u64,
|
||||
pub max_file_size_bytes: u64,
|
||||
pub max_files: u32,
|
||||
}
|
||||
|
||||
/// 日志管理器 —— 负责文件轮转、写入、查询
|
||||
pub struct LogManager {
|
||||
log_dir: PathBuf,
|
||||
max_file_size: u64,
|
||||
max_files: u32,
|
||||
base_name: String,
|
||||
}
|
||||
|
||||
impl LogManager {
|
||||
pub fn new(log_dir: PathBuf) -> Self {
|
||||
fs::create_dir_all(&log_dir).ok();
|
||||
Self {
|
||||
log_dir,
|
||||
max_file_size: 5 * 1024 * 1024,
|
||||
max_files: 5,
|
||||
base_name: "thing".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 写入一条日志
|
||||
pub fn log(&self, level: LogLevel, module: &str, message: &str) {
|
||||
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let level_str = match level {
|
||||
LogLevel::Debug => "DEBUG",
|
||||
LogLevel::Info => "INFO",
|
||||
LogLevel::Warn => "WARN",
|
||||
LogLevel::Error => "ERROR",
|
||||
};
|
||||
let line = format!(
|
||||
"[{}] [{}] [{}] {}\n",
|
||||
timestamp, level_str, module, message
|
||||
);
|
||||
|
||||
let current_log = self.log_dir.join(format!("{}.log", self.base_name));
|
||||
|
||||
// 检查是否需要轮转
|
||||
if let Ok(meta) = fs::metadata(¤t_log) {
|
||||
if meta.len() >= self.max_file_size {
|
||||
self.rotate();
|
||||
}
|
||||
}
|
||||
|
||||
// 追加写入
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(¤t_log)
|
||||
{
|
||||
let _ = file.write_all(line.as_bytes());
|
||||
let _ = file.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// 日志文件轮转: thing.log → thing.1.log, thing.1.log → thing.2.log, …
|
||||
fn rotate(&self) {
|
||||
// 删除最旧的文件
|
||||
let oldest = self
|
||||
.log_dir
|
||||
.join(format!("{}.{}.log", self.base_name, self.max_files));
|
||||
fs::remove_file(&oldest).ok();
|
||||
|
||||
// 依次重命名
|
||||
for i in (1..self.max_files).rev() {
|
||||
let src = self
|
||||
.log_dir
|
||||
.join(format!("{}.{}.log", self.base_name, i));
|
||||
let dst = self
|
||||
.log_dir
|
||||
.join(format!("{}.{}.log", self.base_name, i + 1));
|
||||
fs::rename(&src, &dst).ok();
|
||||
}
|
||||
|
||||
// thing.log → thing.1.log
|
||||
let current = self.log_dir.join(format!("{}.log", self.base_name));
|
||||
let first_rotated = self
|
||||
.log_dir
|
||||
.join(format!("{}.1.log", self.base_name));
|
||||
fs::rename(¤t, &first_rotated).ok();
|
||||
}
|
||||
|
||||
/// 读取日志(支持按模块/级别过滤、条数限制)
|
||||
pub fn get_logs(
|
||||
&self,
|
||||
module: Option<&str>,
|
||||
level: Option<LogLevel>,
|
||||
limit: Option<usize>,
|
||||
) -> Vec<LogEntry> {
|
||||
let mut entries: Vec<LogEntry> = Vec::new();
|
||||
|
||||
// 收集所有日志文件(包括轮转文件)
|
||||
let mut log_files: Vec<PathBuf> = Vec::new();
|
||||
let current = self.log_dir.join(format!("{}.log", self.base_name));
|
||||
if current.exists() {
|
||||
log_files.push(current);
|
||||
}
|
||||
for i in 1..=self.max_files {
|
||||
let rotated = self
|
||||
.log_dir
|
||||
.join(format!("{}.{}.log", self.base_name, i));
|
||||
if rotated.exists() {
|
||||
log_files.push(rotated);
|
||||
}
|
||||
}
|
||||
|
||||
for path in &log_files {
|
||||
if let Ok(file) = File::open(path) {
|
||||
for line in BufReader::new(file).lines().flatten() {
|
||||
if let Some(entry) = Self::parse_line(&line) {
|
||||
if let Some(ref m) = module {
|
||||
if entry.module != *m {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(ref l) = level {
|
||||
if entry.level != *l {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按时间戳降序(最新在前)
|
||||
entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
|
||||
|
||||
if let Some(n) = limit {
|
||||
entries.truncate(n);
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
/// 解析一行日志: `[YYYY-MM-DD HH:MM:SS] [LEVEL] [MODULE] message`
|
||||
fn parse_line(line: &str) -> Option<LogEntry> {
|
||||
let line = line.trim();
|
||||
if line.len() < 22 || !line.starts_with('[') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ts_end = line[1..].find(']')? + 1;
|
||||
let timestamp = line[1..ts_end].to_string();
|
||||
|
||||
let rest = line[ts_end + 1..].trim();
|
||||
if !rest.starts_with('[') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let lv_end = rest[1..].find(']')? + 1;
|
||||
let level_str = &rest[1..lv_end];
|
||||
let level = match level_str {
|
||||
"DEBUG" => LogLevel::Debug,
|
||||
"INFO" => LogLevel::Info,
|
||||
"WARN" => LogLevel::Warn,
|
||||
"ERROR" => LogLevel::Error,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let rest = rest[lv_end + 1..].trim();
|
||||
if !rest.starts_with('[') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mod_end = rest[1..].find(']')? + 1;
|
||||
let module = rest[1..mod_end].to_string();
|
||||
|
||||
let message = rest[mod_end + 1..].trim().to_string();
|
||||
|
||||
Some(LogEntry {
|
||||
timestamp,
|
||||
level,
|
||||
module,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/// 清空所有日志文件
|
||||
pub fn clear_logs(&self) -> Result<(), String> {
|
||||
let current = self.log_dir.join(format!("{}.log", self.base_name));
|
||||
fs::remove_file(¤t).map_err(|e| e.to_string())?;
|
||||
for i in 1..=self.max_files {
|
||||
let rotated = self
|
||||
.log_dir
|
||||
.join(format!("{}.{}.log", self.base_name, i));
|
||||
fs::remove_file(&rotated).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取日志系统信息
|
||||
pub fn get_info(&self) -> LogInfo {
|
||||
let mut log_files: Vec<String> = Vec::new();
|
||||
let mut total_size: u64 = 0;
|
||||
|
||||
let current = self.log_dir.join(format!("{}.log", self.base_name));
|
||||
if current.exists() {
|
||||
if let Ok(meta) = fs::metadata(¤t) {
|
||||
total_size += meta.len();
|
||||
}
|
||||
log_files.push(format!("{}.log", self.base_name));
|
||||
}
|
||||
for i in 1..=self.max_files {
|
||||
let rotated = self
|
||||
.log_dir
|
||||
.join(format!("{}.{}.log", self.base_name, i));
|
||||
if rotated.exists() {
|
||||
if let Ok(meta) = fs::metadata(&rotated) {
|
||||
total_size += meta.len();
|
||||
}
|
||||
log_files.push(format!("{}.{}.log", self.base_name, i));
|
||||
}
|
||||
}
|
||||
|
||||
LogInfo {
|
||||
log_dir: self.log_dir.to_string_lossy().to_string(),
|
||||
log_files,
|
||||
total_size_bytes: total_size,
|
||||
max_file_size_bytes: self.max_file_size,
|
||||
max_files: self.max_files,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tauri 命令 =====
|
||||
|
||||
#[tauri::command]
|
||||
pub fn log_message(
|
||||
state: tauri::State<'_, LogManager>,
|
||||
level: String,
|
||||
module: String,
|
||||
message: String,
|
||||
) -> Result<(), String> {
|
||||
let level = match level.as_str() {
|
||||
"debug" => LogLevel::Debug,
|
||||
"info" => LogLevel::Info,
|
||||
"warn" => LogLevel::Warn,
|
||||
"error" => LogLevel::Error,
|
||||
_ => return Err(format!("无效的日志级别: {}", level)),
|
||||
};
|
||||
state.log(level, &module, &message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_logs(
|
||||
state: tauri::State<'_, LogManager>,
|
||||
module: Option<String>,
|
||||
level: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Vec<LogEntry> {
|
||||
let level = level.and_then(|l| match l.as_str() {
|
||||
"debug" => Some(LogLevel::Debug),
|
||||
"info" => Some(LogLevel::Info),
|
||||
"warn" => Some(LogLevel::Warn),
|
||||
"error" => Some(LogLevel::Error),
|
||||
_ => None,
|
||||
});
|
||||
state.get_logs(module.as_deref(), level, limit)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn clear_logs(state: tauri::State<'_, LogManager>) -> Result<(), String> {
|
||||
state.clear_logs()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_log_info(state: tauri::State<'_, LogManager>) -> LogInfo {
|
||||
state.get_info()
|
||||
}
|
||||
@@ -0,0 +1,705 @@
|
||||
use chrono::Local;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Value as YamlValue;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri::path::BaseDirectory;
|
||||
|
||||
use crate::process_manager::{ProcessInfo, ProcessManager, StartProcessParams};
|
||||
|
||||
// ===================== 数据结构 =====================
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxySettings {
|
||||
pub mixed_port: u16,
|
||||
pub external_controller: String,
|
||||
pub secret: String,
|
||||
pub mode: String,
|
||||
pub log_level: String,
|
||||
pub allow_lan: bool,
|
||||
pub system_proxy: bool,
|
||||
pub auto_start: bool,
|
||||
pub current_profile: Option<String>,
|
||||
pub profiles: Vec<ProfileMeta>,
|
||||
}
|
||||
|
||||
impl Default for ProxySettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mixed_port: 7890,
|
||||
external_controller: "127.0.0.1:9090".into(),
|
||||
secret: String::new(),
|
||||
mode: "rule".into(),
|
||||
log_level: "info".into(),
|
||||
allow_lan: false,
|
||||
system_proxy: false,
|
||||
auto_start: false,
|
||||
current_profile: None,
|
||||
profiles: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileMeta {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub added_at: String,
|
||||
pub updated_at: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelInfo {
|
||||
pub path: String,
|
||||
pub exists: bool,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyStatus {
|
||||
pub running: bool,
|
||||
pub pid: Option<u32>,
|
||||
pub restart_count: u32,
|
||||
}
|
||||
|
||||
// ===================== MihomoManager =====================
|
||||
|
||||
pub struct MihomoManager {
|
||||
root: PathBuf,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl MihomoManager {
|
||||
pub fn new(app_data_dir: PathBuf) -> Self {
|
||||
let root = app_data_dir.join("proxy");
|
||||
for d in ["cores", "mihomo", "profiles", "logs"] {
|
||||
fs::create_dir_all(root.join(d)).ok();
|
||||
}
|
||||
Self {
|
||||
root,
|
||||
client: Client::builder()
|
||||
.build()
|
||||
.unwrap_or_else(|_| Client::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn cores_dir(&self) -> PathBuf {
|
||||
self.root.join("cores")
|
||||
}
|
||||
pub fn kernel_path(&self) -> PathBuf {
|
||||
self.cores_dir().join("mihomo.exe")
|
||||
}
|
||||
fn mihomo_dir(&self) -> PathBuf {
|
||||
self.root.join("mihomo")
|
||||
}
|
||||
fn config_path(&self) -> PathBuf {
|
||||
self.mihomo_dir().join("config.yaml")
|
||||
}
|
||||
fn profiles_dir(&self) -> PathBuf {
|
||||
self.root.join("profiles")
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
fn logs_dir(&self) -> PathBuf {
|
||||
self.root.join("logs")
|
||||
}
|
||||
fn settings_path(&self) -> PathBuf {
|
||||
self.root.join("settings.json")
|
||||
}
|
||||
|
||||
// ---------- 设置 ----------
|
||||
pub fn load_settings(&self) -> ProxySettings {
|
||||
fs::read_to_string(self.settings_path())
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str::<ProxySettings>(&s).ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn save_settings(&self, settings: &ProxySettings) -> Result<(), String> {
|
||||
let s = serde_json::to_string_pretty(settings).map_err(|e| e.to_string())?;
|
||||
fs::write(self.settings_path(), s).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// ---------- 内核 ----------
|
||||
pub fn kernel_info(&self) -> KernelInfo {
|
||||
let path = self.kernel_path();
|
||||
let exists = path.exists();
|
||||
let version = if exists {
|
||||
std::process::Command::new(&path)
|
||||
.arg("-v")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.and_then(|s| {
|
||||
s.lines()
|
||||
.find(|l| l.contains("Mihomo Meta") || l.contains("mihomo"))
|
||||
.map(|l| l.trim().to_string())
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
KernelInfo {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
exists,
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
/// 确保内核就位:若 cores/ 无内核,尝试从 resource 目录复制
|
||||
pub fn prepare_kernel(&self, app: &AppHandle) -> Result<KernelInfo, String> {
|
||||
let kernel = self.kernel_path();
|
||||
if !kernel.exists() {
|
||||
if let Ok(res) = app.path().resolve("binaries/mihomo.exe", BaseDirectory::Resource) {
|
||||
if res.exists() {
|
||||
fs::copy(&res, &kernel).map_err(|e| format!("复制内核失败: {}", e))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(self.kernel_info())
|
||||
}
|
||||
|
||||
// ---------- 配置生成 ----------
|
||||
/// 合并 profile + 控制器设置,生成运行时 config.yaml
|
||||
pub fn generate_config(&self) -> Result<(), String> {
|
||||
let settings = self.load_settings();
|
||||
let mut value: YamlValue = if let Some(id) = &settings.current_profile {
|
||||
let path = self.profiles_dir().join(format!("{}.yaml", id));
|
||||
if path.exists() {
|
||||
let content = fs::read_to_string(&path).map_err(|e| e.to_string())?;
|
||||
serde_yaml::from_str(&content).unwrap_or(YamlValue::Mapping(serde_yaml::Mapping::new()))
|
||||
} else {
|
||||
YamlValue::Mapping(serde_yaml::Mapping::new())
|
||||
}
|
||||
} else {
|
||||
YamlValue::Mapping(serde_yaml::Mapping::new())
|
||||
};
|
||||
|
||||
if !value.is_mapping() {
|
||||
value = YamlValue::Mapping(serde_yaml::Mapping::new());
|
||||
}
|
||||
let m = value.as_mapping_mut().unwrap();
|
||||
m.insert(YamlValue::String("mixed-port".into()), YamlValue::Number(settings.mixed_port.into()));
|
||||
m.insert(
|
||||
YamlValue::String("external-controller".into()),
|
||||
YamlValue::String(settings.external_controller.clone()),
|
||||
);
|
||||
if !settings.secret.is_empty() {
|
||||
m.insert(YamlValue::String("secret".into()), YamlValue::String(settings.secret.clone()));
|
||||
}
|
||||
m.insert(YamlValue::String("mode".into()), YamlValue::String(settings.mode.clone()));
|
||||
m.insert(
|
||||
YamlValue::String("log-level".into()),
|
||||
YamlValue::String(settings.log_level.clone()),
|
||||
);
|
||||
m.insert(YamlValue::String("allow-lan".into()), YamlValue::Bool(settings.allow_lan));
|
||||
|
||||
let yaml = serde_yaml::to_string(&value).map_err(|e| e.to_string())?;
|
||||
fs::write(self.config_path(), yaml).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 构建启动 mihomo 所需的进程参数(含 prepare + config 生成)
|
||||
pub fn prepare_for_start(&self, app: &AppHandle) -> Result<StartProcessParams, String> {
|
||||
let info = self.prepare_kernel(app)?;
|
||||
if !info.exists {
|
||||
return Err(format!(
|
||||
"mihomo 内核未安装。请将 mihomo.exe 放置到 src-tauri/binaries/ 后重新运行,或直接放到:\n{}",
|
||||
self.cores_dir().to_string_lossy()
|
||||
));
|
||||
}
|
||||
self.generate_config()?;
|
||||
Ok(StartProcessParams {
|
||||
id: "proxy".into(),
|
||||
executable: self.kernel_path().to_string_lossy().to_string(),
|
||||
args: vec![
|
||||
"-d".into(),
|
||||
self.mihomo_dir().to_string_lossy().to_string(),
|
||||
"-f".into(),
|
||||
self.config_path().to_string_lossy().to_string(),
|
||||
],
|
||||
cwd: Some(self.mihomo_dir().to_string_lossy().to_string()),
|
||||
name: "mihomo".into(),
|
||||
restart_on_crash: true,
|
||||
max_restarts: 3,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- 订阅管理 ----------
|
||||
pub async fn import_profile(&self, url: &str, name: &str) -> Result<ProfileMeta, String> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(url)
|
||||
.header("User-Agent", "clash.meta/thing")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("下载订阅失败: {}", e))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("订阅下载失败: HTTP {}", resp.status()));
|
||||
}
|
||||
let content = resp.text().await.map_err(|e| e.to_string())?;
|
||||
if !content.contains("proxies") && !content.contains("Proxy") {
|
||||
return Err("订阅内容不像有效的 Clash/mihomo 配置".into());
|
||||
}
|
||||
let id = format!("profile-{}", Local::now().format("%Y%m%d%H%M%S"));
|
||||
let path = self.profiles_dir().join(format!("{}.yaml", id));
|
||||
fs::write(&path, &content).map_err(|e| e.to_string())?;
|
||||
let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let meta = ProfileMeta {
|
||||
id: id.clone(),
|
||||
name: name.to_string(),
|
||||
url: url.to_string(),
|
||||
added_at: now.clone(),
|
||||
updated_at: now,
|
||||
size: content.len() as u64,
|
||||
};
|
||||
let mut settings = self.load_settings();
|
||||
settings.profiles.push(meta.clone());
|
||||
if settings.current_profile.is_none() {
|
||||
settings.current_profile = Some(id);
|
||||
}
|
||||
self.save_settings(&settings)?;
|
||||
Ok(meta)
|
||||
}
|
||||
|
||||
pub async fn update_profile(&self, id: &str) -> Result<ProfileMeta, String> {
|
||||
let mut settings = self.load_settings();
|
||||
let meta = settings
|
||||
.profiles
|
||||
.iter()
|
||||
.find(|p| p.id == id)
|
||||
.cloned()
|
||||
.ok_or_else(|| "订阅不存在".to_string())?;
|
||||
let resp = self
|
||||
.client
|
||||
.get(&meta.url)
|
||||
.header("User-Agent", "clash.meta/thing")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("更新订阅失败: {}", e))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("更新订阅失败: HTTP {}", resp.status()));
|
||||
}
|
||||
let content = resp.text().await.map_err(|e| e.to_string())?;
|
||||
let path = self.profiles_dir().join(format!("{}.yaml", id));
|
||||
fs::write(&path, &content).map_err(|e| e.to_string())?;
|
||||
let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let size = content.len() as u64;
|
||||
if let Some(p) = settings.profiles.iter_mut().find(|p| p.id == id) {
|
||||
p.updated_at = now.clone();
|
||||
p.size = size;
|
||||
}
|
||||
self.save_settings(&settings)?;
|
||||
Ok(ProfileMeta {
|
||||
id: id.to_string(),
|
||||
name: meta.name,
|
||||
url: meta.url,
|
||||
added_at: meta.added_at,
|
||||
updated_at: now,
|
||||
size,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_profile(&self, id: &str) -> Result<(), String> {
|
||||
let path = self.profiles_dir().join(format!("{}.yaml", id));
|
||||
fs::remove_file(&path).ok();
|
||||
let mut settings = self.load_settings();
|
||||
settings.profiles.retain(|p| p.id != id);
|
||||
if settings.current_profile.as_deref() == Some(id) {
|
||||
settings.current_profile = settings.profiles.first().map(|p| p.id.clone());
|
||||
}
|
||||
self.save_settings(&settings)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn activate_profile(&self, id: &str) -> Result<(), String> {
|
||||
let mut settings = self.load_settings();
|
||||
if !settings.profiles.iter().any(|p| p.id == id) {
|
||||
return Err("订阅不存在".into());
|
||||
}
|
||||
settings.current_profile = Some(id.to_string());
|
||||
self.save_settings(&settings)?;
|
||||
self.generate_config()
|
||||
}
|
||||
|
||||
// ---------- mihomo API ----------
|
||||
fn api_url(&self, path: &str) -> String {
|
||||
let s = self.load_settings();
|
||||
format!("http://{}{}", s.external_controller, path)
|
||||
}
|
||||
|
||||
fn api_bearer(&self) -> Option<String> {
|
||||
let s = self.load_settings();
|
||||
if s.secret.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("Bearer {}", s.secret))
|
||||
}
|
||||
}
|
||||
|
||||
async fn api_get(&self, path: &str) -> Result<serde_json::Value, String> {
|
||||
let mut req = self.client.get(self.api_url(path));
|
||||
if let Some(b) = self.api_bearer() {
|
||||
req = req.header("Authorization", b);
|
||||
}
|
||||
let resp = req.send().await.map_err(|e| format!("请求 mihomo 失败: {}", e))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("mihomo API 错误: {}", resp.status()));
|
||||
}
|
||||
resp.json().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
async fn api_request(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: &str,
|
||||
body: Option<serde_json::Value>,
|
||||
) -> Result<(), String> {
|
||||
let mut req = self.client.request(method, self.api_url(path));
|
||||
if let Some(b) = self.api_bearer() {
|
||||
req = req.header("Authorization", b);
|
||||
}
|
||||
if let Some(b) = body {
|
||||
req = req.json(&b);
|
||||
}
|
||||
let resp = req.send().await.map_err(|e| format!("请求 mihomo 失败: {}", e))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("mihomo API 错误: {}", resp.status()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_version(&self) -> Result<serde_json::Value, String> {
|
||||
self.api_get("/version").await
|
||||
}
|
||||
|
||||
pub async fn get_proxies(&self) -> Result<serde_json::Value, String> {
|
||||
self.api_get("/proxies").await
|
||||
}
|
||||
|
||||
pub async fn select_proxy(&self, group: &str, name: &str) -> Result<(), String> {
|
||||
self.api_request(
|
||||
reqwest::Method::PUT,
|
||||
&format!("/proxies/{}", url_encode(group)),
|
||||
Some(serde_json::json!({ "name": name })),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn test_delay(&self, name: &str, url: &str, timeout: u32) -> Result<u32, String> {
|
||||
let path = format!(
|
||||
"/proxies/{}/delay?timeout={}&url={}",
|
||||
url_encode(name),
|
||||
timeout,
|
||||
url_encode(url)
|
||||
);
|
||||
let v = self.api_get(&path).await?;
|
||||
v.get("delay")
|
||||
.and_then(|d| d.as_u64())
|
||||
.map(|d| d as u32)
|
||||
.ok_or_else(|| {
|
||||
v.get("message")
|
||||
.and_then(|m| m.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "测速失败".into())
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_rules(&self) -> Result<serde_json::Value, String> {
|
||||
self.api_get("/rules").await
|
||||
}
|
||||
|
||||
pub async fn get_connections(&self) -> Result<serde_json::Value, String> {
|
||||
self.api_get("/connections").await
|
||||
}
|
||||
|
||||
pub async fn close_connection(&self, id: &str) -> Result<(), String> {
|
||||
self.api_request(
|
||||
reqwest::Method::DELETE,
|
||||
&format!("/connections/{}", url_encode(id)),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn patch_configs(&self, body: serde_json::Value) -> Result<(), String> {
|
||||
self.api_request(reqwest::Method::PATCH, "/configs", Some(body)).await
|
||||
}
|
||||
}
|
||||
|
||||
fn url_encode(s: &str) -> String {
|
||||
// 仅对路径段做最小编码,避免引入额外依赖
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for b in s.bytes() {
|
||||
match b {
|
||||
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
|
||||
out.push(b as char);
|
||||
}
|
||||
_ => out.push_str(&format!("%{:02X}", b)),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// ===================== 系统代理(Windows) =====================
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_system_proxy_windows(addr: &str) -> Result<(), String> {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let (settings, _) = hkcu
|
||||
.create_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
|
||||
.map_err(|e| e.to_string())?;
|
||||
settings
|
||||
.set_value("ProxyEnable", &1u32)
|
||||
.map_err(|e| e.to_string())?;
|
||||
settings
|
||||
.set_value("ProxyServer", &addr)
|
||||
.map_err(|e| e.to_string())?;
|
||||
notify_wininet();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn clear_system_proxy_windows() -> Result<(), String> {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let (settings, _) = hkcu
|
||||
.create_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
|
||||
.map_err(|e| e.to_string())?;
|
||||
settings
|
||||
.set_value("ProxyEnable", &0u32)
|
||||
.map_err(|e| e.to_string())?;
|
||||
notify_wininet();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_system_proxy_windows() -> bool {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
|
||||
.ok()
|
||||
.and_then(|s| s.get_value::<u32, _>("ProxyEnable").ok())
|
||||
.map(|v| v != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn notify_wininet() {
|
||||
unsafe {
|
||||
use windows_sys::Win32::Networking::WinInet::*;
|
||||
InternetSetOptionW(std::ptr::null(), INTERNET_OPTION_SETTINGS_CHANGED, std::ptr::null(), 0);
|
||||
InternetSetOptionW(std::ptr::null(), INTERNET_OPTION_REFRESH, std::ptr::null(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn set_system_proxy_windows(_addr: &str) -> Result<(), String> {
|
||||
Err("系统代理仅支持 Windows".into())
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
fn clear_system_proxy_windows() -> Result<(), String> {
|
||||
Err("系统代理仅支持 Windows".into())
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
fn get_system_proxy_windows() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// ===================== Tauri 命令 =====================
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_get_settings(state: tauri::State<'_, MihomoManager>) -> ProxySettings {
|
||||
state.load_settings()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_save_settings(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
settings: ProxySettings,
|
||||
) -> Result<(), String> {
|
||||
state.save_settings(&settings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_kernel_info(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
app: AppHandle,
|
||||
) -> Result<KernelInfo, String> {
|
||||
state.prepare_kernel(&app)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_status(pm: tauri::State<'_, ProcessManager>) -> ProxyStatus {
|
||||
match pm.get_status("proxy") {
|
||||
Some(p) => ProxyStatus {
|
||||
running: matches!(p.status, crate::process_manager::ProcessStatus::Running),
|
||||
pid: p.pid,
|
||||
restart_count: p.restart_count,
|
||||
},
|
||||
None => ProxyStatus {
|
||||
running: false,
|
||||
pid: None,
|
||||
restart_count: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_start(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
pm: tauri::State<'_, ProcessManager>,
|
||||
app: AppHandle,
|
||||
) -> Result<ProcessInfo, String> {
|
||||
let params = state.prepare_for_start(&app)?;
|
||||
pm.start(params)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_stop(pm: tauri::State<'_, ProcessManager>) -> Result<(), String> {
|
||||
pm.stop("proxy")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_restart(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
pm: tauri::State<'_, ProcessManager>,
|
||||
app: AppHandle,
|
||||
) -> Result<ProcessInfo, String> {
|
||||
let _ = pm.stop("proxy");
|
||||
let params = state.prepare_for_start(&app)?;
|
||||
pm.start(params)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_version(state: tauri::State<'_, MihomoManager>) -> Result<serde_json::Value, String> {
|
||||
state.get_version().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_get_proxies(state: tauri::State<'_, MihomoManager>) -> Result<serde_json::Value, String> {
|
||||
state.get_proxies().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_select_proxy(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
group: String,
|
||||
name: String,
|
||||
) -> Result<(), String> {
|
||||
state.select_proxy(&group, &name).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_test_delay(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
name: String,
|
||||
url: Option<String>,
|
||||
timeout: Option<u32>,
|
||||
) -> Result<u32, String> {
|
||||
state
|
||||
.test_delay(
|
||||
&name,
|
||||
url.as_deref().unwrap_or("https://www.gstatic.com/generate_204"),
|
||||
timeout.unwrap_or(5000),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_get_connections(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
state.get_connections().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_close_connection(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
id: String,
|
||||
) -> Result<(), String> {
|
||||
state.close_connection(&id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_patch_configs(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
body: serde_json::Value,
|
||||
) -> Result<(), String> {
|
||||
state.patch_configs(body).await
|
||||
}
|
||||
|
||||
// ---------- 订阅 ----------
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_import_profile(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
url: String,
|
||||
name: String,
|
||||
) -> Result<ProfileMeta, String> {
|
||||
state.import_profile(&url, &name).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn proxy_update_profile(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
id: String,
|
||||
) -> Result<ProfileMeta, String> {
|
||||
state.update_profile(&id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_delete_profile(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
id: String,
|
||||
) -> Result<(), String> {
|
||||
state.delete_profile(&id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_activate_profile(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
id: String,
|
||||
) -> Result<(), String> {
|
||||
state.activate_profile(&id)
|
||||
}
|
||||
|
||||
// ---------- 系统代理 ----------
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_set_system_proxy(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
) -> Result<(), String> {
|
||||
let settings = state.load_settings();
|
||||
let addr = format!("127.0.0.1:{}", settings.mixed_port);
|
||||
set_system_proxy_windows(&addr)?;
|
||||
let mut settings = settings;
|
||||
settings.system_proxy = true;
|
||||
state.save_settings(&settings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_clear_system_proxy(
|
||||
state: tauri::State<'_, MihomoManager>,
|
||||
) -> Result<(), String> {
|
||||
clear_system_proxy_windows()?;
|
||||
let mut settings = state.load_settings();
|
||||
settings.system_proxy = false;
|
||||
state.save_settings(&settings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn proxy_get_system_proxy() -> bool {
|
||||
get_system_proxy_windows()
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
|
||||
/// 进程状态枚举
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProcessStatus {
|
||||
Running,
|
||||
Stopped,
|
||||
Crashed,
|
||||
#[allow(dead_code)]
|
||||
Starting,
|
||||
}
|
||||
|
||||
/// 进程信息(返回给前端)
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub status: ProcessStatus,
|
||||
pub pid: Option<u32>,
|
||||
pub restart_count: u32,
|
||||
}
|
||||
|
||||
/// 进程启动参数(从前端传入)
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StartProcessParams {
|
||||
pub id: String,
|
||||
pub executable: String,
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub cwd: Option<String>,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub restart_on_crash: bool,
|
||||
#[serde(default = "default_max_restarts")]
|
||||
pub max_restarts: u32,
|
||||
}
|
||||
|
||||
fn default_max_restarts() -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
/// 内部进程条目
|
||||
struct ProcessEntry {
|
||||
child: Child,
|
||||
name: String,
|
||||
restart_count: u32,
|
||||
max_restarts: u32,
|
||||
restart_on_crash: bool,
|
||||
executable: String,
|
||||
args: Vec<String>,
|
||||
cwd: Option<String>,
|
||||
}
|
||||
|
||||
/// 进程管理器 —— 管理子进程的完整生命周期
|
||||
pub struct ProcessManager {
|
||||
processes: Mutex<HashMap<String, ProcessEntry>>,
|
||||
}
|
||||
|
||||
impl ProcessManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
processes: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动一个子进程
|
||||
pub fn start(&self, params: StartProcessParams) -> Result<ProcessInfo, String> {
|
||||
let mut processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
// 如果已有同名进程且仍在运行,返回错误
|
||||
if let Some(entry) = processes.get_mut(¶ms.id) {
|
||||
if entry.child.try_wait().map_err(|e| e.to_string())?.is_none() {
|
||||
return Err(format!("进程 '{}' 已在运行中", params.id));
|
||||
}
|
||||
// 进程已退出,移除旧记录
|
||||
processes.remove(¶ms.id);
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(¶ms.executable);
|
||||
cmd.args(¶ms.args);
|
||||
if let Some(ref dir) = params.cwd {
|
||||
cmd.current_dir(dir);
|
||||
}
|
||||
// 子进程的 stdout/stderr/stdin 不继承主进程
|
||||
cmd.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stdin(Stdio::null());
|
||||
|
||||
let child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("启动进程 '{}' 失败: {}", params.id, e))?;
|
||||
let pid = child.id();
|
||||
|
||||
let entry = ProcessEntry {
|
||||
child,
|
||||
name: params.name.clone(),
|
||||
restart_count: 0,
|
||||
max_restarts: params.max_restarts,
|
||||
restart_on_crash: params.restart_on_crash,
|
||||
executable: params.executable,
|
||||
args: params.args,
|
||||
cwd: params.cwd,
|
||||
};
|
||||
|
||||
processes.insert(params.id.clone(), entry);
|
||||
|
||||
Ok(ProcessInfo {
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
status: ProcessStatus::Running,
|
||||
pid: Some(pid),
|
||||
restart_count: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// 停止指定进程
|
||||
pub fn stop(&self, id: &str) -> Result<(), String> {
|
||||
let mut processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
if let Some(mut entry) = processes.remove(id) {
|
||||
entry
|
||||
.child
|
||||
.kill()
|
||||
.map_err(|e| format!("终止进程 '{}' 失败: {}", id, e))?;
|
||||
let _ = entry.child.wait();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("进程 '{}' 不存在", id))
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止所有进程(应用退出时调用)
|
||||
pub fn stop_all(&self) {
|
||||
if let Ok(mut processes) = self.processes.lock() {
|
||||
for (id, mut entry) in processes.drain() {
|
||||
let _ = entry.child.kill();
|
||||
let _ = entry.child.wait();
|
||||
println!("[ProcessManager] 已停止进程: {}", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取单个进程状态
|
||||
pub fn get_status(&self, id: &str) -> Option<ProcessInfo> {
|
||||
let mut processes = self.processes.lock().ok()?;
|
||||
let entry = processes.get_mut(id)?;
|
||||
|
||||
let (status, pid) = match entry.child.try_wait() {
|
||||
Ok(None) => (ProcessStatus::Running, Some(entry.child.id())),
|
||||
Ok(Some(_)) => (ProcessStatus::Crashed, None),
|
||||
Err(_) => (ProcessStatus::Stopped, None),
|
||||
};
|
||||
|
||||
Some(ProcessInfo {
|
||||
id: id.to_string(),
|
||||
name: entry.name.clone(),
|
||||
status,
|
||||
pid,
|
||||
restart_count: entry.restart_count,
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取所有进程状态
|
||||
pub fn get_all_status(&self) -> Vec<ProcessInfo> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
if let Ok(mut processes) = self.processes.lock() {
|
||||
for (id, entry) in processes.iter_mut() {
|
||||
let (status, pid) = match entry.child.try_wait() {
|
||||
Ok(None) => (ProcessStatus::Running, Some(entry.child.id())),
|
||||
Ok(Some(_)) => (ProcessStatus::Crashed, None),
|
||||
Err(_) => (ProcessStatus::Stopped, None),
|
||||
};
|
||||
|
||||
result.push(ProcessInfo {
|
||||
id: id.clone(),
|
||||
name: entry.name.clone(),
|
||||
status,
|
||||
pid,
|
||||
restart_count: entry.restart_count,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// 检查所有进程,处理崩溃的进程(自动重启或移除)
|
||||
/// 返回状态发生变化的进程列表
|
||||
pub fn check_and_cleanup(&self) -> Vec<ProcessInfo> {
|
||||
let mut changes = Vec::new();
|
||||
|
||||
let mut processes = match self.processes.lock() {
|
||||
Ok(p) => p,
|
||||
Err(_) => return changes,
|
||||
};
|
||||
|
||||
let ids: Vec<String> = processes.keys().cloned().collect();
|
||||
|
||||
for id in ids {
|
||||
if let Some(entry) = processes.get_mut(&id) {
|
||||
match entry.child.try_wait() {
|
||||
Ok(None) => {
|
||||
// 仍在运行,无需处理
|
||||
}
|
||||
Ok(Some(_)) => {
|
||||
// 进程已退出
|
||||
if entry.restart_on_crash
|
||||
&& (entry.max_restarts == 0
|
||||
|| entry.restart_count < entry.max_restarts)
|
||||
{
|
||||
// 自动重启
|
||||
let restart_count = entry.restart_count + 1;
|
||||
let executable = entry.executable.clone();
|
||||
let args = entry.args.clone();
|
||||
let cwd = entry.cwd.clone();
|
||||
let name = entry.name.clone();
|
||||
|
||||
// 先终止旧进程
|
||||
let _ = entry.child.kill();
|
||||
let _ = entry.child.wait();
|
||||
|
||||
// 重新启动
|
||||
let mut cmd = Command::new(&executable);
|
||||
cmd.args(&args);
|
||||
if let Some(ref dir) = cwd {
|
||||
cmd.current_dir(dir);
|
||||
}
|
||||
cmd.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stdin(Stdio::null());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(new_child) => {
|
||||
let pid = new_child.id();
|
||||
entry.child = new_child;
|
||||
entry.restart_count = restart_count;
|
||||
|
||||
changes.push(ProcessInfo {
|
||||
id: id.clone(),
|
||||
name: name.clone(),
|
||||
status: ProcessStatus::Running,
|
||||
pid: Some(pid),
|
||||
restart_count,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[ProcessManager] 重启进程 '{}' 失败: {}",
|
||||
id, e
|
||||
);
|
||||
processes.remove(&id);
|
||||
changes.push(ProcessInfo {
|
||||
id: id.clone(),
|
||||
name,
|
||||
status: ProcessStatus::Crashed,
|
||||
pid: None,
|
||||
restart_count,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 不自动重启,移除记录
|
||||
let name = entry.name.clone();
|
||||
let restart_count = entry.restart_count;
|
||||
processes.remove(&id);
|
||||
changes.push(ProcessInfo {
|
||||
id: id.clone(),
|
||||
name,
|
||||
status: ProcessStatus::Stopped,
|
||||
pid: None,
|
||||
restart_count,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// try_wait 出错
|
||||
let name = entry.name.clone();
|
||||
let restart_count = entry.restart_count;
|
||||
processes.remove(&id);
|
||||
changes.push(ProcessInfo {
|
||||
id: id.clone(),
|
||||
name,
|
||||
status: ProcessStatus::Stopped,
|
||||
pid: None,
|
||||
restart_count,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changes
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tauri 命令 =====
|
||||
|
||||
#[tauri::command]
|
||||
pub fn start_process(
|
||||
state: tauri::State<'_, ProcessManager>,
|
||||
params: StartProcessParams,
|
||||
) -> Result<ProcessInfo, String> {
|
||||
state.start(params)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stop_process(
|
||||
state: tauri::State<'_, ProcessManager>,
|
||||
id: String,
|
||||
) -> Result<(), String> {
|
||||
state.stop(&id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_process_status(
|
||||
state: tauri::State<'_, ProcessManager>,
|
||||
id: String,
|
||||
) -> Option<ProcessInfo> {
|
||||
state.get_status(&id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_all_process_status(
|
||||
state: tauri::State<'_, ProcessManager>,
|
||||
) -> Vec<ProcessInfo> {
|
||||
state.get_all_status()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stop_all_processes(state: tauri::State<'_, ProcessManager>) {
|
||||
state.stop_all()
|
||||
}
|
||||
|
||||
/// 启动后台监控线程,定期检查进程状态并向前端发送事件
|
||||
pub fn start_monitoring_thread(app: AppHandle) {
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(3));
|
||||
|
||||
let state = app.state::<ProcessManager>();
|
||||
let changes = state.check_and_cleanup();
|
||||
|
||||
for change in changes {
|
||||
let _ = app.emit("process-status-changed", &change);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user