diff --git a/src/components/connection-item.tsx b/src/components/connection-item.tsx new file mode 100644 index 0000000..34fa09b --- /dev/null +++ b/src/components/connection-item.tsx @@ -0,0 +1,13 @@ +import { ApiType } from "../services/types"; + +interface Props { + value: ApiType.ConnectionsItem; +} + +const ConnectionItem = (props: Props) => { + const { value } = props; + + return
{value.metadata.host || value.metadata.destinationIP}
; +}; + +export default ConnectionItem; diff --git a/src/components/list-item-link.tsx b/src/components/layout-item.tsx similarity index 94% rename from src/components/list-item-link.tsx rename to src/components/layout-item.tsx index 3e45320..545b36e 100644 --- a/src/components/list-item-link.tsx +++ b/src/components/layout-item.tsx @@ -2,7 +2,7 @@ import { alpha, ListItem, ListItemButton, ListItemText } from "@mui/material"; import { useMatch, useResolvedPath, useNavigate } from "react-router-dom"; import type { LinkProps } from "react-router-dom"; -const ListItemLink = (props: LinkProps) => { +const LayoutItem = (props: LinkProps) => { const { to, children } = props; const resolved = useResolvedPath(to); @@ -41,4 +41,4 @@ const ListItemLink = (props: LinkProps) => { ); }; -export default ListItemLink; +export default LayoutItem; diff --git a/src/components/log-item.tsx b/src/components/log-item.tsx index 89e6629..5a4da19 100644 --- a/src/components/log-item.tsx +++ b/src/components/log-item.tsx @@ -1,6 +1,7 @@ import { styled, Box } from "@mui/material"; +import { ApiType } from "../services/types"; -const LogItem = styled(Box)(({ theme }) => ({ +const Item = styled(Box)(({ theme }) => ({ padding: "8px 0", margin: "0 12px", lineHeight: 1.35, @@ -8,8 +9,7 @@ const LogItem = styled(Box)(({ theme }) => ({ "& .time": {}, "& .type": { display: "inline-block", - width: 50, - margin: "0 4px", + padding: "0 6px", textAlign: "center", borderRadius: 2, textTransform: "uppercase", @@ -18,4 +18,20 @@ const LogItem = styled(Box)(({ theme }) => ({ "& .data": {}, })); +interface Props { + value: ApiType.LogItem; +} + +const LogItem = (props: Props) => { + const { value } = props; + + return ( + + {value.time} + {value.type} + {value.payload} + + ); +}; + export default LogItem; diff --git a/src/components/profile-item.tsx b/src/components/profile-item.tsx index 1140b62..cbc5e6a 100644 --- a/src/components/profile-item.tsx +++ b/src/components/profile-item.tsx @@ -10,7 +10,7 @@ import { IconButton, } from "@mui/material"; import { MenuRounded } from "@mui/icons-material"; -import { ProfileItem } from "../services/command"; +import { CmdType } from "../services/types"; import parseTraffic from "../utils/parse-traffic"; import relativeTime from "dayjs/plugin/relativeTime"; @@ -29,7 +29,7 @@ const Wrapper = styled(Box)(({ theme }) => ({ interface Props { selected: boolean; - itemData: ProfileItem; + itemData: CmdType.ProfileItem; onClick: () => void; onUpdate: () => void; } diff --git a/src/components/proxy-group.tsx b/src/components/proxy-group.tsx index bf3bb4e..e20441c 100644 --- a/src/components/proxy-group.tsx +++ b/src/components/proxy-group.tsx @@ -20,11 +20,11 @@ import { NetworkCheckRounded, CheckCircleOutlineRounded, } from "@mui/icons-material"; -import services from "../services"; -import type { ProxyItem, ProxyGroupItem } from "../services/proxy"; +import { updateProxy } from "../services/api"; +import { ApiType } from "../services/types"; interface ItemProps { - proxy: ProxyItem; + proxy: ApiType.ProxyItem; selected: boolean; onClick?: (name: string) => void; } @@ -66,7 +66,7 @@ const Item = ({ proxy, selected, onClick }: ItemProps) => { }; interface Props { - group: ProxyGroupItem; + group: ApiType.ProxyGroupItem; } const ProxyGroup = ({ group }: Props) => { @@ -85,7 +85,7 @@ const ProxyGroup = ({ group }: Props) => { const oldValue = now; try { setNow(name); - await services.updateProxy(group.name, name); + await updateProxy(group.name, name); } catch { setNow(oldValue); // Todo diff --git a/src/components/setting-clash.tsx b/src/components/setting-clash.tsx index 72092c7..9b6fdcf 100644 --- a/src/components/setting-clash.tsx +++ b/src/components/setting-clash.tsx @@ -8,8 +8,9 @@ import { Select, MenuItem, } from "@mui/material"; -import { ConfigType, getClashConfig, updateConfigs } from "../services/common"; -import { patchClashConfig } from "../services/command"; +import { getClashConfig, updateConfigs } from "../services/api"; +import { patchClashConfig } from "../services/cmds"; +import { ApiType } from "../services/types"; import GuardState from "./guard-state"; import SettingItem from "./setting-item"; @@ -30,11 +31,11 @@ const SettingClash = ({ onError }: Props) => { const onSwitchFormat = (_e: any, value: boolean) => value; - const onChangeData = (patch: Partial) => { + const onChangeData = (patch: Partial) => { mutate("getClashConfig", { ...clashConfig, ...patch }, false); }; - const onUpdateData = async (patch: Partial) => { + const onUpdateData = async (patch: Partial) => { await updateConfigs(patch); await patchClashConfig(patch); }; diff --git a/src/components/setting-verge.tsx b/src/components/setting-verge.tsx index ccfecdc..2f9fedb 100644 --- a/src/components/setting-verge.tsx +++ b/src/components/setting-verge.tsx @@ -4,8 +4,8 @@ import { getVergeConfig, patchVergeConfig, setSysProxy, - VergeConfig, -} from "../services/command"; +} from "../services/cmds"; +import { CmdType } from "../services/types"; import GuardState from "./guard-state"; import SettingItem from "./setting-item"; import PaletteSwitch from "./palette-switch"; @@ -26,7 +26,7 @@ const SettingVerge = ({ onError }: Props) => { const onSwitchFormat = (_e: any, value: boolean) => value; - const onChangeData = (patch: Partial) => { + const onChangeData = (patch: Partial) => { mutate("getVergeConfig", { ...vergeConfig, ...patch }, false); }; diff --git a/src/components/traffic.tsx b/src/components/traffic.tsx index 2786306..caee800 100644 --- a/src/components/traffic.tsx +++ b/src/components/traffic.tsx @@ -1,32 +1,22 @@ -import { CancelTokenSource } from "axios"; import { useEffect, useState } from "react"; import { Box, Typography } from "@mui/material"; import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; +import { getInfomation } from "../services/api"; +import { ApiType } from "../services/types"; import parseTraffic from "../utils/parse-traffic"; -import services from "../services"; const Traffic = () => { const [traffic, setTraffic] = useState({ up: 0, down: 0 }); useEffect(() => { - let timer: any = null; - let source: CancelTokenSource | null = null; + const { server, secret } = getInfomation(); + const ws = new WebSocket(`ws://${server}/traffic?token=${secret}`); - async function onTraffic() { - timer = null; - try { - source = await services.getTraffic(setTraffic); - } catch { - timer = setTimeout(onTraffic, 500); - } - } + ws.addEventListener("message", (event) => { + setTraffic(JSON.parse(event.data) as ApiType.TrafficItem); + }); - onTraffic(); - - return () => { - if (timer) clearTimeout(timer); - source?.cancel(); - }; + return () => ws.close(); }, []); const [up, upUnit] = parseTraffic(traffic.up); diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index 72a61f2..1939610 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -4,7 +4,8 @@ import { Route, Routes } from "react-router-dom"; import { useRecoilState } from "recoil"; import { createTheme, List, Paper, ThemeProvider } from "@mui/material"; import { atomPaletteMode } from "../states/setting"; -import { getVergeConfig } from "../services/command"; +import { getClashInfo, getVergeConfig } from "../services/cmds"; +import { initAxios } from "../services/api"; import LogoSvg from "../assets/image/logo.svg"; import LogPage from "./log"; import HomePage from "./home"; @@ -12,7 +13,7 @@ import ProfilePage from "./profile"; import ProxyPage from "./proxy"; import SettingPage from "./setting"; import ConnectionsPage from "./connections"; -import ListItemLink from "../components/list-item-link"; +import LayoutItem from "../components/layout-item"; import Traffic from "../components/traffic"; const routers = [ @@ -42,6 +43,12 @@ const Layout = () => { const [mode, setMode] = useRecoilState(atomPaletteMode); const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig); + useEffect(() => { + getClashInfo() + .then((result) => initAxios(result?.controller ?? {})) + .catch(() => console.error("can not initialize clash verge")); + }, []); + useEffect(() => { setMode(vergeConfig?.theme_mode ?? "light"); }, [vergeConfig?.theme_mode]); @@ -95,9 +102,9 @@ const Layout = () => { {routers.map((router) => ( - + {router.label} - + ))} diff --git a/src/pages/connections.tsx b/src/pages/connections.tsx index 2bd1695..8a7b534 100644 --- a/src/pages/connections.tsx +++ b/src/pages/connections.tsx @@ -1,21 +1,45 @@ -import { useEffect } from "react"; -import { Box, Typography } from "@mui/material"; -import services from "../services"; +import { useEffect, useState } from "react"; +import { Box, Paper, Typography } from "@mui/material"; +import { Virtuoso } from "react-virtuoso"; +import { getInfomation } from "../services/api"; +import { ApiType } from "../services/types"; +import ConnectionItem from "../components/connection-item"; const ConnectionsPage = () => { - useEffect(() => { - const sourcePromise = services.getLogs(console.log); + const initConn = { uploadTotal: 0, downloadTotal: 0, connections: [] }; + const [conn, setConn] = useState(initConn); - return () => { - sourcePromise.then((src) => src.cancel()); - }; + useEffect(() => { + const { server, secret } = getInfomation(); + const ws = new WebSocket(`ws://${server}/connections?token=${secret}`); + + ws.addEventListener("message", (event) => { + const data = JSON.parse(event.data) as ApiType.Connections; + setConn(data); + }); + + return () => ws.close(); }, []); return ( - + Connections + + + } + /> + ); }; diff --git a/src/pages/log.tsx b/src/pages/log.tsx index cf26566..f10a58c 100644 --- a/src/pages/log.tsx +++ b/src/pages/log.tsx @@ -2,24 +2,26 @@ import dayjs from "dayjs"; import { useEffect, useRef, useState } from "react"; import { Box, Button, Paper, Typography } from "@mui/material"; import { Virtuoso } from "react-virtuoso"; +import { ApiType } from "../services/types"; +import { getInfomation } from "../services/api"; import LogItem from "../components/log-item"; -import services from "../services"; -let logCache: any[] = []; +let logCache: ApiType.LogItem[] = []; const LogPage = () => { - const [logData, setLogData] = useState(logCache); + const [logData, setLogData] = useState(logCache); useEffect(() => { - const sourcePromise = services.getLogs((t) => { + const info = getInfomation(); + const ws = new WebSocket(`ws://${info.server}/logs?token=${info.secret}`); + + ws.addEventListener("message", (event) => { + const data = JSON.parse(event.data) as ApiType.LogItem; const time = dayjs().format("MM-DD HH:mm:ss"); - const item = { ...t, time }; - setLogData((l) => (logCache = [...l, item])); + setLogData((l) => (logCache = [...l, { ...data, time }])); }); - return () => { - sourcePromise.then((src) => src.cancel("cancel")); - }; + return () => ws.close(); }, []); return ( @@ -52,15 +54,7 @@ const LogPage = () => { { - return ( - - {logItem.time} - {logItem.type} - {logItem.payload} - - ); - }} + itemContent={(index, item) => } followOutput={"smooth"} /> diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index 0e23174..8cac0c4 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -1,13 +1,13 @@ import { useRef, useState } from "react"; import useSWR, { useSWRConfig } from "swr"; import { Box, Button, Grid, TextField, Typography } from "@mui/material"; -import services from "../services"; import { getProfiles, importProfile, putProfiles, updateProfile, -} from "../services/command"; +} from "../services/cmds"; +import { getProxies } from "../services/api"; import ProfileItemComp from "../components/profile-item"; import useNotice from "../utils/use-notice"; import noop from "../utils/noop"; @@ -44,7 +44,7 @@ const ProfilePage = () => { putProfiles(index) .then(() => { mutate("getProfiles", { ...profiles, current: index }, true); - mutate("getProxies", services.getProxies()); + mutate("getProxies", getProxies()); }) .catch((err) => { console.error(err); diff --git a/src/pages/proxy.tsx b/src/pages/proxy.tsx index defaa07..98a236f 100644 --- a/src/pages/proxy.tsx +++ b/src/pages/proxy.tsx @@ -1,10 +1,10 @@ import useSWR from "swr"; import { Box, List, Paper, Typography } from "@mui/material"; -import services from "../services"; +import { getProxies } from "../services/api"; import ProxyGroup from "../components/proxy-group"; const ProxyPage = () => { - const { data } = useSWR("getProxies", services.getProxies); + const { data } = useSWR("getProxies", getProxies); const { groups = [] } = data ?? {}; return ( diff --git a/src/services/api.ts b/src/services/api.ts new file mode 100644 index 0000000..f4cdb5c --- /dev/null +++ b/src/services/api.ts @@ -0,0 +1,87 @@ +import axios, { AxiosInstance } from "axios"; +import { ApiType } from "./types"; + +let axiosIns: AxiosInstance = null!; +let server = "127.0.0.1:9090"; +let secret = ""; + +type Callback = (data: T) => void; + +/// initialize some infomation +export function initAxios(info: { server?: string; secret?: string }) { + if (info.server) server = info.server; + if (info.secret) secret = info.secret; + + axiosIns = axios.create({ + baseURL: `http://${server}`, + headers: secret ? { Authorization: `Bearer ${secret}` } : {}, + }); + axiosIns.interceptors.response.use((r) => r.data); +} + +/// get infomation +export function getInfomation() { + return { server, secret }; +} + +/// Get Version +export async function getVersion() { + return axiosIns.get("/version") as Promise<{ + premium: boolean; + version: string; + }>; +} + +/// Get current base configs +export async function getClashConfig() { + return axiosIns.get("/configs") as Promise; +} + +/// Update current configs +export async function updateConfigs(config: Partial) { + return axiosIns.patch("/configs", config); +} + +/// Get current rules +export async function getRules() { + return axiosIns.get("/rules") as Promise; +} + +/// Update the Proxy Choose +export async function updateProxy(group: string, proxy: string) { + return axiosIns.put(`/proxies/${group}`, { name: proxy }); +} + +/// Get the Proxy infomation +export async function getProxies() { + const response = await axiosIns.get("/proxies"); + const proxies = (response?.proxies ?? {}) as Record< + string, + ApiType.ProxyItem + >; + + const global = proxies["GLOBAL"]; + const order = global?.all; + + let groups: ApiType.ProxyGroupItem[] = []; + + if (order) { + groups = order + .filter((name) => proxies[name]?.all) + .map((name) => proxies[name]) + .map((each) => ({ + ...each, + all: each.all!.map((item) => proxies[item]), + })); + } else { + groups = Object.values(proxies) + .filter((each) => each.name !== "GLOBAL" && each.all) + .map((each) => ({ + ...each, + all: each.all!.map((item) => proxies[item]), + })); + groups.sort((a, b) => b.name.localeCompare(a.name)); + } + + return { global, groups, proxies }; +} diff --git a/src/services/base.ts b/src/services/base.ts deleted file mode 100644 index 952d36e..0000000 --- a/src/services/base.ts +++ /dev/null @@ -1,26 +0,0 @@ -import axios, { AxiosInstance } from "axios"; -import { getClashInfo } from "./command"; - -let axiosIns: AxiosInstance | null = null; - -export async function getAxios() { - if (axiosIns) return axiosIns; - - let server = "127.0.0.1:9090"; - let secret = ""; - - try { - const info = await getClashInfo(); - const { server: server_, secret: secret_ } = info?.controller ?? {}; - if (server_) server = server_; - if (secret_) secret = secret_; - } catch {} - - axiosIns = axios.create({ - baseURL: `http://${server}`, - headers: secret ? { Authorization: `Bearer ${secret}` } : {}, - }); - axiosIns.interceptors.response.use((r) => r.data); - - return axiosIns; -} diff --git a/src/services/cmds.ts b/src/services/cmds.ts new file mode 100644 index 0000000..4ca2495 --- /dev/null +++ b/src/services/cmds.ts @@ -0,0 +1,49 @@ +import { invoke } from "@tauri-apps/api/tauri"; +import { ApiType, CmdType } from "./types"; + +export async function restartSidecar() { + return invoke("restart_sidecar"); +} + +export async function getClashInfo() { + return invoke("get_clash_info"); +} + +export async function patchClashConfig(payload: Partial) { + return invoke("patch_clash_config", { payload }); +} + +export async function importProfile(url: string) { + return invoke("import_profile", { url }); +} + +export async function updateProfile(index: number) { + return invoke("update_profile", { index }); +} + +export async function getProfiles() { + return (await invoke("get_profiles")) ?? {}; +} + +export async function setProfiles( + current: number, + profile: CmdType.ProfileItem +) { + return invoke("set_profiles", { current, profile }); +} + +export async function putProfiles(current: number) { + return invoke("put_profiles", { current }); +} + +export async function setSysProxy(enable: boolean) { + return invoke("set_sys_proxy", { enable }); +} + +export async function getVergeConfig() { + return invoke("get_verge_config"); +} + +export async function patchVergeConfig(payload: CmdType.VergeConfig) { + return invoke("patch_verge_config", { payload }); +} diff --git a/src/services/command.ts b/src/services/command.ts deleted file mode 100644 index d6d3c36..0000000 --- a/src/services/command.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { invoke } from "@tauri-apps/api/tauri"; -import { ConfigType } from "./common"; - -export async function restartSidecar() { - return invoke("restart_sidecar"); -} - -export interface ClashInfo { - status: string; - controller?: { server?: string; secret?: string }; - message?: string; -} - -export async function getClashInfo() { - return invoke("get_clash_info"); -} - -export async function patchClashConfig(payload: Partial) { - return invoke("patch_clash_config", { payload }); -} - -export async function importProfile(url: string) { - return invoke("import_profile", { url }); -} - -export async function updateProfile(index: number) { - return invoke("update_profile", { index }); -} - -export interface ProfileItem { - name?: string; - file?: string; - mode?: string; - url?: string; - updated?: number; - selected?: { name?: string; now?: string }[]; - extra?: { - upload: number; - download: number; - total: number; - expire: number; - }; -} - -export interface ProfilesConfig { - current?: number; - items?: ProfileItem[]; -} - -export async function getProfiles() { - return (await invoke("get_profiles")) ?? {}; -} - -export async function setProfiles(current: number, profile: ProfileItem) { - return invoke("set_profiles", { current, profile }); -} - -export async function putProfiles(current: number) { - return invoke("put_profiles", { current }); -} - -export async function setSysProxy(enable: boolean) { - return invoke("set_sys_proxy", { enable }); -} - -export interface VergeConfig { - theme_mode?: "light" | "dark"; - enable_self_startup?: boolean; - enable_system_proxy?: boolean; -} - -export async function getVergeConfig() { - return invoke("get_verge_config"); -} - -export async function patchVergeConfig(payload: VergeConfig) { - return invoke("patch_verge_config", { payload }); -} diff --git a/src/services/common.ts b/src/services/common.ts deleted file mode 100644 index 604a85e..0000000 --- a/src/services/common.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from "axios"; -import { getAxios } from "./base"; - -/// Get Version -export async function getVersion() { - return (await getAxios()).get("/version") as Promise<{ - premium: boolean; - version: string; - }>; -} - -export interface ConfigType { - port: number; - mode: string; - ipv6: boolean; - "socket-port": number; - "allow-lan": boolean; - "log-level": string; - "mixed-port": number; - "redir-port": number; - "socks-port": number; - "tproxy-port": number; -} - -/// Get current base configs -export async function getClashConfig() { - return (await getAxios()).get("/configs") as Promise; -} - -/// Update current configs -export async function updateConfigs(config: Partial) { - return (await getAxios()).patch("/configs", config); -} - -interface RuleItem { - type: string; - payload: string; - proxy: string; -} - -/// Get current rules -export async function getRules() { - return (await getAxios()).get("/rules") as Promise; -} - -/// Get logs stream -export async function getLogs(callback: (t: any) => void) { - const source = axios.CancelToken.source(); - - (await getAxios()).get("/logs", { - cancelToken: source.token, - onDownloadProgress: (progressEvent) => { - const data = progressEvent.currentTarget.response || ""; - const lastData = data.slice(data.trim().lastIndexOf("\n") + 1); - callback(JSON.parse(lastData)); - }, - }); - - return source; -} diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index 434a9d3..0000000 --- a/src/services/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as common from "./common"; -import * as proxy from "./proxy"; -import * as traffic from "./traffic"; - -export default { - ...common, - ...proxy, - ...traffic, -}; diff --git a/src/services/proxy.ts b/src/services/proxy.ts deleted file mode 100644 index 1f29ac9..0000000 --- a/src/services/proxy.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { getAxios } from "./base"; - -export interface ProxyItem { - name: string; - type: string; - udp: boolean; - history: { - time: string; - delay: number; - }[]; - all?: string[]; - now?: string; -} - -export type ProxyGroupItem = Omit & { - all: ProxyItem[]; -}; - -/// Get the Proxy infomation -export async function getProxies() { - const axiosIns = await getAxios(); - const response = await axiosIns.get("/proxies"); - const proxies = (response?.proxies ?? {}) as Record; - - const global = proxies["GLOBAL"]; - const order = global?.all; - - let groups: ProxyGroupItem[] = []; - - if (order) { - groups = order - .filter((name) => proxies[name]?.all) - .map((name) => proxies[name]) - .map((each) => ({ - ...each, - all: each.all!.map((item) => proxies[item]), - })); - } else { - groups = Object.values(proxies) - .filter((each) => each.name !== "GLOBAL" && each.all) - .map((each) => ({ - ...each, - all: each.all!.map((item) => proxies[item]), - })); - groups.sort((a, b) => b.name.localeCompare(a.name)); - } - - return { global, groups, proxies }; -} - -/// Update the Proxy Choose -export async function updateProxy(group: string, proxy: string) { - return (await getAxios()).put(`/proxies/${group}`, { name: proxy }); -} diff --git a/src/services/traffic.ts b/src/services/traffic.ts deleted file mode 100644 index a4c760f..0000000 --- a/src/services/traffic.ts +++ /dev/null @@ -1,29 +0,0 @@ -import axios from "axios"; -import { getAxios } from "./base"; - -export interface TrafficData { - up: number; - down: number; -} - -/// Get the traffic stream -export async function getTraffic(callback: (data: TrafficData) => void) { - const source = axios.CancelToken.source(); - - (await getAxios()).get("/traffic", { - cancelToken: source.token, - onDownloadProgress: (progressEvent) => { - const data = progressEvent.currentTarget.response || ""; - const lastData = data.slice(data.trim().lastIndexOf("\n") + 1); - - if (!lastData) callback({ up: 0, down: 0 }); - try { - callback(JSON.parse(lastData) as TrafficData); - } catch { - callback({ up: 0, down: 0 }); - } - }, - }); - - return source; -} diff --git a/src/services/types.ts b/src/services/types.ts new file mode 100644 index 0000000..3544c42 --- /dev/null +++ b/src/services/types.ts @@ -0,0 +1,115 @@ +/** + * Some interface for clash api + */ +export namespace ApiType { + export interface ConfigData { + port: number; + mode: string; + ipv6: boolean; + "socket-port": number; + "allow-lan": boolean; + "log-level": string; + "mixed-port": number; + "redir-port": number; + "socks-port": number; + "tproxy-port": number; + } + + export interface RuleItem { + type: string; + payload: string; + proxy: string; + } + + export interface ProxyItem { + name: string; + type: string; + udp: boolean; + history: { + time: string; + delay: number; + }[]; + all?: string[]; + now?: string; + } + + export type ProxyGroupItem = Omit & { + all: ProxyItem[]; + }; + + export interface TrafficItem { + up: number; + down: number; + } + + export interface LogItem { + type: string; + time?: string; + payload: string; + } + + export interface ConnectionsItem { + id: string; + metadata: { + network: string; + type: string; + host: string; + sourceIP: string; + sourcePort: string; + destinationPort: string; + destinationIP?: string; + }; + upload: number; + download: number; + start: string; + chains: string[]; + rule: string; + rulePayload: string; + } + + export interface Connections { + downloadTotal: number; + uploadTotal: number; + connections: ConnectionsItem[]; + } +} + +/** + * Some interface for command + */ +export namespace CmdType { + export interface ClashInfo { + status: string; + controller?: { server?: string; secret?: string }; + message?: string; + } + + export interface ProfileItem { + name?: string; + file?: string; + mode?: string; + url?: string; + updated?: number; + selected?: { + name?: string; + now?: string; + }[]; + extra?: { + upload: number; + download: number; + total: number; + expire: number; + }; + } + + export interface ProfilesConfig { + current?: number; + items?: ProfileItem[]; + } + + export interface VergeConfig { + theme_mode?: "light" | "dark"; + enable_self_startup?: boolean; + enable_system_proxy?: boolean; + } +}