feat: 新增websoket事件对应的events

This commit is contained in:
liyuanhu 2025-04-21 14:40:24 +08:00
parent d220b7b0f5
commit 1693316766
12 changed files with 180 additions and 116 deletions

3
.env
View File

@ -3,5 +3,6 @@ COSMOS_ENDPOINT="http://10.66.66.234:26657"
NODE_SECRET="aHVnZSBjb21wYW55IHBob25lIHdlc3QgcGxhY2Ugc2VtaW5hciBtaXJhY2xlIGxlbmQgbWFuZGF0ZSB0aGVuIGFkanVzdCBxdWl0IG1lYXQgY2hlYXAgbm9vZGxlIGNvdXBsZSBkZWZpbmUgbXVzY2xlIHB1bHNlIHNpc3RlciBwaWVjZSBkZXZpY2UgcHJpdmF0ZSBob29k"
IS_DEBUG="true"
ACCOUNT_NAME="de1"
VIET_EVENTS_WS_URL="ws://10.66.66.234:8080/events"
VITE_BASE_URL="http://10.66.66.234:6060"
VITE_BLOCK_URL="http://localhost:3001"
VITE_BLOCK_URL="http://10.66.66.234:1317"

View File

@ -12,6 +12,7 @@ use paw_common::preclude::{CoreConfig, CoreResponse};
#[derive(Debug, Clone, Deserialize, Serialize, specta::Type)]
pub struct ProxyNodeInfo {
pub name: String,
pub ip: String,
pub country_code: String,
pub country_name: String,
pub country_name_zh: String,
@ -76,7 +77,7 @@ pub async fn get_nodes(config: CoreConfig) -> Result<Vec<ProxyNodeInfo>> {
debug!("Successfully got nodes: {:?}", result.data);
Ok(result.data)
} else {
error!("Failed to get nodes: {}", response.text().await?);
debug!("Failed to get nodes: {}", response.text().await?);
bail!("Failed to get nodes")
}
}

View File

@ -12,7 +12,7 @@ import {
setMaliciousNodeList,
setNodeDownList,
} from "@/store/web3Slice";
import type { AppDispatch, RootState } from "@/store";
import type { AppDispatch } from "@/store";
import eventBus, { eventTypes } from "@/utils/eventBus";
import { WebSocketClient } from "@/utils/webSocketClient";
@ -20,6 +20,8 @@ import { WebSocketClient } from "@/utils/webSocketClient";
import Titlebar from "@/components/Titlebar";
import Layout from "@/layout";
import Tray from "@/components/Tray";
import { getRandomCountryKey } from "./data";
import dayjs from "dayjs";
function App() {
// 执行启动自检
@ -37,79 +39,99 @@ function App() {
// const { } = useSelector(
// (state: RootState) => state.web3Reducer
// );
const webSocketClient = useRef<WebSocketClient | null>();
let eventsWs: WebSocketClient | null = 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",
},
}
);
if (eventsWs) return;
eventsWs = new WebSocketClient("ws://10.66.66.234:8080/events", {});
console.log(eventsWs, "openWsTraffic Start");
// 执行 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 里面
dispatch(setNodeDownList(msg.data.name));
console.log("节点下线");
break;
case eventTypes.MALICIOUS_NODE:
// 添加恶意节点到store 里面
dispatch(setMaliciousNodeList(msg.data.name));
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;
await eventsWs?.connect();
await eventsWs?.addListener((msg: any) => {
try {
const msgData = msg ? JSON.parse(msg.data) : {};
if (msgData.code === 0) {
console.log(msgData, "msgDatamsgData");
switch (msgData.event) {
case eventTypes.NODE_UP:
console.log("节点上线");
break;
case eventTypes.NODE_DOWN:
// 添加下线节点到store 里面
if (msgData.data.name) {
// 获取一个随机的国家code
const countryCode = getRandomCountryKey();
dispatch(
setNodeDownList({
name: msgData.data.name,
code: countryCode,
})
);
}
console.log("节点下线");
break;
case eventTypes.MALICIOUS_NODE:
// 添加恶意节点到store 里面
if (msgData.data.name) {
// 获取一个随机的国家code
const countryCode = getRandomCountryKey();
dispatch(
setMaliciousNodeList({
name: msgData.data.name,
code: countryCode,
})
);
}
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;
}
}
} catch (error) {
console.log(error, "error");
}
});
console.log(eventsWs, "openWsTraffic End");
};
const createdSoketEventBus = async () => {
eventBus.on(eventTypes.NODE_INIT_COMPLATE, (data: any) => {
console.log("节点预配置完成", data);
webSocketClient.current?.sendMessage(data);
eventsWs?.sendMessage(data);
});
eventBus.on(eventTypes.NODE_REMOVE, (data: any) => {
console.log("节点清除");
webSocketClient.current?.sendMessage({
const timestamp = dayjs().unix();
const params = {
code: 0,
event: eventTypes.NODE_REMOVE,
data: {
name: data,
},
});
timestamp,
};
console.log(JSON.stringify(params), "节点清除 params");
eventsWs?.sendMessage(JSON.stringify(params));
});
};
const closeWsTraffic = async () => {
if (webSocketClient.current) {
await webSocketClient.current.disconnect();
webSocketClient.current = null;
if (eventsWs) {
await eventsWs.disconnect();
eventsWs = null;
}
};
@ -124,11 +146,13 @@ function App() {
};
useEffect(() => {
// initWebsocketsAndEventBus();
// return () => {
// closeWsTraffic();
// removeSoketEventBus();
// };
setTimeout(() => {
initWebsocketsAndEventBus();
}, 1000);
return () => {
closeWsTraffic();
removeSoketEventBus();
};
}, []);
return (

View File

@ -209,7 +209,7 @@ export type CoreConfig = { socks_port: number; socks_user: string; socks_pass: s
/**
*
*/
export type ProxyNodeInfo = { name: string; country_code: string; country_name: string; country_name_zh: string; city_name: string; city_name_zh: string; delay: number; download: number; upload: number; survive_score: number; exit: boolean; use: boolean }
export type ProxyNodeInfo = { name: string; ip: string; country_code: string; country_name: string; country_name_zh: string; city_name: string; city_name_zh: string; delay: number; download: number; upload: number; survive_score: number; exit: boolean; use: boolean }
/** tauri-specta globals **/

View File

@ -500,6 +500,18 @@ export const countryNameMap = {
Curaçao: '库拉索',
}
// 获取一个随机的国家key
export function getRandomCountryKey() {
const keys = Object.keys(countryCodeMap)
return keys[Math.floor(Math.random() * keys.length)]
}
// 获取一个随机的国家name
export function getRandomCountryName() {
const keys = Object.keys(countryCodeMap)
return countryCodeMap[keys[Math.floor(Math.random() * keys.length)]]
}
// 国家代码映射
export const countryCodeMap: { [key: string]: string } = {
AD: '安道尔',

View File

@ -201,7 +201,6 @@ export function useStartupCheck() {
// 只有当核心正在运行时才更新节点
if (isCoreRunning) {
if (nodesResult.status === "ok") {
console.log("触发了???");
const proxies:any[] = [];
nodesResult.data.forEach((node,index) => {
const { country_code } = node
@ -210,8 +209,8 @@ export function useStartupCheck() {
}
proxies.push({ country_code: country_code,ingress_country_code: nodesResult.data[index + 1].country_code })
})
console.log(proxies,'proxiesproxiesproxies')
// dispatch(setProxiesList1())
// console.log(proxies,'proxiesproxiesproxies')
dispatch(setProxiesList1(proxies))
dispatch(setNodes(nodesResult.data));
} else {
dispatch(setNodesError("Failed to fetch nodes"));

View File

@ -3,17 +3,17 @@ import { FormInstance } from "antd";
import { FormDialog } from "@/components/FormDialog";
import { useDispatch, useSelector } from "react-redux";
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";
import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
import { NODEDIALOGTYPE } from "../../index";
import { cn, getUrl } from "@/lib/utils";
import eventBus, { eventTypes } from "@/utils/eventBus";
import { } from "@/store/web3Slice";
import { AppDispatch, RootState } from "@/store";
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
import { removeMaliciousNodeList, removeNodeDownList } from "@/store/web3Slice";
import { countryCodeMap } from "@/data";
export interface DialogConfig {
title: string;
@ -24,8 +24,7 @@ export interface DialogConfig {
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
props
) => {
const { name, code, exit = false } = props.proxyInfo;
const { code, exit = false } = props.proxyInfo;
return (
<div
className={cn(
@ -44,7 +43,7 @@ export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
text={countryCodeMap[code]}
/>
</div>
</div>
@ -70,7 +69,7 @@ export const ClearNodeDialog = ({
type: DialogConfig;
}) => {
const dispatch = useDispatch<AppDispatch>();
const { } = useSelector(
const { maliciousNodeList, nodeDownList } = useSelector(
(state: RootState) => state.web3Reducer
);
const [isClear, setIsClear] = useState(false);
@ -81,18 +80,26 @@ export const ClearNodeDialog = ({
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
nodeDownList.forEach((item) => {
dispatch(removeNodeDownList(item.name));
eventBus.emit(eventTypes.NODE_REMOVE, item.name);
});
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
maliciousNodeList.forEach((item) => {
dispatch(removeMaliciousNodeList(item.name));
eventBus.emit(eventTypes.NODE_REMOVE, item.name);
});
}
// showDialog(false);
};
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
return newData;
}, [nodeList, open, isClear, type]);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
return nodeDownList;
} else {
return maliciousNodeList;
}
}, [nodeDownList, maliciousNodeList, type.title]);
useEffect(() => {
if (!open) {
@ -119,9 +126,11 @@ export const ClearNodeDialog = ({
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
proxyList
.filter((item: any) => item?.name)
.map((item: any) => {
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 ? (

View File

@ -3,7 +3,6 @@ import { FormInstance } from "antd";
import { FormDialog } from "@/components/FormDialog";
import { useDispatch, useSelector } from "react-redux";
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";
@ -13,8 +12,8 @@ import { cn, getUrl } from "@/lib/utils";
import eventBus, { eventTypes } from "@/utils/eventBus";
import { AppDispatch, RootState } from "@/store";
import {removeMaliciousNodeList,removeNodeDownList} from '@/store/web3Slice'
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
import { removeMaliciousNodeList, removeNodeDownList } from "@/store/web3Slice";
import { countryCodeMap } from "@/data";
export interface DialogConfig {
title: string;
@ -25,8 +24,7 @@ export interface DialogConfig {
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
props
) => {
const { name, code, exit = false } = props.proxyInfo;
const { code, exit = false } = props.proxyInfo;
return (
<div
className={cn(
@ -45,7 +43,7 @@ export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
text={countryCodeMap[code]}
/>
</div>
</div>
@ -82,15 +80,15 @@ export const ClearNodeDialog = ({
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
nodeDownList.forEach((item)=>{
dispatch(removeNodeDownList(item.name));
nodeDownList.forEach((item) => {
dispatch(removeNodeDownList(item));
eventBus.emit(eventTypes.NODE_REMOVE, item.name);
})
});
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
maliciousNodeList.forEach((item)=>{
dispatch(removeMaliciousNodeList(item.name));
maliciousNodeList.forEach((item) => {
dispatch(removeMaliciousNodeList(item));
eventBus.emit(eventTypes.NODE_REMOVE, item.name);
})
});
}
// showDialog(false);
};
@ -127,10 +125,12 @@ export const ClearNodeDialog = ({
}
>
<div className="flex flex-wrap gap-3">
{proxyList?.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
{proxyList.length > 0 ? (
proxyList
.filter((item: any) => item?.name)
.map((item: any) => {
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 ? (

View File

@ -3,16 +3,17 @@ import { FormInstance } from "antd";
import { FormDialog } from "@/components/FormDialog";
import { useDispatch, useSelector } from "react-redux";
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";
import NotWarningNodeIcon from "@/assets/svg/common/not-warning-node.svg?react";
import { NODEDIALOGTYPE } from "../../index";
import { cn, getUrl } from "@/lib/utils";
import eventBus, { eventTypes } from "@/utils/eventBus";
import { AppDispatch, RootState } from "@/store";
import { isTimestampPlusTenMinutesBeforeNow } from "@/utils/tools";
import { removeMaliciousNodeList, removeNodeDownList } from "@/store/web3Slice";
import { countryCodeMap } from "@/data";
export interface DialogConfig {
title: string;
@ -23,8 +24,7 @@ export interface DialogConfig {
export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
props
) => {
const { name, code, exit = false } = props.proxyInfo;
const { code, exit = false } = props.proxyInfo;
return (
<div
className={cn(
@ -43,7 +43,7 @@ export const ProxyItem: React.FC<{ proxyInfo: any; clasName?: string }> = (
</div>
<EllipsisTooltip
className="text-lg flex-1 font-semibold"
text={name}
text={countryCodeMap[code]}
/>
</div>
</div>
@ -69,7 +69,9 @@ export const ClearNodeDialog = ({
type: DialogConfig;
}) => {
const dispatch = useDispatch<AppDispatch>();
const {} = useSelector((state: RootState) => state.web3Reducer);
const { maliciousNodeList, nodeDownList } = useSelector(
(state: RootState) => state.web3Reducer
);
const [isClear, setIsClear] = useState(false);
const showDialog = (open: boolean) => {
setOpen(open);
@ -78,16 +80,26 @@ export const ClearNodeDialog = ({
successHandle();
setIsClear(true);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
nodeDownList.forEach((item) => {
dispatch(removeNodeDownList(item.name));
eventBus.emit(eventTypes.NODE_REMOVE, item.name);
});
} else if (type.title === NODEDIALOGTYPE.ClearWargingNode.title) {
maliciousNodeList.forEach((item) => {
dispatch(removeMaliciousNodeList(item.name));
eventBus.emit(eventTypes.NODE_REMOVE, item.name);
});
}
// showDialog(false);
};
const proxyList = useMemo(() => {
const newData = getRandomNodes(nodeList);
return [];
}, [nodeList, open, isClear, type]);
if (type.title === NODEDIALOGTYPE.ClearFailNode.title) {
return nodeDownList;
} else {
return maliciousNodeList;
}
}, [nodeDownList, maliciousNodeList, type.title]);
useEffect(() => {
if (!open) {
@ -114,9 +126,11 @@ export const ClearNodeDialog = ({
>
<div className="flex flex-wrap gap-3">
{proxyList.length > 0 ? (
proxyList.map((item) => {
return <ProxyItem proxyInfo={item} key={item.name} />;
})
proxyList
.filter((item: any) => item?.name)
.map((item: any) => {
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 ? (

View File

@ -290,14 +290,14 @@ const NewHome = () => {
};
useEffect(() => {
blockChainApi.getLatestBlock().then((res) => {
console.log("res", res);
console.log("getLatestBlock res:", res);
});
initData();
}, []);
useEffect(() => {
console.log(dataInfo, "awaidataInfodataInfotawait");
}, [dataInfo]);
// useEffect(() => {
// console.log(dataInfo, "awaidataInfodataInfotawait");
// }, [dataInfo]);
return (
<div className="decentralized w-full h-full flex flex-col relative">

View File

@ -159,18 +159,18 @@ export const appSlice = createSlice({
reducers: {
removeMaliciousNodeList: (state, action) => {
state.maliciousNodeList = state.maliciousNodeList.filter(
(item) => item !== action.payload
(item) => item.name !== action.payload.name
);
},
removeNodeDownList: (state, action) => {
state.nodeDownList = state.nodeDownList.filter(
(item) => item !== action.payload
(item) => item.name !== action.payload.name
);
},
setMaliciousNodeList: (state, action) => {
// 判断当前节点是否已经存在
const maliciousNode = state.maliciousNodeList.find(
(item) => action.payload === item
(item) => action.payload.name === item.name
);
if (!maliciousNode) {
state.maliciousNodeList.push(action.payload);
@ -179,7 +179,7 @@ export const appSlice = createSlice({
setNodeDownList: (state, action) => {
// 判断当前节点是否已经存在
const nodeDown = state.nodeDownList.find(
(item) => action.payload === item
(item) => action.payload.name === item.name
);
if (!nodeDown) {
state.nodeDownList.push(action.payload);
@ -196,7 +196,10 @@ export const appSlice = createSlice({
data: action.payload,
});
} else {
proxy_info.proxies[0] = action.payload;
proxy_info.proxies[0] = {
...proxy_info.proxies[0],
data: action.payload,
};
}
state.proxy_info = proxy_info;
},

1
src/vite-env.d.ts vendored
View File

@ -4,6 +4,7 @@ interface ImportMetaEnv {
// 定义你的环境变量,例如:
readonly VITE_BASE_URL: string
readonly VITE_BLOCK_URL: string
readonly VIET_EVENTS_WS_URL: string
}
interface ImportMeta {