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

View File

@@ -1,6 +1,7 @@
use super::CmdResult;
use crate::{
core::{service, CoreManager},
feat,
utils::i18n::t,
};
@@ -38,3 +39,11 @@ pub async fn reinstall_service() -> CmdResult {
pub async fn repair_service() -> CmdResult {
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::{dirs, help, tmpl};
use crate::utils::{
dirs, help,
network::{NetworkManager, ProxyType},
tmpl,
};
use anyhow::{bail, Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::fs;
use std::time::Duration;
use std::{fs, time::Duration};
use super::Config;

View File

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

View File

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

View File

@@ -5,13 +5,14 @@ pub mod speed_rate;
use crate::{
cmd,
config::Config,
feat,
feat, logging,
module::{
lightweight::{entry_lightweight_mode, is_in_lightweight_mode},
mihomo::Rate,
},
resolve,
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
Type,
};
use anyhow::Result;
@@ -828,7 +829,13 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
match event.id.as_ref() {
mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
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());
}
"open_window" => {

View File

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

View File

@@ -43,19 +43,6 @@ pub fn toggle_system_proxy() {
/// Toggle TUN mode on/off
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 = enable.unwrap_or(false);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,19 +145,19 @@ export async function getAppDir() {
export async function openAppDir() {
return invoke<void>("open_app_dir").catch((err) =>
showNotice('error', err?.message || err.toString()),
showNotice("error", err?.message || err.toString()),
);
}
export async function openCoreDir() {
return invoke<void>("open_core_dir").catch((err) =>
showNotice('error', err?.message || err.toString()),
showNotice("error", err?.message || err.toString()),
);
}
export async function openLogsDir() {
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 {
await invoke("open_web_url", { url });
} 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() {
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");
};
// 系统服务是否可用
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 () => {
return invoke<void>("entry_lightweight_mode");
};