mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-24 12:42:22 +08:00
chore: merge dev into declarative effects
This commit is contained in:
@@ -22,4 +22,8 @@
|
||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||
|
||||
|
||||
**🖥️ Windows**
|
||||
|
||||
- 优化 Windows 服务安全检查未通过时的启动提示:显示具体原因,并提供修复文档链接
|
||||
|
||||
</details>
|
||||
|
||||
@@ -113,7 +113,7 @@ pub async fn continue_with_sidecar() -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn take_service_fallback_notice() -> bool {
|
||||
pub fn take_service_fallback_notice() -> Option<crate::core::service::ServiceFallbackNotice> {
|
||||
crate::core::service::take_service_fallback_notice()
|
||||
}
|
||||
|
||||
|
||||
@@ -678,7 +678,7 @@ impl CoreManager {
|
||||
SERVICE_MANAGER.withdraw_sidecar_allowance();
|
||||
self.rollback_failed_start().await;
|
||||
} else {
|
||||
crate::core::service::notify_service_fallback();
|
||||
crate::core::service::notify_service_fallback(&reason);
|
||||
}
|
||||
result.map_err(|error| error.context(format!("Service failure before Sidecar startup: {reason}")))
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ pub use env::FakeEnv;
|
||||
pub use env::{RealEnv, RunStateEnv};
|
||||
pub use health::{PendingAction, RunState, RunStateView, ServiceHealth};
|
||||
pub use owner::{OwnerRecoveryReason, OwnerSample, OwnerStep, OwnerWatch};
|
||||
pub use probe::{ServiceVersionCheck, ServiceVersionReply, classify_service_version_reply};
|
||||
pub use probe::{CORE_REJECTED_PREFIX, ServiceVersionCheck, ServiceVersionReply, classify_service_version_reply};
|
||||
|
||||
use crate::core::manager::RunningMode;
|
||||
use health::StoredService;
|
||||
|
||||
@@ -27,6 +27,9 @@ pub enum ServiceVersionCheck {
|
||||
CoreUnavailable(String),
|
||||
}
|
||||
|
||||
/// Leads the Unavailable reason when the Service refused the approved core itself.
|
||||
pub const CORE_REJECTED_PREFIX: &str = "approved core was rejected: ";
|
||||
|
||||
/// What a live probe of the currently-installed Service told us.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CurrentServiceProbe {
|
||||
@@ -47,7 +50,7 @@ pub fn classify_service_version_reply(reply: &ServiceVersionReply) -> ServiceVer
|
||||
return match &reply.core {
|
||||
Some(clash_verge_service_ipc::CoreAvailability::Ready) => ServiceVersionCheck::Ready,
|
||||
Some(clash_verge_service_ipc::CoreAvailability::Rejected { reason }) => {
|
||||
ServiceVersionCheck::CoreUnavailable(format!("approved core was rejected: {reason}"))
|
||||
ServiceVersionCheck::CoreUnavailable(format!("{CORE_REJECTED_PREFIX}{reason}"))
|
||||
}
|
||||
_ => ServiceVersionCheck::CoreUnavailable(
|
||||
"the selected approved core is missing or does not match; reinstall the service to repair it".into(),
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::{
|
||||
owner_identity::current_owner_credentials,
|
||||
proxy_control,
|
||||
runstate::{
|
||||
OwnerRecoveryReason, OwnerSample, OwnerStep, OwnerWatch, PendingAction, RUN_STATE, ReadyWaitError,
|
||||
RunState, RunStateEnv, RunStateStore, ServiceHealth,
|
||||
CORE_REJECTED_PREFIX, OwnerRecoveryReason, OwnerSample, OwnerStep, OwnerWatch, PendingAction, RUN_STATE,
|
||||
ReadyWaitError, RunState, RunStateEnv, RunStateStore, ServiceHealth,
|
||||
},
|
||||
runtime_bundle::{RemoteProviderRef, collect_runtime_bundle, remote_providers_of},
|
||||
tray::Tray,
|
||||
@@ -40,18 +40,31 @@ use std::{
|
||||
|
||||
static OWNER_MONITOR_GENERATION: AtomicU64 = AtomicU64::new(0);
|
||||
static ACTIVE_SERVICE_SESSION: Lazy<Mutex<Option<ActiveServiceSession>>> = Lazy::new(|| Mutex::new(None));
|
||||
static PENDING_SERVICE_FALLBACK_NOTICE: AtomicBool = AtomicBool::new(false);
|
||||
static PENDING_SERVICE_FALLBACK_NOTICE: Mutex<Option<String>> = Mutex::new(None);
|
||||
static PENDING_SERVICE_REPAIR_NOTICE: AtomicBool = AtomicBool::new(false);
|
||||
static PENDING_SERVICE_OWNER_NOTICE: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
/// Why the Service was unavailable when the core fell back to Sidecar.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
#[serde(tag = "kind", content = "reason", rename_all = "camelCase")]
|
||||
pub enum ServiceFallbackNotice {
|
||||
Unavailable,
|
||||
CoreRejected(String),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn notify_service_fallback() {
|
||||
PENDING_SERVICE_FALLBACK_NOTICE.store(true, Ordering::Relaxed);
|
||||
pub(crate) fn notify_service_fallback(reason: &str) {
|
||||
*PENDING_SERVICE_FALLBACK_NOTICE.lock() = Some(reason.to_owned());
|
||||
Handle::notice(NoticeStatus::ServiceCoreSidecarFallback, "");
|
||||
}
|
||||
|
||||
pub(crate) fn take_service_fallback_notice() -> bool {
|
||||
PENDING_SERVICE_FALLBACK_NOTICE.swap(false, Ordering::Relaxed)
|
||||
pub(crate) fn take_service_fallback_notice() -> Option<ServiceFallbackNotice> {
|
||||
let reason = PENDING_SERVICE_FALLBACK_NOTICE.lock().take()?;
|
||||
Some(if reason.starts_with(CORE_REJECTED_PREFIX) {
|
||||
ServiceFallbackNotice::CoreRejected(reason)
|
||||
} else {
|
||||
ServiceFallbackNotice::Unavailable
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn take_service_repair_notice() -> bool {
|
||||
@@ -925,7 +938,7 @@ pub(super) async fn start_with_existing_service(config_file: &Path) -> Result<()
|
||||
// PAC follows the Running Mode; the caller opens it via `core_started(Service)`.
|
||||
start_owner_monitor();
|
||||
tracing::Span::current().record("outcome", "started");
|
||||
PENDING_SERVICE_FALLBACK_NOTICE.store(false, Ordering::Relaxed);
|
||||
PENDING_SERVICE_FALLBACK_NOTICE.lock().take();
|
||||
PENDING_SERVICE_REPAIR_NOTICE.store(false, Ordering::Relaxed);
|
||||
PENDING_SERVICE_OWNER_NOTICE.lock().take();
|
||||
logging!(
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type RunState,
|
||||
getCoreStartupError,
|
||||
takeDnsOverrideNotice,
|
||||
takeServiceFallbackNotice,
|
||||
takeServiceRepairNotice,
|
||||
} from '@/services/cmds'
|
||||
import { subscribeVergeEvents } from '@/services/events'
|
||||
@@ -31,13 +32,13 @@ vi.mock('@/services/cmds', () => ({
|
||||
getCoreStartupError: vi.fn().mockResolvedValue(null),
|
||||
takeDiscardedKeysNotice: vi.fn().mockResolvedValue(null),
|
||||
takeDnsOverrideNotice: vi.fn().mockResolvedValue(false),
|
||||
takeServiceFallbackNotice: vi.fn().mockResolvedValue(false),
|
||||
takeServiceFallbackNotice: vi.fn().mockResolvedValue(null),
|
||||
takeServiceRepairNotice: vi.fn().mockResolvedValue(false),
|
||||
takeServiceOwnerNotice: vi.fn().mockResolvedValue(null),
|
||||
}))
|
||||
vi.mock('@/services/service-request', () => ({ requestService: vi.fn() }))
|
||||
vi.mock('@/services/notice-service', () => ({
|
||||
showNotice: { info: vi.fn(), error: vi.fn() },
|
||||
showNotice: { info: vi.fn(), error: vi.fn(), warning: vi.fn() },
|
||||
}))
|
||||
vi.mock('@/services/query-client', () => ({
|
||||
revalidateQueries: vi.fn(),
|
||||
@@ -184,3 +185,26 @@ it('offers service reinstallation for a startup path refusal even before listene
|
||||
reason: 'serviceLocationRefused',
|
||||
})
|
||||
})
|
||||
|
||||
it('explains a startup core rejection instead of the generic fallback notice', async () => {
|
||||
const reason =
|
||||
'approved core was rejected: core path "C:\\" has an untrusted write ACE'
|
||||
vi.mocked(takeServiceFallbackNotice)
|
||||
.mockResolvedValueOnce({ kind: 'coreRejected', reason })
|
||||
.mockResolvedValue(null)
|
||||
|
||||
useLayoutEvents(([status, message]) => {
|
||||
handleNoticeMessage(status, message, (key) => key, vi.fn())
|
||||
})
|
||||
|
||||
const [handlers, onSubscribed] = vi.mocked(subscribeVergeEvents).mock.calls[0]
|
||||
onSubscribed?.()
|
||||
handlers['verge://notice-message']?.(['service_core::sidecar_fallback', ''])
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
|
||||
expect(showNotice.warning).toHaveBeenCalledExactlyOnceWith(
|
||||
'settings.feedback.notifications.clashService.permissionFallback',
|
||||
{ reason },
|
||||
0,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -117,8 +117,14 @@ export const handleNoticeMessage = (
|
||||
},
|
||||
'service_core::sidecar_fallback': () => {
|
||||
void takeServiceFallbackNotice()
|
||||
.then((pending) => {
|
||||
if (pending) {
|
||||
.then((notice) => {
|
||||
if (notice?.kind === 'coreRejected') {
|
||||
showNotice.warning(
|
||||
'settings.feedback.notifications.clashService.permissionFallback',
|
||||
{ reason: notice.reason },
|
||||
0,
|
||||
)
|
||||
} else if (notice) {
|
||||
showNotice.warning(
|
||||
'settings.feedback.notifications.clashService.sidecarFallback',
|
||||
)
|
||||
|
||||
@@ -186,8 +186,12 @@ export async function takeDnsOverrideNotice() {
|
||||
return invoke<boolean>('take_dns_override_notice')
|
||||
}
|
||||
|
||||
export type ServiceFallbackNotice =
|
||||
| { kind: 'unavailable' }
|
||||
| { kind: 'coreRejected'; reason: string }
|
||||
|
||||
export async function takeServiceFallbackNotice() {
|
||||
return invoke<boolean>('take_service_fallback_notice')
|
||||
return invoke<ServiceFallbackNotice | null>('take_service_fallback_notice')
|
||||
}
|
||||
|
||||
export interface CoreFailure {
|
||||
|
||||
Reference in New Issue
Block a user