diff --git a/.github/update.log b/.github/update.log index 00ec6b198e..bfeabb2c25 100644 --- a/.github/update.log +++ b/.github/update.log @@ -665,3 +665,4 @@ Update On Wed May 29 20:30:48 CEST 2024 Update On Thu May 30 20:33:19 CEST 2024 Update On Fri May 31 20:31:11 CEST 2024 Update On Fri Jun 7 01:07:11 CEST 2024 +Update On Fri Jun 7 20:34:28 CEST 2024 diff --git a/clash-meta/go.mod b/clash-meta/go.mod index bc32a633bd..a6664b5909 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -19,7 +19,7 @@ require ( github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/mdlayher/netlink v1.7.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.44.1-0.20240521004242-fcd70d587e22 + github.com/metacubex/quic-go v0.45.1-0.20240607133845-b24f02b35a22 github.com/metacubex/randv2 v0.2.0 github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 github.com/metacubex/sing-shadowsocks v0.2.6 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 6dbbfd77ae..c75275bce0 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -104,8 +104,8 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= -github.com/metacubex/quic-go v0.44.1-0.20240521004242-fcd70d587e22 h1:hsQ0b2A509b6ubnLtLOcUgZ8vOb+d/667zVEJ1T2fao= -github.com/metacubex/quic-go v0.44.1-0.20240521004242-fcd70d587e22/go.mod h1:88wAATpevav4xdy5N8oejQ2cbbI6EcLYEklFeo+qywA= +github.com/metacubex/quic-go v0.45.1-0.20240607133845-b24f02b35a22 h1:dKYoWnrB5bbCMoMQit4INUDKiDcjc0Azsm3GltYf9Pw= +github.com/metacubex/quic-go v0.45.1-0.20240607133845-b24f02b35a22/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/sing v0.0.0-20240518125217-e63d65a914d1 h1:7hDHLTmjgtRoAp59STwPQpe5Pinwi4cWex+FB3Ohvco= diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index a049097a48..e8d718e634 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "a9689a29b593160de5bc4aacab7b5d54fb52231de70122626c178e6a368994c7" dependencies = [ "clap_builder", "clap_derive", @@ -829,9 +829,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "2e5387378c84f6faa26890ebf9f0a92989f8873d4d380467bcd0d8d8620424df" dependencies = [ "anstream", "anstyle", @@ -841,9 +841,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3079,7 +3079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] diff --git a/clash-nyanpasu/frontend/interface/ipc/useClashCore.ts b/clash-nyanpasu/frontend/interface/ipc/useClashCore.ts index ed8f9bf2c4..b60d0f30c4 100644 --- a/clash-nyanpasu/frontend/interface/ipc/useClashCore.ts +++ b/clash-nyanpasu/frontend/interface/ipc/useClashCore.ts @@ -1,4 +1,9 @@ -import { Clash, clash as clashApi } from "@/service"; +import { + Clash, + ProviderItem, + ProviderRules, + clash as clashApi, +} from "@/service"; import * as tauri from "@/service/tauri"; import useSWR from "swr"; @@ -47,6 +52,28 @@ export const useClashCore = () => { const getRules = useSWR("getRules", clash.getRules); + const getRulesProviders = useSWR<{ [name: string]: ProviderRules }>( + "getRulesProviders", + clash.getRulesProviders, + ); + + const updateRulesProviders = async (name: string) => { + await clash.updateRulesProviders(name); + + await getRulesProviders.mutate(); + }; + + const getProxiesProviders = useSWR<{ [name: string]: ProviderItem }>( + "getProxiesProviders", + clash.getProxiesProviders, + ); + + const updateProxiesProviders = async (name: string) => { + await clash.updateProxiesProviders(name); + + await getProxiesProviders.mutate(); + }; + return { data, isLoading, @@ -54,35 +81,9 @@ export const useClashCore = () => { updateProxiesDelay, setGroupProxy, getRules, + getRulesProviders, + updateRulesProviders, + getProxiesProviders, + updateProxiesProviders, }; }; - -// export class UseClashCore { -// public proxies; - -// constructor() { -// this.proxies = useSWR("getProxies", getProxies); -// } - -// public async updateGroupDelay(index: number, options?: Clash.DelayOptions) { -// console.log(index); -// // const group = this.proxies.data?.groups[index]; -// console.log(this.proxies.data?.groups); - -// // if (!group) { -// // return; -// // } - -// // const result = await getGroupDelay(group?.name, options); - -// // console.log(result); - -// // group.all?.forEach((item) => { -// // if (result) -// // }) - -// // Object.entries(result).forEach(([name, delay]) => { - -// // }) -// } -// } diff --git a/clash-nyanpasu/frontend/interface/ipc/useClashWS.ts b/clash-nyanpasu/frontend/interface/ipc/useClashWS.ts index fddfb59411..cbfb740b68 100644 --- a/clash-nyanpasu/frontend/interface/ipc/useClashWS.ts +++ b/clash-nyanpasu/frontend/interface/ipc/useClashWS.ts @@ -17,15 +17,21 @@ export const useClashWS = () => { return `${getBaseUrl()}/${path}?${getTokenUrl()}`; }; - const connectionsUrl = useMemo(() => { + const url = useMemo(() => { if (getClashInfo.data) { - return resolveUrl("connections"); + return { + connections: resolveUrl("connections"), + logs: resolveUrl("logs"), + }; } }, [getClashInfo.data]); - const connections = useWebSocket(connectionsUrl ?? ""); + const connections = useWebSocket(url?.connections ?? ""); + + const logs = useWebSocket(url?.logs ?? ""); return { connections, + logs, }; }; diff --git a/clash-nyanpasu/frontend/interface/service/clash.ts b/clash-nyanpasu/frontend/interface/service/clash.ts index 97ffedbccd..88e9b7fe49 100644 --- a/clash-nyanpasu/frontend/interface/service/clash.ts +++ b/clash-nyanpasu/frontend/interface/service/clash.ts @@ -1,5 +1,6 @@ import { ofetch } from "ofetch"; import { getClashInfo } from "./tauri"; +import { ProviderItem } from "./types"; export namespace Clash { export interface Config { @@ -147,6 +148,47 @@ export const clash = () => { }); }; + const getRulesProviders = async () => { + return ( + await ( + await buildRequest() + )("/providers/rules", { + method: "GET", + }) + )?.providers; + }; + + const updateRulesProviders = async (name: string) => { + return (await buildRequest())(`/providers/rules/${name}`, { + method: "PUT", + }); + }; + + const getProxiesProviders = async () => { + const result: { [key: string]: ProviderItem } = ( + await ( + await buildRequest() + )("/providers/proxies") + )?.providers; + + const types = ["http", "file"]; + + return Object.fromEntries( + Object.entries(result).filter(([, value]) => + types.includes(value.vehicleType.toLowerCase()), + ), + ); + }; + + const updateProxiesProviders = async (name: string) => { + return (await buildRequest())( + `/providers/proxies/${encodeURIComponent(name)}`, + { + method: "PUT", + }, + ); + }; + return { getConfigs, setConfigs, @@ -157,5 +199,9 @@ export const clash = () => { getProxies, setProxies, deleteConnections, + getRulesProviders, + updateRulesProviders, + getProxiesProviders, + updateProxiesProviders, }; }; diff --git a/clash-nyanpasu/frontend/interface/service/types.ts b/clash-nyanpasu/frontend/interface/service/types.ts index c28fcb04fd..9a8cbe429b 100644 --- a/clash-nyanpasu/frontend/interface/service/types.ts +++ b/clash-nyanpasu/frontend/interface/service/types.ts @@ -165,3 +165,27 @@ export namespace Connection { connections?: Item[]; } } + +export interface LogMessage { + type: string; + time?: string; + payload: string; +} + +export interface ProviderRules { + behavior: string; + format: string; + name: string; + ruleCount: number; + type: string; + updatedAt: string; + vehicleType: string; +} + +export interface ProviderItem { + name: string; + type: string; + proxies: Clash.Proxy[]; + updatedAt: string; + vehicleType: string; +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/log/log-item.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/log/log-item.tsx index 923c7afff1..ad7cfa3c6b 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/log/log-item.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/log/log-item.tsx @@ -58,7 +58,7 @@ const LogItem = (props: Props) => { sx={{ ml: 3.5, mr: 3.5, - pt: index === 0 ? 8 : 0, + // pt: index === 0 ? 8 : 0, }} >
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/clear-log-button.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/clear-log-button.tsx new file mode 100644 index 0000000000..2031260345 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/clear-log-button.tsx @@ -0,0 +1,26 @@ +import { atomLogData } from "@/store"; +import { Close } from "@mui/icons-material"; +import { Tooltip } from "@mui/material"; +import { FloatingButton } from "@nyanpasu/ui"; +import { useSetAtom } from "jotai"; +import { useTranslation } from "react-i18next"; + +export const ClearLogButton = () => { + const { t } = useTranslation(); + + const setLogData = useSetAtom(atomLogData); + + const onClear = () => { + setLogData([]); + }; + + return ( + + + + + + ); +}; + +export default ClearLogButton; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-filter.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-filter.tsx new file mode 100644 index 0000000000..3acefab8d6 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-filter.tsx @@ -0,0 +1,38 @@ +import { FilledInputProps, TextField, alpha, useTheme } from "@mui/material"; +import { useTranslation } from "react-i18next"; + +export interface LogFilterProps { + value: string; + onChange: (value: string) => void; +} + +export const LogFilter = ({ value, onChange }: LogFilterProps) => { + const { t } = useTranslation(); + + const { palette } = useTheme(); + + const inputProps: Partial = { + sx: { + borderRadius: 7, + backgroundColor: alpha(palette.primary.main, 0.1), + + fieldset: { + border: "none", + }, + }, + }; + + return ( + onChange(e.target.value)} + className="!pb-0" + sx={{ input: { py: 1, fontSize: 14 } }} + InputProps={inputProps} + /> + ); +}; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.module.scss b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.module.scss new file mode 100644 index 0000000000..91e1fd338a --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.module.scss @@ -0,0 +1,22 @@ +.item { + :global(.shiki) { + margin-bottom: 0; + background-color: transparent !important; + + * { + font-family: var(--item-font); + } + + span { + white-space: normal; + } + } + + &.dark { + :global(.shiki) { + span { + color: var(--shiki-dark) !important; + } + } + } +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.module.scss.d.ts b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.module.scss.d.ts new file mode 100644 index 0000000000..404fa5bf9f --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.module.scss.d.ts @@ -0,0 +1,6 @@ +declare const classNames: { + readonly item: "item"; + readonly shiki: "shiki"; + readonly dark: "dark"; +}; +export default classNames; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.tsx new file mode 100644 index 0000000000..17090eb9dd --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-item.tsx @@ -0,0 +1,55 @@ +import { formatAnsi } from "@/utils/shiki"; +import { useTheme } from "@mui/material"; +import { LogMessage } from "@nyanpasu/interface"; +import { useAsyncEffect } from "ahooks"; +import { useState } from "react"; +import styles from "./log-item.module.scss"; +import { classNames } from "@/utils"; + +export const LogItem = ({ value }: { value: LogMessage }) => { + const { palette } = useTheme(); + + const [payload, setPayload] = useState(value.payload); + + const colorMapping: { [key: string]: string } = { + error: palette.error.main, + warning: palette.warning.main, + info: palette.info.main, + }; + + useAsyncEffect(async () => { + setPayload(await formatAnsi(value.payload)); + }, [value.payload]); + + return ( +
+
+ {value.time} + + + {value.type} + +
+ +
+

+

+
+ ); +}; + +export default LogItem; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-level.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-level.tsx new file mode 100644 index 0000000000..a2c9eed362 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-level.tsx @@ -0,0 +1,54 @@ +import { Button, Menu, MenuItem, alpha, useTheme } from "@mui/material"; +import { useState } from "react"; + +export interface LogLevelProps { + value: string; + onChange: (value: string) => void; +} + +export const LogLevel = ({ value, onChange }: LogLevelProps) => { + const { palette } = useTheme(); + + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = (value: string) => { + setAnchorEl(null); + onChange(value); + }; + + const mapping: { [key: string]: string } = { + all: "ALL", + inf: "INFO", + warn: "WARN", + err: "ERROR", + }; + + return ( + <> + + + setAnchorEl(null)} + > + {Object.entries(mapping).map(([key, value], index) => { + return ( + handleClick(key)}> + {value} + + ); + })} + + + ); +}; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-list.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-list.tsx new file mode 100644 index 0000000000..fbfd9c2bfe --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-list.tsx @@ -0,0 +1,40 @@ +import { LogMessage } from "@nyanpasu/interface"; +import { useThrottleFn } from "ahooks"; +import { useEffect, useRef } from "react"; +import { VList, VListHandle } from "virtua"; +import LogItem from "./log-item"; + +export const LogList = ({ data }: { data: LogMessage[] }) => { + const vListRef = useRef(null); + + const shouldStickToBottom = useRef(true); + + const { run: scrollToBottom } = useThrottleFn( + () => { + if (shouldStickToBottom.current) { + setTimeout(() => { + vListRef.current?.scrollToIndex(data.length - 1, { + align: "end", + smooth: true, + }); + }, 100); + } + }, + { wait: 100 }, + ); + + useEffect(() => { + scrollToBottom(); + }, [data]); + + return ( + + {data.map((item, index) => { + return ; + })} + + ); +}; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx new file mode 100644 index 0000000000..ad189ccbb5 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx @@ -0,0 +1,37 @@ +import { atomLogData } from "@/store"; +import { LogMessage, useClashWS } from "@nyanpasu/interface"; +import dayjs from "dayjs"; +import { useSetAtom } from "jotai"; +import { useEffect } from "react"; + +const MAX_LOG_NUM = 1000; + +const time = dayjs().format("MM-DD HH:mm:ss"); + +export const LogProvider = () => { + const { + logs: { latestMessage }, + } = useClashWS(); + + const setLogData = useSetAtom(atomLogData); + + useEffect(() => { + if (!latestMessage?.data) { + return; + } + + const data = JSON.parse(latestMessage?.data) as LogMessage; + + setLogData((prev) => { + if (prev.length >= MAX_LOG_NUM) { + prev.shift(); + } + + return [...prev, { ...data, time }]; + }); + }, [latestMessage?.data]); + + return null; +}; + +export default LogProvider; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-toggle.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-toggle.tsx new file mode 100644 index 0000000000..f5c5e48f0e --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-toggle.tsx @@ -0,0 +1,23 @@ +import { atomEnableLog } from "@/store"; +import { IconButton } from "@mui/material"; +import { useAtom } from "jotai"; +import { + PauseCircleOutlineRounded, + PlayCircleOutlineRounded, +} from "@mui/icons-material"; + +export const LogToggle = () => { + const [enableLog, setEnableLog] = useAtom(atomEnableLog); + + return ( + setEnableLog((e) => !e)} + > + {enableLog ? : } + + ); +}; + +export default LogToggle; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/providers/proxies-provider.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/proxies-provider.tsx new file mode 100644 index 0000000000..d25b60f344 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/proxies-provider.tsx @@ -0,0 +1,82 @@ +import { useMessage } from "@/hooks/use-notification"; +import { Refresh } from "@mui/icons-material"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { Chip, Paper } from "@mui/material"; +import { ProviderItem, useClashCore } from "@nyanpasu/interface"; +import { useLockFn } from "ahooks"; +import dayjs from "dayjs"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +export interface ProxiesProviderProps { + provider: ProviderItem; +} + +export const ProxiesProvider = ({ provider }: ProxiesProviderProps) => { + const { t } = useTranslation(); + + const [loading, setLoading] = useState(false); + + const { updateProxiesProviders } = useClashCore(); + + const handleClick = useLockFn(async () => { + try { + setLoading(true); + + await updateProxiesProviders(provider.name); + } catch (e) { + useMessage(`Update ${provider.name} failed.\n${String(e)}`, { + type: "error", + title: t("Error"), + }); + } finally { + setLoading(false); + } + }); + + return ( + +
+
+

{provider.name}

+ +

+ {provider.vehicleType}/{provider.type} +

+
+ +
+ {t("Last Update", { + fromNow: dayjs(provider.updatedAt).fromNow(), + })} +
+
+ +
+ + + + + +
+
+ ); +}; + +export default ProxiesProvider; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/providers/rules-provider.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/rules-provider.tsx index a3b62005cc..ec75bc13d1 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/providers/rules-provider.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/rules-provider.tsx @@ -1,15 +1,8 @@ -import { NotificationType, useNotification } from "@/hooks/use-notification"; -import { updateRulesProviders, type ProviderRules } from "@/services/api"; +import { useMessage } from "@/hooks/use-notification"; import { Refresh } from "@mui/icons-material"; -import { - Box, - Card, - CardContent, - CircularProgress, - IconButton, - Stack, - Typography, -} from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton/LoadingButton"; +import { Chip, Paper } from "@mui/material"; +import { ProviderRules, useClashCore } from "@nyanpasu/interface"; import { useLockFn } from "ahooks"; import dayjs from "dayjs"; import { useState } from "react"; @@ -17,96 +10,71 @@ import { useTranslation } from "react-i18next"; export interface RulesProviderProps { provider: ProviderRules; - onRulesProviderUpdated: () => void; } -export default function RulesProvider(props: RulesProviderProps) { - const { provider, onRulesProviderUpdated } = props; +export default function RulesProvider({ provider }: RulesProviderProps) { const { t } = useTranslation(); - const [updating, setUpdating] = useState(false); - const onUpdate = useLockFn(async () => { - setUpdating(true); + const [loading, setLoading] = useState(false); + + const { updateRulesProviders } = useClashCore(); + + const handleClick = useLockFn(async () => { try { + setLoading(true); + await updateRulesProviders(provider.name); - onRulesProviderUpdated(); - useNotification({ - title: t("Success"), - body: t("Update Rules Providers Success"), - type: NotificationType.Success, - }); - } catch (err: any) { - useNotification({ + } catch (e) { + useMessage(`Update ${provider.name} failed.\n${String(e)}`, { + type: "error", title: t("Error"), - body: err.message || err.toString(), - type: NotificationType.Error, }); } finally { - setUpdating(false); + setLoading(false); } }); return ( - - - +
+
+

{provider.name}

+ +

+ {provider.vehicleType}/{provider.behavior} +

+
+ +
+ {t("Last Update", { + fromNow: dayjs(provider.updatedAt).fromNow(), + })} +
+
+ +
+ + + - {updating ? ( - - ) : ( - - )} - - - - - - {provider.name} - - - {provider.vehicleType}/{provider.behavior} - - - - - - {t("Rule Set rules", { - rule: provider.ruleCount, - })} - - - {t("Last Update", { - fromNow: dayjs(provider.updatedAt).fromNow(), - })} - - - - - + + +
+ ); } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/providers/update-providers.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/update-providers.tsx new file mode 100644 index 0000000000..8e06cc4f44 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/update-providers.tsx @@ -0,0 +1,58 @@ +import { useMessage } from "@/hooks/use-notification"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { useClashCore } from "@nyanpasu/interface"; +import { useLockFn } from "ahooks"; +import { useState } from "react"; +import { Refresh } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; + +export const UpdateProviders = () => { + const { t } = useTranslation(); + + const [loading, setLoading] = useState(false); + + const { getRulesProviders, updateRulesProviders } = useClashCore(); + + const handleProviderUpdate = useLockFn(async () => { + if (!getRulesProviders.data) { + useMessage(`No Providers.`, { + type: "info", + title: t("Info"), + }); + + return; + } + + try { + setLoading(true); + + const providers = Object.entries(getRulesProviders.data).map( + ([name]) => name, + ); + + await Promise.all( + providers.map((provider) => updateRulesProviders(provider)), + ); + } catch (e) { + useMessage(`Update all failed.\n${String(e)}`, { + type: "error", + title: t("Error"), + }); + } finally { + setLoading(false); + } + }); + + return ( + } + onClick={handleProviderUpdate} + > + {t("Update Rules Providers All")} + + ); +}; + +export default UpdateProviders; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/providers/update-proxies-providers.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/update-proxies-providers.tsx new file mode 100644 index 0000000000..e65a1746f0 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/update-proxies-providers.tsx @@ -0,0 +1,58 @@ +import { useMessage } from "@/hooks/use-notification"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { useClashCore } from "@nyanpasu/interface"; +import { useLockFn } from "ahooks"; +import { useState } from "react"; +import { Refresh } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; + +export const UpdateProxiesProviders = () => { + const { t } = useTranslation(); + + const [loading, setLoading] = useState(false); + + const { getProxiesProviders, updateProxiesProviders } = useClashCore(); + + const handleProviderUpdate = useLockFn(async () => { + if (!getProxiesProviders.data) { + useMessage(`No Providers.`, { + type: "info", + title: t("Info"), + }); + + return; + } + + try { + setLoading(true); + + const providers = Object.entries(getProxiesProviders.data).map( + ([name]) => name, + ); + + await Promise.all( + providers.map((provider) => updateProxiesProviders(provider)), + ); + } catch (e) { + useMessage(`Update all failed.\n${String(e)}`, { + type: "error", + title: t("Error"), + }); + } finally { + setLoading(false); + } + }); + + return ( + } + onClick={handleProviderUpdate} + > + {t("Update Proxies Providers All")} + + ); +}; + +export default UpdateProxiesProviders; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/locales/en.json b/clash-nyanpasu/frontend/nyanpasu/src/locales/en.json index ab79eb82f5..101b3d060c 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/locales/en.json +++ b/clash-nyanpasu/frontend/nyanpasu/src/locales/en.json @@ -150,5 +150,7 @@ "Last Update": "Last Updated: {{fromNow}}", "Update Rules Providers Success": "Update Rules Providers Success", "Portable Update Error": "Portable Update is not supported, please download the latest version from the official website.", - "Enable Tray Proxies Selector": "Enable Tray Proxies Selector" + "Enable Tray Proxies Selector": "Enable Tray Proxies Selector", + "Proxy Set proxies": "{{rule}} proxies", + "Update Proxies Providers All": "Update Rules Proxies All" } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/locales/zh.json b/clash-nyanpasu/frontend/nyanpasu/src/locales/zh.json index ed84b973b4..3abba34e99 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/locales/zh.json +++ b/clash-nyanpasu/frontend/nyanpasu/src/locales/zh.json @@ -163,5 +163,7 @@ "Portable Update Error": "便携版无法自动更新,请到 Github 下载最新版本", - "Enable Tray Proxies Selector": "开启托盘代理选择" + "Enable Tray Proxies Selector": "开启托盘代理选择", + "Proxy Set proxies": "{{rule}} 个节点", + "Update Proxies Providers All": "全部更新" } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/_app.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/_app.tsx index b4e0855af6..0b67d542dd 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/_app.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/_app.tsx @@ -30,6 +30,7 @@ import AnimatedLogo from "@/components/layout/animated-logo"; import { FallbackProps } from "react-error-boundary"; import styles from "./_app.module.scss"; import { Experimental_CssVarsProvider as CssVarsProvider } from "@mui/material/styles"; +import LogProvider from "@/components/logs/log-provider"; dayjs.extend(relativeTime); @@ -148,6 +149,8 @@ export default function App() { + + { - return logData.filter((data) => { - return ( - data.payload.includes(filterText) && - (logState === "all" ? true : data.type.includes(logState)) - ); - }); + const [filterLogs, setFilterLogs] = useState([]); + + useEffect(() => { + setFilterLogs( + logData.filter((data) => { + return ( + data.payload.includes(filterText) && + (logState === "all" ? true : data.type.includes(logState)) + ); + }), + ); }, [logData, logState, filterText]); return ( @@ -42,111 +39,25 @@ export default function LogPage() { title={t("Logs")} contentStyle={{ height: "100%" }} header={ - - setEnableLog((e) => !e)} - > - {enableLog ? ( - - ) : ( - - )} - +
+ - - + setLogState(value)} /> + + setFilterText(value)} + /> +
} > - - - - + {filterLogs.length ? ( + + ) : ( + + )} - - setFilterText(e.target.value)} - sx={{ input: { py: 0.65, px: 1.25 } }} - InputProps={{ - sx: { - borderRadius: 7, - }, - }} - /> - - - - - {filterLogs.length > 0 ? ( - ( - - )} - followOutput={"smooth"} - overscan={900} - /> - ) : ( - - )} - + ); } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx index 3274216cad..f69298d8af 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx @@ -1,144 +1,61 @@ -import { BasePage } from "@/components/base"; +import ProxiesProvider from "@/components/providers/proxies-provider"; import RulesProvider from "@/components/providers/rules-provider"; -import { NotificationType, useNotification } from "@/hooks/use-notification"; -import { getRulesProviders, updateRulesProviders } from "@/services/api"; -import { Error as ErrorIcon } from "@mui/icons-material"; -import { LoadingButton } from "@mui/lab"; -import { - Box, - Button, - CircularProgress, - Stack, - Typography, -} from "@mui/material"; +import UpdateProviders from "@/components/providers/update-providers"; +import UpdateProxiesProviders from "@/components/providers/update-proxies-providers"; +import { Chip } from "@mui/material"; import Grid from "@mui/material/Unstable_Grid2"; -import { useLockFn } from "ahooks"; -import { useState } from "react"; +import { useClashCore } from "@nyanpasu/interface"; +import { BasePage } from "@nyanpasu/ui"; import { useTranslation } from "react-i18next"; -import useSWR from "swr"; export default function ProvidersPage() { const { t } = useTranslation(); - const { - data: rulesProviders, - error: rulesProvidersError, - isLoading: rulesProvidersLoading, - mutate: mutateRulesProviders, - } = useSWR("getClashProvidersRules", getRulesProviders); - const [updating, setUpdating] = useState(false); - const onUpdateAll = useLockFn(async () => { - setUpdating(true); - try { - const queue = rulesProviders!.map((provider) => - updateRulesProviders(provider.name), - ); - await Promise.all(queue); - await mutateRulesProviders(); - useNotification({ - title: t("Success"), - body: t("Update Rules Providers Success"), - type: NotificationType.Success, - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - useNotification({ - title: t("Error"), - body: err.message || err.toString(), - type: NotificationType.Error, - }); - } finally { - setUpdating(false); - } - }); - - const onRulesProviderUpdated = () => { - mutateRulesProviders(); - }; + const { getRulesProviders, getProxiesProviders } = useClashCore(); return ( - - - {t("Rules Providers")} - - - {t("Update Rules Providers All")} - - - - { - /* 这里需要抽象个状态机组件出来 */ - rulesProvidersLoading || rulesProvidersError ? ( - - {rulesProvidersLoading ? ( - - ) : ( - -

- {t("Error")} -

- - -
- )} -
- ) : ( - - {rulesProviders!.map((provider) => ( - - +
+
+ + + +
+ + {getProxiesProviders.data && ( + + {Object.entries(getProxiesProviders.data).map( + ([name, provider]) => ( + + - ))} - - ) - } - + ), + )} + + )} + +
+ + + +
+ + {getRulesProviders.data && ( + + {Object.entries(getRulesProviders.data).map(([name, provider]) => ( + + + + ))} + + )} +
); } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx index f73b2ffd3f..236c296a42 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx @@ -1,4 +1,3 @@ -import { ProviderButton } from "@/components/proxy/provider-button"; import { Box, Button, @@ -133,8 +132,6 @@ export default function ProxyPage() { title={t("Proxy Groups")} header={ - - {Object.entries(getCurrentMode).map(([key, value], index) => (