feat: show service mode installation prompts in user mode

This commit is contained in:
wonfen 2025-03-03 14:42:31 +08:00
parent fadae3f7dc
commit dd510b2ee9
9 changed files with 161 additions and 7 deletions

View File

@ -1,6 +1,7 @@
use super::CmdResult;
use crate::{core::handle, model::sysinfo::PlatformSpecification};
use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
@ -15,3 +16,19 @@ pub async fn export_diagnostic_info() -> CmdResult<()> {
}
Ok(())
}
/// 获取当前内核运行模式
#[tauri::command]
pub async fn get_running_mode() -> Result<String, String> {
match CoreManager::global().get_running_mode().await {
core::RunningMode::Service => Ok("service".to_string()),
core::RunningMode::Sidecar => Ok("sidecar".to_string()),
core::RunningMode::NotRunning => Ok("not_running".to_string()),
}
}
/// 安装/重装系统服务
#[tauri::command]
pub async fn install_service() -> CmdResult {
wrap_err!(service::reinstall_service().await)
}

View File

@ -17,6 +17,17 @@ pub struct CoreManager {
running: Arc<Mutex<bool>>,
}
/// 内核运行模式
#[derive(Debug, Clone, serde::Serialize)]
pub enum RunningMode {
/// 服务模式运行
Service,
/// Sidecar模式运行
Sidecar,
/// 未运行
NotRunning,
}
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
@ -571,4 +582,38 @@ impl CoreManager {
}
}
}
/// 获取当前内核运行模式
pub async fn get_running_mode(&self) -> RunningMode {
let running = self.running.lock().await;
if !*running {
return RunningMode::NotRunning;
}
// 检查服务状态
match service::check_service().await {
Ok(_) => {
// 检查服务是否实际运行核心
match service::is_service_running().await {
Ok(true) => RunningMode::Service,
_ => {
// 服务存在但可能没有运行检查是否有sidecar进程
if handle::Handle::global().has_core_process() {
RunningMode::Sidecar
} else {
RunningMode::NotRunning
}
}
}
},
Err(_) => {
// 服务不可用检查是否有sidecar进程
if handle::Handle::global().has_core_process() {
RunningMode::Sidecar
} else {
RunningMode::NotRunning
}
}
}
}
}

View File

@ -81,6 +81,11 @@ impl Handle {
core_process.take()
}
/// 检查是否有运行中的核心进程
pub fn has_core_process(&self) -> bool {
self.core_process.read().is_some()
}
pub fn is_exiting(&self) -> bool {
*self.is_exiting.read()
}

View File

@ -279,3 +279,15 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
Ok(())
}
/// 检查服务是否正在运行
pub async fn is_service_running() -> Result<bool> {
let resp = check_service().await?;
// 检查服务状态码和消息
if resp.code == 200 && resp.msg == "success" && resp.data.is_some() {
Ok(true)
} else {
Ok(false)
}
}

View File

@ -148,6 +148,9 @@ pub fn run() {
cmd::get_network_interfaces,
cmd::restart_core,
cmd::restart_app,
// 添加新的命令
cmd::get_running_mode,
cmd::install_service,
// clash
cmd::get_clash_info,
cmd::patch_clash_config,

View File

@ -5,6 +5,8 @@ import {
SettingsRounded,
PlayArrowRounded,
PauseRounded,
WarningRounded,
BuildRounded,
} from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge";
import { DialogRef, Notice, Switch } from "@/components/base";
@ -13,7 +15,14 @@ import { GuardState } from "./mods/guard-state";
import { SysproxyViewer } from "./mods/sysproxy-viewer";
import { TunViewer } from "./mods/tun-viewer";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { getSystemProxy, getAutotemProxy } from "@/services/cmds";
import {
getSystemProxy,
getAutotemProxy,
getRunningMode,
installService,
} from "@/services/cmds";
import { useLockFn } from "ahooks";
import { Box, Button, Tooltip } from "@mui/material";
interface Props {
onError?: (err: Error) => void;
@ -26,6 +35,13 @@ const SettingSystem = ({ onError }: Props) => {
const { data: sysproxy } = useSWR("getSystemProxy", getSystemProxy);
const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy);
const { data: runningMode, mutate: mutateRunningMode } = useSWR(
"getRunningMode",
getRunningMode,
);
// 是否以sidecar模式运行
const isSidecarMode = runningMode === "sidecar";
const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null);
@ -54,6 +70,19 @@ const SettingSystem = ({ onError }: Props) => {
await mutate("getAutotemProxy");
};
// 安装系统服务
const onInstallService = useLockFn(async () => {
try {
Notice.info(t("Installing Service..."), 1000);
await installService();
Notice.success(t("Service Installed Successfully"), 2000);
// 重新获取运行模式
await mutateRunningMode();
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
}
});
return (
<SettingList title={t("System Setting")}>
<SysproxyViewer ref={sysproxyRef} />
@ -62,11 +91,31 @@ const SettingSystem = ({ onError }: Props) => {
<SettingItem
label={t("Tun Mode")}
extra={
<TooltipIcon
title={t("Tun Mode Info")}
icon={SettingsRounded}
onClick={() => tunRef.current?.open()}
/>
<>
<TooltipIcon
title={t("Tun Mode Info")}
icon={SettingsRounded}
onClick={() => tunRef.current?.open()}
/>
{isSidecarMode && (
<Tooltip title={t("TUN requires Service Mode")}>
<WarningRounded sx={{ color: "warning.main", mr: 1 }} />
</Tooltip>
)}
{isSidecarMode && (
<Tooltip title={t("Install Service")}>
<Button
variant="outlined"
color="primary"
size="small"
onClick={onInstallService}
sx={{ mr: 1, minWidth: "32px", p: "4px" }}
>
<BuildRounded fontSize="small" />
</Button>
</Tooltip>
)}
</>
}
>
<GuardState
@ -75,13 +124,20 @@ const SettingSystem = ({ onError }: Props) => {
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => {
// 当在sidecar模式下禁用切换
if (isSidecarMode) return;
onChangeData({ enable_tun_mode: e });
}}
onGuard={(e) => {
// 当在sidecar模式下禁用切换
if (isSidecarMode) {
Notice.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" />
<Switch edge="end" disabled={isSidecarMode} />
</GuardState>
</SettingItem>
<SettingItem

View File

@ -195,6 +195,8 @@
"Settings": "Settings",
"System Setting": "System Setting",
"Tun Mode": "Tun (Virtual NIC) Mode",
"TUN requires Service Mode": "TUN mode requires service",
"Install Service": "Install Service",
"Reset to Default": "Reset to Default",
"Tun Mode Info": "Tun (Virtual NIC) mode: Captures all system traffic, when enabled, there is no need to enable system proxy.",
"Stack": "Tun Stack",
@ -365,6 +367,7 @@
"Profile Reactivated": "Profile Reactivated",
"Only YAML Files Supported": "Only YAML Files Supported",
"Settings Applied": "Settings Applied",
"Installing Service...": "Installing Service...",
"Service Installed Successfully": "Service Installed Successfully",
"Service Uninstalled Successfully": "Service Uninstalled Successfully",
"Proxy Daemon Duration Cannot be Less than 1 Second": "Proxy Daemon Duration Cannot be Less than 1 Second",

View File

@ -195,6 +195,8 @@
"Settings": "设置",
"System Setting": "系统设置",
"Tun Mode": "TUN虚拟网卡模式",
"TUN requires Service Mode": "TUN 模式需要服务",
"Install Service": "安装服务",
"Reset to Default": "重置为默认值",
"Tun Mode Info": "TUN虚拟网卡模式接管系统所有流量启用时无须打开系统代理",
"Stack": "TUN 模式堆栈",
@ -362,6 +364,7 @@
"Profile Reactivated": "订阅已激活",
"Only YAML Files Supported": "仅支持 YAML 文件",
"Settings Applied": "设置已应用",
"Installing Service...": "安装服务中...",
"Service Installed Successfully": "已成功安装服务",
"Service Uninstalled Successfully": "已成功卸载服务",
"Proxy Daemon Duration Cannot be Less than 1 Second": "代理守护间隔时间不得低于 1 秒",

View File

@ -253,3 +253,13 @@ export async function scriptValidateNotice(status: string, msg: string) {
export async function validateScriptFile(filePath: string) {
return invoke<boolean>("validate_script_file", { filePath });
}
// 获取当前运行模式
export const getRunningMode = async () => {
return invoke<string>("get_running_mode");
};
// 安装/重装系统服务
export const installService = async () => {
return invoke<void>("install_service");
};