feat: windows service mode ui

This commit is contained in:
GyDi 2022-04-25 16:12:04 +08:00 committed by GitHub
parent 2d0b63c29d
commit 6b368953f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 234 additions and 14 deletions

View File

@ -251,6 +251,11 @@ pub mod service {
wrap_err!(crate::core::Service::start_service().await)
}
#[tauri::command]
pub async fn stop_service() -> Result<(), String> {
wrap_err!(crate::core::Service::stop_service().await)
}
#[tauri::command]
pub async fn check_service() -> Result<JsonResponse, String> {
wrap_err!(crate::core::Service::check_service().await)
@ -258,11 +263,13 @@ pub mod service {
#[tauri::command]
pub async fn install_service() -> Result<(), String> {
wrap_err!(crate::core::Service::install_service().await)
wrap_err!(crate::core::Service::install_service().await)?;
wrap_err!(crate::core::Service::start_service().await)
}
#[tauri::command]
pub async fn uninstall_service() -> Result<(), String> {
log_if_err!(crate::core::Service::stop_service().await);
wrap_err!(crate::core::Service::uninstall_service().await)
}
}
@ -276,6 +283,11 @@ pub mod service {
Ok(())
}
#[tauri::command]
pub async fn stop_service() -> Result<(), String> {
Ok(())
}
#[tauri::command]
pub async fn check_service() -> Result<(), String> {
Ok(())

View File

@ -294,15 +294,32 @@ pub mod win_service {
}
}
/// stop service
pub async fn stop_service() -> Result<()> {
let url = format!("{SERVICE_URL}/stop_service");
let res = reqwest::Client::new()
.post(url)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
}
Ok(())
}
/// check the windows service status
pub async fn check_service() -> Result<JsonResponse> {
let url = format!("{SERVICE_URL}/get_clash");
let response = reqwest::get(url)
.await
.context("failed to connect to the Clash Verge Service")?
.await?
.json::<JsonResponse>()
.await
.context("failed to parse the Clash Verge Service response")?;
.context("failed to connect to the Clash Verge Service")?;
Ok(response)
}
@ -335,11 +352,10 @@ pub mod win_service {
.post(url)
.json(&map)
.send()
.await
.context("failed to connect to the Clash Verge Service")?
.await?
.json::<JsonResponse>()
.await
.context("failed to parse the Clash Verge Service response")?;
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
@ -354,11 +370,10 @@ pub mod win_service {
let res = reqwest::Client::new()
.post(url)
.send()
.await
.context("failed to connect to the Clash Verge Service")?
.await?
.json::<JsonResponse>()
.await
.context("failed to parse the Clash Verge Service response")?;
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);

View File

@ -129,6 +129,7 @@ fn main() -> std::io::Result<()> {
cmds::save_profile_file,
// service mode
cmds::service::start_service,
cmds::service::stop_service,
cmds::service::check_service,
cmds::service::install_service,
cmds::service::uninstall_service,

View File

@ -0,0 +1,104 @@
import useSWR, { useSWRConfig } from "swr";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
import {
Button,
Dialog,
DialogContent,
DialogTitle,
Stack,
Typography,
} from "@mui/material";
import {
checkService,
installService,
uninstallService,
patchVergeConfig,
} from "../../services/cmds";
import Notice from "../base/base-notice";
import noop from "../../utils/noop";
interface Props {
open: boolean;
enable: boolean;
onClose: () => void;
onError?: (err: Error) => void;
}
const ServiceMode = (props: Props) => {
const { open, enable, onClose, onError = noop } = props;
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data: status } = useSWR("checkService", checkService, {
revalidateIfStale: true,
shouldRetryOnError: false,
});
const state = status != null ? status : "pending";
const onInstall = useLockFn(async () => {
try {
await installService();
mutate("checkService");
Notice.success("Service installed successfully");
onClose();
} catch (err: any) {
mutate("checkService");
onError(err);
}
});
const onUninstall = useLockFn(async () => {
try {
if (state === "active" && enable) {
await patchVergeConfig({ enable_service_mode: false });
}
await uninstallService();
Notice.success("Service uninstalled successfully");
mutate("checkService");
onClose();
} catch (err: any) {
mutate("checkService");
onError(err);
}
});
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{t("Service Mode")}</DialogTitle>
<DialogContent sx={{ width: 360, userSelect: "text" }}>
<Typography>Current State: {state}</Typography>
{(state === "unknown" || state === "uninstall") && (
<Typography>
Infomation: Please make sure the Clash Verge Service is installed
and enabled
</Typography>
)}
<Stack
direction="row"
spacing={1}
sx={{ mt: 4, justifyContent: "flex-end" }}
>
{state === "uninstall" && (
<Button variant="contained" onClick={onInstall}>
Install
</Button>
)}
{(state === "active" || state === "installed") && (
<Button variant="outlined" onClick={onUninstall}>
Uninstall
</Button>
)}
</Stack>
</DialogContent>
</Dialog>
);
};
export default ServiceMode;

View File

@ -1,24 +1,49 @@
import useSWR, { useSWRConfig } from "swr";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Box, ListItemText, Switch, TextField } from "@mui/material";
import { getVergeConfig, patchVergeConfig } from "../../services/cmds";
import {
Box,
IconButton,
ListItemText,
Switch,
TextField,
} from "@mui/material";
import { ArrowForward, PrivacyTipRounded } from "@mui/icons-material";
import {
checkService,
getVergeConfig,
patchVergeConfig,
} from "../../services/cmds";
import { SettingList, SettingItem } from "./setting";
import { CmdType } from "../../services/types";
import GuardState from "./guard-state";
import ServiceMode from "./service-mode";
import SysproxyTooltip from "./sysproxy-tooltip";
import getSystem from "../../utils/get-system";
interface Props {
onError?: (err: Error) => void;
}
const isWIN = getSystem() === "windows";
const SettingSystem = ({ onError }: Props) => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
// service mode
const [serviceOpen, setServiceOpen] = useState(false);
const { data: serviceStatus } = useSWR(
isWIN ? "checkService" : null,
checkService,
{ revalidateIfStale: true, shouldRetryOnError: false }
);
const {
enable_tun_mode,
enable_auto_launch,
enable_service_mode,
enable_silent_start,
enable_system_proxy,
system_proxy_bypass,
@ -46,6 +71,56 @@ const SettingSystem = ({ onError }: Props) => {
</GuardState>
</SettingItem>
{isWIN && (
<SettingItem>
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center" }}>
<span style={{ marginRight: 4 }}>{t("Service Mode")}</span>
{(serviceStatus === "active" ||
serviceStatus === "installed") && (
<PrivacyTipRounded
fontSize="small"
onClick={() => setServiceOpen(true)}
/>
)}
</Box>
}
/>
{serviceStatus === "active" || serviceStatus === "installed" ? (
<GuardState
value={enable_service_mode ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => onChangeData({ enable_service_mode: e })}
onGuard={(e) => patchVergeConfig({ enable_service_mode: e })}
>
<Switch edge="end" />
</GuardState>
) : (
<IconButton
color="inherit"
size="small"
onClick={() => setServiceOpen(true)}
>
<ArrowForward />
</IconButton>
)}
{serviceOpen && (
<ServiceMode
open={serviceOpen}
enable={!!enable_service_mode}
onError={onError}
onClose={() => setServiceOpen(false)}
/>
)}
</SettingItem>
)}
<SettingItem>
<ListItemText primary={t("Auto Launch")} />
<GuardState
@ -78,7 +153,7 @@ const SettingSystem = ({ onError }: Props) => {
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center" }}>
{t("System Proxy")}
<span style={{ marginRight: 4 }}>{t("System Proxy")}</span>
<SysproxyTooltip />
</Box>
}

View File

@ -41,6 +41,7 @@
"Mixed Port": "Mixed Port",
"Clash core": "Clash core",
"Tun Mode": "Tun Mode",
"Service Mode": "Service Mode",
"Auto Launch": "Auto Launch",
"Silent Start": "Silent Start",
"System Proxy": "System Proxy",

View File

@ -41,6 +41,7 @@
"Mixed Port": "端口设置",
"Clash core": "Clash 内核",
"Tun Mode": "Tun 模式",
"Service Mode": "服务模式",
"Auto Launch": "开机自启",
"Silent Start": "静默启动",
"System Proxy": "系统代理",

View File

@ -112,8 +112,19 @@ export async function startService() {
return invoke<void>("start_service");
}
export async function stopService() {
return invoke<void>("stop_service");
}
export async function checkService() {
return invoke<any>("check_service");
try {
const result = await invoke<any>("check_service");
if (result?.code === 0) return "active";
if (result?.code === 400) return "installed";
return "unknown";
} catch (err: any) {
return "uninstall";
}
}
export async function installService() {