Compare commits
6 Commits
11
README.md
11
README.md
@@ -58,6 +58,15 @@ This is a learning project for Rust practice.
|
||||
|
||||
PR welcome!
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Clash Verge was based on or inspired by these projects and so on:
|
||||
|
||||
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop applications with a web frontend.
|
||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash): A rule-based tunnel in Go.
|
||||
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): A Windows/macOS GUI based on Clash.
|
||||
- [vitejs/vite](https://github.com/vitejs/vite): Next generation frontend tooling. It's fast!
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0 License
|
||||
GPL-3.0 License. See [License here](./LICENSE) for details.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "0.0.12",
|
||||
"version": "0.0.13",
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "cargo tauri dev",
|
||||
|
||||
@@ -4,4 +4,5 @@ mixed-port: 7890
|
||||
log-level: info
|
||||
allow-lan: false
|
||||
external-controller: 127.0.0.1:9090
|
||||
mode: rule
|
||||
secret: ""
|
||||
|
||||
@@ -306,6 +306,7 @@ pub async fn activate_profile(
|
||||
"allow-lan",
|
||||
"external-controller",
|
||||
"secret",
|
||||
"mode",
|
||||
"ipv6",
|
||||
];
|
||||
valid_keys.iter().for_each(|key| {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "clash-verge",
|
||||
"version": "0.0.12"
|
||||
"version": "0.0.13"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -72,9 +72,9 @@
|
||||
|
||||
.the-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 30px;
|
||||
left: 0;
|
||||
right: 2px;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
width: 90%;
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
padding-right: 4px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -20,7 +20,7 @@ const BasePage: React.FC<Props> = (props) => {
|
||||
{header}
|
||||
</header>
|
||||
|
||||
<section data-windrag>
|
||||
<section>
|
||||
<div className="base-content" style={contentStyle} data-windrag>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -107,7 +107,7 @@ const Layout = () => {
|
||||
<LayoutControl />
|
||||
</div>
|
||||
|
||||
<div className="the-content" data-windrag>
|
||||
<div className="the-content">
|
||||
<Routes>
|
||||
{routers.map(({ label, link, ele: Ele }) => (
|
||||
<Route key={label} path={link} element={<Ele />} />
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import { useEffect } from "react";
|
||||
import { List, Paper } from "@mui/material";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
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 BasePage from "../components/base-page";
|
||||
import ProxyItem from "../components/proxy-item";
|
||||
@@ -9,38 +12,115 @@ import ProxyGroup from "../components/proxy-group";
|
||||
const ProxyPage = () => {
|
||||
const { mutate } = useSWRConfig();
|
||||
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(() => {
|
||||
// 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 === "rule" && !groups.length) ||
|
||||
(curMode === "global" && filterProxies.length < 4)
|
||||
) {
|
||||
setTimeout(() => mutate("getProxies"), 500);
|
||||
}
|
||||
}, [groups, filterProxies, curMode]);
|
||||
|
||||
// update the current proxy
|
||||
useEffect(() => {
|
||||
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);
|
||||
};
|
||||
|
||||
// difference style
|
||||
const pageStyle = asGroup ? {} : { height: "100%" };
|
||||
const paperStyle: any = asGroup
|
||||
? { mb: 0.5 }
|
||||
: { py: 1, height: "100%", boxSizing: "border-box" };
|
||||
|
||||
return (
|
||||
<BasePage title={groups.length ? "Proxy Groups" : "Proxies"}>
|
||||
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 1 }}>
|
||||
{groups.length > 0 && (
|
||||
<BasePage
|
||||
contentStyle={pageStyle}
|
||||
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, ...paperStyle }}>
|
||||
{asGroup ? (
|
||||
<List>
|
||||
{groups.map((group) => (
|
||||
<ProxyGroup key={group.name} group={group} />
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
|
||||
{!groups.length && (
|
||||
<List>
|
||||
{Object.values(proxies).map((proxy) => (
|
||||
) : (
|
||||
// virtual list
|
||||
<Virtuoso
|
||||
style={{ height: "100%" }}
|
||||
totalCount={filterProxies.length}
|
||||
itemContent={(index) => (
|
||||
<ProxyItem
|
||||
key={proxy.name}
|
||||
proxy={proxy}
|
||||
selected={false}
|
||||
proxy={filterProxies[index]}
|
||||
selected={filterProxies[index].name === curProxy}
|
||||
onClick={onChangeProxy}
|
||||
sx={{ py: 0, px: 2 }}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
</BasePage>
|
||||
|
||||
Reference in New Issue
Block a user