feat: better service status and TUN mode usable checks in Setting Page

This commit is contained in:
Tunglies
2025-05-12 19:04:08 +08:00
parent d587ed09a5
commit 5b6c9be99f
14 changed files with 127 additions and 88 deletions

View File

@@ -237,8 +237,7 @@ pub async fn get_dns_config_content() -> CmdResult<String> {
/// 验证DNS配置文件 /// 验证DNS配置文件
#[tauri::command] #[tauri::command]
pub async fn validate_dns_config() -> CmdResult<(bool, String)> { pub async fn validate_dns_config() -> CmdResult<(bool, String)> {
use crate::core::CoreManager; use crate::{core::CoreManager, utils::dirs};
use crate::utils::dirs;
let app_dir = dirs::app_home_dir().map_err(|e| e.to_string())?; let app_dir = dirs::app_home_dir().map_err(|e| e.to_string())?;
let dns_path = app_dir.join("dns_config.yaml"); let dns_path = app_dir.join("dns_config.yaml");

View File

@@ -1,6 +1,7 @@
use super::CmdResult; use super::CmdResult;
use crate::{ use crate::{
core::{service, CoreManager}, core::{service, CoreManager},
feat,
utils::i18n::t, utils::i18n::t,
}; };
@@ -38,3 +39,11 @@ pub async fn reinstall_service() -> CmdResult {
pub async fn repair_service() -> CmdResult { pub async fn repair_service() -> CmdResult {
execute_service_operation(service::force_reinstall_service(), "Repair").await execute_service_operation(service::force_reinstall_service(), "Repair").await
} }
#[tauri::command]
pub async fn is_service_available() -> CmdResult<bool> {
service::is_service_available()
.await
.map(|_| true)
.map_err(|e| e.to_string())
}

View File

@@ -1,11 +1,13 @@
use crate::utils::network::{NetworkManager, ProxyType}; use crate::utils::{
use crate::utils::{dirs, help, tmpl}; dirs, help,
network::{NetworkManager, ProxyType},
tmpl,
};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::fs; use std::{fs, time::Duration};
use std::time::Duration;
use super::Config; use super::Config;

View File

@@ -473,7 +473,7 @@ pub async fn reinstall_service() -> Result<()> {
} }
/// 检查服务状态 - 使用IPC通信 /// 检查服务状态 - 使用IPC通信
pub async fn check_service() -> Result<JsonResponse> { pub async fn check_ipc_service_status() -> Result<JsonResponse> {
logging!(info, Type::Service, true, "开始检查服务状态 (IPC)"); logging!(info, Type::Service, true, "开始检查服务状态 (IPC)");
// 使用IPC通信 // 使用IPC通信
@@ -876,7 +876,7 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
} }
// 检查服务状态 // 检查服务状态
match check_service().await { match check_ipc_service_status().await {
Ok(_) => { Ok(_) => {
log::info!(target: "app", "服务可用但未运行核心,尝试启动"); log::info!(target: "app", "服务可用但未运行核心,尝试启动");
if let Ok(()) = start_with_existing_service(config_file).await { if let Ok(()) = start_with_existing_service(config_file).await {
@@ -947,7 +947,7 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
pub async fn is_service_running() -> Result<bool> { pub async fn is_service_running() -> Result<bool> {
logging!(info, Type::Service, true, "开始检查服务是否正在运行"); logging!(info, Type::Service, true, "开始检查服务是否正在运行");
match check_service().await { match check_ipc_service_status().await {
Ok(resp) => { Ok(resp) => {
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() { if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(info, Type::Service, true, "服务正在运行"); logging!(info, Type::Service, true, "服务正在运行");
@@ -1010,7 +1010,7 @@ pub async fn is_service_running() -> Result<bool> {
pub async fn is_service_available() -> Result<()> { pub async fn is_service_available() -> Result<()> {
logging!(info, Type::Service, true, "开始检查服务是否可用"); logging!(info, Type::Service, true, "开始检查服务是否可用");
match check_service().await { match check_ipc_service_status().await {
Ok(resp) => { Ok(resp) => {
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() { if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(info, Type::Service, true, "服务可用"); logging!(info, Type::Service, true, "服务可用");

View File

@@ -1,5 +1,4 @@
use crate::logging; use crate::{logging, utils::logging::Type};
use crate::utils::logging::Type;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -117,14 +116,18 @@ pub async fn send_ipc_request(
command: IpcCommand, command: IpcCommand,
payload: serde_json::Value, payload: serde_json::Value,
) -> Result<IpcResponse> { ) -> Result<IpcResponse> {
use std::ffi::CString; use std::{
use std::fs::File; ffi::CString,
use std::io::{Read, Write}; fs::File,
use std::os::windows::io::{FromRawHandle, RawHandle}; io::{Read, Write},
use std::ptr; os::windows::io::{FromRawHandle, RawHandle},
use winapi::um::fileapi::{CreateFileA, OPEN_EXISTING}; ptr,
use winapi::um::handleapi::INVALID_HANDLE_VALUE; };
use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}; use winapi::um::{
fileapi::{CreateFileA, OPEN_EXISTING},
handleapi::INVALID_HANDLE_VALUE,
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
};
logging!( logging!(
info, info,

View File

@@ -5,13 +5,14 @@ pub mod speed_rate;
use crate::{ use crate::{
cmd, cmd,
config::Config, config::Config,
feat, feat, logging,
module::{ module::{
lightweight::{entry_lightweight_mode, is_in_lightweight_mode}, lightweight::{entry_lightweight_mode, is_in_lightweight_mode},
mihomo::Rate, mihomo::Rate,
}, },
resolve, resolve,
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION}, utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
Type,
}; };
use anyhow::Result; use anyhow::Result;
@@ -828,7 +829,13 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
match event.id.as_ref() { match event.id.as_ref() {
mode @ ("rule_mode" | "global_mode" | "direct_mode") => { mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
let mode = &mode[0..mode.len() - 5]; let mode = &mode[0..mode.len() - 5];
println!("change mode to: {}", mode); logging!(
info,
Type::ProxyMode,
true,
"Switch Proxy Mode To: {}",
mode
);
feat::change_clash_mode(mode.into()); feat::change_clash_mode(mode.into());
} }
"open_window" => { "open_window" => {

View File

@@ -240,8 +240,10 @@ pub struct Traffic {
impl Traffic { impl Traffic {
pub async fn get_traffic_stream() -> Result<impl Stream<Item = Result<Traffic, anyhow::Error>>> pub async fn get_traffic_stream() -> Result<impl Stream<Item = Result<Traffic, anyhow::Error>>>
{ {
use futures::future::FutureExt; use futures::{
use futures::stream::{self, StreamExt}; future::FutureExt,
stream::{self, StreamExt},
};
use std::time::Duration; use std::time::Duration;
// 先处理错误和超时情况 // 先处理错误和超时情况

View File

@@ -43,19 +43,6 @@ pub fn toggle_system_proxy() {
/// Toggle TUN mode on/off /// Toggle TUN mode on/off
pub fn toggle_tun_mode(not_save_file: Option<bool>) { pub fn toggle_tun_mode(not_save_file: Option<bool>) {
// AsyncHandler::spawn(async {
// logging!(
// info,
// Type::Service,
// true,
// "Toggle TUN mode need install service"
// );
// if is_service_available().await.is_err() {
// logging_error!(Type::Service, true, install_service().await);
// }
// logging_error!(Type::Core, true, CoreManager::global().restart_core().await);
// });
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);

View File

@@ -235,6 +235,7 @@ pub fn run() {
cmd::uninstall_service, cmd::uninstall_service,
cmd::reinstall_service, cmd::reinstall_service,
cmd::repair_service, cmd::repair_service,
cmd::is_service_available,
// clash // clash
cmd::get_clash_info, cmd::get_clash_info,
cmd::patch_clash_config, cmd::patch_clash_config,

View File

@@ -1,4 +1,4 @@
use std::fmt; use std::fmt::{self, write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Type { pub enum Type {
@@ -16,6 +16,7 @@ pub enum Type {
Backup, Backup,
Lightweight, Lightweight,
Network, Network,
ProxyMode,
} }
impl fmt::Display for Type { impl fmt::Display for Type {
@@ -35,6 +36,7 @@ impl fmt::Display for Type {
Type::Backup => write!(f, "[Backup]"), Type::Backup => write!(f, "[Backup]"),
Type::Lightweight => write!(f, "[Lightweight]"), Type::Lightweight => write!(f, "[Lightweight]"),
Type::Network => write!(f, "[Network]"), Type::Network => write!(f, "[Network]"),
Type::ProxyMode => write!(f, "[ProxMode]"),
} }
} }
} }

View File

@@ -1,8 +1,10 @@
use anyhow::Result; use anyhow::Result;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder, Response}; use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder, Response};
use std::sync::{Arc, Mutex, Once}; use std::{
use std::time::{Duration, Instant}; sync::{Arc, Mutex, Once},
time::{Duration, Instant},
};
use tokio::runtime::{Builder, Runtime}; use tokio::runtime::{Builder, Runtime};
use crate::{config::Config, logging, utils::logging::Type}; use crate::{config::Config, logging, utils::logging::Type};

View File

@@ -20,6 +20,7 @@ import {
getAutotemProxy, getAutotemProxy,
installService, installService,
getAutoLaunchStatus, getAutoLaunchStatus,
restartCore,
} from "@/services/cmds"; } from "@/services/cmds";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { Button, Tooltip } from "@mui/material"; import { Button, Tooltip } from "@mui/material";
@@ -41,25 +42,18 @@ const SettingSystem = ({ onError }: Props) => {
const { data: autoLaunchEnabled } = useSWR( const { data: autoLaunchEnabled } = useSWR(
"getAutoLaunchStatus", "getAutoLaunchStatus",
getAutoLaunchStatus, getAutoLaunchStatus,
{ revalidateOnFocus: false } { revalidateOnFocus: false },
); );
const { isAdminMode, isSidecarMode, mutateRunningMode } = useSystemState(); const { isAdminMode, isSidecarMode, mutateRunningMode, isServiceOk } =
useSystemState();
console.log("Is service running:", isServiceOk);
// 判断Tun模式是否可用 - 当处于服务模式或管理员模式时可用 // 判断Tun模式是否可用 - 当处于服务模式或管理员模式时可用
const isTunAvailable = !isSidecarMode || isAdminMode; const isTunAvailable = isServiceOk || (isSidecarMode && !isAdminMode);
// 当实际自启动状态与配置不同步时更新配置 console.log("is tun isTunAvailable:", isTunAvailable);
useEffect(() => {
if (
autoLaunchEnabled !== undefined &&
verge &&
verge.enable_auto_launch !== autoLaunchEnabled
) {
// 静默更新配置不触发UI刷新
mutateVerge({ ...verge, enable_auto_launch: autoLaunchEnabled }, false);
}
}, [autoLaunchEnabled]);
const sysproxyRef = useRef<DialogRef>(null); const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null); const tunRef = useRef<DialogRef>(null);
@@ -87,13 +81,16 @@ const SettingSystem = ({ onError }: Props) => {
// 安装系统服务 // 安装系统服务
const onInstallService = useLockFn(async () => { const onInstallService = useLockFn(async () => {
try { try {
showNotice('info', t("Installing Service..."), 1000); showNotice("info", t("Installing Service..."), 1000);
await installService(); await installService();
showNotice('success', t("Service Installed Successfully"), 2000); showNotice("success", t("Service Installed Successfully"), 2000);
await restartCore();
showNotice("info", t("Restarting Core"), 1000);
console.log("restartCore");
// 重新获取运行模式 // 重新获取运行模式
await mutateRunningMode(); await mutateRunningMode();
} catch (err: any) { } catch (err: any) {
showNotice('error', err.message || err.toString(), 3000); showNotice("error", err.message || err.toString(), 3000);
} }
}); });
@@ -111,12 +108,12 @@ const SettingSystem = ({ onError }: Props) => {
icon={SettingsRounded} icon={SettingsRounded}
onClick={() => tunRef.current?.open()} onClick={() => tunRef.current?.open()}
/> />
{isSidecarMode && !isAdminMode && ( {!isServiceOk && !isAdminMode && (
<Tooltip title={t("TUN requires Service Mode")}> <Tooltip title={t("TUN requires Service Mode")}>
<WarningRounded sx={{ color: "warning.main", mr: 1 }} /> <WarningRounded sx={{ color: "warning.main", mr: 1 }} />
</Tooltip> </Tooltip>
)} )}
{isSidecarMode && !isAdminMode && ( {!isServiceOk && !isAdminMode && (
<Tooltip title={t("Install Service")}> <Tooltip title={t("Install Service")}>
<Button <Button
variant="outlined" variant="outlined"
@@ -138,20 +135,20 @@ const SettingSystem = ({ onError }: Props) => {
onCatch={onError} onCatch={onError}
onFormat={onSwitchFormat} onFormat={onSwitchFormat}
onChange={(e) => { onChange={(e) => {
// 当在sidecar模式下且非管理员模式时禁用切换 // 非 Service 状态禁止切换
if (isSidecarMode && !isAdminMode) return; if (!isServiceOk) return;
onChangeData({ enable_tun_mode: e }); onChangeData({ enable_tun_mode: e });
}} }}
onGuard={(e) => { onGuard={(e) => {
// 当在sidecar模式下且非管理员模式时禁用切换 // 非 Service 状态禁止切换
if (isSidecarMode && !isAdminMode) { if (!isServiceOk) {
showNotice('error', t("TUN requires Service Mode"), 2000); showNotice("error", t("TUN requires Service Mode"), 2000);
return Promise.reject(new Error(t("TUN requires Service Mode"))); return Promise.reject(new Error(t("TUN requires Service Mode")));
} }
return patchVerge({ enable_tun_mode: e }); return patchVerge({ enable_tun_mode: e });
}} }}
> >
<Switch edge="end" disabled={isSidecarMode && !isAdminMode} /> <Switch edge="end" disabled={isServiceOk} />
</GuardState> </GuardState>
</SettingItem> </SettingItem>
<SettingItem <SettingItem
@@ -195,11 +192,13 @@ const SettingSystem = ({ onError }: Props) => {
</GuardState> </GuardState>
</SettingItem> </SettingItem>
<SettingItem <SettingItem
label={t("Auto Launch")} label={t("Auto Launch")}
extra={ extra={
isAdminMode && ( isAdminMode && (
<Tooltip title={t("Administrator mode may not support auto launch")}> <Tooltip
title={t("Administrator mode may not support auto launch")}
>
<WarningRounded sx={{ color: "warning.main", mr: 1 }} /> <WarningRounded sx={{ color: "warning.main", mr: 1 }} />
</Tooltip> </Tooltip>
) )
@@ -216,9 +215,13 @@ const SettingSystem = ({ onError }: Props) => {
}} }}
onGuard={async (e) => { onGuard={async (e) => {
if (isAdminMode) { if (isAdminMode) {
showNotice('info', t("Administrator mode may not support auto launch"), 2000); showNotice(
"info",
t("Administrator mode may not support auto launch"),
2000,
);
} }
try { try {
// 在应用更改之前先触发UI更新让用户立即看到反馈 // 在应用更改之前先触发UI更新让用户立即看到反馈
onChangeData({ enable_auto_launch: e }); onChangeData({ enable_auto_launch: e });

View File

@@ -1,29 +1,42 @@
import useSWR from "swr"; import useSWR from "swr";
import { getRunningMode, isAdmin } from "@/services/cmds"; import { getRunningMode, isAdmin, isServiceAvailable } from "@/services/cmds";
/** /**
* 自定义 hook 用于获取系统运行状态 * 自定义 hook 用于获取系统运行状态
* 包括运行模式管理员状态 * 包括运行模式管理员状态、系统服务是否可用
*/ */
export function useSystemState() { export function useSystemState() {
// 获取运行模式 // 获取运行模式
const { data: runningMode = "Sidecar", mutate: mutateRunningMode } = const { data: runningMode = "Sidecar", mutate: mutateRunningMode } = useSWR(
useSWR("getRunningMode", getRunningMode, { "getRunningMode",
getRunningMode,
{
suspense: false, suspense: false,
revalidateOnFocus: false revalidateOnFocus: false,
}); },
);
// 获取管理员状态 // 获取管理员状态
const { data: isAdminMode = false } = const { data: isAdminMode = false } = useSWR("isAdmin", isAdmin, {
useSWR("isAdmin", isAdmin, { suspense: false,
revalidateOnFocus: false,
});
// 获取系统服务状态
const { data: isServiceOk = false } = useSWR(
"isServiceAvailable",
isServiceAvailable,
{
suspense: false, suspense: false,
revalidateOnFocus: false revalidateOnFocus: false,
}); },
);
return { return {
runningMode, runningMode,
isAdminMode, isAdminMode,
isSidecarMode: runningMode === "Sidecar", isSidecarMode: runningMode === "Sidecar",
mutateRunningMode mutateRunningMode,
isServiceOk,
}; };
} }

View File

@@ -145,19 +145,19 @@ export async function getAppDir() {
export async function openAppDir() { export async function openAppDir() {
return invoke<void>("open_app_dir").catch((err) => return invoke<void>("open_app_dir").catch((err) =>
showNotice('error', err?.message || err.toString()), showNotice("error", err?.message || err.toString()),
); );
} }
export async function openCoreDir() { export async function openCoreDir() {
return invoke<void>("open_core_dir").catch((err) => return invoke<void>("open_core_dir").catch((err) =>
showNotice('error', err?.message || err.toString()), showNotice("error", err?.message || err.toString()),
); );
} }
export async function openLogsDir() { export async function openLogsDir() {
return invoke<void>("open_logs_dir").catch((err) => return invoke<void>("open_logs_dir").catch((err) =>
showNotice('error', err?.message || err.toString()), showNotice("error", err?.message || err.toString()),
); );
} }
@@ -165,7 +165,7 @@ export const openWebUrl = async (url: string) => {
try { try {
await invoke("open_web_url", { url }); await invoke("open_web_url", { url });
} catch (err: any) { } catch (err: any) {
showNotice('error', err.toString()); showNotice("error", err.toString());
} }
}; };
@@ -218,7 +218,7 @@ export async function cmdTestDelay(url: string) {
export async function invoke_uwp_tool() { export async function invoke_uwp_tool() {
return invoke<void>("invoke_uwp_tool").catch((err) => return invoke<void>("invoke_uwp_tool").catch((err) =>
showNotice('error', err?.message || err.toString(), 1500), showNotice("error", err?.message || err.toString(), 1500),
); );
} }
@@ -347,6 +347,15 @@ export const repairService = async () => {
return invoke<void>("repair_service"); return invoke<void>("repair_service");
}; };
// 系统服务是否可用
export const isServiceAvailable = async () => {
try {
return await invoke<boolean>("is_service_available");
} catch (error) {
console.error("Service check failed:", error);
return false;
}
};
export const entry_lightweight_mode = async () => { export const entry_lightweight_mode = async () => {
return invoke<void>("entry_lightweight_mode"); return invoke<void>("entry_lightweight_mode");
}; };