mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-16 03:32:36 +08:00
chore: update
This commit is contained in:
parent
20d163cf3a
commit
c3e24d7b96
247
src-tauri/Cargo.lock
generated
247
src-tauri/Cargo.lock
generated
|
@ -23,6 +23,17 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
|
@ -841,6 +852,27 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.18.5"
|
||||
|
@ -973,6 +1005,16 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.0.0"
|
||||
|
@ -995,6 +1037,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"port_scanner",
|
||||
"reqwest",
|
||||
"reqwest_dav",
|
||||
"runas",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -1020,6 +1063,7 @@ dependencies = [
|
|||
"warp",
|
||||
"window-shadows",
|
||||
"winreg 0.52.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1156,6 +1200,12 @@ dependencies = [
|
|||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
|
@ -1245,6 +1295,21 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
|
@ -1420,6 +1485,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
||||
|
||||
[[package]]
|
||||
name = "delay_timer"
|
||||
version = "0.11.6"
|
||||
|
@ -1555,6 +1626,20 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest_auth"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3054f4e81d395e50822796c5e99ca522e6ba7be98947d6d4b0e5e61640bdb894"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"hex",
|
||||
"md-5",
|
||||
"rand 0.8.5",
|
||||
"sha2 0.10.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2622,6 +2707,15 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
|
@ -3077,6 +3171,15 @@ dependencies = [
|
|||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
|
@ -3461,6 +3564,12 @@ dependencies = [
|
|||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lockfree-object-pool"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
|
@ -3522,6 +3631,16 @@ dependencies = [
|
|||
"hashbrown 0.15.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rs"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
|
@ -3594,6 +3713,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
@ -4459,6 +4588,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -5294,6 +5433,26 @@ dependencies = [
|
|||
"windows-registry 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest_dav"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea79cbb695b7cc877ae9c0f0eeb8468e36cd03dc9c41a93bcf237396357c7b42"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"digest_auth",
|
||||
"http 1.1.0",
|
||||
"httpdate",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.0"
|
||||
|
@ -5664,6 +5823,18 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-xml-rs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.213"
|
||||
|
@ -8527,6 +8698,12 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.4"
|
||||
|
@ -8663,6 +8840,20 @@ name = "zeroize"
|
|||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
|
@ -8692,13 +8883,69 @@ version = "2.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"deflate64",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"indexmap 2.6.0",
|
||||
"lzma-rs",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"time",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"lockfree-object-pool",
|
||||
"log",
|
||||
"once_cell",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.13+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -54,6 +54,8 @@ tauri-plugin-clipboard-manager = "2.0.1"
|
|||
tauri-plugin-deep-link = "2.0.1"
|
||||
tauri-plugin-devtools = "2.0.0-rc"
|
||||
url = "2.5.2"
|
||||
zip = "2.2.0"
|
||||
reqwest_dav = "0.1.14"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
|
|
|
@ -11,6 +11,7 @@ use serde_yaml::Mapping;
|
|||
use std::collections::{HashMap, VecDeque};
|
||||
use sysproxy::{Autoproxy, Sysproxy};
|
||||
type CmdResult<T = ()> = Result<T, String>;
|
||||
use reqwest_dav::list_cmd::ListFile;
|
||||
use tauri::Manager;
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -375,6 +376,37 @@ pub async fn exit_app() {
|
|||
feat::quit(Some(0));
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_webdav_config(url: String, username: String, password: String) -> CmdResult<()> {
|
||||
let patch = IVerge {
|
||||
webdav_url: Some(url),
|
||||
webdav_username: Some(username),
|
||||
webdav_password: Some(password),
|
||||
..IVerge::default()
|
||||
};
|
||||
Config::verge().draft().patch_config(patch.clone());
|
||||
Config::verge().apply();
|
||||
Config::verge()
|
||||
.data()
|
||||
.save_file()
|
||||
.map_err(|err| err.to_string())?;
|
||||
backup::WebDavClient::global().reset();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_webdav_backup() -> CmdResult<()> {
|
||||
feat::create_backup_and_upload_webdav()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
|
||||
feat::list_wevdav_backup().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use crate::core::service;
|
||||
|
|
|
@ -147,6 +147,10 @@ pub struct IVerge {
|
|||
pub verge_port: Option<u16>,
|
||||
|
||||
pub verge_http_enabled: Option<bool>,
|
||||
|
||||
pub webdav_url: Option<String>,
|
||||
pub webdav_username: Option<String>,
|
||||
pub webdav_password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
|
@ -304,6 +308,10 @@ impl IVerge {
|
|||
patch!(proxy_layout_column);
|
||||
patch!(test_list);
|
||||
patch!(auto_log_clean);
|
||||
|
||||
patch!(webdav_url);
|
||||
patch!(webdav_username);
|
||||
patch!(webdav_password);
|
||||
}
|
||||
|
||||
/// 在初始化前尝试拿到单例端口的值
|
||||
|
|
118
src-tauri/src/core/backup.rs
Normal file
118
src-tauri/src/core/backup.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
use crate::config::Config;
|
||||
use crate::utils::dirs;
|
||||
use anyhow::Error;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest_dav::list_cmd::{ListEntity, ListFile};
|
||||
use std::env::{consts::OS, temp_dir};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use zip::write::SimpleFileOptions;
|
||||
|
||||
pub struct WebDavClient {
|
||||
client: Arc<Mutex<Option<reqwest_dav::Client>>>,
|
||||
}
|
||||
|
||||
impl WebDavClient {
|
||||
pub fn global() -> &'static WebDavClient {
|
||||
static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new();
|
||||
WEBDAV_CLIENT.get_or_init(|| WebDavClient {
|
||||
client: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_client(&self) -> Result<reqwest_dav::Client, Error> {
|
||||
if self.client.lock().is_none() {
|
||||
let verge = Config::verge().latest().clone();
|
||||
if verge.webdav_url.is_none()
|
||||
|| verge.webdav_username.is_none()
|
||||
|| verge.webdav_password.is_none()
|
||||
{
|
||||
let msg =
|
||||
"Unable to create web dav client, please make sure the webdav config is correct"
|
||||
.to_string();
|
||||
log::error!(target: "app","{}",msg);
|
||||
return Err(anyhow::Error::msg(msg));
|
||||
}
|
||||
|
||||
let url = verge.webdav_url.unwrap_or_default();
|
||||
let username = verge.webdav_username.unwrap_or_default();
|
||||
let password = verge.webdav_password.unwrap_or_default();
|
||||
|
||||
let client = reqwest_dav::ClientBuilder::new()
|
||||
.set_host(url.to_owned())
|
||||
.set_auth(reqwest_dav::Auth::Basic(
|
||||
username.to_owned(),
|
||||
password.to_owned(),
|
||||
))
|
||||
.build()?;
|
||||
*self.client.lock() = Some(client.clone());
|
||||
}
|
||||
Ok(self.client.lock().clone().unwrap())
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
if !self.client.lock().is_none() {
|
||||
self.client.lock().take();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
if client.get(dirs::BACKUP_DIR).await.is_err() {
|
||||
client.mkcol(dirs::BACKUP_DIR).await?;
|
||||
}
|
||||
|
||||
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||
client
|
||||
.put(webdav_path.as_ref(), fs::read(file_path)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_files(&self) -> Result<Vec<ListFile>, Error> {
|
||||
let client = self.get_client().await?;
|
||||
let files = client
|
||||
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(1))
|
||||
.await?;
|
||||
let mut final_files = Vec::new();
|
||||
for file in files {
|
||||
if let ListEntity::File(file) = file {
|
||||
final_files.push(file);
|
||||
}
|
||||
}
|
||||
Ok(final_files)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_backup() -> Result<(String, PathBuf), Error> {
|
||||
let now = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
|
||||
let zip_file_name = format!("{}-backup-{}.zip", OS, now);
|
||||
let zip_path = temp_dir().join(&zip_file_name);
|
||||
|
||||
let file = fs::File::create(&zip_path)?;
|
||||
let mut zip = zip::ZipWriter::new(file);
|
||||
zip.add_directory("profiles/", SimpleFileOptions::default())?;
|
||||
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
if let Ok(entries) = fs::read_dir(dirs::app_profiles_dir()?) {
|
||||
for entry in entries {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let backup_path = format!("profiles/{}", entry.file_name().to_str().unwrap());
|
||||
zip.start_file(backup_path, options)?;
|
||||
zip.write_all(fs::read(path).unwrap().as_slice())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
zip.start_file(dirs::CLASH_CONFIG, options)?;
|
||||
zip.write_all(fs::read(dirs::clash_path()?)?.as_slice())?;
|
||||
zip.start_file(dirs::VERGE_CONFIG, options)?;
|
||||
zip.write_all(fs::read(dirs::verge_path()?)?.as_slice())?;
|
||||
zip.start_file(dirs::PROFILE_YAML, options)?;
|
||||
zip.write_all(fs::read(dirs::profiles_path()?)?.as_slice())?;
|
||||
zip.finish()?;
|
||||
Ok((zip_file_name, zip_path))
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod backup;
|
||||
pub mod clash_api;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod core;
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::core::*;
|
|||
use crate::log_err;
|
||||
use crate::utils::resolve;
|
||||
use anyhow::{bail, Result};
|
||||
use reqwest_dav::list_cmd::ListFile;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
|
||||
|
@ -401,3 +402,42 @@ pub async fn test_delay(url: String) -> Result<u32> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||
if let Err(err) = async {
|
||||
let (file_name, temp_file_path) = backup::create_backup().map_err(|err| {
|
||||
log::error!(target: "app", "Failed to create backup: {:#?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
backup::WebDavClient::global()
|
||||
.upload(temp_file_path.clone(), file_name)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!(target: "app", "Failed to upload to WebDAV: {:#?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
std::fs::remove_file(&temp_file_path).map_err(|err| {
|
||||
log::warn!(target: "app", "Failed to remove temp file: {:#?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_wevdav_backup() -> Result<Vec<ListFile>> {
|
||||
backup::WebDavClient::global()
|
||||
.list_files()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!(target: "app", "Failed to list WebDAV backup files: {:#?}", err);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
|
|
@ -123,7 +123,11 @@ pub fn run() {
|
|||
// service mode
|
||||
cmds::service::check_service,
|
||||
// clash api
|
||||
cmds::clash_api_get_proxy_delay
|
||||
cmds::clash_api_get_proxy_delay,
|
||||
// backup
|
||||
cmds::create_webdav_backup,
|
||||
cmds::save_webdav_config,
|
||||
cmds::list_webdav_backup,
|
||||
]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
|
|
@ -6,14 +6,19 @@ use tauri::Manager;
|
|||
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
pub static BACKUP_DIR: &str = "clash-verge-rev-backup";
|
||||
|
||||
#[cfg(feature = "verge-dev")]
|
||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
|
||||
#[cfg(feature = "verge-dev")]
|
||||
pub static BACKUP_DIR: &str = "clash-verge-rev-backup-dev";
|
||||
|
||||
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
|
||||
|
||||
static CLASH_CONFIG: &str = "config.yaml";
|
||||
static VERGE_CONFIG: &str = "verge.yaml";
|
||||
static PROFILE_YAML: &str = "profiles.yaml";
|
||||
pub static CLASH_CONFIG: &str = "config.yaml";
|
||||
pub static VERGE_CONFIG: &str = "verge.yaml";
|
||||
pub static PROFILE_YAML: &str = "profiles.yaml";
|
||||
|
||||
/// init portable flag
|
||||
pub fn init_portable_flag() -> Result<()> {
|
||||
|
|
33
src/components/base/base-loading-overlay.tsx
Normal file
33
src/components/base/base-loading-overlay.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from "react";
|
||||
import { Box, CircularProgress } from "@mui/material";
|
||||
|
||||
export interface BaseLoadingOverlayProps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const BaseLoadingOverlay: React.FC<BaseLoadingOverlayProps> = ({
|
||||
isLoading,
|
||||
}) => {
|
||||
if (!isLoading) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.7)",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseLoadingOverlay;
|
|
@ -5,3 +5,4 @@ export { BaseLoading } from "./base-loading";
|
|||
export { BaseErrorBoundary } from "./base-error-boundary";
|
||||
export { Notice } from "./base-notice";
|
||||
export { Switch } from "./base-switch";
|
||||
export { BaseLoadingOverlay } from "./base-loading-overlay";
|
||||
|
|
330
src/components/setting/mods/backup-viewer.tsx
Normal file
330
src/components/setting/mods/backup-viewer.tsx
Normal file
|
@ -0,0 +1,330 @@
|
|||
import { forwardRef, useImperativeHandle, useState, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { Typography } from "@mui/material";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { isValidUrl } from "@/utils/helper";
|
||||
import { BaseLoadingOverlay } from "@/components/base";
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
Grid,
|
||||
Box,
|
||||
Paper,
|
||||
Stack,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
Divider,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import RestoreIcon from "@mui/icons-material/Restore";
|
||||
import { createWebdavBackup, saveWebdavConfig } from "@/services/cmds";
|
||||
import { save } from "@tauri-apps/plugin-dialog";
|
||||
|
||||
export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { verge, mutateVerge } = useVerge();
|
||||
const { webdav_url, webdav_username, webdav_password } = verge || {};
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const usernameRef = useRef<HTMLInputElement>(null);
|
||||
const passwordRef = useRef<HTMLInputElement>(null);
|
||||
const urlRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
||||
defaultValues: {
|
||||
url: webdav_url,
|
||||
username: webdav_username,
|
||||
password: webdav_password,
|
||||
},
|
||||
});
|
||||
|
||||
const url = watch("url");
|
||||
const username = watch("username");
|
||||
const password = watch("password");
|
||||
const webdavChanged =
|
||||
webdav_url !== url ||
|
||||
webdav_username !== username ||
|
||||
webdav_password !== password;
|
||||
|
||||
// const backups = [] as any[];
|
||||
const backups = [
|
||||
{ name: "backup1.zip" },
|
||||
{ name: "backup2.zip" },
|
||||
{ name: "backup3.zip" },
|
||||
];
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const checkForm = () => {
|
||||
const username = usernameRef.current?.value;
|
||||
const password = passwordRef.current?.value;
|
||||
const url = urlRef.current?.value;
|
||||
|
||||
if (!url) {
|
||||
Notice.error(t("Webdav url cannot be empty"));
|
||||
urlRef.current?.focus();
|
||||
return;
|
||||
} else if (!isValidUrl(url)) {
|
||||
Notice.error(t("Webdav address must be url"));
|
||||
urlRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
if (!username) {
|
||||
Notice.error(t("Username cannot be empty"));
|
||||
usernameRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
if (!password) {
|
||||
Notice.error(t("Password cannot be empty"));
|
||||
passwordRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async (data: IWebDavConfig) => {
|
||||
checkForm();
|
||||
setIsLoading(true);
|
||||
await saveWebdavConfig(data.url, data.username, data.password)
|
||||
.then(() => {
|
||||
mutateVerge(
|
||||
{
|
||||
webdav_url: data.url,
|
||||
webdav_username: data.username,
|
||||
webdav_password: data.password,
|
||||
},
|
||||
false
|
||||
);
|
||||
Notice.success(t("Webdav Config Saved Successfully"), 1500);
|
||||
})
|
||||
.catch((e) => {
|
||||
Notice.error(t("Webdav Config Save Failed", { error: e }), 3000);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const handleBackup = useLockFn(async () => {
|
||||
checkForm();
|
||||
setIsLoading(true);
|
||||
await createWebdavBackup()
|
||||
.then(() => {
|
||||
Notice.success(t("Backup Successfully"), 1500);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e, "backup failed");
|
||||
Notice.error(t("Backup Failed", { error: e }), 3000);
|
||||
});
|
||||
});
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Backup Setting")}
|
||||
contentSx={{ width: 600, maxHeight: 800 }}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Cancel")}
|
||||
disableFooter={true}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<Box sx={{ maxWidth: 800 }}>
|
||||
<BaseLoadingOverlay isLoading={isLoading} />
|
||||
<Paper elevation={2} sx={{ padding: 2 }}>
|
||||
<form onSubmit={handleSubmit(submit)}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={9}>
|
||||
<Grid container spacing={2}>
|
||||
{/* WebDAV Server Address */}
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="WebDAV Server URL"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
{...register("url")}
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
inputRef={urlRef}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Username and Password */}
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
label="Username"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
{...register("username")}
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
inputRef={usernameRef}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
label="Password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
inputRef={passwordRef}
|
||||
{...register("password")}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={handleClickShowPassword}
|
||||
edge="end"
|
||||
>
|
||||
{showPassword ? (
|
||||
<VisibilityOff />
|
||||
) : (
|
||||
<Visibility />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={3}>
|
||||
<Stack
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
sx={{ height: "100%" }}
|
||||
>
|
||||
{webdavChanged ||
|
||||
webdav_url === null ||
|
||||
webdav_username == null ||
|
||||
webdav_password == null ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ height: "100%" }}
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
sx={{ height: "100%" }}
|
||||
onClick={handleBackup}
|
||||
type="button"
|
||||
>
|
||||
Backup
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
<Divider sx={{ marginY: 2 }} />
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>文件名称</TableCell>
|
||||
<TableCell align="right">操作</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{backups.length > 0 ? (
|
||||
backups?.map((backup, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell component="th" scope="row">
|
||||
{backup.name}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
aria-label="delete"
|
||||
size="small"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
flexItem
|
||||
sx={{ mx: 1, height: 24 }}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="restore"
|
||||
size="small"
|
||||
>
|
||||
<RestoreIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} align="center">
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: 150,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
暂无备份
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</Box>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
|
@ -23,6 +23,7 @@ import { ThemeViewer } from "./mods/theme-viewer";
|
|||
import { GuardState } from "./mods/guard-state";
|
||||
import { LayoutViewer } from "./mods/layout-viewer";
|
||||
import { UpdateViewer } from "./mods/update-viewer";
|
||||
import { BackupViewer } from "./mods/backup-viewer";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { routers } from "@/pages/_routers";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
|
@ -52,6 +53,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||
const themeRef = useRef<DialogRef>(null);
|
||||
const layoutRef = useRef<DialogRef>(null);
|
||||
const updateRef = useRef<DialogRef>(null);
|
||||
const backupRef = useRef<DialogRef>(null);
|
||||
|
||||
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
||||
mutateVerge({ ...verge, ...patch }, false);
|
||||
|
@ -83,6 +85,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||
<MiscViewer ref={miscRef} />
|
||||
<LayoutViewer ref={layoutRef} />
|
||||
<UpdateViewer ref={updateRef} />
|
||||
<BackupViewer ref={backupRef} />
|
||||
|
||||
<SettingItem label={t("Language")}>
|
||||
<GuardState
|
||||
|
@ -238,6 +241,11 @@ const SettingVerge = ({ onError }: Props) => {
|
|||
label={t("Hotkey Setting")}
|
||||
/>
|
||||
|
||||
<SettingItem
|
||||
onClick={() => backupRef.current?.open()}
|
||||
label={t("Backup Setting")}
|
||||
/>
|
||||
|
||||
<SettingItem
|
||||
onClick={() => configRef.current?.open()}
|
||||
label={t("Runtime Config")}
|
||||
|
|
|
@ -332,6 +332,7 @@
|
|||
"clash_mode_direct": "Direct Mode",
|
||||
"toggle_system_proxy": "Enable/Disable System Proxy",
|
||||
"toggle_tun_mode": "Enable/Disable Tun Mode",
|
||||
"Backup Setting": "Backup Setting",
|
||||
"Runtime Config": "Runtime Config",
|
||||
"Open Conf Dir": "Open Conf Dir",
|
||||
"Open Core Dir": "Open Core Dir",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"clash_mode_direct": "حالت مستقیم",
|
||||
"toggle_system_proxy": "فعال/غیرفعال کردن پراکسی سیستم",
|
||||
"toggle_tun_mode": "فعال/غیرفعال کردن حالت Tun",
|
||||
"Backup Setting": "تنظیمات پشتیبان",
|
||||
"Runtime Config": "پیکربندی زمان اجرا",
|
||||
"Open Conf Dir": "باز کردن پوشه برنامه",
|
||||
"Open Core Dir": "باز کردن پوشه هسته",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"clash_mode_direct": "Прямой режим",
|
||||
"toggle_system_proxy": "Включить/Отключить системный прокси",
|
||||
"toggle_tun_mode": "Включить/Отключить режим туннеля",
|
||||
"Backup Setting": "Настройки резервного копирования",
|
||||
"Runtime Config": "Используемый конфиг",
|
||||
"Open Conf Dir": "Открыть папку приложения",
|
||||
"Open Core Dir": "Открыть папку ядра",
|
||||
|
|
|
@ -332,6 +332,7 @@
|
|||
"clash_mode_direct": "直连模式",
|
||||
"toggle_system_proxy": "打开/关闭系统代理",
|
||||
"toggle_tun_mode": "打开/关闭 Tun 模式",
|
||||
"Backup Setting": "备份设置",
|
||||
"Runtime Config": "当前配置",
|
||||
"Open Conf Dir": "配置目录",
|
||||
"Open Core Dir": "内核目录",
|
||||
|
|
|
@ -236,3 +236,26 @@ export async function getNetworkInterfaces() {
|
|||
export async function getNetworkInterfacesInfo() {
|
||||
return invoke<INetworkInterface[]>("get_network_interfaces_info");
|
||||
}
|
||||
|
||||
export async function createWebdavBackup() {
|
||||
return invoke<void>("create_webdav_backup");
|
||||
}
|
||||
export async function saveWebdavConfig(
|
||||
url: string,
|
||||
username: string,
|
||||
password: String
|
||||
) {
|
||||
return invoke<void>("save_webdav_config", {
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
}
|
||||
|
||||
export async function listWebDavBackup() {
|
||||
let list: IWebDavFile[] = await invoke<IWebDavFile[]>("list_webdav_backup");
|
||||
list.map((item) => {
|
||||
item.filename = item.href.split("/").pop() as string;
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
|
18
src/services/types.d.ts
vendored
18
src/services/types.d.ts
vendored
|
@ -744,4 +744,22 @@ interface IVergeConfig {
|
|||
auto_log_clean?: 0 | 1 | 2 | 3;
|
||||
proxy_layout_column?: number;
|
||||
test_list?: IVergeTestItem[];
|
||||
webdav_url?: string;
|
||||
webdav_username?: string;
|
||||
webdav_password?: string;
|
||||
}
|
||||
|
||||
interface IWebDavFile {
|
||||
filename: string;
|
||||
href: string;
|
||||
last_modified: string;
|
||||
content_length: number;
|
||||
content_type: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
interface IWebDavConfig {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
|
9
src/utils/helper.ts
Normal file
9
src/utils/helper.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const isValidUrl = (url: string) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return false;
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user