fix:bug
This commit is contained in:
parent
9f09e8ba61
commit
ffabc1ccbe
64
__unconfig_vite.config.ts
Normal file
64
__unconfig_vite.config.ts
Normal 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;
|
||||
@ -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
27
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
111
src/App.tsx
111
src/App.tsx
@ -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
26
src/hooks/useEventBus.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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'
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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],
|
||||
},
|
||||
// 线条样式
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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],
|
||||
},
|
||||
// 线条样式
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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的坐标
|
||||
|
||||
@ -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} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -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
23
src/utils/eventBus.ts
Normal 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;
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user