拉坨大的

This commit is contained in:
VirtualHotBar
2024-04-12 16:33:38 +08:00
parent 27c4cc224d
commit be47b8765f
47 changed files with 1874 additions and 236 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "netmount-gui",
"private": true,
"version": "0.0.0",
"version": "0.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,3 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/res/

581
src-tauri/Cargo.lock generated
View File

@@ -66,10 +66,13 @@ checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
name = "app"
version = "0.1.0"
dependencies = [
"futures-util",
"reqwest",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tokio",
"window-shadows",
]
@@ -654,7 +657,7 @@ dependencies = [
"rustc_version",
"toml 0.8.12",
"vswhom",
"winreg",
"winreg 0.52.0",
]
[[package]]
@@ -845,6 +848,12 @@ dependencies = [
"syn 2.0.53",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
@@ -858,8 +867,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -1171,6 +1183,25 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap 2.2.5",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1241,12 +1272,72 @@ dependencies = [
"itoa 1.0.10",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa 1.0.10",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
@@ -1364,6 +1455,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itoa"
version = "0.4.8"
@@ -1615,6 +1712,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
version = "0.7.2"
@@ -1625,6 +1728,35 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ndk"
version = "0.6.0"
@@ -1764,6 +1896,81 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "open"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8"
dependencies = [
"pathdiff",
"windows-sys 0.42.0",
]
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags 2.5.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"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.53",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "os_info"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbb46d5d01695d7a1fb8be5f0d1968bd2b2b8ba1d1b3e7062ce2a0593e57af1"
dependencies = [
"log",
"serde",
"windows-sys 0.52.0",
]
[[package]]
name = "os_pipe"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "overload"
version = "0.1.1"
@@ -1818,6 +2025,12 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -2239,6 +2452,48 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"winreg 0.50.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -2267,6 +2522,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustversion"
version = "1.0.14"
@@ -2294,6 +2558,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -2306,6 +2579,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.22.0"
@@ -2387,6 +2683,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa 1.0.10",
"ryu",
"serde",
]
[[package]]
name = "serde_with"
version = "3.7.0"
@@ -2469,6 +2777,25 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shared_child"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
@@ -2496,6 +2823,16 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "soup2"
version = "0.2.1"
@@ -2593,6 +2930,46 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sys-locale"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee"
dependencies = [
"js-sys",
"libc",
"wasm-bindgen",
"web-sys",
"windows-sys 0.45.0",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "system-deps"
version = "5.0.0"
@@ -2718,15 +3095,21 @@ dependencies = [
"ignore",
"objc",
"once_cell",
"open",
"os_info",
"os_pipe",
"percent-encoding",
"rand 0.8.5",
"raw-window-handle",
"regex",
"semver",
"serde",
"serde_json",
"serde_repr",
"serialize-to-javascript",
"shared_child",
"state",
"sys-locale",
"tar",
"tauri-macros",
"tauri-runtime",
@@ -2775,6 +3158,7 @@ dependencies = [
"png",
"proc-macro2",
"quote",
"regex",
"semver",
"serde",
"serde_json",
@@ -2994,8 +3378,50 @@ checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.53",
]
[[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-util"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
@@ -3066,6 +3492,12 @@ dependencies = [
"winnow 0.6.5",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.40"
@@ -3136,6 +3568,12 @@ dependencies = [
"serde_json",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.17.0"
@@ -3202,6 +3640,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[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.0.11"
@@ -3250,6 +3694,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
@@ -3287,6 +3740,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
@@ -3316,6 +3781,29 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "wasm-streams"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webkit2gtk"
version = "0.18.2"
@@ -3502,6 +3990,30 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -3520,6 +4032,21 @@ dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@@ -3565,6 +4092,12 @@ dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "windows_aarch64_gnullvm"
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"
@@ -3583,6 +4116,12 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
[[package]]
name = "windows_aarch64_msvc"
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"
@@ -3601,6 +4140,12 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
[[package]]
name = "windows_i686_gnu"
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"
@@ -3619,6 +4164,12 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
[[package]]
name = "windows_i686_msvc"
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"
@@ -3637,6 +4188,12 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
[[package]]
name = "windows_x86_64_gnu"
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"
@@ -3649,6 +4206,12 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
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"
@@ -3667,6 +4230,12 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
[[package]]
name = "windows_x86_64_msvc"
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"
@@ -3697,6 +4266,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.52.0"

View File

@@ -17,9 +17,13 @@ tauri-build = { version = "1.5.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.6.1", features = [ "process-all", "system-tray", "window-all"] }
tauri = { version = "1.6.1", features = [ "shell-open", "fs-all", "os-all", "shell-execute", "process-all", "system-tray", "window-all"] }
window-shadows = "0.2.2"
reqwest = { version = "0.11", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
futures-util = "0.3"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.

Binary file not shown.

Binary file not shown.

View File

@@ -1,64 +0,0 @@
{
"mount": {
"lists": [
{
"storageName": "Webdav",
"mountPath": "Z:",
"parameters": {
"mountOpt": {
"VolumeName": "",
"AllowNonEmpty": false,
"AllowOther": false,
"AllowRoot": false,
"AsyncRead": true,
"AttrTimeout": 1000000000,
"Daemon": false,
"DaemonTimeout": 0,
"DebugFUSE": false,
"DefaultPermissions": false,
"ExtraFlags": [],
"ExtraOptions": [],
"MaxReadAhead": 131072,
"NoAppleDouble": true,
"NoAppleXattr": false,
"WritebackCache": false,
"DaemonWait": 0,
"DeviceName": "",
"NetworkMode": false
},
"vfsOpt": {
"ReadOnly": false,
"CacheMaxAge": 3600000000000,
"CacheMaxSize": -1,
"CacheMode": "minimal",
"CachePollInterval": 60000000000,
"CaseInsensitive": false,
"ChunkSize": 134217728,
"ChunkSizeLimit": -1,
"DirCacheTime": 300000000000,
"DirPerms": 511,
"FilePerms": 438,
"NoChecksum": false,
"NoModTime": false,
"NoSeek": false,
"PollInterval": 60000000000,
"ReadAhead": 0,
"ReadWait": 20000000,
"WriteBack": 5000000000,
"WriteWait": 1000000000,
"Refresh": false,
"BlockNormDupes": false,
"UsedIsSize": false,
"FastFingerprint": false,
"DiskSpaceTotalSize": -1,
"UID": 4294967295,
"GID": 4294967295,
"Umask": 0
}
},
"autoMount": true
}
]
},
"task": {}
}

View File

@@ -1 +0,0 @@
rclone.exe rcd --rc-addr=:5572 --rc-user= --rc-pass= --rc-allow-origin=* --rc-no-auth

View File

@@ -3,21 +3,18 @@
use serde_json::{to_string_pretty, Value};
use std::env;
use std::error::Error;
use std::fs;
use std::process::Command;
use tauri::Manager;
mod tray;
mod utils;
use crate::utils::set_window_shadow;
use crate::utils::download_with_progress;
use crate::utils::find_first_available_drive_letter;
use crate::utils::set_window_shadow;
const CONFIG_PATH: &str = "config.json";
const CONFIG_PATH: &str = "res/config.json";
fn main() {
tauri::Builder::default()
.setup(|app| {
set_window_shadow(app);
@@ -28,13 +25,17 @@ fn main() {
.invoke_handler(tauri::generate_handler![
read_config_file,
write_config_file,
start_rclone,
download_file,
get_available_drive_letter
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
/*
use std::error::Error;
use std::process::Command;
fn run_command(cmd: &str) -> Result<std::process::Child, Box<dyn Error>> {
let cmd_str = if cfg!(target_os = "windows") {
format!("{}", cmd.replace("/", "\\"))
@@ -49,18 +50,20 @@ fn run_command(cmd: &str) -> Result<std::process::Child, Box<dyn Error>> {
};
Ok(child)
}
} */
#[tauri::command]
fn start_rclone(parameter: String) -> Result<(), String> {
match run_command(&("res/bin/rclone.exe".to_owned() + &parameter)) {
Ok(child) => {
println!("rclone.exe started with PID: {}", child.id());
Ok(())
}
Err(error) => Err(format!("Failed to start rclone: {}", error)),
}
fn download_file(url: String, out_path: String) -> Result<bool, usize> {
download_with_progress(&url, &out_path, |total_size, downloaded| {
println!(
"下载进度: {}/{} {}%",
total_size,
downloaded,
(100 * downloaded / total_size)
);
})
.expect("下载失败");
Ok(true)
}
#[tauri::command]
@@ -68,7 +71,7 @@ fn get_available_drive_letter() -> Result<String, String> {
match find_first_available_drive_letter() {
Ok(Some(drive)) => Ok(drive),
Ok(None) => Ok(String::from("")),
Err(e) => Ok(format!("{}", e))
Err(e) => Ok(format!("{}", e)),
}
}

View File

@@ -1,8 +1,9 @@
use tauri::{Manager, Runtime};
use window_shadows::set_shadow;
use std::fs;
use std::io;
use std::io::{self, Write};
pub fn set_window_shadow<R: Runtime>(app: &tauri::App<R>) {
let window = app.get_window("main").unwrap();
@@ -21,3 +22,35 @@ pub fn find_first_available_drive_letter() -> Result<Option<String>, io::Error>
// 如果所有盘符都被占用返回None
Ok(None)
}
use reqwest::Client;
use std::fs::File;
use futures_util::stream::StreamExt; // 此处使用futures_util
#[tokio::main]
pub async fn download_with_progress<F>(url: &str, output_path: &str, mut callback: F) -> io::Result<()>
where
F: FnMut(usize,usize),
{
let response = Client::new().get(url).send().await.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let total_size = response.content_length().unwrap_or(1) as usize;
if response.status().is_success() {
let mut file = File::create(output_path)?;
let mut downloaded: usize = 0;
let mut stream = response.bytes_stream();
while let Some(item) = stream.next().await {
let chunk = item.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
file.write_all(&chunk)?;
downloaded += chunk.len();
callback(total_size,downloaded);
}
} else {
return Err(io::Error::new(io::ErrorKind::Other, "请求失败"));
}
Ok(())
}

View File

@@ -8,11 +8,11 @@
},
"package": {
"productName": "NetMount",
"version": "0.1.0"
"version": "0.1.0-240410"
},
"tauri": {
"systemTray": {
"iconPath": "icons/icon.png",
"iconPath": "icons/icon.png",
"iconAsTemplate": true
},
"allowlist": {
@@ -22,9 +22,45 @@
},
"process": {
"all": true
},
"fs": {
"all": true,
"scope": [
"res/*"
]
},
"os": {
"all": true
},
"shell": {
"all": false,
"execute": true,
"open": true,
"scope": [
{
"name": "ria2c",
"cmd": "res/bin/aria2c",
"args": true
},
{
"name": "rclone",
"cmd": "res/bin/rclone",
"args": true
},
{
"name": "msiexec",
"cmd": "msiexec.exe",
"args": true
},
{
"name": "curl",
"cmd": "curl",
"args": true
}
],
"sidecar": false
}
},
"bundle": {
"active": true,
"category": "DeveloperTool",
@@ -56,7 +92,6 @@
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
@@ -74,7 +109,7 @@
"width": 800,
"minHeight": 450,
"minWidth": 700,
"transparent":true,
"transparent": true,
"decorations": false
}
]

View File

@@ -18,6 +18,7 @@ import AddMount_page from './page/mount/add';
import { IconClose, IconMinus } from '@arco-design/web-react/icon';
import { windowsHide, windowsMini } from './controller/window';
import { rcloneInfo } from './services/rclone';
import { AddTask_page } from './page/task/add';
const { Item: MenuItem, SubMenu } = Menu;
const { Sider, Header, Content, Footer } = Layout;
@@ -50,7 +51,7 @@ function mapMenuItem(routes: Routers[]): JSX.Element {
if (item.hide) {
return <></>
} else if (item.children && item.children.length > 0 && !item.hideChildren) {
return (<SubMenu key={item.path} title={item.title} > {mapMenuItem(item.children)}</SubMenu>)
return (<SubMenu key={item.path} title={item.title} >{mapMenuItem(item.children)}</SubMenu>)
} else {
return (<MenuItem key={item.path} > {item.title}</MenuItem>)
}
@@ -181,6 +182,15 @@ function App() {
title: t('task'),
path: '/task',
component: <Task_page />,
hideChildren: true,
children: [
{
title: t('add'),
path: '/task/add',
key: '/task',//因为父菜单隐藏了子菜单项在此页面时设置父菜单key以选择父菜单项
component: <AddTask_page />,
}
]
},
{
title: t('setting'),
@@ -241,6 +251,7 @@ function App() {
style={{ height: '100%' }}
onClickMenuItem={(path) => {
if (path != location.pathname) {
location.pathname.includes('add')&&Message.warning(t('prompt_for_leaving_the_add_or_edit_page'))
navigate(path)
}
}}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,14 +7,19 @@ import { reupMount } from "./storage/mount/mount"
import { reupStorage } from "./storage/storage"
import { listenWindow } from "./window"
import { NMConfig } from "../type/config"
import { randomString } from "../utils/rclone/utils"
import { randomString } from "../utils/utils"
import { t } from "i18next"
import { startRclone, stopRclone } from "../utils/rclone/process"
import { getOsInfo } from "../utils/tauri/osInfo"
import { startTaskScheduler } from "./task/task"
async function init(setStartStr: Function) {
setStartStr(t('init'))
listenWindow()
await getOsInfo()
setStartStr(t('read_config'))
await invoke('read_config_file').then(configData => {
@@ -23,32 +28,26 @@ async function init(setStartStr: Function) {
console.log(err);
})
/* const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
darkThemeMq.addListener(e => {
if (e.matches) {
document.body.setAttribute('arco-theme', 'dark');
} else {
document.body.removeAttribute('arco-theme');
}
}); */
await startRclone()
/* const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
darkThemeMq.addListener(e => {
if (e.matches) {
document.body.setAttribute('arco-theme', 'dark');
} else {
document.body.removeAttribute('arco-theme');
}
}); */
document.body.removeAttribute('arco-theme');
//rcloneInfo.endpoint.auth.user = randomString(32)
//rcloneInfo.endpoint.auth.pass = randomString(128)
await invoke('start_rclone', {
parameter: ' rcd --rc-addr=:' + rcloneInfo.endpoint.localhost.port.toString()
+ ' --rc-user=' + rcloneInfo.endpoint.auth.user
+ ' --rc-pass=' + rcloneInfo.endpoint.auth.pass
+ ' --rc-allow-origin=* --rc-no-auth'//--rc-no-auth
})
rcloneInfo.endpoint.url = 'http://localhost:' + rcloneInfo.endpoint.localhost.port.toString()
startUpdateCont()
await reupRcloneVersion()
await reupStorage()
await reupMount()
//开始任务队列
await startTaskScheduler()
}
async function reupRcloneVersion() {
@@ -64,10 +63,7 @@ function main() {
}
async function exit() {
await rclone_api_post(
'/core/quit',
)
await stopRclone()
await invoke('write_config_file', {
configData: nmConfig
});

View File

@@ -4,13 +4,26 @@ import { RcloneStats } from "../../type/rclone/stats";
import { rclone_api_post } from "../../utils/rclone/request";
async function reupStats() {
const stats:RcloneStats = await rclone_api_post(
const stats: RcloneStats = await rclone_api_post(
'/core/stats',
)
rcloneInfo.stats = stats
let realSpeed: number = 0
if (stats.transferring && stats.transferring.length > 0) {
stats.transferring.forEach(item => {
realSpeed += item.speed
})
}
rcloneInfo.stats = {
...stats,
realSpeed: realSpeed
}
//历史状态
rcloneStatsHistory.push(stats)
if (rcloneStatsHistory.length > 32) {
rcloneStatsHistory.splice(0, rcloneStatsHistory.length - 32);
}

View File

@@ -1,7 +1,7 @@
import { DefaultParams } from "../../type/rclone/storage/defaults";
import { rclone_api_post } from "../../utils/rclone/request";
import { isEmptyObject } from "../../utils/rclone/utils";
import { isEmptyObject } from "../../utils/utils";
import { reupStorage } from "./storage";

View File

@@ -114,6 +114,7 @@ function formatPathRclone(path: string, isDir?: boolean): string {
}
}
path = path.replace(/\/+/g, '/');
return path;
}
@@ -128,13 +129,14 @@ async function copyFile(storageName: string, path: string, destStoragename: stri
}, true)
}
async function moveFile(storageName: string, path: string, destStoragename: string, destPath: string) {
async function moveFile(storageName: string, path: string, destStoragename: string, destPath: string, newNmae?: string) {
const backData = await rclone_api_post(
'/operations/movefile', {
srcFs: storageName + ':',
srcRemote: formatPathRclone(path),
dstFs: destStoragename + ':',
dstRemote: formatPathRclone(destPath, true) + getFileName(path)
dstRemote: formatPathRclone(destPath, true) + (newNmae ? newNmae : getFileName(path))
}, true)
}
@@ -152,11 +154,11 @@ async function copyDir(storageName: string, path: string, destStoragename: strin
}, true)
}
async function moveDir(storageName: string, path: string, destStoragename: string, destPath: string) {
async function moveDir(storageName: string, path: string, destStoragename: string, destPath: string, newNmae?: string) {
const backData = await rclone_api_post(
'/sync/move', {
srcFs: storageName + ':' + formatPathRclone(path, true),
dstFs: destStoragename + ':' + formatPathRclone(destPath, true) + getFileName(path)
dstFs: destStoragename + ':' + formatPathRclone(destPath, true) + (newNmae ? newNmae : getFileName(path))
}, true)
}

View File

@@ -0,0 +1,29 @@
import { TaskListItem } from "../../type/config";
async function runTask(task:TaskListItem ) :Promise<TaskListItem>{
const executeTask = (task: TaskListItem) => {
console.log(`Executing ${task.taskType} task: ${task.name}`);
// 实际的任务逻辑,可能包括复制、移动、删除文件等操作
console.log(`${task.name} task completed.`);
//一次性任务,执行完毕后禁用
if(task.run.mode==='disposable'){
task.enable=false;
}
};
try {
if (task.enable) {
executeTask(task);
task.runInfo = { ...task.runInfo, error: false, mag: 'Task executed successfully.' };
}
} catch (error) {
console.error(`Error executing task ${task.name}:`, error);
task.runInfo = { ...task.runInfo, error: true, mag: error instanceof Error ? error.message : String(error) };
}
return task;
}
export{runTask}

View File

@@ -0,0 +1,76 @@
import { TaskListItem } from "../../type/config";
import { runTask } from "./runner";
import { delTask } from "./task";
class TaskScheduler {
tasks: TaskListItem[];
constructor() {
this.tasks = [];
}
public async addTask(task: TaskListItem) {
if (task.enable) {
this.tasks.push(task);
this.scheduleTask(task);
}
}
private async scheduleTask(task: TaskListItem) {
switch (task.run.mode) {
case 'start':
await this.executeTask(task);
this.cancelTask(task.name);
break;
case 'disposable':
await this.executeTask(task);
this.cancelTask(task.name);
delTask(task.name);
break;
case 'time':
const executeTaskInterval = () => {
const now = new Date();
const scheduledTime = new Date(now);
scheduledTime.setDate(scheduledTime.getDate() + task.run.time.intervalDays);
scheduledTime.setHours(task.run.time.h, task.run.time.m, task.run.time.s);
// 如果设置的时间比现在的时间早,则表示下一次执行是明天
if (scheduledTime < now) {
scheduledTime.setDate(scheduledTime.getDate() + 1);
}
const timeout = scheduledTime.getTime() - now.getTime();
if (timeout >= 0) {
task.run.runId = window.setTimeout(async () => {
await this.executeTask(task);
// 完成执行后,重新计划下一次执行
executeTaskInterval();
}, timeout);
}
};
executeTaskInterval();
break;
case 'interval':
task.run.runId = window.setInterval(async () => await this.executeTask(task), task.run.interval);
break;
default:
console.error('Invalid task mode:', task.run.mode);
}
}
public async executeTask(task: TaskListItem) {
const updatedTask = await runTask(task)
this.tasks = this.tasks.map(t => t.name === updatedTask.name ? updatedTask : t);
}
cancelTask(taskName: string) {
const task = this.tasks.find(t => t.name === taskName);
if (task && task.run.runId !== undefined) {
window.clearInterval(task.run.runId);
window.clearTimeout(task.run.runId);
console.log(`${taskName} task cancelled.`);
}
}
}
export { TaskScheduler }

View File

@@ -0,0 +1,33 @@
import { nmConfig } from "../../services/config";
import { TaskListItem } from "../../type/config";
import { TaskScheduler } from "./scheduler";
const taskScheduler = new TaskScheduler()
function saveTask(taskInfo: TaskListItem) {
const existingTaskIndex = nmConfig.task.findIndex(
(task) => task.name === taskInfo.name
);
if (existingTaskIndex !== -1) {
// 存在同名任务,更新已有任务
nmConfig.task[existingTaskIndex] = taskInfo;
} else {
// 不存在同名任务,直接添加新任务
nmConfig.task.push(taskInfo);
}
return true
}
function delTask(taskName: string) {
nmConfig.task = nmConfig.task.filter((task) => task.name !== taskName);
return true;
}
async function startTaskScheduler() {
for (let task of nmConfig.task) {
await taskScheduler.addTask(task)
}
}
export { saveTask, delTask, taskScheduler, startTaskScheduler }

View File

@@ -5,8 +5,9 @@ import { invoke } from '@tauri-apps/api';
import { appWindow } from "@tauri-apps/api/window";
import { app } from "@tauri-apps/api";
import { nmConfig } from "../services/config";
import { nmConfig, osInfo } from "../services/config";
import { Aria2 } from "../utils/aria2/aria2";
import { checkUpdate } from "./update/update";
export async function Test() {
@@ -36,4 +37,25 @@ export async function Test() {
/* const aria2Test = new Aria2('https://down.hotpe.top/d/Package/HotPE-V2.7.240201.exe',
'F:/',
'test1.7z',
8, (back) => {
console.log(back);
})
aria2Test.start() */
//console.log(await runCmd('curl', [url,'-o', path]));
console.log(osInfo);
}

View File

@@ -0,0 +1,23 @@
import { fs } from "@tauri-apps/api";
import { downloadFile, takeRightStr } from "../../utils/utils";
import { ResItem, ResList } from "../../type/controller/update";
import { nmConfig, osInfo } from "../../services/config";
import { getVersion } from "@tauri-apps/api/app";
import { Modal } from "@arco-design/web-react";
async function checkUpdate(updateCall: (resList: ResItem,localUpdateId: number) => void) {
const localUpdateId = await getUpdateId()
const resList: ResItem = (await (await fetch(nmConfig.api.url + '/GetUpdate/?arch=' + osInfo.arch + '&osType=' + osInfo.osType)).json()).data
if (resList.id && Number(resList.id) < localUpdateId) {
updateCall(resList,localUpdateId)
}
}
async function getUpdateId() {
return Number(takeRightStr(await getVersion(), '-'))
}
export { checkUpdate }

View File

@@ -7,19 +7,26 @@ function listenWindow() {
windowsHide()
})
// 阻止F5或Ctrl+RWindows/Linux和Command+RMac刷新页面
document.addEventListener('keydown', function (event) {
// 阻止F5或Ctrl+RWindows/Linux和Command+RMac刷新页面
if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) {
event.preventDefault();
}
});
//禁止右键
document.oncontextmenu = () => {
return false;
}
}
function windowsHide(){
function windowsHide() {
appWindow.hide()
}
function windowsMini(){
function windowsMini() {
appWindow.minimize()
}
@@ -27,4 +34,5 @@ listen('exit_app', async () => {
await exit()
});
export { listenWindow,windowsHide,windowsMini }
export { listenWindow, windowsHide, windowsMini }

View File

@@ -6,6 +6,24 @@ p {
color: var(--color-text-1);
}
/* 字体 */
@font-face {
font-family: "Inter";
src: url("./assets/font/HarmonyOS_Sans_Regular.ttf") format("truetype");
}
@font-face {
font-family: "Inter-Bold";
src: url("./assets/font/HarmonyOS_Sans_Bold.ttf") format("truetype");
}
@font-face {
font-family: 'emoji';
src: url('./assets/font/seguiemj.woff2') format('woff2');
}
/* 美化滚动条 */
::-webkit-scrollbar {
width: 6px;

View File

@@ -1,23 +1,128 @@
import React, { useEffect, useReducer, useState } from 'react'
import { Button } from "@arco-design/web-react"
import { Button, Card, Descriptions, Grid, Modal, Space, Typography } from "@arco-design/web-react"
import { Test } from "../../controller/test"
import { rcloneInfo } from '../../services/rclone'
import { hooks } from '../../services/hook';
import { checkUpdate } from '../../controller/update/update';
import { getVersion } from '@tauri-apps/api/app';
import { shell } from '@tauri-apps/api';
import { formatETA, formatSize } from '../../utils/utils';
import { useTranslation } from 'react-i18next';
import { nmConfig } from '../../services/config';
const Row = Grid.Row;
const Col = Grid.Col;
let checkedUpdate: boolean = false;
function Home_page() {
const { t } = useTranslation()
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);//刷新组件
const [modal, contextHolder] = Modal.useModal();
useEffect(() => {
hooks.upStats = forceUpdate;
if (!checkedUpdate) {
checkUpdate(async (info) => {
modal.confirm!({
title: '发现新版本',
content: <>
{`当前版本为${await getVersion()},最新版本为${info.name}`}
<br />
</>,
onOk: () => {
shell.open(info.website!)
},
})
})
checkedUpdate = true;
}
}, [])
return (
<div>
{contextHolder}
<Space direction='vertical' style={{ width: '100%' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>使,</h2>
{/*<Row >
<Col flex={'auto'}style={{ paddingLeft: '0rem', paddingRight: '0rem' }} >
<Card style={{padding:'1.5rem',textAlign:'center'}} bordered={false}>
<span style={{fontSize:'4.5rem',fontFamily:'emoji'}}>🧐</span>
<p style={{fontSize:'1rem',fontWeight:'bold'}}>初次使用,请点击下方按钮进行配置</p>
</Card>
</Col>
</Row> */}
<Card>
<br />
{formatETA(rcloneInfo.stats.elapsedTime)}
</Card>
<Card>
<br />
{rcloneInfo.storageList.length}
<br />
{nmConfig.mount.lists.length}
<br />
{rcloneInfo.mountList.length}
</Card>
<Card>
<Descriptions colon=' :' data={[
{
label:t('speed'),
value: `${formatSize(rcloneInfo.stats.realSpeed!)}/s`
},
{
label: t('size'),
value: `${formatSize(rcloneInfo.stats.bytes)}/${formatSize(rcloneInfo.stats.totalBytes)}`
},
...(rcloneInfo.stats.transferTime > 0 ? [
{
label:t('used_time'),
value: formatETA(rcloneInfo.stats.transferTime)
}
] : []),
...(Number(rcloneInfo.stats.eta) > 0 ? [
{
label: t('eta'),
value: formatETA(rcloneInfo.stats.eta!)
}
] : []),
...(rcloneInfo.stats.transferring && Number(rcloneInfo.stats.transferring.length) > 0 ? [
{
label: t('transferring'),
value: rcloneInfo.stats.transferring.length
}
] : []),
...(Number(rcloneInfo.stats.totalTransfers) > 0 ? [
{
label: t('transferred'),
value: rcloneInfo.stats.totalTransfers
}
] : []),
]} />
</Card>
</Space>
</div>
)
}
/* 软件名称:NetMount
软件功能:挂载云存储到本地
主菜单(位于左边):首页(待实现),存储(添加存储,编辑存储,浏览和管理存储内文件),挂载存储(挂载为本地路径或盘符),传输(当前在传输的文件信息、速度、剩余时间等),任务(定时或间隔,可执行存储的文件同步、文件复制、文件删除、挂载等)
软件整体布局为左:主菜单,右:对应页面
现在就还有软件首页没有写了,请你为我的软件设计一个首页 */
export { Home_page }

View File

@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom';
import { ParametersType } from '../../type/rclone/storage/defaults';
import { getProperties, getURLSearchParam } from '../../utils/rclone/utils';
import { getProperties, getURLSearchParam } from '../../utils/utils';
import { defaultMountConfig, defaultVfsConfig } from '../../controller/storage/mount/parameters/defaults';
import { InputItem_module } from '../other/inputItem';
import { rcloneInfo } from '../../services/rclone';

View File

@@ -5,7 +5,7 @@ import { searchStorage, storageListAll } from "../../controller/storage/listAll"
import { CSSProperties, useEffect, useState } from "react";
import { checkParams, createStorage } from "../../controller/storage/create";
import { useNavigate, useParams } from "react-router-dom";
import { getProperties, getURLSearchParam } from "../../utils/rclone/utils";
import { getProperties, getURLSearchParam } from "../../utils/utils";
import { getStorageParams } from "../../controller/storage/storage";
import { InputItem_module } from "../other/inputItem";
const FormItem = Form.Item;

View File

@@ -1,11 +1,11 @@
import React, { CSSProperties, useEffect, useReducer, useState } from 'react'
import { BackTop, Badge, Button, Divider, Dropdown, Grid, Input, Link, List, Menu, Message, Modal, Notification, Popconfirm, Select, Space, Spin, Table, TableColumnProps, Tabs, Typography, Upload } from '@arco-design/web-react';
import { IconCopy, IconDelete, IconFolderAdd, IconLeft, IconMore, IconPaste, IconRefresh, IconScissor, IconUpCircle, IconUpload } from '@arco-design/web-react/icon';
import { BackTop, Badge, Button, Divider, Dropdown, Grid, Input, Link, List, Menu, Message, Modal, Notification, Popconfirm, Select, Space, Spin, Table, TableColumnProps, Tabs, Tooltip, Typography, Upload } from '@arco-design/web-react';
import { IconCopy, IconDelete, IconEdit, IconFolderAdd, IconLeft, IconMore, IconPaste, IconRefresh, IconScissor, IconUpCircle, IconUpload } from '@arco-design/web-react/icon';
import { rcloneInfo } from '../../services/rclone';
import { useTranslation } from 'react-i18next';
import { copyDir, copyFile, delDir, delFile, formatPathRclone, getFileList, mkDir, moveDir, moveFile } from '../../controller/storage/storage';
import { FileInfo } from '../../type/rclone/rcloneInfo';
import { formatSize, getURLSearchParam } from '../../utils/rclone/utils';
import { formatSize, getURLSearchParam } from '../../utils/utils';
import { rcloneApiHeaders } from '../../utils/rclone/request';
import { RequestOptions } from '@arco-design/web-react/es/Upload';
import { NoData_module } from '../other/noData';
@@ -82,19 +82,26 @@ function ExplorerItem() {
{
title: t('name'),
dataIndex: 'fileName',
ellipsis: true,
},
{
title: t('modified_time'),
dataIndex: 'fileModTime',
ellipsis: true,
width: '10.5rem',
},
{
title: t('size'),
dataIndex: 'fileSize',
ellipsis: true,
width: '7rem',
},
{
title: t('actions'),
dataIndex: 'actions',
align: 'right'
align: 'right',
width: '10rem',
}
]
@@ -176,6 +183,7 @@ function ExplorerItem() {
})
}
}
function UploadFile() {
const customRequest = (option: RequestOptions) => {
@@ -216,7 +224,25 @@ function ExplorerItem() {
}
}
function fileRename(filePath: string, isDir: boolean) {
console.log(getParentPath(filePath));
let nameTemp = filePath.split('/').pop()!;
modal.info!({
title: t('rename'),
icon: null,
content: <Input placeholder={t('please_input')} defaultValue={nameTemp} onChange={(value) => nameTemp = value} />,
onOk: async () => {
if (nameTemp) {
isDir ? await moveDir(storageName!, filePath, storageName!, getParentPath(filePath), nameTemp) :
await moveFile(storageName!, filePath, storageName!, getParentPath(filePath), nameTemp);
fileInfo();
} else {
Message.error(t('name_cannot_empty'))
}
},
})
}
return (
<div style={{ height: '100%', width: '100%' }}>
@@ -224,14 +250,12 @@ function ExplorerItem() {
{contextHolder}
<Row >
<Col flex='2rem'>
<Button /* type='secondary' */ icon={<IconLeft />} onClick={() => { updatePath(getParentPath(path!)) }} disabled={!storageName} type='text' />
<Button /* type='secondary' */ icon={<IconLeft />} onClick={() => { updatePath(getParentPath(path!)) }} disabled={!storageName} type='text' title={t('parent_directory')} />
</Col>
<Col flex='2rem'>
<Button /* type='secondary' */ icon={<IconRefresh />} onClick={fileInfo} disabled={!storageName} type='text' />
<Button /* type='secondary' */ icon={<IconRefresh />} onClick={fileInfo} disabled={!storageName} type='text' title={t('refresh')} />
</Col>
<Col style={{ paddingLeft: '1rem', paddingRight: '0.2rem' }} flex='10rem'>
<Select /* bordered={false} */ value={storageName} placeholder={t('please_select')} onChange={(value) => {
if (value !== storageName) {
setStorageName(value)
@@ -239,7 +263,6 @@ function ExplorerItem() {
setPath('/')
}
}
}>
{
rcloneInfo.storageList.map((item) => {
@@ -255,13 +278,13 @@ function ExplorerItem() {
</Col>
<Col flex='2rem'>
<Button icon={<IconFolderAdd />} onClick={MakeDir} disabled={!storageName && !path} type='text' />
<Button icon={<IconFolderAdd />} onClick={MakeDir} disabled={!storageName && !path} type='text' title={t('create_directory')} />
</Col>
<Col flex='2rem'>
<Button icon={<IconUpload />} onClick={UploadFile} disabled={!storageName && !path} type='text' />
<Button icon={<IconUpload />} onClick={UploadFile} disabled={!storageName && !path} type='text' title={t('upload_file')} />
</Col>
<Col flex='2rem' >
<Badge count={clipList.length} maxCount={9}>
<Badge count={clipList.length} maxCount={9} title={t('clip_board')}>
<Dropdown disabled={clipList.length == 0} droplist={
<Menu>
<Menu.Item onClick={() => {
@@ -285,59 +308,38 @@ function ExplorerItem() {
title: t('success'),
content: t('transm_task_created'),
})
}} key='p' disabled={!storageName && !path}>({clipList.length})</Menu.Item>
<Menu.Item onClick={() => setClipList([])} key='q'></Menu.Item>
}} key='p' disabled={!storageName && !path}>{t('paste')}({clipList.length})</Menu.Item>
<Menu.Item onClick={() => setClipList([])} key='q'>{t('empty_the_clipboard')}</Menu.Item>
</Menu>} position='bl'>
<Button icon={<IconPaste />} type='text' />
</Dropdown>
</Badge>
</Col>
</Row>
</div>
<div style={{ height: 'calc(100% - 2rem)', paddingTop: '1rem' }}>
<div style={{ height: 'calc(100% - 2rem)', marginTop:'1rem',overflow: 'auto' }}>
{storageName ?
<>{
fileList ?
<Table columns={columns}
loading={loading}
pagination={false}
tableLayoutFixed
rowKey='Path'
/* rowSelection={
{
type: 'checkbox',
selectedRowKeys: selectedRowKeys,
onChange: (selectedKeys, selectedRows) => {
setSelectedRowKeys(selectedKeys);
console.log('onChange:', selectedKeys, selectedRows);
},
onSelect: (selected, record, selectedRows) => {
console.log('onSelect:', selected, record, selectedRows);
},
}
}
*/
size='small'
noDataElement={<NoData_module />}
data={
fileList.map((item) => {
return {
...item, fileName: <Link style={{ width: '100%' }} onClick={() => { item.IsDir && updatePath(item.Path) }}><Typography.Ellipsis showTooltip>{item.Name}</Typography.Ellipsis></Link>,
fileSize: (item.Size != -1 ? formatSize(item.Size) : t('dir')),
fileModTime: (new Date(item.ModTime)).toLocaleString(),
actions: <Space size={'mini'}>
<Button onClick={() => { addCilp({ isMove: false, storageName: storageName!, path: item.Path, isDir: item.IsDir }) }} type='text' icon={<IconCopy />} />
<Button onClick={() => { addCilp({ isMove: true, storageName: storageName!, path: item.Path, isDir: item.IsDir }) }} type='text' icon={<IconScissor />} />
<Button onClick={() => { addCilp({ isMove: false, storageName: storageName!, path: item.Path, isDir: item.IsDir }) }} type='text' icon={<IconCopy />} title={t('copy')} />
<Button onClick={() => { addCilp({ isMove: true, storageName: storageName!, path: item.Path, isDir: item.IsDir }) }} type='text' icon={<IconScissor />} title={t('cut')} />
<Dropdown unmountOnExit={false} droplist={
<Menu>
<Menu.Item key='rename' style={{ color: 'var(primary-4)' }} onClick={() => fileRename(item.Path, item.IsDir)}><IconEdit /> {t('rename')}</Menu.Item>
<Menu.Item key='del' /* style={{ color: 'var(danger-4)' }} */>
<Popconfirm
focusLock
@@ -347,14 +349,11 @@ function ExplorerItem() {
delFile(storageName!, item.Path, fileInfo)
}}
>
<IconDelete />
{t('delete')}
<IconDelete /> {t('delete')}
</Popconfirm></Menu.Item>
{/* <Menu.Item key='rename' style={{ color: 'var(primary-4)' }}></Menu.Item> */}
</Menu>} position='bl'>
<Button icon={<IconMore />} type='text' />
<Button icon={<IconMore />} type='text' title={t('more')} />
</Dropdown>
</Space>
}
})} />

298
src/page/task/add.tsx Normal file
View File

@@ -0,0 +1,298 @@
import { Button, Divider, Form, Grid, Input, InputNumber, Notification, Select, Space, Tooltip } from '@arco-design/web-react';
import React, { useReducer, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { nmConfig, roConfig } from '../../services/config';
import { TaskListItem } from '../../type/config';
import { rcloneInfo } from '../../services/rclone';
import { IconQuestionCircle } from '@arco-design/web-react/icon';
import { formatPathRclone } from '../../controller/storage/storage';
import { useNavigate } from 'react-router-dom';
import { saveTask } from '../../controller/task/task';
const Row = Grid.Row;
const Col = Grid.Col;
const formatPath = (path: string) => {
path = path.replace(/\\/g, '/');
path = path.replace(/\/+/g, '/');
if (path.substring(0, 1) != '/') {
path = '/' + path;
}
return path
}
// 定义状态和 action 类型
type TaskInfoState = TaskListItem;
type Action =
| { type: 'setName'; payload: string }
| { type: 'setRunTypeMode'; payload: string }
| { type: 'setTaskType'; payload: string }
| { type: 'setSourceStorageName'; payload: string }
| { type: 'setSourcePath'; payload: string }
| { type: 'setTargetStorageName'; payload: string }
| { type: 'setTargetPath'; payload: string }
| { type: 'setIntervalDays'; payload: number }
| { type: 'setRunTime'; payload: { h: number, m: number, s: number } };
// 定义 reducer 函数
const reducer = (state: TaskInfoState, action: Action): TaskInfoState => {
switch (action.type) {
case 'setName':
return { ...state, name: action.payload };
case 'setRunTypeMode':
return { ...state, run: { ...state.run, mode: action.payload } };
case 'setTaskType':
return { ...state, taskType: action.payload };
case 'setSourceStorageName':
return { ...state, source: { ...state.source, storageName: action.payload } };
case 'setSourcePath':
return { ...state, source: { ...state.source, path: formatPath(action.payload) } };
case 'setTargetStorageName':
return { ...state, target: { ...state.target, storageName: action.payload } };
case 'setTargetPath':
return { ...state, target: { ...state.target, path: formatPath(action.payload) } };
case 'setIntervalDays':
return { ...state, run: { ...state.run, time: { ...state.run.time, intervalDays: action.payload } } };
case 'setRunTime':
return { ...state, run: { ...state.run, time: { ...state.run.time, ...action.payload } } };
default:
throw new Error('Invalid action');
}
};
function AddTask_page() {
const { t } = useTranslation();
const navigate = useNavigate();
const [taskInfo, dispatch] = useReducer(reducer, {
name: 'task_' + (nmConfig.task ? nmConfig.task.length + 1 : 1),
taskType: roConfig.options.task.taskType.select[roConfig.options.task.taskType.defIndex],
source: {
storageName:
rcloneInfo.storageList && rcloneInfo.storageList.length > 0
? rcloneInfo.storageList[0].name
: '',
path: '/',
},
target: {
storageName:
rcloneInfo.storageList && rcloneInfo.storageList.length > 0
? (rcloneInfo.storageList.length > 1
? rcloneInfo.storageList[1].name
: rcloneInfo.storageList[0].name)
: '',
path: '/',
},
run: {
mode: roConfig.options.task.runMode.select[roConfig.options.task.runMode.defIndex], time: {
intervalDays: 1,
h: 10,
m: 30,
s: 0,
}
},
enable: true,
});
const [timeMultiplier, setTimeMultiplier] = useState({
...roConfig.options.task.dateMultiplier.select[roConfig.options.task.dateMultiplier.defIndex],
multiplicand: 1
})
useEffect(() => {
if (taskInfo.run.mode === 'time') {
setTimeMultiplier({ ...roConfig.options.task.dateMultiplier.select[roConfig.options.task.dateMultiplier.defIndex], multiplicand: 1 })
} else if (taskInfo.run.mode === 'interval') {
setTimeMultiplier({ ...roConfig.options.task.intervalMultiplier.select[roConfig.options.task.intervalMultiplier.defIndex], multiplicand: 1 })
}
}, [taskInfo.run.mode])
return (
<div style={{ width: '100%', height: '100%' }}>
<Form style={{ paddingRight: '10%' }}>
<Form.Item label={t('task_name')}>
<Input
value={taskInfo.name}
onChange={(value) => dispatch({ type: 'setName', payload: value })}
placeholder={t('please_input')}
/>
</Form.Item>
<Form.Item label={t('task_run_mode')}>
<Select
onChange={(value) => dispatch({ type: 'setRunTypeMode', payload: value })}
value={taskInfo.run.mode}
>
{roConfig.options.task.runMode.select.map((item) => (
<Select.Option value={item}>{t(`task_run_mode_${item}_opt`)}</Select.Option>
))}
</Select>
</Form.Item>
{taskInfo.run.mode != 'start' && taskInfo.run.mode !== 'disposable' &&
<>
<Form.Item label={t('interval')}>
<Row>
<Col flex={'6rem'}>
<Select value={timeMultiplier.value} onChange={(value) => {
setTimeMultiplier({ ...(taskInfo.run.mode === 'time' ? roConfig.options.task.dateMultiplier.select.find(item => item.value === value)! : roConfig.options.task.intervalMultiplier.select.find(item => item.value === value)!), multiplicand: timeMultiplier.multiplicand });
}}>
{(taskInfo.run.mode === 'time' ? roConfig.options.task.dateMultiplier.select : roConfig.options.task.intervalMultiplier.select).map((item) => (
<Select.Option value={item.value}>{t(item.name)}(*{item.value})</Select.Option>
))}
</Select>
</Col>
<Col flex={'auto'}>
<InputNumber mode='button' min={1} max={10000} value={timeMultiplier.multiplicand} precision={0}
onChange={
(value) => {
setTimeMultiplier({ ...timeMultiplier, multiplicand: value });
}
} />
</Col>
</Row>
</Form.Item>
{
taskInfo.run.mode === 'time' && <>
<Form.Item label={t('time')}>
<Row gutter={10}>
<Col flex={'1'}>
<InputNumber
min={0} max={23} precision={0}
value={taskInfo.run.time.h}
suffix={t('hour')}
onChange={(value) => dispatch({ type: 'setRunTime', payload: { ...taskInfo.run.time, h: value } })}
/>
</Col>
<Col flex={'1'}>
<InputNumber
min={0} max={59} precision={0}
value={taskInfo.run.time.m}
suffix={t('minute')}
onChange={(value) => dispatch({ type: 'setRunTime', payload: { ...taskInfo.run.time, m: value } })}
/>
</Col>
<Col flex={'1'}>
<InputNumber
min={0} max={59} precision={0}
value={taskInfo.run.time.s}
suffix={t('second')}
onChange={(value) => dispatch({ type: 'setRunTime', payload: { ...taskInfo.run.time, s: value } })}
/>
</Col>
</Row>
</Form.Item>
</>
}
</>
}
<Form.Item label={t('task_type')}>
<Select
onChange={(value) => dispatch({ type: 'setTaskType', payload: value })}
value={taskInfo.taskType}
>
{roConfig.options.task.taskType.select.map((item) => (
<Select.Option value={item}>{t(`${item}`)}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label={t('source_path')}>
<Row>
<Col flex={'7rem'}>
<Select
value={taskInfo.source.storageName}
placeholder={t('please_select')}
onChange={(value) => dispatch({ type: 'setSourceStorageName', payload: value })}
>
{rcloneInfo.storageList.map((item) => (
<Select.Option key={item.name} value={item.name}>
{item.name}
</Select.Option>
))}
</Select>
</Col>
<Col flex={'auto'}>
<Input
value={taskInfo.source.path}
onChange={(value) => dispatch({ type: 'setSourcePath', payload: value })}
disabled={!taskInfo.source.storageName}
/>
</Col>
<Col flex={'2rem'}>
<Tooltip content={t('explain_for_task_path_format')}>
<Button icon={<IconQuestionCircle />}></Button>
</Tooltip>
</Col>
</Row>
</Form.Item>
{taskInfo.taskType !== 'delete' && (
<Form.Item label={t('target_path')}>
<Row>
<Col flex={'7rem'}>
<Select
value={taskInfo.target.storageName}
placeholder={t('please_select')}
onChange={(value) => dispatch({ type: 'setTargetStorageName', payload: value })}
>
{rcloneInfo.storageList.map((item) => (
<Select.Option key={item.name} value={item.name}>
{item.name}
</Select.Option>
))}
</Select>
</Col>
<Col flex={'auto'}>
<Input
value={taskInfo.target.path}
onChange={(value) => dispatch({ type: 'setTargetPath', payload: value })}
disabled={!taskInfo.target.storageName}
/>
</Col>
<Col flex={'2rem'}>
<Tooltip content={t('explain_for_task_path_format')}>
<Button icon={<IconQuestionCircle />}></Button>
</Tooltip>
</Col>
</Row>
</Form.Item>
)}
</Form>
<div style={{ marginTop: '20px', textAlign: 'right' }}>
<Space>
<Button onClick={() => {
navigate('/task/')
}}>{t('step_back')}</Button>
<Button type='primary' onClick={() => {
if (taskInfo.run.mode === 'time') {
taskInfo.run.time.intervalDays = timeMultiplier.multiplicand * timeMultiplier.value;
} else if (taskInfo.run.mode === 'interval') {
taskInfo.run.interval = timeMultiplier.multiplicand * timeMultiplier.value;
}
if (nmConfig.task && nmConfig.task.forEach(item => item.name == taskInfo.name)! || !taskInfo.name) {
Notification.error({
title: t('error'),
content: t('the_task_name_is_illegal'),
})
} else {
if (saveTask(taskInfo)) {
Notification.success({
title: t('success'),
content: t('task_added_successfully'),
})
navigate('/task/')
}
}
}}>{t('add')}</Button>
</Space>
</div>
</div>
);
}
export { AddTask_page };

View File

@@ -1,12 +1,63 @@
import React from 'react'
import React, { useReducer } from 'react'
import { DevTips_module } from '../other/devTips'
import { Button, Space, Table, TableColumnProps } from '@arco-design/web-react'
import { useTranslation } from 'react-i18next'
import { nmConfig } from '../../services/config'
import { useNavigate } from 'react-router-dom'
import { delTask } from '../../controller/task/task'
import { NoData_module } from '../other/noData'
function Task_page() {
return (
<div>
<DevTips_module/>
</div>
)
const { t } = useTranslation()
const navigate = useNavigate();
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);//刷新组件
const columns: TableColumnProps[] = [
{
title: t('task_name'),
dataIndex: 'name',
},
{
title: t('state'),
dataIndex: 'state',
}, {
title: t('cycle'),
dataIndex: 'cycle',
}
, {
title: t('run_info'),
dataIndex: 'runInfo',
},
{
title: t('actions'),
dataIndex: 'actions',
}
];
console.log(nmConfig.task);
return (
<div style={{ width: '100%', height: '100%' }}>
<div style={{ width: '100%', height: '2rem' }}>
<Space>
<Button type='primary' onClick={() => { navigate('/task/add') }}>{t('add')}</Button>
<Button onClick={() => { forceUpdate() }}>{t('refresh')}</Button>
</Space>
</div>
<div style={{ height: "calc(100% - 3rem)", marginTop: "1rem" }}>
<Table columns={columns} noDataElement={ <NoData_module />} data={nmConfig.task.map((taskItem) => {
return {
...taskItem,
state: taskItem.enable ? t('enabled') : t('disabled'),
cycle: t('task_run_mode_' + taskItem.run.mode),
runInfo:taskItem.runInfo?.mag,
actions: <>
<Button onClick={()=>{delTask(taskItem.name);forceUpdate()}}>{t('delete')}</Button>
</>
}
})} />
</div>
</div>
)
}
export { Task_page }

View File

@@ -3,7 +3,7 @@ import { rcloneInfo, rcloneStatsHistory } from '../../services/rclone'
import { hooks } from '../../services/hook'
import { RcloneTransferItem } from '../../type/rclone/stats'
import { Card, Descriptions, List, Progress, Space, Statistic, Grid, Typography } from '@arco-design/web-react'
import { formatETA, formatSize } from '../../utils/rclone/utils'
import { formatETA, formatSize } from '../../utils/utils'
import { Area } from '@ant-design/charts'
import { NoData_module } from '../other/noData'
import { useTranslation } from 'react-i18next'
@@ -37,7 +37,7 @@ function Transmit_page() {
<Descriptions colon=' :' data={[
{
label:t('speed'),
value: `${formatSize(rcloneInfo.stats.speed)}/s`
value: `${formatSize(rcloneInfo.stats.realSpeed!)}/s`
},
{
@@ -47,7 +47,7 @@ function Transmit_page() {
...(rcloneInfo.stats.transferTime > 0 ? [
{
label:t('time'),
label:t('used_time'),
value: formatETA(rcloneInfo.stats.transferTime)
}
] : []),
@@ -77,7 +77,6 @@ function Transmit_page() {
>
<List noDataElement={ <NoData_module />}>
{
transmitList.map((item, index) => {
return <List.Item key={index}>

View File

@@ -65,7 +65,8 @@
"transferring": "传输中",
"overview":"总览",
"transferred": "已传输",
"time": "用时",
"used_time": "用时",
"time":"时间",
"speed": "速度",
"eta": "剩余时间",
"speed_avg":"平均速度",
@@ -73,5 +74,45 @@
"source":"来源",
"read_config":"读取配置",
"init":"初始化",
"transm_task_created":"已创建任务,请到[传输]页查看信息"
"transm_task_created":"已创建任务,请到[传输]页查看信息",
"clip_board": "剪切板",
"paste":"粘贴",
"empty_the_clipboard":"清空剪贴板",
"parent_directory":"上级目录",
"cut":"剪切",
"more":"更多",
"copy":"复制",
"rename": "重命名",
"name_cannot_empty":"名称不能为空",
"task_name":"任务名称",
"state":"状态",
"enabled":"启用",
"disabled":"禁用",
"cycle":"周期",
"run_info":"运行信息",
"task_type":"任务类型",
"task_run_mode_start":"启动时",
"task_run_mode_start_opt":"启动时(软件启动时执行)",
"task_run_mode_time":"定时",
"task_run_mode_time_opt":"定时(在特定时间执行)",
"task_run_mode_interval":"间隔",
"task_run_mode_interval_opt":"间隔(每隔一段时间执行)",
"task_run_mode_disposable":"一次性",
"task_run_mode_disposable_opt":"一次性(添加后立即执行,并自动删除任务)",
"task_run_mode": "执行模式",
"move":"移动",
"sync":"同步",
"source_path":"源路径",
"target_path":"目标路径",
"prompt_for_leaving_the_add_or_edit_page":"离开添加或编辑页面,未保存的设置将丢失。",
"explain_for_task_path_format":"路径格式:目录以斜杠结尾,文件以不以斜杠结尾",
"interval":"间隔",
"day":"天",
"week":"周",
"month":"月",
"hour":"小时",
"minute":"分钟",
"second":"秒",
"the_task_name_is_illegal":"任务名称不合法",
"task_added_successfully":"任务添加成功"
}

View File

@@ -1,11 +1,12 @@
import { NMConfig } from "../type/config"
import { NMConfig, OSInfo } from "../type/config"
let nmConfig: NMConfig = {
mount: {
lists: [],
},
task: {
task: [],
api: {
url: 'https://api.hotpe.top/test/NetMount',
}
}
@@ -13,4 +14,41 @@ const setNmConfig = (config: NMConfig) => {
nmConfig = config
}
export { nmConfig ,setNmConfig}
let osInfo: OSInfo = {
arch: 'unknown',
osType: 'unknown',
platform: 'unknown',
tempDir: '',
osVersion: ''
}
const setOsInfo = (osinfo: OSInfo) => {
osInfo = osinfo
}
const roConfig = {
options: {
task:{
runMode: {
defIndex: 0,
select: [ 'start', 'time', 'interval','disposable']
},
taskType:{
defIndex: 0,
select: [ 'sync', 'copy','move', 'delete']
},
dateMultiplier:{
defIndex: 0,
select: [{ name: 'day', value: 1 }, { name: 'week', value: 7 }, { name: 'month', value: 30 }]
},
intervalMultiplier:{
defIndex: 0,
select: [{ name: 'hour', value: 60*60 }, { name: 'minute', value: 60 }, { name: 'second', value: 1 }]
},
}
}
}
export { nmConfig, setNmConfig, osInfo, setOsInfo, roConfig }

View File

@@ -4,7 +4,9 @@ import { RcloneInfo } from "../type/rclone/rcloneInfo"
import { RcloneStats } from "../type/rclone/stats"
let rcloneInfo: RcloneInfo = {
process:{
},
endpoint: {
url: '',
isLocal: true,

58
src/type/config.d.ts vendored
View File

@@ -1,10 +1,14 @@
import { Arch, OsType, Platform } from "@tauri-apps/api/os"
import { ParametersType } from "./rclone/storage/defaults"
interface NMConfig {
mount: {
lists: MountListItem[]
},
task: TaskListItem
task: TaskListItem[],
api: {
url: string
}
}
interface MountListItem {
@@ -15,23 +19,41 @@ interface MountListItem {
}
interface TaskListItem {
[key: string]: {
taskType: 'copy' | 'move'| 'delete'| 'sync',
source: string,
target?: string,
parameters?: ParametersType,
autoRun: {
enable: boolean,
type: 'time' | 'interval'|'start',//start软件启动时执行time:定时执行interval:周期执行
time?: {
intervalDay: number,//间隔天数
h: number,//小时
m: number,//分钟
s: number,//秒
},
interval?: number,
name: string,
taskType: 'copy' | 'move' | 'delete' | 'sync' | string,
source: {
storageName: string,
path: string,
},
target: {
storageName: string,
path: string,
},
parameters?: ParametersType,
enable: boolean
run: {
runId?: number,//任务id,setTimeout或setInterval的返回值
mode: 'time' | 'interval' | 'start' |'disposable'| string,//start软件启动时执行time:定时执行interval:间隔执行 , disposable:一次性执行(执行后删除任务)
time: {
intervalDays: number,//间隔天数
h: number,//小时
m: number,//分钟
s: number,//秒
},
exeId?:number,//任务id,setTimeout或setInterval的返回值
interval?: number,//周期执行单位ms
},
runInfo?: {
error:boolean
mag: string,
}
}
export { NMConfig, MountListItem,TaskListItem}
interface OSInfo {
arch: Arch | 'unknown',
osType: OsType | 'unknown',
platform: Platform | 'unknown',
tempDir: string,
osVersion: string
}
export { NMConfig, MountListItem, TaskListItem, OSInfo }

20
src/type/controller/update.d.ts vendored Normal file
View File

@@ -0,0 +1,20 @@
export interface ResList {
[key: string]: ResItem;
}
export interface ResItem {
id: string;
name: string;
pushTime: string;
body?: string;
assets: ResAsset[];
website?: string;
download_url?: string;
}
export interface ResAsset {
name: string;
size: number;
download_url: string;
}

View File

@@ -1,6 +1,11 @@
import { Child, Command } from "@tauri-apps/api/shell";
import { RcloneStats } from "./stats";
interface RcloneInfo {
process:{
command?:Command,
child?:Child
},
endpoint: {
url: string,
isLocal: boolean,// 是否为本地地址

View File

@@ -51,6 +51,7 @@ interface RcloneStats {
serverSideMoveBytes: number; // 通过服务器端移动传输的字节数
serverSideMoves: number; // 服务器端移动操作的数量
speed: number; // 当前速度(字节/秒)
realSpeed?: number; // 实时速度(字节/秒)
totalBytes: number; // 总共处理的字节数
totalChecks: number; // 总共完成的校验数
totalTransfers: number; // 总共完成的传输操作数

13
src/type/utils/aria2.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// 定义Aria2的属性
interface Aria2Attrib {
state: 'doing' | 'done' | 'request' | 'error',//状态,错误:error发送请求:request下载中:doing完成:done
speed: string,//速度
percentage: number,//进度百分比
eta: string,//剩余时间
size: string,//总大小
newSize: string,//已下载大小
message: string,//当前的Aria2返回
}
export {Aria2Attrib}

108
src/utils/aria2/aria2.ts Normal file
View File

@@ -0,0 +1,108 @@
import { Child, Command } from '@tauri-apps/api/shell';
import { takeMidStr, takeRightStr } from '../utils';
import { Aria2Attrib } from '../../type/utils/aria2';
class Aria2 {
private filePath: string = '';
private command: Command;
private process: Child | null = null;
constructor(url: string, saveDir: string, saveName: string, thread: number = 8, callback: (attrib: Aria2Attrib) => void) {
this.filePath = saveDir + saveName;
const args = [
'-d', saveDir,
'-o', saveName,
'-s', thread.toString(),
'-x', thread.toString(),
'--file-allocation=none',
'-c',
'--check-certificate=false',
'--force-save=false',
url
]
this.command = new Command('ria2c', args);
this.command.stdout.on('data', (data) => {
const output = data.toString();
if (output.includes('NOTICE') || output.includes('#')) {
callback(this.parseOutput(output));
}
});
this.command.on('close', (data) => {
this.process = null;
const attrib: Aria2Attrib = this.parseOutput('');
if (data.code === 0) {
attrib.state = 'done';
} else {
attrib.state = 'error';
}
callback(attrib);
});
}
// 解析aria2的命令行输出
private parseOutput(output: string): Aria2Attrib {
let tempAria2Attrib: Aria2Attrib = {
state: 'request',
speed: '',
percentage: 0,
eta: '',
size: '',
newSize: '',
message: output
}
if (output.includes('DL:')) {//正在下载,[#46fea8 210MiB/583MiB(36%) CN:4 DL:10MiB ETA:35s]
tempAria2Attrib.state = 'doing';
//速度speed,str.substring(str.indexOf("DL:") + 3, str.indexOf("iB ETA")) + 'B/S'
//进度百分比,Number(str.substring(str.indexOf("B(") + 2, str.indexOf("%)"))))
tempAria2Attrib.percentage = Number(takeMidStr(output, 'B(', '%)'))
if (output.includes('ETA')) {
tempAria2Attrib.speed = takeMidStr(output, 'DL:', 'iB ETA') + 'B/s'
//剩余时间 eta
tempAria2Attrib.eta = takeMidStr(output, 'ETA:', ']')
} else {
tempAria2Attrib.speed = takeMidStr(output, 'DL:', 'iB]') + 'B/s'
}
//总大小,size
tempAria2Attrib.size = takeMidStr(output, '/', 'iB(') + 'B'
//已下载大小,newSize
tempAria2Attrib.newSize = takeRightStr(takeMidStr(output, '[#', 'iB/'), ' ') + 'B'
} else {
tempAria2Attrib.state = 'request'
}
return tempAria2Attrib
}
// 启动aria2下载
async start(): Promise<void> {
this.process = await this.command.spawn()
}
// 停止aria2下载
async stop(): Promise<boolean> {
if (this.process) {
try {
await this.process.kill();
this.process = null;
return true;
} catch (error) {
return false;
}
} else {
return false;
}
}
}
export { Aria2 };

View File

@@ -0,0 +1,37 @@
import { invoke } from "@tauri-apps/api";
import { Command } from "@tauri-apps/api/shell";
import { rcloneInfo } from "../../services/rclone";
import { rclone_api_post } from "./request";
async function startRclone() {
if (rcloneInfo.process.child) {
await stopRclone()
}
//rcloneInfo.endpoint.auth.user = randomString(32)
//rcloneInfo.endpoint.auth.pass = randomString(128)
rcloneInfo.endpoint.url = 'http://localhost:' + rcloneInfo.endpoint.localhost.port.toString()
const args = [
'rcd',
`--rc-addr=:${rcloneInfo.endpoint.localhost.port.toString()}`,
`--rc-user=${rcloneInfo.endpoint.auth.user}`,
`--rc-pass=${rcloneInfo.endpoint.auth.pass}`,
'--rc-allow-origin=*',
'--rc-no-auth'
];
rcloneInfo.process.command = new Command('rclone', args)
rcloneInfo.process.child = await rcloneInfo.process.command.spawn()
}
async function stopRclone() {
await rclone_api_post('/core/quit')
if (rcloneInfo.process.child) {
await rcloneInfo.process.child.kill()
}
}
export { startRclone, stopRclone }

View File

@@ -27,20 +27,33 @@ function rclone_api_post(path: string, data?: object, ignoreError?: boolean) {
body: JSON.stringify(data)
}).then((response) => {
if (!response.ok && !ignoreError) {
Message.error(`Request failed with status ${response.status}: ${response.statusText}`);
throw new Error(`Request failed with status ${response.status}: ${response.statusText}`);
printError(response);
}
return response.json();
}).then((jsonResponse) => {
return jsonResponse;
}).catch((error) => {
if (ignoreError) { return }
Message.error(error.message);
console.error("Error fetching from Rclone API:", error.message);
throw error;
printError(error);
});
}
async function printError(error: Response) {
console.log(error);
let str = ''
if (error.status) {
str += `HTTP ${error.status} - ${error.statusText}\n`
}
if (error.body) {
str += "\n" + (await error.json()).error;
}
if (str) {
Message.error('Error:' + str);
}
}
/* export function rclone_api_get(path:string){
return fetch(rcloneApiEndpoint + path,{
method: 'GET',

27
src/utils/tauri/cmd.ts Normal file
View File

@@ -0,0 +1,27 @@
import { Command } from "@tauri-apps/api/shell"
function runCmd(cmd: string, args: string[]): Promise<string> {
return new Promise( (resolve, reject) => {
const command = new Command(cmd, args)
let output = ""
command.stdout.on('data', (data: string) => {
output += data.toString()
})
command.on("error", (err: Error) => {
reject(err)
})
command.on('close', () => {
resolve(output)
})
command.spawn()
})
}
export { runCmd }

15
src/utils/tauri/osInfo.ts Normal file
View File

@@ -0,0 +1,15 @@
import { os } from "@tauri-apps/api";
import { OSInfo } from "../../type/config";
import { setOsInfo } from "../../services/config";
async function getOsInfo() {
setOsInfo({
arch: await os.arch(),
osType: await os.type(),
platform: await os.platform(),
tempDir: await os.tempdir(),
osVersion: await os.version(),
})
}
export { getOsInfo }

View File

@@ -1,3 +1,6 @@
import { fs, invoke } from "@tauri-apps/api";
import { runCmd } from "./tauri/cmd";
export function isEmptyObject(back: any): boolean {
return Object.keys(back).length === 0 && back.constructor === Object;
}
@@ -38,28 +41,28 @@ export function formatSize(v: number) {
//格式化剩余时间
export function formatETA(etaInSeconds: number): string {
if (isNaN(etaInSeconds) || etaInSeconds <= 0) {
return '未知';
}
if (isNaN(etaInSeconds) || etaInSeconds <= 0) {
return '未知';
}
const hours = Math.floor(etaInSeconds / 3600);
const minutes = Math.floor((etaInSeconds % 3600) / 60);
const seconds = Math.floor(etaInSeconds % 60);
const hours = Math.floor(etaInSeconds / 3600);
const minutes = Math.floor((etaInSeconds % 3600) / 60);
const seconds = Math.floor(etaInSeconds % 60);
let formattedETA = '';
if (hours > 0) {
formattedETA += `${hours.toString().padStart(2, '0')}h `;
}
if (minutes > 0) {
formattedETA += `${minutes.toString().padStart(2, '0')}m `;
}
let formattedETA = '';
formattedETA += `${seconds.toString().padStart(2, '0')}s`;
if (hours > 0) {
formattedETA += `${hours.toString().padStart(2, '0')}h `;
}
if (minutes > 0) {
formattedETA += `${minutes.toString().padStart(2, '0')}m `;
}
return formattedETA;
formattedETA += `${seconds.toString().padStart(2, '0')}s`;
return formattedETA;
}
export function randomString(length: number): string {
const alphanumericChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
//const specialChars = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
@@ -71,4 +74,24 @@ export function randomString(length: number): string {
).join('');
return randomString;
}
}
export function takeMidStr(input: string, startMarker: string, endMarker: string): string {
const startIndex = input.indexOf(startMarker) + startMarker.length;
const endIndex = input.indexOf(endMarker, startIndex);
return input.substring(startIndex, endIndex);
}
//取字符串右边
export function takeRightStr(str: string, taggedStr: string) {
return str.substring(str.indexOf(taggedStr) + taggedStr.length, str.length)
}
//下载文件
export async function downloadFile(url: string, path: string) {
await invoke('download_file', {
url: url,
outPath: path
})
return await fs.exists(path)
}