mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-15 13:50:47 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a00c67e8ae | ||
|
|
6ebdaaa7ff | ||
|
|
15de400f2f | ||
|
|
6f36613103 |
@@ -17,3 +17,4 @@ CLAUDE.md
|
||||
.vfox.toml
|
||||
.vfox/
|
||||
.claude
|
||||
AGENTS.md
|
||||
|
||||
Generated
+44
-5
@@ -1150,7 +1150,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -1665,6 +1665,19 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
@@ -1693,6 +1706,15 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
@@ -3560,7 +3582,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
"windows-registry 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7785,7 +7807,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
"windows-registry 0.5.3",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
@@ -7899,12 +7921,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-mihomo"
|
||||
version = "0.1.9"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#a185fe1a8802367d0b3d50d402cb823759d2c57e"
|
||||
version = "0.1.5"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo?branch=revert#1cc80bc0fbe1245315617f4cecd93710a152325b"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"crossbeam",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http-body-util",
|
||||
"httparse",
|
||||
"hyper 1.9.0",
|
||||
"libc",
|
||||
"log",
|
||||
"pin-project",
|
||||
"rand 0.9.4",
|
||||
@@ -9803,6 +9831,17 @@ dependencies = [
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
||||
+4
-31
@@ -1,35 +1,8 @@
|
||||
## v2.5.0
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 关于版本的说明:Clash Verge 版本号遵循 x.y.z:x 为重大架构变更,y 为功能新增,z 为 Bug 修复。
|
||||
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.25**
|
||||
## v2.5.1
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复系统代理关闭后在 PAC 模式下未完全关闭
|
||||
- 修复 macOS 开关代理时可能的卡死
|
||||
- 修复修改定时自动更新后记时未及时刷新
|
||||
- 修复 Linux 关闭 TUN 不立即生效
|
||||
- 修复系统代理关闭序列逻辑(防止快速退出时系统代理关闭状态没有保存)
|
||||
- 修复 Linux 快捷键映射错误
|
||||
- 修复 Linux Wayland 兼容性导致渲染错误
|
||||
- 修复 Linux 边缘情况无法加载页面
|
||||
- 备份设置功能异常
|
||||
- 修复 Windows 节点交互异常
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
- 订阅 QR code 分享
|
||||
- 新增 macOS 托盘速率显示
|
||||
- 快捷键操作通知操作结果
|
||||
- 软件自动更新(后台下载,下次启动自动安装)
|
||||
|
||||
### 🚀 优化改进
|
||||
|
||||
- 优化 macOS 读取系统代理性能
|
||||
- 优化前端 CPU 性能
|
||||
- 更健壮的服务模式与边缘状况内核恢复
|
||||
- 优化白名单网络下的订阅 TLS 更新兼容性
|
||||
|
||||
### 👙 界面样式
|
||||
|
||||
- 代理组实现 Sticky Scroll 效果
|
||||
</details>
|
||||
|
||||
@@ -1,3 +1,40 @@
|
||||
## v2.5.0
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 关于版本的说明:Clash Verge 版本号遵循 x.y.z:x 为重大架构变更,y 为功能新增,z 为 Bug 修复。
|
||||
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.25**
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复系统代理关闭后在 PAC 模式下未完全关闭
|
||||
- 修复 macOS 开关代理时可能的卡死
|
||||
- 修复修改定时自动更新后记时未及时刷新
|
||||
- 修复 Linux 关闭 TUN 不立即生效
|
||||
- 修复系统代理关闭序列逻辑(防止快速退出时系统代理关闭状态没有保存)
|
||||
- 修复 Linux 快捷键映射错误
|
||||
- 修复 Linux Wayland 兼容性导致渲染错误
|
||||
- 修复 Linux 边缘情况无法加载页面
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
- 订阅 QR code 分享
|
||||
- 新增 macOS 托盘速率显示
|
||||
- 快捷键操作通知操作结果
|
||||
- 软件自动更新(后台下载,下次启动自动安装)
|
||||
|
||||
### 🚀 优化改进
|
||||
|
||||
- 优化 macOS 读取系统代理性能
|
||||
- 优化前端 CPU 性能
|
||||
- 更健壮的服务模式与边缘状况内核恢复
|
||||
- 优化白名单网络下的订阅 TLS 更新兼容性
|
||||
|
||||
### 👙 界面样式
|
||||
|
||||
- 代理组实现 Sticky Scroll 效果
|
||||
|
||||
|
||||
## v2.4.7
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
+1
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"prepare": "husky || true",
|
||||
@@ -42,7 +42,6 @@
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@mui/icons-material": "^9.0.0",
|
||||
"@mui/lab": "9.0.0-beta.2",
|
||||
"@mui/material": "^9.0.0",
|
||||
"@tanstack/react-query": "^5.96.1",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
|
||||
Generated
-40
@@ -32,9 +32,6 @@ importers:
|
||||
'@mui/icons-material':
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@mui/lab':
|
||||
specifier: 9.0.0-beta.2
|
||||
version: 9.0.0-beta.2(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@mui/material':
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
@@ -1068,27 +1065,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/lab@9.0.0-beta.2':
|
||||
resolution: {integrity: sha512-UqykXQCn7HNSExyYBvrFpaaEEmUwHbEgjFDBApEkawVPcBILyYNFhpXoqkwkafiZy+WsvcxIeRF0z4tCgisTRg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@mui/material': ^9.0.0
|
||||
'@mui/material-pigment-css': ^9.0.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@mui/material-pigment-css':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/material@9.0.0':
|
||||
resolution: {integrity: sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -4651,22 +4627,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/lab@9.0.0-beta.2(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/material': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@mui/system': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@mui/types': 9.0.0(@types/react@19.2.14)
|
||||
'@mui/utils': 9.0.0(@types/react@19.2.14)(react@19.2.5)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -95,7 +95,7 @@ backon = { version = "1.6.0", features = ["tokio-sleep"] }
|
||||
tauri-plugin-http = "2.5.7"
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
tauri-plugin-devtools = { version = "2.0.1" }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "revert" }
|
||||
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
|
||||
async-trait = "0.1.89"
|
||||
clash_verge_service_ipc = { version = "2.3.0", features = [
|
||||
|
||||
@@ -6,7 +6,7 @@ use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::async_runtime::JoinHandle;
|
||||
use tauri_plugin_mihomo::models::WebSocketConnectionId;
|
||||
use tauri_plugin_mihomo::models::ConnectionId;
|
||||
|
||||
/// 托盘速率流异常后的重连间隔。
|
||||
const TRAY_SPEED_RETRY_DELAY: Duration = Duration::from_secs(1);
|
||||
@@ -19,7 +19,7 @@ const TRAY_SPEED_STALE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
#[derive(Clone)]
|
||||
pub struct TraySpeedController {
|
||||
speed_task: Arc<Mutex<Option<JoinHandle<()>>>>,
|
||||
speed_connection_id: Arc<Mutex<Option<WebSocketConnectionId>>>,
|
||||
speed_connection_id: Arc<Mutex<Option<ConnectionId>>>,
|
||||
}
|
||||
|
||||
impl Default for TraySpeedController {
|
||||
@@ -162,19 +162,17 @@ impl TraySpeedController {
|
||||
}
|
||||
|
||||
fn set_speed_connection_id(
|
||||
speed_connection_id: &Arc<Mutex<Option<WebSocketConnectionId>>>,
|
||||
connection_id: Option<WebSocketConnectionId>,
|
||||
speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>,
|
||||
connection_id: Option<ConnectionId>,
|
||||
) {
|
||||
*speed_connection_id.lock() = connection_id;
|
||||
}
|
||||
|
||||
fn take_speed_connection_id(
|
||||
speed_connection_id: &Arc<Mutex<Option<WebSocketConnectionId>>>,
|
||||
) -> Option<WebSocketConnectionId> {
|
||||
fn take_speed_connection_id(speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>) -> Option<ConnectionId> {
|
||||
speed_connection_id.lock().take()
|
||||
}
|
||||
|
||||
async fn disconnect_speed_connection(speed_connection_id: &Arc<Mutex<Option<WebSocketConnectionId>>>) {
|
||||
async fn disconnect_speed_connection(speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>) {
|
||||
if let Some(connection_id) = Self::take_speed_connection_id(speed_connection_id) {
|
||||
connections_stream::disconnect_connection(connection_id).await;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use tauri::{AppHandle, Manager as _};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_deep_link::DeepLinkExt as _;
|
||||
use tauri_plugin_mihomo::RejectPolicy;
|
||||
|
||||
pub static APP_HANDLE: OnceCell<AppHandle> = OnceCell::new();
|
||||
/// Application initialization helper functions
|
||||
@@ -58,6 +59,15 @@ mod app_init {
|
||||
tauri_plugin_mihomo::Builder::new()
|
||||
.protocol(tauri_plugin_mihomo::models::Protocol::LocalSocket)
|
||||
.socket_path(crate::config::IClashTemp::guard_external_controller_ipc())
|
||||
.pool_config(
|
||||
tauri_plugin_mihomo::IpcPoolConfigBuilder::new()
|
||||
.min_connections(3)
|
||||
.max_connections(32)
|
||||
.idle_timeout(std::time::Duration::from_secs(60))
|
||||
.health_check_interval(std::time::Duration::from_secs(60))
|
||||
.reject_policy(RejectPolicy::Wait)
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
use tauri_plugin_mihomo::models::{WebSocketConnectionId, WebSocketMessage};
|
||||
use tauri_plugin_mihomo::models::{ConnectionId, WebSocketMessage};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::Instant;
|
||||
|
||||
@@ -39,7 +39,7 @@ enum InternalWsEvent<T> {
|
||||
/// Mihomo WebSocket 订阅句柄(通用事件流)。
|
||||
pub struct MihomoWsEventStream<T> {
|
||||
/// 当前订阅连接 ID,用于主动断开。
|
||||
pub connection_id: WebSocketConnectionId,
|
||||
pub connection_id: ConnectionId,
|
||||
/// 当前订阅消息接收器。
|
||||
receiver: mpsc::Receiver<InternalWsEvent<T>>,
|
||||
/// 最近一次收到有效事件的时间戳。
|
||||
@@ -162,7 +162,7 @@ impl<T> MihomoWsEventStream<T> {
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `connection_id` - 目标连接 ID
|
||||
pub async fn disconnect_connection(connection_id: WebSocketConnectionId) {
|
||||
pub async fn disconnect_connection(connection_id: ConnectionId) {
|
||||
if let Err(err) = handle::Handle::mihomo()
|
||||
.await
|
||||
.disconnect(connection_id, Some(MIHOMO_WS_STREAM_CLOSE_CODE))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"bundle": {
|
||||
"active": true,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { LoadingButton } from '@mui/lab'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -66,9 +65,9 @@ export const BaseDialog: React.FC<Props> = ({
|
||||
</Button>
|
||||
)}
|
||||
{!disableOk && (
|
||||
<LoadingButton loading={loading} variant="contained" onClick={onOk}>
|
||||
<Button loading={loading} variant="contained" onClick={onOk}>
|
||||
{okBtn}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
)}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@mui/material'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
title: string
|
||||
message: string
|
||||
onClose: () => void
|
||||
onConfirm: () => void
|
||||
}
|
||||
|
||||
export const ConfirmViewer = (props: Props) => {
|
||||
const { open, title, message, onClose, onConfirm } = props
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
}, [open])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ pb: 1, userSelect: 'text' }}>
|
||||
{message}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
{t('shared.actions.cancel')}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} variant="contained">
|
||||
{t('shared.actions.confirm')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import dayjs from 'dayjs'
|
||||
import { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { ConfirmViewer } from '@/components/profile/confirm-viewer'
|
||||
import { BaseDialog } from '@/components/base'
|
||||
import { EditorViewer } from '@/components/profile/editor-viewer'
|
||||
import { GroupsEditorViewer } from '@/components/profile/groups-editor-viewer'
|
||||
import { RulesEditorViewer } from '@/components/profile/rules-editor-viewer'
|
||||
@@ -990,16 +990,23 @@ export const ProfileItem = (props: Props) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConfirmViewer
|
||||
<BaseDialog
|
||||
title={t('profiles.modals.confirmDelete.title')}
|
||||
message={t('profiles.modals.confirmDelete.message')}
|
||||
open={confirmOpen}
|
||||
okBtn={t('shared.actions.confirm')}
|
||||
cancelBtn={t('shared.actions.cancel')}
|
||||
contentSx={{ width: { xs: 320, sm: 420 }, userSelect: 'text' }}
|
||||
onCancel={() => setConfirmOpen(false)}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
onOk={() => {
|
||||
onDelete()
|
||||
setConfirmOpen(false)
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Typography variant="body2" sx={{ wordBreak: 'break-word' }}>
|
||||
{t('profiles.modals.confirmDelete.message')}
|
||||
</Typography>
|
||||
</BaseDialog>
|
||||
{qrOpen && itemData.url && (
|
||||
<QrViewer
|
||||
open={true}
|
||||
|
||||
@@ -49,6 +49,11 @@ const DATE_FORMAT = 'YYYY-MM-DD_HH-mm-ss'
|
||||
const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/
|
||||
|
||||
type BackupSource = 'local' | 'webdav'
|
||||
type PendingConfirmation = {
|
||||
action: 'delete' | 'restore'
|
||||
filename: string
|
||||
source: BackupSource
|
||||
} | null
|
||||
|
||||
interface BackupHistoryViewerProps {
|
||||
open: boolean
|
||||
@@ -67,11 +72,6 @@ interface BackupRow {
|
||||
sort_value: number
|
||||
}
|
||||
|
||||
const confirmAsync = async (message: string) => {
|
||||
const fn = window.confirm as (msg?: string) => boolean
|
||||
return fn(message)
|
||||
}
|
||||
|
||||
export const BackupHistoryViewer = ({
|
||||
open,
|
||||
source,
|
||||
@@ -86,6 +86,9 @@ export const BackupHistoryViewer = ({
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isRestoring, setIsRestoring] = useState(false)
|
||||
const [isRestarting, setIsRestarting] = useState(false)
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
const [pendingConfirmation, setPendingConfirmation] =
|
||||
useState<PendingConfirmation>(null)
|
||||
const isLocal = source === 'local'
|
||||
const isWebDavConfigured = Boolean(
|
||||
verge?.webdav_url && verge?.webdav_username && verge?.webdav_password,
|
||||
@@ -94,7 +97,7 @@ export const BackupHistoryViewer = ({
|
||||
const webdavStatus = getWebdavStatus(webdavSignature)
|
||||
const shouldSkipWebDav = !isLocal && !isWebDavConfigured
|
||||
const pageSize = 8
|
||||
const isBusy = loading || isRestoring || isRestarting
|
||||
const isBusy = loading || isRestoring || isRestarting || isConfirming
|
||||
|
||||
const buildRow = useCallback(
|
||||
(item: ILocalBackupFile | IWebDavFile): BackupRow | null => {
|
||||
@@ -204,45 +207,54 @@ export const BackupHistoryViewer = ({
|
||||
})
|
||||
}, [isLocal, rows, shouldSkipWebDav, t, total, webdavStatus])
|
||||
|
||||
const handleDelete = useLockFn(async (filename: string) => {
|
||||
const handleDelete = (filename: string) => {
|
||||
if (isRestarting) return
|
||||
if (
|
||||
!(await confirmAsync(t('settings.modals.backup.messages.confirmDelete')))
|
||||
)
|
||||
return
|
||||
if (isLocal) {
|
||||
await deleteLocalBackup(filename)
|
||||
} else {
|
||||
await deleteWebdavBackup(filename)
|
||||
}
|
||||
await fetchRows()
|
||||
})
|
||||
setPendingConfirmation({ action: 'delete', filename, source })
|
||||
}
|
||||
|
||||
const handleRestore = useLockFn(async (filename: string) => {
|
||||
const handleRestore = (filename: string) => {
|
||||
if (isRestoring || isRestarting) return
|
||||
if (
|
||||
!(await confirmAsync(t('settings.modals.backup.messages.confirmRestore')))
|
||||
)
|
||||
return
|
||||
setIsRestoring(true)
|
||||
setPendingConfirmation({ action: 'restore', filename, source })
|
||||
}
|
||||
|
||||
const handleConfirmAction = useLockFn(async () => {
|
||||
if (!pendingConfirmation) return
|
||||
const { action, filename, source: actionSource } = pendingConfirmation
|
||||
const actionIsLocal = actionSource === 'local'
|
||||
setIsConfirming(true)
|
||||
if (action === 'restore') {
|
||||
setIsRestoring(true)
|
||||
}
|
||||
try {
|
||||
if (isLocal) {
|
||||
await restoreLocalBackup(filename)
|
||||
if (action === 'delete') {
|
||||
if (actionIsLocal) {
|
||||
await deleteLocalBackup(filename)
|
||||
} else {
|
||||
await deleteWebdavBackup(filename)
|
||||
}
|
||||
setPendingConfirmation(null)
|
||||
await fetchRows()
|
||||
} else {
|
||||
await restoreWebDavBackup(filename)
|
||||
if (actionIsLocal) {
|
||||
await restoreLocalBackup(filename)
|
||||
} else {
|
||||
await restoreWebDavBackup(filename)
|
||||
}
|
||||
setPendingConfirmation(null)
|
||||
showNotice.success('settings.modals.backup.messages.restoreSuccess')
|
||||
setIsRestarting(true)
|
||||
window.setTimeout(() => {
|
||||
void restartApp().catch((err: unknown) => {
|
||||
setIsRestarting(false)
|
||||
showNotice.error(err)
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
showNotice.success('settings.modals.backup.messages.restoreSuccess')
|
||||
setIsRestarting(true)
|
||||
window.setTimeout(() => {
|
||||
void restartApp().catch((err: unknown) => {
|
||||
setIsRestarting(false)
|
||||
showNotice.error(err)
|
||||
})
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
showNotice.error(error)
|
||||
} finally {
|
||||
setIsConfirming(false)
|
||||
setIsRestoring(false)
|
||||
}
|
||||
})
|
||||
@@ -267,6 +279,20 @@ export const BackupHistoryViewer = ({
|
||||
void fetchRows({ force: true })
|
||||
}
|
||||
|
||||
const closeConfirmDialog = () => {
|
||||
if (isConfirming) return
|
||||
setPendingConfirmation(null)
|
||||
}
|
||||
|
||||
const confirmTitle =
|
||||
pendingConfirmation?.action === 'delete'
|
||||
? t('settings.modals.backup.actions.deleteBackup')
|
||||
: t('settings.modals.backup.actions.restoreBackup')
|
||||
const confirmMessage =
|
||||
pendingConfirmation?.action === 'delete'
|
||||
? t('settings.modals.backup.messages.confirmDelete')
|
||||
: t('settings.modals.backup.messages.confirmRestore')
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
@@ -423,6 +449,30 @@ export const BackupHistoryViewer = ({
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
<BaseDialog
|
||||
open={pendingConfirmation !== null}
|
||||
title={confirmTitle}
|
||||
okBtn={t('shared.actions.confirm')}
|
||||
cancelBtn={t('shared.actions.cancel')}
|
||||
contentSx={{ width: { xs: 320, sm: 420 } }}
|
||||
loading={isConfirming}
|
||||
onCancel={closeConfirmDialog}
|
||||
onClose={closeConfirmDialog}
|
||||
onOk={handleConfirmAction}
|
||||
>
|
||||
<Typography variant="body2" sx={{ wordBreak: 'break-word' }}>
|
||||
{confirmMessage}
|
||||
</Typography>
|
||||
{pendingConfirmation?.filename && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ display: 'block', mt: 1, wordBreak: 'break-all' }}
|
||||
>
|
||||
{pendingConfirmation.filename}
|
||||
</Typography>
|
||||
)}
|
||||
</BaseDialog>
|
||||
</BaseDialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { LoadingButton } from '@mui/lab'
|
||||
import {
|
||||
Button,
|
||||
List,
|
||||
@@ -156,7 +155,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
title: t('settings.modals.backup.tabs.local'),
|
||||
description: t('settings.modals.backup.manual.local'),
|
||||
actions: [
|
||||
<LoadingButton
|
||||
<Button
|
||||
key="backup"
|
||||
variant="contained"
|
||||
size="small"
|
||||
@@ -165,7 +164,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
onClick={() => handleBackup('local')}
|
||||
>
|
||||
{t('settings.modals.backup.actions.backup')}
|
||||
</LoadingButton>,
|
||||
</Button>,
|
||||
<Button
|
||||
key="history"
|
||||
variant="outlined"
|
||||
@@ -175,7 +174,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
>
|
||||
{t('settings.modals.backup.actions.viewHistory')}
|
||||
</Button>,
|
||||
<LoadingButton
|
||||
<Button
|
||||
key="import"
|
||||
variant="text"
|
||||
size="small"
|
||||
@@ -184,7 +183,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
onClick={() => handleImport()}
|
||||
>
|
||||
{t('settings.modals.backup.actions.importBackup')}
|
||||
</LoadingButton>,
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -192,7 +191,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
title: t('settings.modals.backup.tabs.webdav'),
|
||||
description: t('settings.modals.backup.manual.webdav'),
|
||||
actions: [
|
||||
<LoadingButton
|
||||
<Button
|
||||
key="backup"
|
||||
variant="contained"
|
||||
size="small"
|
||||
@@ -200,7 +199,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
onClick={() => handleBackup('webdav')}
|
||||
>
|
||||
{t('settings.modals.backup.actions.backup')}
|
||||
</LoadingButton>,
|
||||
</Button>,
|
||||
<Button
|
||||
key="history"
|
||||
variant="outlined"
|
||||
|
||||
@@ -2,9 +2,9 @@ import {
|
||||
RestartAltRounded,
|
||||
SwitchAccessShortcutRounded,
|
||||
} from '@mui/icons-material'
|
||||
import { LoadingButton } from '@mui/lab'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
List,
|
||||
@@ -120,7 +120,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{t('settings.sections.clash.form.fields.clashCore')}
|
||||
<Box>
|
||||
<LoadingButton
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<SwitchAccessShortcutRounded />}
|
||||
@@ -131,8 +131,8 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
onClick={onUpgrade}
|
||||
>
|
||||
{t('shared.actions.upgrade')}
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<RestartAltRounded />}
|
||||
@@ -142,7 +142,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
onClick={onRestart}
|
||||
>
|
||||
{t('shared.actions.restart')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
RefreshRounded,
|
||||
TextSnippetOutlined,
|
||||
} from '@mui/icons-material'
|
||||
import { LoadingButton } from '@mui/lab'
|
||||
import { Box, Button, Divider, Grid, IconButton, Stack } from '@mui/material'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { listen, TauriEvent } from '@tauri-apps/api/event'
|
||||
@@ -970,7 +969,7 @@ const ProfilePage = () => {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<LoadingButton
|
||||
<Button
|
||||
disabled={!url || disabled}
|
||||
loading={loading}
|
||||
variant="contained"
|
||||
@@ -979,7 +978,7 @@ const ProfilePage = () => {
|
||||
onClick={onImport}
|
||||
>
|
||||
{t('profiles.page.actions.import')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
|
||||
Reference in New Issue
Block a user