Compare commits

...

15 Commits

27 changed files with 477 additions and 153 deletions

View File

@@ -1,4 +1,4 @@
## v2.1.1 ## v2.1.2
**发行代号:臻** **发行代号:臻**
@@ -6,8 +6,16 @@
感谢 Tychristine 对社区群组管理做出的重大贡献! 感谢 Tychristine 对社区群组管理做出的重大贡献!
2.1.1相对2.1.0(已下架不在提供)更新了: 2.1.2相对2.1.1(已下架不在提供)更新了:
- 无法更新和签名验证失败的问题(该死的CDN缓存)
- 设置菜单区分Verge基本设置和高级设置
- 增加v2 Updater的更多功能和权限
- 退出Verge后Tun代理状态仍保留的问题
2.1.1相对2.1.0(已下架不在提供)更新了:
- 检测所需的Clash Verge Service版本杀毒软件误报可能与此有关因为检测和安装新版本Service需管理员权限
- MacOS下支持彩色托盘图标和更好速率显示感谢Tunglies - MacOS下支持彩色托盘图标和更好速率显示感谢Tunglies
- 文件类型判断不准导致脚本检测报错的问题 - 文件类型判断不准导致脚本检测报错的问题
- 打开Win下的阴影(Win10因底层兼容性问题可能圆角和边框显示不太完美) - 打开Win下的阴影(Win10因底层兼容性问题可能圆角和边框显示不太完美)
@@ -15,6 +23,8 @@
- 修复Linux下编译问题 - 修复Linux下编译问题
- 修复热键无法关闭面板的问题 - 修复热键无法关闭面板的问题
## v2.1.0 - 发行代号:臻
### 功能新增 ### 功能新增
- 新增窗口状态实时监控与自动保存功能 - 新增窗口状态实时监控与自动保存功能

View File

@@ -1,6 +1,6 @@
{ {
"name": "clash-verge", "name": "clash-verge",
"version": "2.1.1", "version": "2.1.2",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"dev": "cross-env RUST_BACKTRACE=1 tauri dev", "dev": "cross-env RUST_BACKTRACE=1 tauri dev",

View File

@@ -60,35 +60,35 @@ async function resolveUpdater() {
const { name, browser_download_url } = asset; const { name, browser_download_url } = asset;
// win64 url // win64 url
if (name.endsWith("x64-setup.nsis.zip")) { if (name.endsWith("x64-setup.exe")) {
updateData.platforms.win64.url = browser_download_url; updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url; updateData.platforms["windows-x86_64"].url = browser_download_url;
} }
// win64 signature // win64 signature
if (name.endsWith("x64-setup.nsis.zip.sig")) { if (name.endsWith("x64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url); const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig; updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig; updateData.platforms["windows-x86_64"].signature = sig;
} }
// win32 url // win32 url
if (name.endsWith("x86-setup.nsis.zip")) { if (name.endsWith("x86-setup.exe")) {
updateData.platforms["windows-x86"].url = browser_download_url; updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url; updateData.platforms["windows-i686"].url = browser_download_url;
} }
// win32 signature // win32 signature
if (name.endsWith("x86-setup.nsis.zip.sig")) { if (name.endsWith("x86-setup.exe.sig")) {
const sig = await getSignature(browser_download_url); const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig; updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig; updateData.platforms["windows-i686"].signature = sig;
} }
// win arm url // win arm url
if (name.endsWith("arm64-setup.nsis.zip")) { if (name.endsWith("arm64-setup.exe")) {
updateData.platforms["windows-aarch64"].url = browser_download_url; updateData.platforms["windows-aarch64"].url = browser_download_url;
} }
// win arm signature // win arm signature
if (name.endsWith("arm64-setup.nsis.zip.sig")) { if (name.endsWith("arm64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url); const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig; updateData.platforms["windows-aarch64"].signature = sig;
} }

2
src-tauri/Cargo.lock generated
View File

@@ -999,7 +999,7 @@ dependencies = [
[[package]] [[package]]
name = "clash-verge" name = "clash-verge"
version = "2.1.1" version = "2.1.2"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "clash-verge" name = "clash-verge"
version = "2.1.1" version = "2.1.2"
description = "clash verge" description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"] authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only" license = "GPL-3.0-only"

View File

@@ -6,6 +6,13 @@
"permissions": [ "permissions": [
"global-shortcut:default", "global-shortcut:default",
"updater:default", "updater:default",
"dialog:default",
"dialog:allow-ask",
"dialog:allow-message",
"updater:default",
"updater:allow-check",
"updater:allow-download-and-install",
"process:allow-restart",
"deep-link:default", "deep-link:default",
"window-state:default", "window-state:default",
"window-state:default", "window-state:default",

View File

@@ -187,24 +187,57 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
} }
// 在异步操作前完成所有文件操作 // 在异步操作前完成所有文件操作
let (file_path, original_content) = { let (file_path, original_content, is_merge_file) = {
let profiles = Config::profiles(); let profiles = Config::profiles();
let profiles_guard = profiles.latest(); let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?; let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件
let is_merge = item.itype.as_ref().map_or(false, |t| t == "merge");
let content = wrap_err!(item.read_file())?; let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?; let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?; let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
(profiles_dir.join(path), content) (profiles_dir.join(path), content, is_merge)
}; };
// 保存新的配置文件 // 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?; wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy(); let file_path_str = file_path.to_string_lossy().to_string();
println!("[cmd配置save] 开始验证配置文件: {}", file_path_str); println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file);
// 验证配置文件 // 对于 merge 文件,只进行语法验证,不进行后续内核验证
match CoreManager::global().validate_config_file(&file_path_str).await { if is_merge_file {
println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await {
Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置
if let Err(e) = CoreManager::global().update_config().await {
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
}
return Ok(());
}
Ok((false, error_msg)) => {
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 发送合并文件专用错误通知
let result = (false, error_msg.clone());
handle_yaml_validation_notice(&result, "合并配置文件");
return Ok(());
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
return Err(e.to_string());
}
}
}
// 非merge文件使用完整验证流程
match CoreManager::global().validate_config_file(&file_path_str, None).await {
Ok((true, _)) => { Ok((true, _)) => {
println!("[cmd配置save] 验证成功"); println!("[cmd配置save] 验证成功");
Ok(()) Ok(())
@@ -214,18 +247,27 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
// 恢复原始配置文件 // 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?; wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断是否为脚本错误 // 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js") || let is_script_error = file_path_str.ends_with(".js") ||
error_msg.contains("Script syntax error") || error_msg.contains("Script syntax error") ||
error_msg.contains("Script must contain a main function") || error_msg.contains("Script must contain a main function") ||
error_msg.contains("Failed to read script file"); error_msg.contains("Failed to read script file");
if is_script_error { if error_msg.contains("YAML syntax error") ||
error_msg.contains("Failed to read file:") ||
(!file_path_str.ends_with(".js") && !is_script_error) {
// 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败发送通知");
let result = (false, error_msg.clone());
handle_yaml_validation_notice(&result, "YAML配置文件");
} else if is_script_error {
// 脚本错误使用专门的通知处理 // 脚本错误使用专门的通知处理
println!("[cmd配置save] 脚本文件验证失败,发送通知");
let result = (false, error_msg.clone()); let result = (false, error_msg.clone());
handle_script_validation_notice(&result, "脚本文件"); handle_script_validation_notice(&result, "脚本文件");
} else { } else {
// 普通配置错误使用一般通知 // 普通配置错误使用一般通知
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg); handle::Handle::notice_message("config_validate::error", &error_msg);
} }
@@ -292,7 +334,7 @@ pub fn get_verge_config() -> CmdResult<IVergeResponse> {
#[tauri::command] #[tauri::command]
pub async fn patch_verge_config(payload: IVerge) -> CmdResult { pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload).await) wrap_err!(feat::patch_verge(payload, false).await)
} }
#[tauri::command] #[tauri::command]
@@ -593,7 +635,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> { pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path); log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global().validate_config_file(&file_path).await { match CoreManager::global().validate_config_file(&file_path, None).await {
Ok(result) => { Ok(result) => {
handle_script_validation_notice(&result, "脚本文件"); handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值 Ok(result.0) // 返回验证结果布尔值
@@ -606,3 +648,51 @@ pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
} }
} }
} }
/// 处理YAML验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
// 检查是否为merge文件
let is_merge_file = file_type.contains("合并");
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read file:") {
"config_validate::yaml_read_error"
} else if error_msg.starts_with("YAML syntax error:") {
if is_merge_file {
"config_validate::merge_syntax_error"
} else {
"config_validate::yaml_syntax_error"
}
} else if error_msg.contains("mapping values are not allowed") {
if is_merge_file {
"config_validate::merge_mapping_error"
} else {
"config_validate::yaml_mapping_error"
}
} else if error_msg.contains("did not find expected key") {
if is_merge_file {
"config_validate::merge_key_error"
} else {
"config_validate::yaml_key_error"
}
} else {
// 如果是其他类型错误,根据文件类型作为一般错误处理
if is_merge_file {
"config_validate::merge_error"
} else {
"config_validate::yaml_error"
}
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}

View File

@@ -240,7 +240,7 @@ impl CoreManager {
} }
/// 验证指定的配置文件 /// 验证指定的配置文件
pub async fn validate_config_file(&self, config_path: &str) -> Result<(bool, String)> { pub async fn validate_config_file(&self, config_path: &str, is_merge_file: Option<bool>) -> Result<(bool, String)> {
// 检查文件是否存在 // 检查文件是否存在
if !std::path::Path::new(config_path).exists() { if !std::path::Path::new(config_path).exists() {
let error_msg = format!("File not found: {}", config_path); let error_msg = format!("File not found: {}", config_path);
@@ -248,6 +248,12 @@ impl CoreManager {
return Ok((false, error_msg)); return Ok((false, error_msg));
} }
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
if is_merge_file.unwrap_or(false) {
println!("[core配置验证] 检测到Merge文件仅进行语法检查: {}", config_path);
return self.validate_file_syntax(config_path).await;
}
// 检查是否为脚本文件 // 检查是否为脚本文件
let is_script = if config_path.ends_with(".js") { let is_script = if config_path.ends_with(".js") {
true true
@@ -345,7 +351,8 @@ impl CoreManager {
Ok(content) => content, Ok(content) => content,
Err(err) => { Err(err) => {
let error_msg = format!("Failed to read script file: {}", err); let error_msg = format!("Failed to read script file: {}", err);
//handle::Handle::notice_message("config_validate::script_error", &error_msg); log::warn!(target: "app", "脚本语法错误: {}", err);
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
return Ok((false, error_msg)); return Ok((false, error_msg));
} }
}; };
@@ -440,4 +447,34 @@ impl CoreManager {
} }
} }
} }
/// 只进行文件语法检查,不进行完整验证
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
println!("[core配置语法检查] 开始检查文件: {}", config_path);
// 读取文件内容
let content = match std::fs::read_to_string(config_path) {
Ok(content) => content,
Err(err) => {
let error_msg = format!("Failed to read file: {}", err);
println!("[core配置语法检查] 无法读取文件: {}", error_msg);
return Ok((false, error_msg));
}
};
// 对YAML文件尝试解析只检查语法正确性
println!("[core配置语法检查] 进行YAML语法检查");
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
Ok(_) => {
println!("[core配置语法检查] YAML语法检查通过");
Ok((true, String::new()))
},
Err(err) => {
// 使用标准化的前缀,以便错误处理函数能正确识别
let error_msg = format!("YAML syntax error: {}", err);
println!("[core配置语法检查] YAML语法错误: {}", error_msg);
Ok((false, error_msg))
}
}
}
} }

View File

@@ -140,7 +140,7 @@ impl Hotkey {
"clash_mode_global" => || feat::change_clash_mode("global".into()), "clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()), "clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"toggle_system_proxy" => || feat::toggle_system_proxy(), "toggle_system_proxy" => || feat::toggle_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(), "toggle_tun_mode" => || feat::toggle_tun_mode(None),
"quit" => || feat::quit(Some(0)), "quit" => || feat::quit(Some(0)),
_ => { _ => {

View File

@@ -10,6 +10,7 @@ use tokio::time::Duration;
// Windows only // Windows only
const SERVICE_URL: &str = "http://127.0.0.1:33211"; const SERVICE_URL: &str = "http://127.0.0.1:33211";
const REQUIRED_SERVICE_VERSION: &str = "1.0.1"; // 定义所需的服务版本号
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ResponseBody { pub struct ResponseBody {
@@ -19,6 +20,12 @@ pub struct ResponseBody {
pub log_file: String, pub log_file: String,
} }
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct VersionResponse {
pub service: String,
pub version: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct JsonResponse { pub struct JsonResponse {
pub code: u64, pub code: u64,
@@ -26,6 +33,13 @@ pub struct JsonResponse {
pub data: Option<ResponseBody>, pub data: Option<ResponseBody>,
} }
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct VersionJsonResponse {
pub code: u64,
pub msg: String,
pub data: Option<VersionResponse>,
}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub async fn reinstall_service() -> Result<()> { pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service"); log::info!(target:"app", "reinstall service");
@@ -177,8 +191,43 @@ pub async fn check_service() -> Result<JsonResponse> {
Ok(response) Ok(response)
} }
/// check the service version
pub async fn check_service_version() -> Result<String> {
let url = format!("{SERVICE_URL}/version");
let response = reqwest::ClientBuilder::new()
.no_proxy()
.timeout(Duration::from_secs(3))
.build()?
.get(url)
.send()
.await
.context("failed to connect to the Clash Verge Service")?
.json::<VersionJsonResponse>()
.await
.context("failed to parse the Clash Verge Service version response")?;
match response.data {
Some(data) => Ok(data.version),
None => bail!("service version not found in response"),
}
}
/// check if service needs to be reinstalled
pub async fn check_service_needs_reinstall() -> bool {
match check_service_version().await {
Ok(version) => version != REQUIRED_SERVICE_VERSION,
Err(_) => true, // 如果无法获取版本或服务未运行,也需要重新安装
}
}
/// start the clash by service /// start the clash by service
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> { pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
// 检查服务版本,如果不匹配则重新安装
if check_service_needs_reinstall().await {
log::info!(target: "app", "service version mismatch, reinstalling");
reinstall_service().await?;
}
let clash_core = { Config::verge().latest().clash_core.clone() }; let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into()); let clash_core = clash_core.unwrap_or("verge-mihomo".into());

View File

@@ -87,7 +87,7 @@ impl Tray {
{ {
match tray_event.as_str() { match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(), "system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(), "tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(), "main_window" => resolve::create_window(),
_ => {} _ => {}
} }
@@ -102,7 +102,7 @@ impl Tray {
{ {
match tray_event.as_str() { match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(), "system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(), "tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(), "main_window" => resolve::create_window(),
_ => {} _ => {}
} }
@@ -594,7 +594,7 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
} }
"open_window" => resolve::create_window(), "open_window" => resolve::create_window(),
"system_proxy" => feat::toggle_system_proxy(), "system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(), "tun_mode" => feat::toggle_tun_mode(None),
"copy_env" => feat::copy_clash_env(), "copy_env" => feat::copy_clash_env(),
"open_app_dir" => crate::log_err!(cmds::open_app_dir()), "open_app_dir" => crate::log_err!(cmds::open_app_dir()),
"open_core_dir" => crate::log_err!(cmds::open_core_dir()), "open_core_dir" => crate::log_err!(cmds::open_core_dir()),

View File

@@ -151,7 +151,7 @@ pub fn toggle_system_proxy() {
match patch_verge(IVerge { match patch_verge(IVerge {
enable_system_proxy: Some(!enable), enable_system_proxy: Some(!enable),
..IVerge::default() ..IVerge::default()
}) }, false)
.await .await
{ {
Ok(_) => handle::Handle::refresh_verge(), Ok(_) => handle::Handle::refresh_verge(),
@@ -176,7 +176,7 @@ pub fn toggle_proxy_profile(profile_index: String) {
} }
// 切换tun模式 // 切换tun模式
pub fn toggle_tun_mode() { pub fn toggle_tun_mode(not_save_file: Option<bool>) {
let enable = Config::verge().data().enable_tun_mode; let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false); let enable = enable.unwrap_or(false);
@@ -184,7 +184,7 @@ pub fn toggle_tun_mode() {
match patch_verge(IVerge { match patch_verge(IVerge {
enable_tun_mode: Some(!enable), enable_tun_mode: Some(!enable),
..IVerge::default() ..IVerge::default()
}) }, not_save_file.unwrap_or(false))
.await .await
{ {
Ok(_) => handle::Handle::refresh_verge(), Ok(_) => handle::Handle::refresh_verge(),
@@ -196,6 +196,7 @@ pub fn toggle_tun_mode() {
pub fn quit(code: Option<i32>) { pub fn quit(code: Option<i32>) {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
handle::Handle::global().set_is_exiting(); handle::Handle::global().set_is_exiting();
toggle_tun_mode(Some(true));
resolve::resolve_reset(); resolve::resolve_reset();
log_err!(handle::Handle::global().get_window().unwrap().close()); log_err!(handle::Handle::global().get_window().unwrap().close());
app_handle.exit(code.unwrap_or(0)); app_handle.exit(code.unwrap_or(0));
@@ -236,7 +237,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
/// 修改verge的订阅 /// 修改verge的订阅
/// 一般都是一个个的修改 /// 一般都是一个个的修改
pub async fn patch_verge(patch: IVerge) -> Result<()> { pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
Config::verge().draft().patch_config(patch.clone()); Config::verge().draft().patch_config(patch.clone());
let tun_mode = patch.enable_tun_mode; let tun_mode = patch.enable_tun_mode;
@@ -397,7 +398,9 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
match res { match res {
Ok(()) => { Ok(()) => {
Config::verge().apply(); Config::verge().apply();
Config::verge().data().save_file()?; if !not_save_file {
Config::verge().data().save_file()?;
}
Ok(()) Ok(())
} }
@@ -617,7 +620,8 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
webdav_username, webdav_username,
webdav_password, webdav_password,
..IVerge::default() ..IVerge::default()
}) },
false)
.await .await
); );
// 最后删除临时文件 // 最后删除临时文件

View File

@@ -16,7 +16,7 @@
"copyright": "GNU General Public License v3.0", "copyright": "GNU General Public License v3.0",
"category": "DeveloperTool", "category": "DeveloperTool",
"shortDescription": "Clash Verge Rev", "shortDescription": "Clash Verge Rev",
"createUpdaterArtifacts": "v1Compatible" "createUpdaterArtifacts": true
}, },
"build": { "build": {
"beforeBuildCommand": "pnpm run web:build", "beforeBuildCommand": "pnpm run web:build",
@@ -25,24 +25,23 @@
"devUrl": "http://localhost:3000/" "devUrl": "http://localhost:3000/"
}, },
"productName": "Clash Verge", "productName": "Clash Verge",
"version": "2.1.1", "version": "2.1.2",
"identifier": "io.github.clash-verge-rev.clash-verge-rev", "identifier": "io.github.clash-verge-rev.clash-verge-rev",
"plugins": { "plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
"endpoints": [
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json"
],
"windows": {
"installMode": "basicUi"
}
},
"deep-link": { "deep-link": {
"desktop": { "desktop": {
"schemes": ["clash", "clash-verge"] "schemes": ["clash", "clash-verge"]
} }
},
"updater": {
"active": true,
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
"endpoints": [
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json",
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/alpha/update-alpha-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/alpha/update-alpha.json"
]
} }
}, },
"app": { "app": {

View File

@@ -111,7 +111,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0px; right: 1px;
bottom: 0px; bottom: 0px;
} }
} }

View File

@@ -51,8 +51,11 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const chains = [...data.chains].reverse().join(" / "); const chains = [...data.chains].reverse().join(" / ");
const rule = rulePayload ? `${data.rule}(${rulePayload})` : data.rule; const rule = rulePayload ? `${data.rule}(${rulePayload})` : data.rule;
const host = metadata.host const host = metadata.host
? `${metadata.host}:${metadata.remoteDestination}` ? `${metadata.host}:${metadata.destinationPort}`
: `${metadata.remoteDestination}:${metadata.destinationPort}`; : `${metadata.remoteDestination}:${metadata.destinationPort}`;
const Destination = metadata.destinationIP
? metadata.destinationIP
: metadata.remoteDestination;
const information = [ const information = [
{ label: t("Host"), value: host }, { label: t("Host"), value: host },
@@ -79,7 +82,7 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
label: t("Source"), label: t("Source"),
value: `${metadata.sourceIP}:${metadata.sourcePort}`, value: `${metadata.sourceIP}:${metadata.sourcePort}`,
}, },
{ label: t("Destination"), value: metadata.remoteDestination }, { label: t("Destination"), value: Destination },
{ label: t("DestinationPort"), value: `${metadata.destinationPort}` }, { label: t("DestinationPort"), value: `${metadata.destinationPort}` },
{ label: t("Type"), value: `${metadata.type}(${metadata.network})` }, { label: t("Type"), value: `${metadata.type}(${metadata.network})` },
]; ];

View File

@@ -138,6 +138,9 @@ export const ConnectionTable = (props: Props) => {
const { metadata, rulePayload } = each; const { metadata, rulePayload } = each;
const chains = [...each.chains].reverse().join(" / "); const chains = [...each.chains].reverse().join(" / ");
const rule = rulePayload ? `${each.rule}(${rulePayload})` : each.rule; const rule = rulePayload ? `${each.rule}(${rulePayload})` : each.rule;
const Destination = metadata.destinationIP
? `${metadata.destinationIP}:${metadata.destinationPort}`
: `${metadata.remoteDestination}:${metadata.destinationPort}`;
return { return {
id: each.id, id: each.id,
host: metadata.host host: metadata.host
@@ -152,7 +155,7 @@ export const ConnectionTable = (props: Props) => {
process: truncateStr(metadata.process || metadata.processPath), process: truncateStr(metadata.process || metadata.processPath),
time: each.start, time: each.start,
source: `${metadata.sourceIP}:${metadata.sourcePort}`, source: `${metadata.sourceIP}:${metadata.sourcePort}`,
remoteDestination: `${metadata.remoteDestination}:${metadata.destinationPort}`, remoteDestination: Destination,
type: `${metadata.type}(${metadata.network})`, type: `${metadata.type}(${metadata.network})`,
connectionData: each, connectionData: each,
}; };

View File

@@ -0,0 +1,121 @@
import { useRef } from "react";
import { useTranslation } from "react-i18next";
import { Typography } from "@mui/material";
import {
exitApp,
openAppDir,
openCoreDir,
openLogsDir,
openDevTools,
} from "@/services/cmds";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useVerge } from "@/hooks/use-verge";
import { version } from "@root/package.json";
import { DialogRef, Notice } from "@/components/base";
import { SettingList, SettingItem } from "./mods/setting-comp";
import { ConfigViewer } from "./mods/config-viewer";
import { HotkeyViewer } from "./mods/hotkey-viewer";
import { MiscViewer } from "./mods/misc-viewer";
import { ThemeViewer } from "./mods/theme-viewer";
import { LayoutViewer } from "./mods/layout-viewer";
import { UpdateViewer } from "./mods/update-viewer";
import { BackupViewer } from "./mods/backup-viewer";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
interface Props {
onError?: (err: Error) => void;
}
const SettingVergeAdvanced = ({ onError }: Props) => {
const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge();
const configRef = useRef<DialogRef>(null);
const hotkeyRef = useRef<DialogRef>(null);
const miscRef = useRef<DialogRef>(null);
const themeRef = useRef<DialogRef>(null);
const layoutRef = useRef<DialogRef>(null);
const updateRef = useRef<DialogRef>(null);
const backupRef = useRef<DialogRef>(null);
const onCheckUpdate = async () => {
try {
const info = await checkUpdate();
if (!info?.available) {
Notice.success(t("Currently on the Latest Version"));
} else {
updateRef.current?.open();
}
} catch (err: any) {
Notice.error(err.message || err.toString());
}
};
return (
<SettingList title={t("Verge Advanced Setting")}>
<ThemeViewer ref={themeRef} />
<ConfigViewer ref={configRef} />
<HotkeyViewer ref={hotkeyRef} />
<MiscViewer ref={miscRef} />
<LayoutViewer ref={layoutRef} />
<UpdateViewer ref={updateRef} />
<BackupViewer ref={backupRef} />
<SettingItem
onClick={() => backupRef.current?.open()}
label={t("Backup Setting")}
extra={
<TooltipIcon
title={t("Backup Setting Info")}
sx={{ opacity: "0.7" }}
/>
}
/>
<SettingItem
onClick={() => configRef.current?.open()}
label={t("Runtime Config")}
/>
<SettingItem
onClick={openAppDir}
label={t("Open Conf Dir")}
extra={
<TooltipIcon
title={t("Open Conf Dir Info")}
sx={{ opacity: "0.7" }}
/>
}
/>
<SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />
<SettingItem onClick={openLogsDir} label={t("Open Logs Dir")} />
<SettingItem onClick={onCheckUpdate} label={t("Check for Updates")} />
<SettingItem onClick={openDevTools} label={t("Open Dev Tools")} />
<SettingItem
label={t("Lite Mode")}
extra={
<TooltipIcon title={t("Lite Mode Info")} sx={{ opacity: "0.7" }} />
}
onClick={() => patchVerge({ enable_lite_mode: true })}
/>
<SettingItem
onClick={() => {
exitApp();
}}
label={t("Exit")}
/>
<SettingItem label={t("Verge Version")}>
<Typography sx={{ py: "7px", pr: 1 }}>v{version}</Typography>
</SettingItem>
</SettingList>
);
};
export default SettingVergeAdvanced;

View File

@@ -1,18 +1,9 @@
import { useCallback, useRef } from "react"; import { useCallback, useRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { open } from "@tauri-apps/plugin-dialog"; import { open } from "@tauri-apps/plugin-dialog";
import { Button, MenuItem, Select, Input, Typography } from "@mui/material"; import { Button, MenuItem, Select, Input } from "@mui/material";
import { import { copyClashEnv } from "@/services/cmds";
exitApp,
openAppDir,
openCoreDir,
openLogsDir,
openDevTools,
copyClashEnv,
} from "@/services/cmds";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { version } from "@root/package.json";
import { DialogRef, Notice } from "@/components/base"; import { DialogRef, Notice } from "@/components/base";
import { SettingList, SettingItem } from "./mods/setting-comp"; import { SettingList, SettingItem } from "./mods/setting-comp";
import { ThemeModeSwitch } from "./mods/theme-mode-switch"; import { ThemeModeSwitch } from "./mods/theme-mode-switch";
@@ -49,7 +40,7 @@ const languageOptions = Object.entries(languages).map(([code, _]) => {
return { code, label: labels[code] }; return { code, label: labels[code] };
}); });
const SettingVerge = ({ onError }: Props) => { const SettingVergeBasic = ({ onError }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge(); const { verge, patchVerge, mutateVerge } = useVerge();
@@ -60,7 +51,6 @@ const SettingVerge = ({ onError }: Props) => {
env_type, env_type,
startup_script, startup_script,
start_page, start_page,
enable_lite_mode,
} = verge ?? {}; } = verge ?? {};
const configRef = useRef<DialogRef>(null); const configRef = useRef<DialogRef>(null);
const hotkeyRef = useRef<DialogRef>(null); const hotkeyRef = useRef<DialogRef>(null);
@@ -74,26 +64,13 @@ const SettingVerge = ({ onError }: Props) => {
mutateVerge({ ...verge, ...patch }, false); mutateVerge({ ...verge, ...patch }, false);
}; };
const onCheckUpdate = async () => {
try {
const info = await checkUpdate();
if (!info?.available) {
Notice.success(t("Currently on the Latest Version"));
} else {
updateRef.current?.open();
}
} catch (err: any) {
Notice.error(err.message || err.toString());
}
};
const onCopyClashEnv = useCallback(async () => { const onCopyClashEnv = useCallback(async () => {
await copyClashEnv(); await copyClashEnv();
Notice.success(t("Copy Success"), 1000); Notice.success(t("Copy Success"), 1000);
}, []); }, []);
return ( return (
<SettingList title={t("Verge Setting")}> <SettingList title={t("Verge Basic Setting")}>
<ThemeViewer ref={themeRef} /> <ThemeViewer ref={themeRef} />
<ConfigViewer ref={configRef} /> <ConfigViewer ref={configRef} />
<HotkeyViewer ref={hotkeyRef} /> <HotkeyViewer ref={hotkeyRef} />
@@ -261,62 +238,8 @@ const SettingVerge = ({ onError }: Props) => {
onClick={() => hotkeyRef.current?.open()} onClick={() => hotkeyRef.current?.open()}
label={t("Hotkey Setting")} label={t("Hotkey Setting")}
/> />
<SettingItem
onClick={() => backupRef.current?.open()}
label={t("Backup Setting")}
extra={
<TooltipIcon
title={t("Backup Setting Info")}
sx={{ opacity: "0.7" }}
/>
}
/>
<SettingItem
onClick={() => configRef.current?.open()}
label={t("Runtime Config")}
/>
<SettingItem
onClick={openAppDir}
label={t("Open Conf Dir")}
extra={
<TooltipIcon
title={t("Open Conf Dir Info")}
sx={{ opacity: "0.7" }}
/>
}
/>
<SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />
<SettingItem onClick={openLogsDir} label={t("Open Logs Dir")} />
<SettingItem onClick={onCheckUpdate} label={t("Check for Updates")} />
<SettingItem onClick={openDevTools} label={t("Open Dev Tools")} />
<SettingItem
label={t("Lite Mode")}
extra={
<TooltipIcon title={t("Lite Mode Info")} sx={{ opacity: "0.7" }} />
}
onClick={() => patchVerge({ enable_lite_mode: true })}
/>
<SettingItem
onClick={() => {
exitApp();
}}
label={t("Exit")}
/>
<SettingItem label={t("Verge Version")}>
<Typography sx={{ py: "7px", pr: 1 }}>v{version}</Typography>
</SettingItem>
</SettingList> </SettingList>
); );
}; };
export default SettingVerge; export default SettingVergeBasic;

View File

@@ -447,5 +447,7 @@
"File Not Found": "الملف غير موجود، تم التراجع عن التغييرات", "File Not Found": "الملف غير موجود، تم التراجع عن التغييرات",
"Script File Error": "خطأ في ملف السكريبت، تم التراجع عن التغييرات", "Script File Error": "خطأ في ملف السكريبت، تم التراجع عن التغييرات",
"Core Changed Successfully": "تم تغيير النواة بنجاح", "Core Changed Successfully": "تم تغيير النواة بنجاح",
"Failed to Change Core": "فشل تغيير النواة" "Failed to Change Core": "فشل تغيير النواة",
"Verge Basic Setting": "الإعدادات الأساسية Verge",
"Verge Advanced Setting": "الإعدادات الأساسية Verge"
} }

View File

@@ -277,7 +277,8 @@
"Open UWP tool": "Open UWP tool", "Open UWP tool": "Open UWP tool",
"Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction", "Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
"Update GeoData": "Update GeoData", "Update GeoData": "Update GeoData",
"Verge Setting": "Verge Setting", "Verge Basic Setting": "Verge Basic Setting",
"Verge Advanced Setting": "Verge Advanced Setting",
"Language": "Language", "Language": "Language",
"Theme Mode": "Theme Mode", "Theme Mode": "Theme Mode",
"theme.light": "Light", "theme.light": "Light",
@@ -451,5 +452,18 @@
"File Not Found": "File missing, changes reverted", "File Not Found": "File missing, changes reverted",
"Script File Error": "Script file error, changes reverted", "Script File Error": "Script file error, changes reverted",
"Core Changed Successfully": "Core changed successfully", "Core Changed Successfully": "Core changed successfully",
"Failed to Change Core": "Failed to change core" "Failed to Change Core": "Failed to change core",
"YAML Syntax Error": "YAML syntax error, changes reverted",
"YAML Read Error": "YAML read error, changes reverted",
"YAML Mapping Error": "YAML mapping error, changes reverted",
"YAML Key Error": "YAML key error, changes reverted",
"YAML Error": "YAML error, changes reverted",
"Merge File Syntax Error": "Merge file syntax error, changes reverted",
"Merge File Mapping Error": "Merge file mapping error, changes reverted",
"Merge File Key Error": "Merge file key error, changes reverted",
"Merge File Error": "Merge file error, changes reverted",
"Validate YAML File": "Validate YAML File",
"Validate Merge File": "Validate Merge File",
"Validation Success": "Validation Success",
"Validation Failed": "Validation Failed"
} }

View File

@@ -444,5 +444,7 @@
"File Not Found": "فایل یافت نشد، تغییرات برگشت داده شد", "File Not Found": "فایل یافت نشد، تغییرات برگشت داده شد",
"Script File Error": "خطای فایل اسکریپت، تغییرات برگشت داده شد", "Script File Error": "خطای فایل اسکریپت، تغییرات برگشت داده شد",
"Core Changed Successfully": "هسته با موفقیت تغییر کرد", "Core Changed Successfully": "هسته با موفقیت تغییر کرد",
"Failed to Change Core": "تغییر هسته ناموفق بود" "Failed to Change Core": "تغییر هسته ناموفق بود",
"Verge Basic Setting": "تنظیمات پایه Verge",
"Verge Advanced Setting": "تنظیمات پیشرفته Verge"
} }

View File

@@ -443,5 +443,7 @@
"File Not Found": "File tidak ditemukan, perubahan dibatalkan", "File Not Found": "File tidak ditemukan, perubahan dibatalkan",
"Script File Error": "Kesalahan file skrip, perubahan dibatalkan", "Script File Error": "Kesalahan file skrip, perubahan dibatalkan",
"Core Changed Successfully": "Inti berhasil diubah", "Core Changed Successfully": "Inti berhasil diubah",
"Failed to Change Core": "Gagal mengubah inti" "Failed to Change Core": "Gagal mengubah inti",
"Verge Basic Setting": "Pengaturan Dasar Verge",
"Verge Advanced Setting": "Pengaturan Lanjutan Verge"
} }

View File

@@ -444,5 +444,7 @@
"File Not Found": "Файл не найден, изменения отменены", "File Not Found": "Файл не найден, изменения отменены",
"Script File Error": "Ошибка файла скрипта, изменения отменены", "Script File Error": "Ошибка файла скрипта, изменения отменены",
"Core Changed Successfully": "Ядро успешно сменено", "Core Changed Successfully": "Ядро успешно сменено",
"Failed to Change Core": "Не удалось сменить ядро" "Failed to Change Core": "Не удалось сменить ядро",
"Verge Basic Setting": "Основные настройки Verge",
"Verge Advanced Setting": "Расширенные настройки Verge"
} }

View File

@@ -443,5 +443,7 @@
"File Not Found": "Файл табылмады, үзгәрешләр кире кайтарылды", "File Not Found": "Файл табылмады, үзгәрешләр кире кайтарылды",
"Script File Error": "Скрипт файлы хатасы, үзгәрешләр кире кайтарылды", "Script File Error": "Скрипт файлы хатасы, үзгәрешләр кире кайтарылды",
"Core Changed Successfully": "Ядро уңышлы алыштырылды", "Core Changed Successfully": "Ядро уңышлы алыштырылды",
"Failed to Change Core": "Ядро алыштыру уңышсыз булды" "Failed to Change Core": "Ядро алыштыру уңышсыз булды",
"Verge Basic Setting": "Verge Төп көйләүләр",
"Verge Advanced Setting": "Verge Киңәйтелгән көйләүләр"
} }

View File

@@ -135,9 +135,9 @@
"Hidden": "隐藏代理组", "Hidden": "隐藏代理组",
"Group Name Required": "代理组名称不能为空", "Group Name Required": "代理组名称不能为空",
"Group Name Already Exists": "代理组名称已存在", "Group Name Already Exists": "代理组名称已存在",
"Extend Config": "扩展配置", "Extend Config": "扩展覆写配置",
"Extend Script": "扩展脚本", "Extend Script": "扩展脚本",
"Global Merge": "全局扩展配置", "Global Merge": "全局扩展覆写配置",
"Global Script": "全局扩展脚本", "Global Script": "全局扩展脚本",
"Type": "类型", "Type": "类型",
"Name": "名称", "Name": "名称",
@@ -444,5 +444,20 @@
"File Not Found": "文件丢失,变更已撤销", "File Not Found": "文件丢失,变更已撤销",
"Script File Error": "脚本文件错误,变更已撤销", "Script File Error": "脚本文件错误,变更已撤销",
"Core Changed Successfully": "内核切换成功", "Core Changed Successfully": "内核切换成功",
"Failed to Change Core": "无法切换内核" "Failed to Change Core": "无法切换内核",
"YAML Syntax Error": "YAML语法错误变更已撤销",
"YAML Read Error": "YAML读取错误变更已撤销",
"YAML Mapping Error": "YAML映射错误变更已撤销",
"YAML Key Error": "YAML键错误变更已撤销",
"YAML Error": "YAML错误变更已撤销",
"Merge File Syntax Error": "覆写文件语法错误,变更已撤销",
"Merge File Mapping Error": "覆写文件映射错误,变更已撤销",
"Merge File Key Error": "覆写文件键错误,变更已撤销",
"Merge File Error": "覆写文件错误,变更已撤销",
"Validate YAML File": "验证YAML文件",
"Validate Merge File": "验证覆写文件",
"Validation Success": "验证成功",
"Validation Failed": "验证失败",
"Verge Basic Setting": "Verge 基础设置",
"Verge Advanced Setting": "Verge 高级设置"
} }

View File

@@ -84,6 +84,33 @@ const handleNoticeMessage = (
case "config_validate::file_not_found": case "config_validate::file_not_found":
Notice.error(`${t("File Not Found")} ${msg}`); Notice.error(`${t("File Not Found")} ${msg}`);
break; break;
case "config_validate::yaml_syntax_error":
Notice.error(`${t("YAML Syntax Error")} ${msg}`);
break;
case "config_validate::yaml_read_error":
Notice.error(`${t("YAML Read Error")} ${msg}`);
break;
case "config_validate::yaml_mapping_error":
Notice.error(`${t("YAML Mapping Error")} ${msg}`);
break;
case "config_validate::yaml_key_error":
Notice.error(`${t("YAML Key Error")} ${msg}`);
break;
case "config_validate::yaml_error":
Notice.error(`${t("YAML Error")} ${msg}`);
break;
case "config_validate::merge_syntax_error":
Notice.error(`${t("Merge File Syntax Error")} ${msg}`);
break;
case "config_validate::merge_mapping_error":
Notice.error(`${t("Merge File Mapping Error")} ${msg}`);
break;
case "config_validate::merge_key_error":
Notice.error(`${t("Merge File Key Error")} ${msg}`);
break;
case "config_validate::merge_error":
Notice.error(`${t("Merge File Error")} ${msg}`);
break;
case "config_core::change_success": case "config_core::change_success":
Notice.success(`${t("Core Changed Successfully")}: ${msg}`); Notice.success(`${t("Core Changed Successfully")}: ${msg}`);
break; break;
@@ -202,12 +229,14 @@ const Layout = () => {
}} }}
sx={[ sx={[
({ palette }) => ({ bgcolor: palette.background.paper }), ({ palette }) => ({ bgcolor: palette.background.paper }),
{ OS === "linux"
borderRadius: "8px", ? {
border: "0px solid var(--divider-color)", borderRadius: "8px",
width: "calc(100vw - 1px)", border: "1px solid var(--divider-color)",
height: "calc(100vh - 1px)", width: "calc(100vw - 0px)",
}, height: "calc(100vh - 0px)",
}
: {},
]} ]}
> >
<div className="layout__left"> <div className="layout__left">

View File

@@ -11,7 +11,8 @@ import { useTranslation } from "react-i18next";
import { BasePage, Notice } from "@/components/base"; import { BasePage, Notice } from "@/components/base";
import { GitHub, HelpOutlineRounded, Telegram } from "@mui/icons-material"; import { GitHub, HelpOutlineRounded, Telegram } from "@mui/icons-material";
import { openWebUrl } from "@/services/cmds"; import { openWebUrl } from "@/services/cmds";
import SettingVerge from "@/components/setting/setting-verge"; import SettingVergeBasic from "@/components/setting/setting-verge-basic";
import SettingVergeAdvanced from "@/components/setting/setting-verge-advanced";
import SettingClash from "@/components/setting/setting-clash"; import SettingClash from "@/components/setting/setting-clash";
import SettingSystem from "@/components/setting/setting-system"; import SettingSystem from "@/components/setting/setting-system";
import { useThemeMode } from "@/services/states"; import { useThemeMode } from "@/services/states";
@@ -95,10 +96,19 @@ const SettingPage = () => {
<Box <Box
sx={{ sx={{
borderRadius: 2, borderRadius: 2,
marginBottom: 1.5,
backgroundColor: isDark ? "#282a36" : "#ffffff", backgroundColor: isDark ? "#282a36" : "#ffffff",
}} }}
> >
<SettingVerge onError={onError} /> <SettingVergeBasic onError={onError} />
</Box>
<Box
sx={{
borderRadius: 2,
backgroundColor: isDark ? "#282a36" : "#ffffff",
}}
>
<SettingVergeAdvanced onError={onError} />
</Box> </Box>
</Grid> </Grid>
</Grid> </Grid>