This commit is contained in:
liyuanhu 2025-04-18 11:12:35 +08:00
parent 9f09e8ba61
commit ffabc1ccbe
18 changed files with 774 additions and 700 deletions

64
__unconfig_vite.config.ts Normal file
View File

@ -0,0 +1,64 @@
let __unconfig_data;
let __unconfig_stub = function (data = {}) { __unconfig_data = data };
__unconfig_stub.default = (data = {}) => { __unconfig_data = data };
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import AutoImport from 'unplugin-auto-import/vite'
import svgr from 'vite-plugin-svgr'
import { CodeInspectorPlugin } from 'code-inspector-plugin'
import path from 'path'
// const host = process.env.TAURI_DEV_HOST;
const host = '127.0.0.1'
// https://vitejs.dev/config/
const __unconfig_default = defineConfig(async () => ({
plugins: [
react(),
AutoImport({
dts: './auto-imports.d.ts', //此文件配置保存后系统自动生成
imports: [
'react', // 自动导入 React
],
}),
svgr({ include: '**/*.svg?react' }),
CodeInspectorPlugin({
bundler: 'vite',
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'package.json': path.resolve(__dirname, './package.json'),
},
},
build: {
sourcemap: true,
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
host: host,
hmr: host
? {
protocol: 'ws',
host,
port: 1421,
}
: undefined,
watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ['**/src-tauri/**'],
},
},
}))
if (typeof __unconfig_default === "function") __unconfig_default(...[{"command":"serve","mode":"development"}]);export default __unconfig_data;

View File

@ -48,6 +48,7 @@
"i18next": "^24.2.0",
"lodash-es": "^4.17.21",
"lucide-react": "^0.469.0",
"mitt": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.54.2",

27
pnpm-lock.yaml generated
View File

@ -119,6 +119,9 @@ importers:
lucide-react:
specifier: ^0.469.0
version: 0.469.0(react@18.3.1)
mitt:
specifier: ^3.0.1
version: 3.0.1
react:
specifier: ^18.2.0
version: 18.3.1
@ -569,42 +572,36 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.0':
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.0':
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.0':
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.0':
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.0':
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.0':
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@ -1320,55 +1317,46 @@ packages:
resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.27.4':
resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.27.4':
resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.27.4':
resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.27.4':
resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.27.4':
resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.27.4':
resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.27.4':
resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.27.4':
resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
@ -1490,28 +1478,24 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-arm64-musl@2.1.0':
resolution: {integrity: sha512-NzwqjUCilhnhJzusz3d/0i0F1GFrwCQbkwR6yAHUxItESbsGYkZRJk0yMEWkg3PzFnyK4cWTlQJMEU52TjhEzA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tauri-apps/cli-linux-x64-gnu@2.1.0':
resolution: {integrity: sha512-TyiIpMEtZxNOQmuFyfJwaaYbg3movSthpBJLIdPlKxSAB2BW0VWLY3/ZfIxm/G2YGHyREkjJvimzYE0i37PnMA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-x64-musl@2.1.0':
resolution: {integrity: sha512-/dQd0TlaxBdJACrR72DhynWftzHDaX32eBtS5WBrNJ+nnNb+znM3gON6nJ9tSE9jgDa6n1v2BkI/oIDtypfUXw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tauri-apps/cli-win32-arm64-msvc@2.1.0':
resolution: {integrity: sha512-NdQJO7SmdYqOcE+JPU7bwg7+odfZMWO6g8xF9SXYCMdUzvM2Gv/AQfikNXz5yS7ralRhNFuW32i5dcHlxh4pDg==}
@ -2097,6 +2081,9 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
@ -4782,6 +4769,8 @@ snapshots:
minipass@7.1.2: {}
mitt@3.0.1: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8

View File

@ -2,14 +2,23 @@ import { useStartupCheck } from "@/hooks/useStartupCheck";
import { usePreventDefault } from "@/hooks/usePreventDefaultEventListener";
import { useGlobalShortcut } from "@/hooks/useGlobalShortcut";
import { useRecreateTheCircuit } from "@/hooks/useRecreateTheCircuit";
import { useCoreConfig } from "./hooks/useCoreConfig";
// import { commands } from '@/bindings'
import Titlebar from "@/components/Titlebar";
import { useCoreConfig } from "@/hooks/useCoreConfig";
import { useDispatch, useSelector } from "react-redux";
import {
setProxyInfoProxies,
setProxiesList1,
setProxiesList2,
setProxiesLine,
} from "@/store/web3Slice";
import type { AppDispatch, RootState } from "@/store";
import eventBus, { eventTypes } from "@/utils/eventBus";
import { WebSocketClient } from "@/utils/webSocketClient";
import Titlebar from "@/components/Titlebar";
import Layout from "@/layout";
import Tray from "@/components/Tray";
function App() {
// 执行启动自检
useStartupCheck();
@ -22,7 +31,101 @@ function App() {
// 读取配置,若文件不存在则持久化到本地 `%APPDATA%/com.paw.paw-gui`
const { loadCoreConfig } = useCoreConfig();
loadCoreConfig();
const dispatch = useDispatch<AppDispatch>();
const { web3List, web3List2, proxy_info, path_list } = useSelector(
(state: RootState) => state.web3Reducer
);
const webSocketClient = useRef<WebSocketClient | null>();
const openWsTraffic = async () => {
if (webSocketClient.current) return;
const { api_port } = await loadCoreConfig();
// todo! 后面会把二级制文件启动的参数作为配置项,这里暂时写死
webSocketClient.current = new WebSocketClient(
`ws://127.0.0.1:${api_port}/traffic`,
{
headers: {
Authorization: "Bearer secret",
},
}
);
// 执行 WebSocket 操作
await webSocketClient.current.connect();
webSocketClient.current.addListener((msg: any) => {
if (msg.code === 0) {
switch (msg.event) {
case eventTypes.NODE_UP:
console.log("节点上线");
break;
case eventTypes.NODE_DOWN:
// 添加下线节点到store 里面
console.log("节点下线");
break;
case eventTypes.MALICIOUS_NODE:
// 添加恶意节点到store 里面
console.log("检测到恶意节点");
break;
case eventTypes.NODE_INIT_COMPLATE:
console.log("节点预配置完成");
break;
case eventTypes.NODE_REMOVE:
console.log("节点清除");
break;
case eventTypes.NODE_ADD:
console.log("添加节点");
break;
case eventTypes.NODE_INIT:
console.log("节点预配置");
break;
default:
break;
}
}
});
};
const createdSoketEventBus = async () => {
eventBus.on(eventTypes.NODE_INIT_COMPLATE, (data: any) => {
console.log("节点预配置完成", data);
webSocketClient.current?.sendMessage(data);
});
eventBus.on(eventTypes.NODE_REMOVE, () => {
console.log("节点清除");
webSocketClient.current?.sendMessage({
code: 0,
event: eventTypes.NODE_REMOVE,
data: {
name: "proxy-xxx",
},
});
});
};
const closeWsTraffic = async () => {
if (webSocketClient.current) {
await webSocketClient.current.disconnect();
webSocketClient.current = null;
}
};
const removeSoketEventBus = () => {
eventBus.off(eventTypes.NODE_INIT_COMPLATE);
eventBus.off(eventTypes.NODE_REMOVE);
};
const initWebsocketsAndEventBus = async () => {
await openWsTraffic();
await createdSoketEventBus();
};
useEffect(() => {
// initWebsocketsAndEventBus();
// return () => {
// closeWsTraffic();
// removeSoketEventBus();
// };
}, []);
return (
<main>

26
src/hooks/useEventBus.ts Normal file
View File

@ -0,0 +1,26 @@
// src/hooks/useEventBus.ts
import { useEffect } from 'react';
import eventBus from '@/utils/eventBus';
export function useEventBus(
event:any,
handler:any,
) {
useEffect(() => {
// 添加事件监听器
eventBus.on(event, handler as any);
// 清理函数
return () => {
eventBus.off(event, handler as any);
};
}, [event, handler]); // 依赖项包含 event 和 handler
// 返回发布事件的函数
return {
emit: (eventName:any, data: any) => {
eventBus.emit(eventName, data);
}
};
}

View File

@ -7,6 +7,7 @@ import { app } from '@tauri-apps/api'
import { createCircuit } from '@/store/circuitSlice'
import { setServiceStatus, getProxy, getVersion } from '@/store/serviceSlice'
import { setNodes, setNodesError, clearNodes } from '@/store/nodesSlice'
// import {} from '@/store/web3Slice'
// import { clearCircuitState } from '@/store/circuitSlice';
import { AppDispatch, RootState } from '@/store'

View File

@ -37,11 +37,11 @@ export default function Layout() {
title: "面向溯源对抗的数据转发",
icon: <PoolSvg className="w-5 h-5" />,
},
// {
// id: 'proxies',
// title: '节点池',
// icon: <PoolSvg className="w-5 h-5" />,
// },
{
id: 'proxies',
title: '节点池',
icon: <PoolSvg className="w-5 h-5" />,
},
];
const handleClickMenu = (index: number) => {

View File

@ -3,7 +3,7 @@ import { FormInstance } from "antd";
import { FormDialog } from "@/components/FormDialog";
import { useDispatch, useSelector } from "react-redux";
import { nodeList,getRandomNodes } from "@/store/datas";
import { nodeList, getRandomNodes } from "@/store/datas";
import "./index.scss";
import { EllipsisTooltip } from "@/components/Encapsulation";
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
@ -11,170 +11,133 @@ import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
import { NODEDIALOGTYPE } from "../../index";
import { cn, getUrl } from "@/lib/utils";
import {
setClearFailTimer,
setClearWarningTimer,
} from "@/store/web3Slice";
import { } from "@/store/web3Slice";
import { AppDispatch, RootState } from "@/store";
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
export interface DialogConfig {
title: string;
desc: string;
successText: string;
title: string;
desc: string;
successText: string;
}
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
props
props
) => {
const { name, code, exit = false } = props.proxyInfo;
const { name, code, exit = false } = props.proxyInfo;
return (
<div
className={cn(
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
exit && "hover:bg-[#EFF6FF]",
props.clasName
)}
>
<div className="flex-1 flex items-center justify-end w-full h-7">
<div className="flex-1 flex space-x-3 items-center">
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
<img
className={cn(
"w-full h-full object-cover rounded-sm"
)}
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
/>
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
/>
</div>
</div>
return (
<div
className={cn(
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
exit && "hover:bg-[#EFF6FF]",
props.clasName
)}
>
<div className="flex-1 flex items-center justify-end w-full h-7">
<div className="flex-1 flex space-x-3 items-center">
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
<img
className={cn("w-full h-full object-cover rounded-sm")}
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
/>
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
/>
</div>
);
</div>
</div>
);
};
export const ClearNodeDialog = ({
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
}: {
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
}) => {
const dispatch = useDispatch<AppDispatch>();
const { clearWarningTimer, clearFailTimer } = useSelector(
(state: RootState) => state.web3Reducer
);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
};
const onSuccessHandle = () => {
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
dispatch(setClearFailTimer(Date.now()));
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
dispatch(setClearWarningTimer(Date.now()));
}
// showDialog(false);
};
const dispatch = useDispatch<AppDispatch>();
const { } = useSelector(
(state: RootState) => state.web3Reducer
);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
};
const onSuccessHandle = () => {
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
}
// showDialog(false);
};
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
if (open) {
if (isClear) return [];
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
if (clearFailTimer) {
const clear =
isTimestampPlusTenMinutesBeforeNow(clearFailTimer);
if (clear) {
return newData
} else {
return [];
}
} else {
return newData;
}
}
if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
if (clearWarningTimer) {
const clear =
isTimestampPlusTenMinutesBeforeNow(clearWarningTimer);
console.log(clear,'clear')
if (clear) {
return newData
} else {
return [];
}
} else {
return newData;
}
}
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
return newData;
}
// 随机 2-9条 nodelist里面的数据
return [];
}, [nodeList, open, isClear, clearFailTimer, clearWarningTimer, type]);
return newData;
}, [nodeList, open, isClear, type]);
useEffect(() => {
if (!open) {
setIsClear(false);
}
}, [open]);
useEffect(() => {
if (!open) {
setIsClear(false);
}
}, [open]);
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={onSuccessHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
: "opacity-50"
}
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
) : (
<div className="w-full h-[382px] flex flex-col items-center justify-center">
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
<NotFailNodeIcon />
) : (
<NotWarningNodeIcon />
)}
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={onSuccessHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
: "opacity-50"
}
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
) : (
<div className="w-full h-[382px] flex flex-col items-center justify-center">
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
<NotFailNodeIcon />
) : (
<NotWarningNodeIcon />
)}
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
{type.title === NODEDIALOGTYPE.ClearFailNode.title
? "暂无掉线节点"
: "暂无恶意节点"}
</div>
</div>
)}
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
{type.title === NODEDIALOGTYPE.ClearFailNode.title
? "暂无掉线节点"
: "暂无恶意节点"}
</div>
</FormDialog>
);
</div>
)}
</div>
</FormDialog>
);
};

View File

@ -702,7 +702,8 @@ export const WorldGeo = memo(
show: true, // 是否显示
period: 4, // 特效动画时间
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
symbol: planePathImg, // 特效图形标记
color: pathColor, // 特效颜色
// symbol: planePathImg, // 特效图形标记
symbolSize: [10, 20],
},
// 线条样式

View File

@ -3,7 +3,7 @@ import { FormInstance } from "antd";
import { FormDialog } from "@/components/FormDialog";
import { useDispatch, useSelector } from "react-redux";
import { nodeList,getRandomNodes } from "@/store/datas";
import { nodeList, getRandomNodes } from "@/store/datas";
import "./index.scss";
import { EllipsisTooltip } from "@/components/Encapsulation";
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
@ -11,170 +11,127 @@ import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
import { NODEDIALOGTYPE } from "../../index";
import { cn, getUrl } from "@/lib/utils";
import {
setClearFailTimer,
setClearWarningTimer,
} from "@/store/web3Slice";
import { AppDispatch, RootState } from "@/store";
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
export interface DialogConfig {
title: string;
desc: string;
successText: string;
title: string;
desc: string;
successText: string;
}
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
props
props
) => {
const { name, code, exit = false } = props.proxyInfo;
const { name, code, exit = false } = props.proxyInfo;
return (
<div
className={cn(
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
exit && "hover:bg-[#EFF6FF]",
props.clasName
)}
>
<div className="flex-1 flex items-center justify-end w-full h-7">
<div className="flex-1 flex space-x-3 items-center">
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
<img
className={cn(
"w-full h-full object-cover rounded-sm"
)}
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
/>
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
/>
</div>
</div>
return (
<div
className={cn(
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
exit && "hover:bg-[#EFF6FF]",
props.clasName
)}
>
<div className="flex-1 flex items-center justify-end w-full h-7">
<div className="flex-1 flex space-x-3 items-center">
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
<img
className={cn("w-full h-full object-cover rounded-sm")}
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
/>
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
/>
</div>
);
</div>
</div>
);
};
export const ClearNodeDialog = ({
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
}: {
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
}) => {
const dispatch = useDispatch<AppDispatch>();
const { clearWarningTimer, clearFailTimer } = useSelector(
(state: RootState) => state.web3Reducer
);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
};
const onSuccessHandle = () => {
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
dispatch(setClearFailTimer(Date.now()));
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
dispatch(setClearWarningTimer(Date.now()));
}
// showDialog(false);
};
const dispatch = useDispatch<AppDispatch>();
const {} = useSelector((state: RootState) => state.web3Reducer);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
};
const onSuccessHandle = () => {
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
}
// showDialog(false);
};
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
if (open) {
if (isClear) return [];
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
if (clearFailTimer) {
const clear =
isTimestampPlusTenMinutesBeforeNow(clearFailTimer);
if (clear) {
return newData
} else {
return [];
}
} else {
return newData;
}
}
if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
if (clearWarningTimer) {
const clear =
isTimestampPlusTenMinutesBeforeNow(clearWarningTimer);
console.log(clear,'clear')
if (clear) {
return newData
} else {
return [];
}
} else {
return newData;
}
}
const proxyList = useMemo(() => {
// 随机 2-9条 nodelist里面的数据
return [];
}, [nodeList, open, isClear, type]);
return newData;
}
// 随机 2-9条 nodelist里面的数据
return [];
}, [nodeList, open, isClear, clearFailTimer, clearWarningTimer, type]);
useEffect(() => {
if (!open) {
setIsClear(false);
}
}, [open]);
useEffect(() => {
if (!open) {
setIsClear(false);
}
}, [open]);
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={onSuccessHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
: "opacity-50"
}
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
) : (
<div className="w-full h-[382px] flex flex-col items-center justify-center">
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
<NotFailNodeIcon />
) : (
<NotWarningNodeIcon />
)}
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={onSuccessHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
: "opacity-50"
}
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
) : (
<div className="w-full h-[382px] flex flex-col items-center justify-center">
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
<NotFailNodeIcon />
) : (
<NotWarningNodeIcon />
)}
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
{type.title === NODEDIALOGTYPE.ClearFailNode.title
? "暂无掉线节点"
: "暂无恶意节点"}
</div>
</div>
)}
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
{type.title === NODEDIALOGTYPE.ClearFailNode.title
? "暂无掉线节点"
: "暂无恶意节点"}
</div>
</FormDialog>
);
</div>
)}
</div>
</FormDialog>
);
};

View File

@ -8,217 +8,237 @@ import { DIALOGTYPE } from "../../index";
import "./index.scss";
import { DefaultLink } from "./pathChoose";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
export interface DialogConfig {
title: string;
desc: string;
successText: string;
title: string;
desc: string;
successText: string;
}
const PledgeAmount = ({
value,
onChange,
value,
onChange,
}: {
value?: string;
onChange?: (data: string) => void;
value?: string;
onChange?: (data: string) => void;
}) => {
return (
<div className="flex items-center gap-1.5">
<Input
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
placeholder="请输质押金额*"
value={value}
onChange={(e) => {
onChange?.(e.target.value);
}}
/>
<span>SOL</span>
</div>
);
return (
<div className="flex items-center gap-1.5">
<Input
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
placeholder="请输质押金额*"
value={value}
onChange={(e) => {
onChange?.(e.target.value);
}}
/>
<span>SOL</span>
</div>
);
};
const IpPortInput = ({
value,
onChange,
value,
onChange,
}: {
value?: { port: string; ip: string };
onChange?: (data: { port: string; ip: string }) => void;
value?: { port: string; ip: string };
onChange?: (data: { port: string; ip: string }) => void;
}) => {
const [ipAndPort, setIpAndPort] = useState<{
port: string;
ip: string;
}>(value ? value : { port: "", ip: "" });
const handleChagne = ({
key,
val,
}: {
key: "port" | "ip";
val: string;
}) => {
const newValue = value ? { ...value } : { port: "", ip: "" };
newValue[key] = val;
onChange?.(newValue);
};
useEffect(() => {
if (value) {
setIpAndPort(value);
}
}, [value]);
return (
<div className="flex items-center gap-1.5">
<Input
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
placeholder="请输入IP*"
value={ipAndPort?.ip}
onChange={(e) => {
handleChagne?.({ key: "ip", val: e.target.value });
}}
/>
<Input
className="data-[state=checked]:bg-[#1E3A8A] "
placeholder="请输入端口"
type="number"
min={0}
max={65535}
value={ipAndPort?.port}
onChange={(e) => {
handleChagne?.({ key: "port", val: e.target.value });
}}
/>
</div>
);
const [ipAndPort, setIpAndPort] = useState<{
port: string;
ip: string;
}>(value ? value : { port: "", ip: "" });
const handleChagne = ({ key, val }: { key: "port" | "ip"; val: string }) => {
const newValue = value ? { ...value } : { port: "", ip: "" };
newValue[key] = val;
onChange?.(newValue);
};
useEffect(() => {
if (value) {
setIpAndPort(value);
}
}, [value]);
return (
<div className="flex items-center gap-1.5">
<Input
className="data-[state=checked]:bg-[#1E3A8A] flex-shrink-0 !w-[600px]"
placeholder="请输入IP"
value={ipAndPort?.ip}
onChange={(e) => {
handleChagne?.({ key: "ip", val: e.target.value });
}}
/>
<Input
className="data-[state=checked]:bg-[#1E3A8A] "
placeholder="请输入端口"
type="number"
min={0}
max={65535}
value={ipAndPort?.port}
onChange={(e) => {
handleChagne?.({ key: "port", val: e.target.value });
}}
/>
</div>
);
};
const SwitchComponent = ({
value,
onChange,
}: {
value?: boolean;
onChange?: (data: boolean) => void;
}) => {
const [checked, setChecked] = useState(value);
return (
<div className="flex items-center">
<Switch
className="data-[state=checked]:bg-[#1E3A8A]"
checked={checked}
onCheckedChange={(e) => {
setChecked(e);
onChange?.(e);
}}
/>
<div className="ml-2 text-zinc-900 text-sm font-medium leading-tight">
</div>
</div>
);
};
const NodeForm = ({ form }: { form: FormInstance }) => {
return (
<Form
className="-mt-1"
form={form}
name="dynamic_form_nest_item"
autoComplete="off"
layout="vertical"
>
{/* <Form.Item name="uid" className="hidden">
return (
<Form
className="-mt-1"
form={form}
name="dynamic_form_nest_item"
autoComplete="off"
layout="vertical"
>
{/* <Form.Item name="uid" className="hidden">
<div className="hidden">uid</div>
</Form.Item> */}
<Form.Item name="public_key" label="节点身份公钥">
<Input
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
placeholder="节点身份公钥*"
/>
</Form.Item>
<Form.Item name="private_key" label="节点元数据">
<Input
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
placeholder="请输入TLS Pubkey*"
/>
</Form.Item>
<Form.Item name="ipAndPort" label="IP+端口">
<IpPortInput />
</Form.Item>
<Form.Item name="pledgeAmount" label="质押金额">
<PledgeAmount />
</Form.Item>
<Form.Item name="walletAddress" label="钱包地址">
<Input
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
placeholder="钱包地址*"
/>
</Form.Item>
</Form>
);
<Form.Item name="name" label="节点名称" rules={[{ required: true, message: '请输入节点名称' }]}>
<Input
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
placeholder="节点名称"
/>
</Form.Item>
<Form.Item name="private_key" label="私钥" rules={[{ required: true, message: '请输入私钥' }]}>
<Input
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
placeholder="私钥"
/>
</Form.Item>
<Form.Item name="public_key" label="公钥" rules={[{ required: true, message: '请输入公钥' }]}>
<Input
className="link_name_input placeholder:text-base placeholder:text-zinc-400 text-[16px]"
placeholder="公钥"
/>
</Form.Item>
<Form.Item name="ipAndPort" label="IP+端口" rules={[{ required: true, message: '请输入IP+端口' }]}>
<IpPortInput />
</Form.Item>
<Form.Item name="exit">
<SwitchComponent />
</Form.Item>
</Form>
);
};
const NetworkForm = ({ form }: { form: FormInstance }) => {
return (
<Form
className="-mt-1"
form={form}
name="dynamic_form_nest_item"
autoComplete="off"
layout="vertical"
>
<Form.Item
name="inbound"
label="入口节点"
rules={[{ required: true, message: "请选择入口节点" }]}
>
<DefaultLink
des="入口节点"
type="entry"
countries={Object.keys(countryCodeMap)
// .splice(0, 50)
.map((key) => key)}
/>
</Form.Item>
<Form.Item
name="outbound"
label="出口节点"
rules={[{ required: true, message: "请选择出口节点" }]}
>
<DefaultLink
des="出口节点"
type="exit"
countries={Object.keys(countryCodeMap)
// .splice(0, 50)
.map((key) => key)}
/>
</Form.Item>
</Form>
);
return (
<Form
className="-mt-1"
form={form}
name="dynamic_form_nest_item"
autoComplete="off"
layout="vertical"
>
<Form.Item
name="inbound"
label="入口节点"
rules={[{ required: true, message: "请选择入口节点" }]}
>
<DefaultLink
des="入口节点"
type="entry"
countries={Object.keys(countryCodeMap)
// .splice(0, 50)
.map((key) => key)}
/>
</Form.Item>
<Form.Item
name="outbound"
label="出口节点"
rules={[{ required: true, message: "请选择出口节点" }]}
>
<DefaultLink
des="出口节点"
type="exit"
countries={Object.keys(countryCodeMap)
// .splice(0, 50)
.map((key) => key)}
/>
</Form.Item>
</Form>
);
};
export const FormAlertDialog = ({
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
handleSelectFile,
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
handleSelectFile,
}: {
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
handleSelectFile: () => void;
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
handleSelectFile: () => void;
}) => {
const showDialog = (open: boolean) => {
setOpen(open);
};
const showDialog = (open: boolean) => {
setOpen(open);
};
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={successHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[850px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#1E3A8A] hover:bg-[#1D4ED8] active:bg-[#1E40AF]"
: "bg-[#1E3A8A] hover:bg-[#1E3A8A] opacity-50"
}
>
<Button
className="absolute top-3 right-12 bg-transparent text-zinc-900 border border-zinc-200"
onClick={() => handleSelectFile()}
>
</Button>
{open && type.title === DIALOGTYPE.ADDNode.title ? (
<NodeForm form={form} />
) : (
<NetworkForm form={form} />
)}
</FormDialog>
);
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={successHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[850px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#1E3A8A] hover:bg-[#1D4ED8] active:bg-[#1E40AF]"
: "bg-[#1E3A8A] hover:bg-[#1E3A8A] opacity-50"
}
>
<Button
className="absolute top-3 right-12 bg-transparent text-zinc-900 border border-zinc-200"
onClick={() => handleSelectFile()}
>
</Button>
{open && type.title === DIALOGTYPE.ADDNode.title ? (
<NodeForm form={form} />
) : (
<NetworkForm form={form} />
)}
</FormDialog>
);
};

View File

@ -91,7 +91,6 @@ export const WorldGeo = memo(({ screenData }: { screenData: any }) => {
const mainToData = useMemo(() => {
// 使用新的数据结构
const proxiesList = screenData?.proxy_info?.proxies ?? [{data:[{country_code: 'AI', ingress_country_code: 'AE'}], isLine: true}];
console.log(proxiesList,'proxiesList')
// 初始化数据数组 - 不再包含 startCountry
const data: any = [];
@ -418,7 +417,8 @@ export const WorldGeo = memo(({ screenData }: { screenData: any }) => {
show: true, // 是否显示
period: 4, // 特效动画时间
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
symbol: planePathImg, // 特效图形标记
// symbol: planePathImg, // 特效图形标记
color:"#0ea5e9",
symbolSize: [10, 20],
},
// 线条样式

View File

@ -3,7 +3,7 @@ import { FormInstance } from "antd";
import { FormDialog } from "@/components/FormDialog";
import { useDispatch, useSelector } from "react-redux";
import { nodeList,getRandomNodes } from "@/store/datas";
import { nodeList, getRandomNodes } from "@/store/datas";
import "./index.scss";
import { EllipsisTooltip } from "@/components/Encapsulation";
import NotFailNodeIcon from "@/assets/svg/common/not-fail-node.svg?react";
@ -11,170 +11,128 @@ import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
import { NODEDIALOGTYPE } from "../../index";
import { cn, getUrl } from "@/lib/utils";
import {
setClearFailTimer,
setClearWarningTimer,
} from "@/store/web3Slice";
import { AppDispatch, RootState } from "@/store";
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
export interface DialogConfig {
title: string;
desc: string;
successText: string;
title: string;
desc: string;
successText: string;
}
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
props
props
) => {
const { name, code, exit = false } = props.proxyInfo;
const { name, code, exit = false } = props.proxyInfo;
return (
<div
className={cn(
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
exit && "hover:bg-[#EFF6FF]",
props.clasName
)}
>
<div className="flex-1 flex items-center justify-end w-full h-7">
<div className="flex-1 flex space-x-3 items-center">
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
<img
className={cn(
"w-full h-full object-cover rounded-sm"
)}
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
/>
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
/>
</div>
</div>
return (
<div
className={cn(
"w-[251px] flex p-3 rounded-lg group text-[#111322] cursor-pointer",
exit && "hover:bg-[#EFF6FF]",
props.clasName
)}
>
<div className="flex-1 flex items-center justify-end w-full h-7">
<div className="flex-1 flex space-x-3 items-center">
<div className="w-[27px] h-[20px] proxy-item-flag rounded-sm overflow-hidden">
<img
className={cn("w-full h-full object-cover rounded-sm")}
src={getUrl(`image/res/flag3/${code.toLowerCase()}.svg`)}
/>
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
/>
</div>
);
</div>
</div>
);
};
export const ClearNodeDialog = ({
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
open,
setOpen,
successHandle,
dialogLoading,
form,
type,
canSubmit,
}: {
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
open: boolean;
setOpen: (open: boolean) => void;
successHandle: () => void;
dialogLoading: boolean;
canSubmit: boolean;
form: FormInstance;
type: DialogConfig;
}) => {
const dispatch = useDispatch<AppDispatch>();
const { clearWarningTimer, clearFailTimer } = useSelector(
(state: RootState) => state.web3Reducer
);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
};
const onSuccessHandle = () => {
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
dispatch(setClearFailTimer(Date.now()));
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
dispatch(setClearWarningTimer(Date.now()));
}
// showDialog(false);
};
const dispatch = useDispatch<AppDispatch>();
const {} = useSelector((state: RootState) => state.web3Reducer);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
};
const onSuccessHandle = () => {
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
}
// showDialog(false);
};
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
if (open) {
if (isClear) return [];
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
if (clearFailTimer) {
const clear =
isTimestampPlusTenMinutesBeforeNow(clearFailTimer);
if (clear) {
return newData
} else {
return [];
}
} else {
return newData;
}
}
if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
if (clearWarningTimer) {
const clear =
isTimestampPlusTenMinutesBeforeNow(clearWarningTimer);
console.log(clear,'clear')
if (clear) {
return newData
} else {
return [];
}
} else {
return newData;
}
}
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
return newData;
}
// 随机 2-9条 nodelist里面的数据
return [];
}, [nodeList, open, isClear, clearFailTimer, clearWarningTimer, type]);
return [];
}, [nodeList, open, isClear, type]);
useEffect(() => {
if (!open) {
setIsClear(false);
}
}, [open]);
useEffect(() => {
if (!open) {
setIsClear(false);
}
}, [open]);
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={onSuccessHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
: "opacity-50"
}
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
) : (
<div className="w-full h-[382px] flex flex-col items-center justify-center">
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
<NotFailNodeIcon />
) : (
<NotWarningNodeIcon />
)}
return (
<FormDialog
open={open}
openChange={showDialog}
title={type.title}
describe={type.desc}
successText={type.successText}
successHandle={onSuccessHandle}
submitLoading={dialogLoading}
form={form}
contentClass="w-[834px] flex flex-col max-h-[calc(100vh-100px)] overflow-y-hidden "
successStyle={
canSubmit
? "bg-[#dc2626] hover:bg-[#dc2626] active:bg-[#dc2626]"
: "opacity-50"
}
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
) : (
<div className="w-full h-[382px] flex flex-col items-center justify-center">
{type.title === NODEDIALOGTYPE.ClearFailNode.title ? (
<NotFailNodeIcon />
) : (
<NotWarningNodeIcon />
)}
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
{type.title === NODEDIALOGTYPE.ClearFailNode.title
? "暂无掉线节点"
: "暂无恶意节点"}
</div>
</div>
)}
<div className="text-lg font-medium text-zinc-950 leading-relaxed mt-5">
{type.title === NODEDIALOGTYPE.ClearFailNode.title
? "暂无掉线节点"
: "暂无恶意节点"}
</div>
</FormDialog>
);
</div>
)}
</div>
</FormDialog>
);
};

View File

@ -59,7 +59,7 @@ export const WorldGeo = memo(
const labelsRef = useRef<HTMLDivElement[]>([]);
const mainToData = useMemo(() => {
const newList = [
...dataInfo.passAuthentication.data,
dataInfo.passAuthentication,
...dataInfo.trafficObfuscation,
...dataInfo.nestedEncryption,
...dataInfo.dynamicRouteGeneration,
@ -239,7 +239,8 @@ export const WorldGeo = memo(
const positionCustomTooltip2 = () => {
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
// 找到US点
const coords = geoCoordMap[dataInfo.trafficObfuscation?.[0]?.code ?? "ZA"];
const coords =
geoCoordMap[dataInfo.trafficObfuscation?.[0]?.code ?? "ZA"];
if (!coords) return;
try {
// 将地理坐标转换为屏幕坐标
@ -261,27 +262,7 @@ export const WorldGeo = memo(
console.error("Error positioning tooltip:", error);
}
};
// 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个
const mianLineData = (data: typeof mainToData) => {
return (
data
.map((item: any) => {
const countryCode = item.country_code.toUpperCase();
if (!(["RU", "FR"].includes(countryCode) && item.type === "start"))
return null;
const coords = geoCoordMap[countryCode] as
| [number, number]
| undefined;
if (!coords) return null;
return {
name: countryCodeMap[countryCode],
coords: [coords, [coords[0], coords[1] + 4]],
value: countryCode,
};
})
.filter((v: any) => !!v) ?? []
);
};
const getLineItem = (
preCode: string,
nextCode: string
@ -746,7 +727,8 @@ export const WorldGeo = memo(
show: true, // 是否显示
period: 4, // 特效动画时间
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
symbol: planePathImg, // 特效图形标记
color: lastExitColor, // 特效颜色
// symbol: planePathImg, // 特效图形标记
symbolSize: [10, 20],
},
// 线条样式
@ -772,7 +754,6 @@ export const WorldGeo = memo(
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
const lastExitColor =
(item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
// 添加虚线
series.push({
name: item[0],
@ -807,7 +788,7 @@ export const WorldGeo = memo(
});
return true;
};
// 创建A点和B点并添加飞线和标签
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
// 定义点A和点B的坐标

View File

@ -1,5 +1,5 @@
import { createBrowserRouter, Navigate } from 'react-router-dom'
// import HomePage from '@/pages/home'
import HomePage from '@/pages/home'
import NewHomePage from '@/pages/new-home'
import DecentralizedElasticNetworkPage from '@/pages/decentralized-lastic-network'
import AntiForensicsForwardingPage from '@/pages/anti-forensics-forwarding'
@ -35,7 +35,7 @@ export const router = createBrowserRouter([
// },
{
path: '/proxies',
element: <LazyLoader component={ProxiesPage} />,
element: <LazyLoader component={HomePage} />,
},
],
},

View File

@ -23,8 +23,6 @@ interface Iweb3Slice {
newHomeProxies: any[];
path_list: any;
proxy_info: any;
clearWarningTimer: number | null;
clearFailTimer: number | null;
isLine: boolean;
}
@ -104,8 +102,6 @@ const initialState: Iweb3Slice = {
change_at: 0,
proxies: [],
},
clearWarningTimer: null,
clearFailTimer: null,
newHomeProxies: [
{
authenticationPoint: [
@ -187,7 +183,6 @@ export const appSlice = createSlice({
// 进一步优化的代码
if (state.proxy_info.proxies.length === 0) return;
// 标记所有代理为在线 - 这个操作仍然需要
state.proxy_info.proxies = state.proxy_info.proxies.map((item: any) => {
item.isLine = true;
@ -244,12 +239,6 @@ export const appSlice = createSlice({
setIsLine: (state, action) => {
state.isLine = action.payload;
},
setClearWarningTimer: (state, action) => {
state.clearWarningTimer = action.payload;
},
setClearFailTimer: (state, action) => {
state.clearFailTimer = action.payload;
},
setWeb3List: (state, action) => {
state.web3List = action.payload;
},
@ -281,8 +270,6 @@ export const {
setProxyInfoProxies,
randomUpdateWeb3List,
randomUpdateWeb3List2,
setClearWarningTimer,
setClearFailTimer,
reset,
setIsLine,
setProxiesList1,

23
src/utils/eventBus.ts Normal file
View File

@ -0,0 +1,23 @@
import mitt from "mitt";
const eventBus = mitt();
// 事件类型
export const eventTypes = {
// 节点上线
NODE_UP: "node_up",
// 节点下线
NODE_DOWN: "node_down",
// 检测到恶意节点
MALICIOUS_NODE: "malicious_node",
// 节点预配置完成
NODE_INIT_COMPLATE: "node_init_complate",
// 节点清除
NODE_REMOVE: "node_remove",
// 添加节点
NODE_ADD: "node_add",
// 节点预配置
NODE_INIT: "node_init",
};
export default eventBus;

View File

@ -27,7 +27,7 @@ export class WebSocketClient {
}
// 发送消息
async sendMessage(message: string): Promise<void> {
async sendMessage(message: any): Promise<void> {
if (!this.ws) {
console.error('WebSocket is not connected')
return