Compare commits
6 Commits
l10n_dev
...
fix/downgr
@@ -36,6 +36,7 @@
|
||||
- 修复 macOS 从 Dock 栏退出轻量模式状态不同步
|
||||
- 修复 Linux 系统主题切换不生效
|
||||
- 修复 `允许自动更新` 字段使手动订阅刷新失效
|
||||
- 修复连接界面长时间显示后报错问题
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
@@ -85,6 +86,7 @@
|
||||
- 添加热键绑定错误的提示信息
|
||||
- 在 macOS 10.15 及更高版本默认包含 Mihomo-go122,以解决 Intel 架构 Mac 无法运行内核的问题
|
||||
- Tun 模式不可用时,禁用系统托盘的 Tun 模式菜单
|
||||
- 改进订阅更新方式,仍失败需打开订阅设置 `允许危险证书`
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@mui/icons-material": "^7.3.4",
|
||||
"@mui/lab": "7.0.0-beta.17",
|
||||
"@mui/material": "^7.3.4",
|
||||
"@mui/x-data-grid": "^8.16.0",
|
||||
"@mui/x-data-grid": "^7.29.9",
|
||||
"@tauri-apps/api": "2.9.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
|
||||
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@@ -36,8 +36,8 @@ importers:
|
||||
specifier: ^7.3.4
|
||||
version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@mui/x-data-grid':
|
||||
specifier: ^8.16.0
|
||||
version: 8.16.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
specifier: ^7.29.9
|
||||
version: 7.29.9(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@tauri-apps/api':
|
||||
specifier: 2.9.0
|
||||
version: 2.9.0
|
||||
@@ -1239,8 +1239,8 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/x-data-grid@8.16.0':
|
||||
resolution: {integrity: sha512-yJ+v+E1yI1HxrEUdOfgrUTCxobAFvotGggU6cy6MnM7c7/TPPg9d5mDzjzxb0imOCJ6WyiM/vtd5WKbY/5sUNw==}
|
||||
'@mui/x-data-grid@7.29.9':
|
||||
resolution: {integrity: sha512-RfK7Fnuu4eyv/4eD3MEB1xxZsx0xRBsofb1kifghIjyQV1EKAeRcwvczyrzQggj7ZRT5AqkwCzhLsZDvE5O0nQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.9.0
|
||||
@@ -1255,19 +1255,12 @@ packages:
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@mui/x-internals@8.16.0':
|
||||
resolution: {integrity: sha512-JR53WOFqmQYQzurOpB0H91K7/9uMcte1ooxHxTLGB+97PgB+rKY6siRWvUALGS56XyPV+1a2ALI33hd2E7+Rgg==}
|
||||
'@mui/x-internals@7.29.0':
|
||||
resolution: {integrity: sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@mui/x-virtualizer@0.2.6':
|
||||
resolution: {integrity: sha512-t45EHhD9kStSwIYMkqYYQIFbZNVQws9LRANktf0e/+j+MxsRTFk41r0rgiazMSOSugJlCuSh/H8xUUuMCZdtow==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
@@ -5481,18 +5474,18 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.2
|
||||
|
||||
'@mui/x-data-grid@8.16.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
'@mui/x-data-grid@7.29.9(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)
|
||||
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0)
|
||||
'@mui/x-internals': 8.16.0(@types/react@19.2.2)(react@19.2.0)
|
||||
'@mui/x-virtualizer': 0.2.6(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@mui/x-internals': 7.29.0(@types/react@19.2.2)(react@19.2.0)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.6.0(react@19.2.0)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0)
|
||||
@@ -5500,23 +5493,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@mui/x-internals@8.16.0(@types/react@19.2.2)(react@19.2.0)':
|
||||
'@mui/x-internals@7.29.0(@types/react@19.2.2)(react@19.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0)
|
||||
react: 19.2.0
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.6.0(react@19.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@mui/x-virtualizer@0.2.6(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0)
|
||||
'@mui/x-internals': 8.16.0(@types/react@19.2.2)(react@19.2.0)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
"description": "Group all GitHub Actions updates into a single PR",
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "github actions"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["@mui/x-data-grid"],
|
||||
"matchCurrentVersion": "<8.0.0"
|
||||
}
|
||||
],
|
||||
"postUpdateOptions": ["pnpmDedupe", "updateCargoLock"],
|
||||
|
||||
@@ -570,14 +570,16 @@ pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> Cm
|
||||
pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
// 保存修改前检查是否有更新 update_interval
|
||||
let profiles = Config::profiles().await;
|
||||
let should_refresh_timer = if let Ok(old_profile) = profiles.latest_ref().get_item(&index) {
|
||||
let should_refresh_timer = if let Ok(old_profile) = profiles.latest_ref().get_item(&index)
|
||||
&& let Some(new_option) = profile.option.as_ref()
|
||||
{
|
||||
let old_interval = old_profile.option.as_ref().and_then(|o| o.update_interval);
|
||||
let new_interval = profile.option.as_ref().and_then(|o| o.update_interval);
|
||||
let new_interval = new_option.update_interval;
|
||||
let old_allow_auto_update = old_profile
|
||||
.option
|
||||
.as_ref()
|
||||
.and_then(|o| o.allow_auto_update);
|
||||
let new_allow_auto_update = profile.option.as_ref().and_then(|o| o.allow_auto_update);
|
||||
let new_allow_auto_update = new_option.allow_auto_update;
|
||||
(old_interval != new_interval) || (old_allow_auto_update != new_allow_auto_update)
|
||||
} else {
|
||||
false
|
||||
@@ -589,14 +591,13 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
|
||||
// 如果更新间隔或允许自动更新变更,异步刷新定时器
|
||||
if should_refresh_timer {
|
||||
let index_clone = index.clone();
|
||||
crate::process::AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Timer, "定时器更新间隔已变更,正在刷新定时器...");
|
||||
if let Err(e) = crate::core::Timer::global().refresh().await {
|
||||
logging!(error, Type::Timer, "刷新定时器失败: {}", e);
|
||||
} else {
|
||||
// 刷新成功后发送自定义事件,不触发配置重载
|
||||
crate::core::handle::Handle::notify_timer_updated(index_clone);
|
||||
crate::core::handle::Handle::notify_timer_updated(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use sysproxy::{Autoproxy, Sysproxy};
|
||||
use tauri_plugin_autostart::ManagerExt;
|
||||
|
||||
pub struct Sysopt {
|
||||
initialed: AtomicBool,
|
||||
update_sysproxy: AtomicBool,
|
||||
reset_sysproxy: AtomicBool,
|
||||
}
|
||||
@@ -84,6 +85,7 @@ async fn execute_sysproxy_command(args: Vec<std::string::String>) -> Result<()>
|
||||
impl Default for Sysopt {
|
||||
fn default() -> Self {
|
||||
Sysopt {
|
||||
initialed: AtomicBool::new(false),
|
||||
update_sysproxy: AtomicBool::new(false),
|
||||
reset_sysproxy: AtomicBool::new(false),
|
||||
}
|
||||
@@ -94,6 +96,10 @@ impl Default for Sysopt {
|
||||
singleton_lazy!(Sysopt, SYSOPT, Sysopt::default);
|
||||
|
||||
impl Sysopt {
|
||||
pub fn is_initialed(&self) -> bool {
|
||||
self.initialed.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn init_guard_sysproxy(&self) -> Result<()> {
|
||||
// 使用事件驱动代理管理器
|
||||
let proxy_manager = EventDrivenProxyManager::global();
|
||||
@@ -105,6 +111,7 @@ impl Sysopt {
|
||||
|
||||
/// init the sysproxy
|
||||
pub async fn update_sysproxy(&self) -> Result<()> {
|
||||
self.initialed.store(true, Ordering::SeqCst);
|
||||
if self
|
||||
.update_sysproxy
|
||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{config::Config, feat, logging, logging_error, singleton, utils::logging::Type};
|
||||
use crate::{
|
||||
config::Config, core::sysopt::Sysopt, feat, logging, logging_error, singleton,
|
||||
utils::logging::Type,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
|
||||
use parking_lot::RwLock;
|
||||
@@ -10,7 +13,9 @@ use std::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::time::{sleep, timeout};
|
||||
|
||||
type TaskID = u64;
|
||||
|
||||
@@ -390,6 +395,7 @@ impl Timer {
|
||||
.spawn_async_routine(move || {
|
||||
let uid = uid.clone();
|
||||
Box::pin(async move {
|
||||
Self::wait_until_sysopt(Duration::from_millis(1000)).await;
|
||||
Self::async_task(&uid).await;
|
||||
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send>>
|
||||
})
|
||||
@@ -519,6 +525,16 @@ impl Timer {
|
||||
// Emit completed event
|
||||
Self::emit_update_event(uid, false);
|
||||
}
|
||||
|
||||
async fn wait_until_sysopt(max_wait: Duration) {
|
||||
let _ = timeout(max_wait, async {
|
||||
while !Sysopt::global().is_initialed() {
|
||||
logging!(warn, Type::Timer, "Waiting for Sysopt to be initialized...");
|
||||
sleep(Duration::from_millis(30)).await;
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -308,7 +308,6 @@ impl Tray {
|
||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||
let tun_mode_available = cmd::system::is_admin().unwrap_or_default()
|
||||
|| service::is_service_available().await.is_ok();
|
||||
println!("tun_mode_available: {}", tun_mode_available);
|
||||
let mode = {
|
||||
Config::clash()
|
||||
.await
|
||||
|
||||
@@ -86,20 +86,25 @@ async fn perform_profile_update(
|
||||
option: Option<&PrfOption>,
|
||||
) -> Result<bool> {
|
||||
logging!(info, Type::Config, "[订阅更新] 开始下载新的订阅内容");
|
||||
let merged_opt = PrfOption::merge(opt, option);
|
||||
let mut merged_opt = PrfOption::merge(opt, option);
|
||||
let is_current = {
|
||||
let profiles = Config::profiles().await;
|
||||
profiles.latest_ref().is_current_profile_index(uid)
|
||||
};
|
||||
let profile_name = {
|
||||
let profiles = Config::profiles().await;
|
||||
profiles
|
||||
.latest_ref()
|
||||
.get_name_by_uid(uid)
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let mut last_err;
|
||||
|
||||
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
|
||||
Ok(mut item) => {
|
||||
logging!(info, Type::Config, "[订阅更新] 更新订阅配置成功");
|
||||
let profiles = Config::profiles().await;
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
let is_current = Some(uid.clone()) == profiles.latest_ref().get_current();
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"[订阅更新] 是否为当前使用的订阅: {is_current}"
|
||||
);
|
||||
Ok(is_current)
|
||||
return Ok(is_current);
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(
|
||||
@@ -107,57 +112,65 @@ async fn perform_profile_update(
|
||||
Type::Config,
|
||||
"Warning: [订阅更新] 正常更新失败: {err},尝试使用Clash代理更新"
|
||||
);
|
||||
|
||||
let original_with_proxy = merged_opt.as_ref().and_then(|o| o.with_proxy);
|
||||
let original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy);
|
||||
|
||||
let mut fallback_opt = merged_opt.unwrap_or_default();
|
||||
fallback_opt.with_proxy = Some(false);
|
||||
fallback_opt.self_proxy = Some(true);
|
||||
|
||||
match PrfItem::from_url(url, None, None, Some(&fallback_opt)).await {
|
||||
Ok(mut item) => {
|
||||
logging!(info, Type::Config, "[订阅更新] 使用Clash代理更新成功");
|
||||
|
||||
if let Some(option) = item.option.as_mut() {
|
||||
option.with_proxy = original_with_proxy;
|
||||
option.self_proxy = original_self_proxy;
|
||||
}
|
||||
|
||||
let profiles = Config::profiles().await;
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
|
||||
let profile_name = item.name.clone().unwrap_or_else(|| uid.clone());
|
||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||
|
||||
let is_current = Some(uid.clone()) == profiles.latest_ref().get_current();
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"[订阅更新] 是否为当前使用的订阅: {is_current}"
|
||||
);
|
||||
Ok(is_current)
|
||||
}
|
||||
Err(retry_err) => {
|
||||
let failed_profile_name = Config::profiles()
|
||||
.await
|
||||
.latest_ref()
|
||||
.get_name_by_uid(uid)
|
||||
.unwrap_or_default();
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
"[订阅更新] 使用Clash代理更新仍然失败: {failed_profile_name} - {retry_err}"
|
||||
);
|
||||
handle::Handle::notice_message(
|
||||
"update_failed_even_with_clash",
|
||||
format!("{failed_profile_name} - {retry_err}"),
|
||||
);
|
||||
Err(retry_err)
|
||||
}
|
||||
}
|
||||
last_err = err;
|
||||
}
|
||||
}
|
||||
|
||||
merged_opt.get_or_insert_with(PrfOption::default).self_proxy = Some(true);
|
||||
merged_opt.get_or_insert_with(PrfOption::default).with_proxy = Some(false);
|
||||
|
||||
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
|
||||
Ok(mut item) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"[订阅更新] 使用 Clash代理 更新订阅配置成功"
|
||||
);
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||
drop(last_err);
|
||||
return Ok(is_current);
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Warning: [订阅更新] 正常更新失败: {err},尝试使用Clash代理更新"
|
||||
);
|
||||
last_err = err;
|
||||
}
|
||||
}
|
||||
|
||||
merged_opt.get_or_insert_with(PrfOption::default).self_proxy = Some(false);
|
||||
merged_opt.get_or_insert_with(PrfOption::default).with_proxy = Some(true);
|
||||
|
||||
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
|
||||
Ok(mut item) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"[订阅更新] 使用 系统代理 更新订阅配置成功"
|
||||
);
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||
drop(last_err);
|
||||
return Ok(is_current);
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Warning: [订阅更新] 正常更新失败: {err},尝试使用系统代理更新"
|
||||
);
|
||||
last_err = err;
|
||||
}
|
||||
}
|
||||
|
||||
handle::Handle::notice_message(
|
||||
"update_failed_even_with_clash",
|
||||
format!("{profile_name} - {last_err}"),
|
||||
);
|
||||
Ok(is_current)
|
||||
}
|
||||
|
||||
pub async fn update_profile(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use isahc::config::DnsCache;
|
||||
use isahc::prelude::*;
|
||||
use isahc::{HttpClient, config::SslOption};
|
||||
use isahc::{
|
||||
@@ -143,6 +144,12 @@ impl NetworkManager {
|
||||
|
||||
builder = builder.redirect_policy(RedirectPolicy::Follow);
|
||||
|
||||
// 禁用缓存,不关心连接复用
|
||||
builder = builder.connection_cache_size(0);
|
||||
|
||||
// 禁用 DNS 缓存,避免因 DNS 变化导致的问题
|
||||
builder = builder.dns_cache(DnsCache::Disable);
|
||||
|
||||
Ok(builder.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ErrorBoundary, FallbackProps } from "react-error-boundary";
|
||||
|
||||
function ErrorFallback({ error }: FallbackProps) {
|
||||
return (
|
||||
<div role="alert" style={{ padding: 16 }}>
|
||||
<div role="alert" style={{ padding: 16, height: "100%", overflow: "auto" }}>
|
||||
<h4>Something went wrong:(</h4>
|
||||
|
||||
<pre>{error.message}</pre>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import {
|
||||
DataGrid,
|
||||
GridActionsCellItem,
|
||||
GridCloseIcon,
|
||||
GridColDef,
|
||||
GridColumnResizeParams,
|
||||
useGridApiRef,
|
||||
} from "@mui/x-data-grid";
|
||||
import dayjs from "dayjs";
|
||||
import { useLocalStorage } from "foxact/use-local-storage";
|
||||
import { useLayoutEffect, useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { closeConnections } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
import { truncateStr } from "@/utils/truncate-str";
|
||||
@@ -21,129 +24,6 @@ export const ConnectionTable = (props: Props) => {
|
||||
const { connections, onShowDetail } = props;
|
||||
const { t } = useTranslation();
|
||||
const apiRef = useGridApiRef();
|
||||
useLayoutEffect(() => {
|
||||
const PATCH_FLAG_KEY = "__clashPatchedPublishEvent" as const;
|
||||
const ORIGINAL_KEY = "__clashOriginalPublishEvent" as const;
|
||||
let isUnmounted = false;
|
||||
let retryHandle: ReturnType<typeof setTimeout> | null = null;
|
||||
let cleanupOriginal: (() => void) | null = null;
|
||||
|
||||
const scheduleRetry = () => {
|
||||
if (isUnmounted || retryHandle !== null) return;
|
||||
retryHandle = setTimeout(() => {
|
||||
retryHandle = null;
|
||||
ensurePatched();
|
||||
}, 16);
|
||||
};
|
||||
|
||||
// Safari occasionally emits grid events without an event object,
|
||||
// and MUI expects `defaultMuiPrevented` to exist. Normalize here to avoid crashes.
|
||||
const createFallbackEvent = () => {
|
||||
const fallback = {
|
||||
defaultMuiPrevented: false,
|
||||
preventDefault() {
|
||||
fallback.defaultMuiPrevented = true;
|
||||
},
|
||||
};
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const ensureMuiEvent = (
|
||||
value: unknown,
|
||||
): {
|
||||
defaultMuiPrevented: boolean;
|
||||
preventDefault: () => void;
|
||||
[key: string]: unknown;
|
||||
} => {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return createFallbackEvent();
|
||||
}
|
||||
|
||||
const eventObject = value as {
|
||||
defaultMuiPrevented?: unknown;
|
||||
preventDefault?: () => void;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
if (typeof eventObject.defaultMuiPrevented !== "boolean") {
|
||||
eventObject.defaultMuiPrevented = false;
|
||||
}
|
||||
|
||||
if (typeof eventObject.preventDefault !== "function") {
|
||||
eventObject.preventDefault = () => {
|
||||
eventObject.defaultMuiPrevented = true;
|
||||
};
|
||||
}
|
||||
|
||||
return eventObject as {
|
||||
defaultMuiPrevented: boolean;
|
||||
preventDefault: () => void;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
const ensurePatched = () => {
|
||||
if (isUnmounted) return;
|
||||
const api = apiRef.current;
|
||||
|
||||
if (!api?.publishEvent) {
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
const metadataApi = api as unknown as typeof api &
|
||||
Record<string, unknown>;
|
||||
if (metadataApi[PATCH_FLAG_KEY] === true) return;
|
||||
|
||||
const originalPublishEvent = api.publishEvent;
|
||||
|
||||
// Use Proxy to create a more resilient wrapper that always normalizes events
|
||||
const patchedPublishEvent = new Proxy(originalPublishEvent, {
|
||||
apply(target, thisArg, rawArgs: unknown[]) {
|
||||
rawArgs[2] = ensureMuiEvent(rawArgs[2]);
|
||||
|
||||
return Reflect.apply(
|
||||
target as (...args: unknown[]) => unknown,
|
||||
thisArg,
|
||||
rawArgs,
|
||||
);
|
||||
},
|
||||
}) as typeof originalPublishEvent;
|
||||
|
||||
api.publishEvent = patchedPublishEvent;
|
||||
metadataApi[PATCH_FLAG_KEY] = true;
|
||||
metadataApi[ORIGINAL_KEY] = originalPublishEvent;
|
||||
|
||||
cleanupOriginal = () => {
|
||||
const storedOriginal = metadataApi[ORIGINAL_KEY] as
|
||||
| typeof originalPublishEvent
|
||||
| undefined;
|
||||
|
||||
api.publishEvent = (
|
||||
typeof storedOriginal === "function"
|
||||
? storedOriginal
|
||||
: originalPublishEvent
|
||||
) as typeof originalPublishEvent;
|
||||
|
||||
delete metadataApi[PATCH_FLAG_KEY];
|
||||
delete metadataApi[ORIGINAL_KEY];
|
||||
};
|
||||
};
|
||||
|
||||
ensurePatched();
|
||||
|
||||
return () => {
|
||||
isUnmounted = true;
|
||||
if (retryHandle !== null) {
|
||||
clearTimeout(retryHandle);
|
||||
retryHandle = null;
|
||||
}
|
||||
if (cleanupOriginal) {
|
||||
cleanupOriginal();
|
||||
cleanupOriginal = null;
|
||||
}
|
||||
};
|
||||
}, [apiRef]);
|
||||
|
||||
const [columnVisible, setColumnVisible] = useState<
|
||||
Partial<Record<keyof IConnectionsItem, boolean>>
|
||||
@@ -160,6 +40,29 @@ export const ConnectionTable = (props: Props) => {
|
||||
|
||||
const columns = useMemo<GridColDef[]>(() => {
|
||||
return [
|
||||
{
|
||||
field: "actions",
|
||||
type: "actions",
|
||||
width: 30,
|
||||
className: "actions",
|
||||
getActions: ({ id }) => {
|
||||
return [
|
||||
<GridActionsCellItem
|
||||
key={id.toString()}
|
||||
icon={<GridCloseIcon />}
|
||||
label="Close"
|
||||
onClick={() => closeConnections(id.toString())}
|
||||
color="inherit"
|
||||
/>,
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "type",
|
||||
headerName: t("Type"),
|
||||
width: columnWidths["type"] || 100,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: "host",
|
||||
headerName: t("Host"),
|
||||
@@ -239,12 +142,6 @@ export const ConnectionTable = (props: Props) => {
|
||||
width: columnWidths["remoteDestination"] || 200,
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
field: "type",
|
||||
headerName: t("Type"),
|
||||
width: columnWidths["type"] || 160,
|
||||
minWidth: 100,
|
||||
},
|
||||
];
|
||||
}, [columnWidths, t]);
|
||||
|
||||
@@ -266,6 +163,7 @@ export const ConnectionTable = (props: Props) => {
|
||||
? `${metadata.destinationIP}:${metadata.destinationPort}`
|
||||
: `${metadata.remoteDestination}:${metadata.destinationPort}`;
|
||||
return {
|
||||
type: `${metadata.type}(${metadata.network})`,
|
||||
id: each.id,
|
||||
host: metadata.host
|
||||
? `${metadata.host}:${metadata.destinationPort}`
|
||||
@@ -280,7 +178,6 @@ export const ConnectionTable = (props: Props) => {
|
||||
time: each.start,
|
||||
source: `${metadata.sourceIP}:${metadata.sourcePort}`,
|
||||
remoteDestination: Destination,
|
||||
type: `${metadata.type}(${metadata.network})`,
|
||||
connectionData: each,
|
||||
};
|
||||
});
|
||||
@@ -289,7 +186,6 @@ export const ConnectionTable = (props: Props) => {
|
||||
return (
|
||||
<DataGrid
|
||||
apiRef={apiRef}
|
||||
hideFooter
|
||||
rows={connRows}
|
||||
columns={columns}
|
||||
onRowClick={(e) => onShowDetail(e.row.connectionData)}
|
||||
|
||||
@@ -62,7 +62,9 @@ export const useProfiles = () => {
|
||||
const patchCurrent = async (value: Partial<IProfileItem>) => {
|
||||
if (profiles?.current) {
|
||||
await patchProfile(profiles.current, value);
|
||||
mutateProfiles();
|
||||
if (!value.selected) {
|
||||
mutateProfiles();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ export const handleNoticeMessage = (
|
||||
showNotice("error", msg);
|
||||
},
|
||||
"set_config::error": () => showNotice("error", msg),
|
||||
// 后端暂时没有启用相关通知, 批量更新可能造成扰人提醒
|
||||
// update_success: () => showNotice("success", t("Update subscription successfully")),
|
||||
update_with_clash_proxy: () =>
|
||||
showNotice(
|
||||
"success",
|
||||
|
||||
Reference in New Issue
Block a user