|
@@ -1,6 +1,8 @@
|
|
import useSWR, { useSWRConfig } from "swr";
|
|
import useSWR, { useSWRConfig } from "swr";
|
|
-import { useEffect } from "react";
|
|
|
|
-import { List, Paper } from "@mui/material";
|
|
|
|
|
|
+import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
|
+import { Button, ButtonGroup, List, Paper } from "@mui/material";
|
|
|
|
+import { getClashConfig, updateConfigs, updateProxy } from "../services/api";
|
|
|
|
+import { patchClashConfig } from "../services/cmds";
|
|
import { getProxies } from "../services/api";
|
|
import { getProxies } from "../services/api";
|
|
import BasePage from "../components/base-page";
|
|
import BasePage from "../components/base-page";
|
|
import ProxyItem from "../components/proxy-item";
|
|
import ProxyItem from "../components/proxy-item";
|
|
@@ -9,34 +11,103 @@ import ProxyGroup from "../components/proxy-group";
|
|
const ProxyPage = () => {
|
|
const ProxyPage = () => {
|
|
const { mutate } = useSWRConfig();
|
|
const { mutate } = useSWRConfig();
|
|
const { data: proxiesData } = useSWR("getProxies", getProxies);
|
|
const { data: proxiesData } = useSWR("getProxies", getProxies);
|
|
- const { groups = [], proxies = [] } = proxiesData ?? {};
|
|
|
|
|
|
+ const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
|
|
|
|
+ const [curProxy, setCurProxy] = useState<string>("DIRECT");
|
|
|
|
+ const curMode = clashConfig?.mode.toLowerCase();
|
|
|
|
|
|
|
|
+ // proxy groups
|
|
|
|
+ const { groups = [] } = proxiesData ?? {};
|
|
|
|
+ // proxies and sorted
|
|
|
|
+ const filterProxies = useMemo(() => {
|
|
|
|
+ if (!proxiesData?.proxies) return [];
|
|
|
|
+
|
|
|
|
+ const list = Object.values(proxiesData.proxies);
|
|
|
|
+ const retList = list.filter(
|
|
|
|
+ (p) => !p.all?.length && p.name !== "DIRECT" && p.name !== "REJECT"
|
|
|
|
+ );
|
|
|
|
+ const direct = list.filter((p) => p.name === "DIRECT");
|
|
|
|
+ const reject = list.filter((p) => p.name === "REJECT");
|
|
|
|
+
|
|
|
|
+ return direct.concat(retList).concat(reject);
|
|
|
|
+ }, [proxiesData]);
|
|
|
|
+
|
|
|
|
+ const modeList = ["rule", "global", "direct"];
|
|
|
|
+ const asGroup = curMode === "rule" || !groups.length;
|
|
|
|
+
|
|
|
|
+ // make sure that fetch the proxies successfully
|
|
|
|
+ useEffect(() => {
|
|
|
|
+ if (
|
|
|
|
+ (curMode === "rule" && !groups.length) ||
|
|
|
|
+ (curMode === "global" && filterProxies.length < 4)
|
|
|
|
+ ) {
|
|
|
|
+ setTimeout(() => mutate("getProxies"), 500);
|
|
|
|
+ }
|
|
|
|
+ }, [groups, filterProxies, curMode]);
|
|
|
|
+
|
|
|
|
+ // update the current proxy
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
- // fix the empty proxies on the first sight
|
|
|
|
- // this bud only show on the build version
|
|
|
|
- // call twice to avoid something unknown or the delay of the clash startup
|
|
|
|
- setTimeout(() => mutate("getProxies"), 250);
|
|
|
|
- setTimeout(() => mutate("getProxies"), 1000);
|
|
|
|
- }, []);
|
|
|
|
|
|
+ if (curMode === "direct") setCurProxy("DIRECT");
|
|
|
|
+ if (curMode === "global") {
|
|
|
|
+ const globalNow = proxiesData?.proxies?.GLOBAL?.now;
|
|
|
|
+ setCurProxy(globalNow || "DIRECT");
|
|
|
|
+ }
|
|
|
|
+ }, [curMode, proxiesData]);
|
|
|
|
+
|
|
|
|
+ const changeLockRef = useRef(false);
|
|
|
|
+ const onChangeMode = async (mode: string) => {
|
|
|
|
+ if (changeLockRef.current) return;
|
|
|
|
+ changeLockRef.current = true;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // switch rapidly
|
|
|
|
+ await updateConfigs({ mode });
|
|
|
|
+ await patchClashConfig({ mode });
|
|
|
|
+ mutate("getClashConfig");
|
|
|
|
+ } finally {
|
|
|
|
+ changeLockRef.current = false;
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const onChangeProxy = async (name: string) => {
|
|
|
|
+ if (curMode !== "global") return;
|
|
|
|
+ await updateProxy("GLOBAL", name);
|
|
|
|
+ setCurProxy(name);
|
|
|
|
+ };
|
|
|
|
|
|
return (
|
|
return (
|
|
- <BasePage title={groups.length ? "Proxy Groups" : "Proxies"}>
|
|
|
|
|
|
+ <BasePage
|
|
|
|
+ title={asGroup ? "Proxy Groups" : "Proxies"}
|
|
|
|
+ header={
|
|
|
|
+ <ButtonGroup size="small">
|
|
|
|
+ {modeList.map((mode) => (
|
|
|
|
+ <Button
|
|
|
|
+ key={mode}
|
|
|
|
+ variant={mode === curMode ? "contained" : "outlined"}
|
|
|
|
+ onClick={() => onChangeMode(mode)}
|
|
|
|
+ sx={{ textTransform: "capitalize" }}
|
|
|
|
+ >
|
|
|
|
+ {mode}
|
|
|
|
+ </Button>
|
|
|
|
+ ))}
|
|
|
|
+ </ButtonGroup>
|
|
|
|
+ }
|
|
|
|
+ >
|
|
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 1 }}>
|
|
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 1 }}>
|
|
- {groups.length > 0 && (
|
|
|
|
|
|
+ {asGroup ? (
|
|
<List>
|
|
<List>
|
|
{groups.map((group) => (
|
|
{groups.map((group) => (
|
|
<ProxyGroup key={group.name} group={group} />
|
|
<ProxyGroup key={group.name} group={group} />
|
|
))}
|
|
))}
|
|
</List>
|
|
</List>
|
|
- )}
|
|
|
|
-
|
|
|
|
- {!groups.length && (
|
|
|
|
|
|
+ ) : (
|
|
|
|
+ // todo: virtual list
|
|
<List>
|
|
<List>
|
|
- {Object.values(proxies).map((proxy) => (
|
|
|
|
|
|
+ {filterProxies.map((proxy) => (
|
|
<ProxyItem
|
|
<ProxyItem
|
|
key={proxy.name}
|
|
key={proxy.name}
|
|
proxy={proxy}
|
|
proxy={proxy}
|
|
- selected={false}
|
|
|
|
|
|
+ selected={proxy.name === curProxy}
|
|
|
|
+ onClick={onChangeProxy}
|
|
sx={{ py: 0, px: 2 }}
|
|
sx={{ py: 0, px: 2 }}
|
|
/>
|
|
/>
|
|
))}
|
|
))}
|