feat: support web ui
This commit is contained in:
parent
0891b5e7b7
commit
5564c966a5
@ -257,6 +257,12 @@ pub fn open_logs_dir() -> Result<(), String> {
|
|||||||
wrap_err!(open::that(log_dir))
|
wrap_err!(open::that(log_dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// open url
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn open_web_url(url: String) -> Result<(), String> {
|
||||||
|
wrap_err!(open::that(url))
|
||||||
|
}
|
||||||
|
|
||||||
/// service mode
|
/// service mode
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod service {
|
pub mod service {
|
||||||
|
@ -46,6 +46,9 @@ pub struct Verge {
|
|||||||
/// theme setting
|
/// theme setting
|
||||||
pub theme_setting: Option<VergeTheme>,
|
pub theme_setting: Option<VergeTheme>,
|
||||||
|
|
||||||
|
/// web ui list
|
||||||
|
pub web_ui_list: Option<Vec<String>>,
|
||||||
|
|
||||||
/// clash core path
|
/// clash core path
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub clash_core: Option<String>,
|
pub clash_core: Option<String>,
|
||||||
@ -84,55 +87,31 @@ impl Verge {
|
|||||||
/// patch verge config
|
/// patch verge config
|
||||||
/// only save to file
|
/// only save to file
|
||||||
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
|
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
|
||||||
// only change it
|
macro_rules! patch {
|
||||||
if patch.language.is_some() {
|
($key: tt) => {
|
||||||
self.language = patch.language;
|
if patch.$key.is_some() {
|
||||||
|
self.$key = patch.$key;
|
||||||
}
|
}
|
||||||
if patch.theme_mode.is_some() {
|
};
|
||||||
self.theme_mode = patch.theme_mode;
|
|
||||||
}
|
|
||||||
if patch.theme_blur.is_some() {
|
|
||||||
self.theme_blur = patch.theme_blur;
|
|
||||||
}
|
|
||||||
if patch.theme_setting.is_some() {
|
|
||||||
self.theme_setting = patch.theme_setting;
|
|
||||||
}
|
|
||||||
if patch.traffic_graph.is_some() {
|
|
||||||
self.traffic_graph = patch.traffic_graph;
|
|
||||||
}
|
|
||||||
if patch.clash_core.is_some() {
|
|
||||||
self.clash_core = patch.clash_core;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// system setting
|
patch!(language);
|
||||||
if patch.enable_silent_start.is_some() {
|
patch!(theme_mode);
|
||||||
self.enable_silent_start = patch.enable_silent_start;
|
patch!(theme_blur);
|
||||||
}
|
patch!(traffic_graph);
|
||||||
if patch.enable_auto_launch.is_some() {
|
|
||||||
self.enable_auto_launch = patch.enable_auto_launch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxy
|
patch!(enable_tun_mode);
|
||||||
if patch.enable_system_proxy.is_some() {
|
patch!(enable_service_mode);
|
||||||
self.enable_system_proxy = patch.enable_system_proxy;
|
patch!(enable_auto_launch);
|
||||||
}
|
patch!(enable_silent_start);
|
||||||
if patch.system_proxy_bypass.is_some() {
|
patch!(enable_system_proxy);
|
||||||
self.system_proxy_bypass = patch.system_proxy_bypass;
|
patch!(enable_proxy_guard);
|
||||||
}
|
patch!(system_proxy_bypass);
|
||||||
if patch.enable_proxy_guard.is_some() {
|
patch!(proxy_guard_duration);
|
||||||
self.enable_proxy_guard = patch.enable_proxy_guard;
|
|
||||||
}
|
|
||||||
if patch.proxy_guard_duration.is_some() {
|
|
||||||
self.proxy_guard_duration = patch.proxy_guard_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tun mode
|
patch!(theme_setting);
|
||||||
if patch.enable_tun_mode.is_some() {
|
patch!(web_ui_list);
|
||||||
self.enable_tun_mode = patch.enable_tun_mode;
|
patch!(clash_core);
|
||||||
}
|
|
||||||
if patch.enable_service_mode.is_some() {
|
|
||||||
self.enable_service_mode = patch.enable_service_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.save_file()
|
self.save_file()
|
||||||
}
|
}
|
||||||
|
@ -108,6 +108,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
cmds::get_cur_proxy,
|
cmds::get_cur_proxy,
|
||||||
cmds::open_app_dir,
|
cmds::open_app_dir,
|
||||||
cmds::open_logs_dir,
|
cmds::open_logs_dir,
|
||||||
|
cmds::open_web_url,
|
||||||
cmds::kill_sidecar,
|
cmds::kill_sidecar,
|
||||||
cmds::restart_sidecar,
|
cmds::restart_sidecar,
|
||||||
// clash
|
// clash
|
||||||
|
31
src/components/base/base-empty.tsx
Normal file
31
src/components/base/base-empty.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { alpha, Box, Typography } from "@mui/material";
|
||||||
|
import { BlurOnRounded } from "@mui/icons-material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
text?: React.ReactNode;
|
||||||
|
extra?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BaseEmpty = (props: Props) => {
|
||||||
|
const { text = "Empty", extra } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={({ palette }) => ({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: alpha(palette.text.secondary, 0.75),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<BlurOnRounded sx={{ fontSize: "4em" }} />
|
||||||
|
<Typography sx={{ fontSize: "1.25em" }}>{text}</Typography>
|
||||||
|
{extra}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BaseEmpty;
|
106
src/components/setting/mods/web-ui-item.tsx
Normal file
106
src/components/setting/mods/web-ui-item.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { IconButton, Stack, TextField, Typography } from "@mui/material";
|
||||||
|
import {
|
||||||
|
CheckRounded,
|
||||||
|
CloseRounded,
|
||||||
|
DeleteRounded,
|
||||||
|
EditRounded,
|
||||||
|
OpenInNewRounded,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value?: string;
|
||||||
|
onlyEdit?: boolean;
|
||||||
|
onChange: (value?: string) => void;
|
||||||
|
onOpenUrl?: (value?: string) => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebUIItem = (props: Props) => {
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
onlyEdit = false,
|
||||||
|
onChange,
|
||||||
|
onDelete,
|
||||||
|
onOpenUrl,
|
||||||
|
onCancel,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
const [editValue, setEditValue] = useState(value);
|
||||||
|
|
||||||
|
if (editing || onlyEdit) {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1} direction="row" mt={1} mb={2} alignItems="center">
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
value={editValue}
|
||||||
|
onChange={(e) => setEditValue(e.target.value)}
|
||||||
|
placeholder={`Support %host %port %secret`}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
title="Save"
|
||||||
|
onClick={() => {
|
||||||
|
onChange(editValue);
|
||||||
|
setEditing(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckRounded fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
title="Cancel"
|
||||||
|
onClick={() => {
|
||||||
|
onCancel?.();
|
||||||
|
setEditing(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseRounded fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={1} direction="row" alignItems="center" mt={1} mb={2}>
|
||||||
|
<Typography
|
||||||
|
component="div"
|
||||||
|
width="100%"
|
||||||
|
title={value}
|
||||||
|
sx={{
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value || "NULL"}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
title="Open URL"
|
||||||
|
onClick={() => onOpenUrl?.(value)}
|
||||||
|
>
|
||||||
|
<OpenInNewRounded fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
title="Edit"
|
||||||
|
onClick={() => {
|
||||||
|
setEditing(true);
|
||||||
|
setEditValue(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditRounded fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton size="small" title="Delete" onClick={onDelete}>
|
||||||
|
<DeleteRounded fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebUIItem;
|
154
src/components/setting/mods/web-ui-viewer.tsx
Normal file
154
src/components/setting/mods/web-ui-viewer.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
getClashInfo,
|
||||||
|
getVergeConfig,
|
||||||
|
openWebUrl,
|
||||||
|
patchVergeConfig,
|
||||||
|
} from "@/services/cmds";
|
||||||
|
import { ModalHandler } from "@/hooks/use-modal-handler";
|
||||||
|
import BaseEmpty from "@/components/base/base-empty";
|
||||||
|
import WebUIItem from "./web-ui-item";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
handler: ModalHandler;
|
||||||
|
onError: (err: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebUIViewer = ({ handler, onError }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: vergeConfig, mutate: mutateVerge } = useSWR(
|
||||||
|
"getVergeConfig",
|
||||||
|
getVergeConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
const webUIList = vergeConfig?.web_ui_list || [];
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
handler.current = {
|
||||||
|
open: () => setOpen(true),
|
||||||
|
close: () => setOpen(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAdd = useLockFn(async (value: string) => {
|
||||||
|
const newList = [value, ...webUIList];
|
||||||
|
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
|
||||||
|
await patchVergeConfig({ web_ui_list: newList });
|
||||||
|
await mutateVerge();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = useLockFn(async (index: number, value?: string) => {
|
||||||
|
const newList = [...webUIList];
|
||||||
|
newList[index] = value ?? "";
|
||||||
|
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
|
||||||
|
await patchVergeConfig({ web_ui_list: newList });
|
||||||
|
await mutateVerge();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDelete = useLockFn(async (index: number) => {
|
||||||
|
const newList = [...webUIList];
|
||||||
|
newList.splice(index, 1);
|
||||||
|
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
|
||||||
|
await patchVergeConfig({ web_ui_list: newList });
|
||||||
|
await mutateVerge();
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: clashInfo } = useSWR("getClashInfo", getClashInfo);
|
||||||
|
|
||||||
|
const handleOpenUrl = useLockFn(async (value?: string) => {
|
||||||
|
if (!value) return;
|
||||||
|
try {
|
||||||
|
let url = value.trim().replaceAll("%host", "127.0.0.1");
|
||||||
|
|
||||||
|
if (url.includes("%port") || url.includes("%secret")) {
|
||||||
|
if (!clashInfo) throw new Error("failed to get clash info");
|
||||||
|
|
||||||
|
url = url.replaceAll("%port", clashInfo.port || "9090");
|
||||||
|
url = url.replaceAll("%secret", clashInfo.secret || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
await openWebUrl(url);
|
||||||
|
} catch (e: any) {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||||
|
<DialogTitle display="flex" justifyContent="space-between">
|
||||||
|
{t("Web UI")}
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
disabled={editing}
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
>
|
||||||
|
{t("New")}
|
||||||
|
</Button>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent
|
||||||
|
sx={{
|
||||||
|
width: 450,
|
||||||
|
height: 300,
|
||||||
|
pb: 1,
|
||||||
|
overflowY: "auto",
|
||||||
|
userSelect: "text",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{editing && (
|
||||||
|
<WebUIItem
|
||||||
|
value=""
|
||||||
|
onlyEdit
|
||||||
|
onChange={(v) => {
|
||||||
|
setEditing(false);
|
||||||
|
handleAdd(v || "");
|
||||||
|
}}
|
||||||
|
onCancel={() => setEditing(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!editing && webUIList.length === 0 && (
|
||||||
|
<BaseEmpty
|
||||||
|
text="Empty List"
|
||||||
|
extra={
|
||||||
|
<Typography mt={2} sx={{ fontSize: "12px" }}>
|
||||||
|
Replace host, port, secret with "%host" "%port" "%secret"
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webUIList.map((item, index) => (
|
||||||
|
<WebUIItem
|
||||||
|
key={index}
|
||||||
|
value={item}
|
||||||
|
onChange={(v) => handleChange(index, v)}
|
||||||
|
onDelete={() => handleDelete(index)}
|
||||||
|
onOpenUrl={handleOpenUrl}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpen(false)}>{t("Back")}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebUIViewer;
|
@ -11,12 +11,14 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { atomClashPort } from "@/services/states";
|
import { atomClashPort } from "@/services/states";
|
||||||
import { ArrowForward } from "@mui/icons-material";
|
import { ArrowForward } from "@mui/icons-material";
|
||||||
import { openWebUrl, patchClashConfig } from "@/services/cmds";
|
import { patchClashConfig } from "@/services/cmds";
|
||||||
import { SettingList, SettingItem } from "./setting";
|
import { SettingList, SettingItem } from "./setting";
|
||||||
import { getClashConfig, getVersion, updateConfigs } from "@/services/api";
|
import { getClashConfig, getVersion, updateConfigs } from "@/services/api";
|
||||||
|
import useModalHandler from "@/hooks/use-modal-handler";
|
||||||
import Notice from "../base/base-notice";
|
import Notice from "../base/base-notice";
|
||||||
import GuardState from "./mods/guard-state";
|
import GuardState from "./mods/guard-state";
|
||||||
import CoreSwitch from "./mods/core-switch";
|
import CoreSwitch from "./mods/core-switch";
|
||||||
|
import WebUIViewer from "./mods/web-ui-viewer";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError: (err: Error) => void;
|
onError: (err: Error) => void;
|
||||||
@ -37,6 +39,8 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const setGlobalClashPort = useSetRecoilState(atomClashPort);
|
const setGlobalClashPort = useSetRecoilState(atomClashPort);
|
||||||
|
|
||||||
|
const webUIHandler = useModalHandler();
|
||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const onSwitchFormat = (_e: any, value: boolean) => value;
|
||||||
const onChangeData = (patch: Partial<ApiType.ConfigData>) => {
|
const onChangeData = (patch: Partial<ApiType.ConfigData>) => {
|
||||||
mutate("getClashConfig", { ...clashConfig, ...patch }, false);
|
mutate("getClashConfig", { ...clashConfig, ...patch }, false);
|
||||||
@ -68,6 +72,8 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingList title={t("Clash Setting")}>
|
<SettingList title={t("Clash Setting")}>
|
||||||
|
<WebUIViewer handler={webUIHandler} onError={onError} />
|
||||||
|
|
||||||
<SettingItem label={t("Allow Lan")}>
|
<SettingItem label={t("Allow Lan")}>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={allowLan ?? false}
|
value={allowLan ?? false}
|
||||||
@ -94,6 +100,17 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem label={t("Web UI")}>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: "2px" }}
|
||||||
|
onClick={() => webUIHandler.current.open()}
|
||||||
|
>
|
||||||
|
<ArrowForward />
|
||||||
|
</IconButton>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem label={t("Log Level")}>
|
<SettingItem label={t("Log Level")}>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={logLevel ?? "info"}
|
value={logLevel ?? "info"}
|
||||||
@ -132,12 +149,6 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
<SettingItem label={t("Clash Core")} extra={<CoreSwitch />}>
|
<SettingItem label={t("Clash Core")} extra={<CoreSwitch />}>
|
||||||
<Typography sx={{ py: "7px" }}>{clashVer}</Typography>
|
<Typography sx={{ py: "7px" }}>{clashVer}</Typography>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
{/* <SettingItem label={t("Web UI")}>
|
|
||||||
<IconButton color="inherit" size="small" sx={{ my: "2px" }}>
|
|
||||||
<ArrowForward />
|
|
||||||
</IconButton>
|
|
||||||
</SettingItem> */}
|
|
||||||
</SettingList>
|
</SettingList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
14
src/hooks/use-modal-handler.ts
Normal file
14
src/hooks/use-modal-handler.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { MutableRefObject, useRef } from "react";
|
||||||
|
|
||||||
|
interface Handler {
|
||||||
|
open: () => void;
|
||||||
|
close: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModalHandler = MutableRefObject<Handler>;
|
||||||
|
|
||||||
|
const useModalHandler = (): ModalHandler => {
|
||||||
|
return useRef({ open: () => {}, close: () => {} });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useModalHandler;
|
@ -60,6 +60,7 @@
|
|||||||
"theme.dark": "Dark",
|
"theme.dark": "Dark",
|
||||||
"theme.system": "System",
|
"theme.system": "System",
|
||||||
|
|
||||||
|
"Back": "Back",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Cancel": "Cancel"
|
"Cancel": "Cancel"
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"theme.dark": "深色",
|
"theme.dark": "深色",
|
||||||
"theme.system": "系统",
|
"theme.system": "系统",
|
||||||
|
|
||||||
|
"Back": "返回",
|
||||||
"Save": "保存",
|
"Save": "保存",
|
||||||
"Cancel": "取消"
|
"Cancel": "取消"
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,10 @@ export async function openLogsDir() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function openWebUrl(url: string) {
|
||||||
|
return invoke<void>("open_web_url", { url });
|
||||||
|
}
|
||||||
|
|
||||||
/// service mode
|
/// service mode
|
||||||
|
|
||||||
export async function startService() {
|
export async function startService() {
|
||||||
|
1
src/services/types.d.ts
vendored
1
src/services/types.d.ts
vendored
@ -137,6 +137,7 @@ declare namespace CmdType {
|
|||||||
enable_system_proxy?: boolean;
|
enable_system_proxy?: boolean;
|
||||||
enable_proxy_guard?: boolean;
|
enable_proxy_guard?: boolean;
|
||||||
system_proxy_bypass?: string;
|
system_proxy_bypass?: string;
|
||||||
|
web_ui_list?: string[];
|
||||||
theme_setting?: {
|
theme_setting?: {
|
||||||
primary_color?: string;
|
primary_color?: string;
|
||||||
secondary_color?: string;
|
secondary_color?: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user