Add front-end functionality and improved server.py and node.py
All checks were successful
Test CI / test speed (push) Successful in 17s
All checks were successful
Test CI / test speed (push) Successful in 17s
This commit is contained in:
29
frontend/src/App.css
Normal file
29
frontend/src/App.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 20px auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.log-info {
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
118
frontend/src/App.js
Normal file
118
frontend/src/App.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import WebSocketComponent from './WebSocketComponent';
|
||||
|
||||
function App() {
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [node, setNode] = useState(null);
|
||||
const [heartbeat, setHeartbeat] = useState(null);
|
||||
const [nodesList, setNodesList] = useState([]);
|
||||
const [ip, setIp] = useState('');
|
||||
const [count, setCount] = useState('');
|
||||
|
||||
const fetchNodes = async () => {
|
||||
try {
|
||||
const response = await axios.get('/server/show_nodes');
|
||||
setNodes(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching nodes:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchNode = async (ip) => {
|
||||
try {
|
||||
const response = await axios.get('/server/get_node', { params: { ip } });
|
||||
setNode(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching node:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchHeartbeat = async (ip) => {
|
||||
try {
|
||||
const response = await axios.get('/server/heartbeat', { params: { ip } });
|
||||
setHeartbeat(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching heartbeat:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchNodesList = async (count) => {
|
||||
try {
|
||||
const response = await axios.get('/server/send_nodes_list', { params: { count } });
|
||||
setNodesList(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching nodes list:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchNodes(); // 获取所有节点
|
||||
}, []);
|
||||
|
||||
const handleFetchNode = () => {
|
||||
fetchNode(ip); // 根据输入的 IP 获取单个节点
|
||||
};
|
||||
|
||||
const handleFetchHeartbeat = () => {
|
||||
fetchHeartbeat(ip); // 根据输入的 IP 获取心跳信息
|
||||
};
|
||||
|
||||
const handleFetchNodesList = () => {
|
||||
fetchNodesList(count); // 根据输入的数量获取节点列表
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<h1>中央服务器路由</h1>
|
||||
|
||||
<h2>所有节点</h2>
|
||||
{nodes.length > 0 ? (
|
||||
<ul>
|
||||
{nodes.map((node, index) => (
|
||||
<li key={index}>{JSON.stringify(node)}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>没有节点数据</p>
|
||||
)}
|
||||
|
||||
<h2>单个节点</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={ip}
|
||||
onChange={(e) => setIp(e.target.value)}
|
||||
placeholder="输入节点 IP"
|
||||
/>
|
||||
<button onClick={handleFetchNode}>获取节点</button>
|
||||
{node ? <p>{JSON.stringify(node)}</p> : <p>没有单个节点数据</p>}
|
||||
|
||||
<h2>心跳信息</h2>
|
||||
<button onClick={handleFetchHeartbeat}>获取心跳信息</button>
|
||||
{heartbeat ? <p>{JSON.stringify(heartbeat)}</p> : <p>没有心跳数据</p>}
|
||||
|
||||
<h2>节点列表</h2>
|
||||
<input
|
||||
type="number"
|
||||
value={count}
|
||||
onChange={(e) => setCount(e.target.value)}
|
||||
placeholder="输入节点数量"
|
||||
/>
|
||||
<button onClick={handleFetchNodesList}>获取节点列表</button>
|
||||
{nodesList.length > 0 ? (
|
||||
<ul>
|
||||
{nodesList.map((node, index) => (
|
||||
<li key={index}>{JSON.stringify(node)}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>没有节点列表数据</p>
|
||||
)}
|
||||
</header>
|
||||
<WebSocketComponent/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
115
frontend/src/WebSocketComponent.js
Normal file
115
frontend/src/WebSocketComponent.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
|
||||
const WebSocketComponent = () => {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const wsRef = useRef(null);
|
||||
const heartbeatIntervalRef = useRef(null);
|
||||
|
||||
const connectWebSocket = useCallback(() => {
|
||||
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
||||
return; // 如果连接已经打开,不再重新连接
|
||||
}
|
||||
|
||||
wsRef.current = new WebSocket('ws://localhost:8000/ws/logs');
|
||||
|
||||
wsRef.current.onopen = () => {
|
||||
console.log('WebSocket 连接成功');
|
||||
heartbeatIntervalRef.current = setInterval(() => {
|
||||
if (wsRef.current.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({ type: 'heartbeat' }));
|
||||
}
|
||||
}, 10000); // 心跳包间隔调整为 10 秒
|
||||
};
|
||||
|
||||
wsRef.current.onmessage = (event) => {
|
||||
setLogs((prevLogs) => [...prevLogs, event.data]); // 直接加入收到的消息
|
||||
};
|
||||
|
||||
|
||||
wsRef.current.onerror = (error) => {
|
||||
console.error('WebSocket 错误: ', error);
|
||||
};
|
||||
|
||||
wsRef.current.onclose = () => {
|
||||
console.log('WebSocket 连接关闭,尝试重新连接...');
|
||||
clearInterval(heartbeatIntervalRef.current);
|
||||
// 确保 WebSocket 连接在关闭后再进行重连
|
||||
setTimeout(() => {
|
||||
connectWebSocket();
|
||||
}, 5000); // 延迟 5 秒再重连
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
connectWebSocket();
|
||||
return () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
}
|
||||
clearInterval(heartbeatIntervalRef.current);
|
||||
};
|
||||
}, [connectWebSocket]);
|
||||
// 获取节点信息
|
||||
const fetchNodes = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch('/server/show_nodes');
|
||||
const data = await response.json();
|
||||
setNodes(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching nodes:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
connectWebSocket();
|
||||
fetchNodes(); // 组件加载时获取节点信息
|
||||
return () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
}
|
||||
clearInterval(heartbeatIntervalRef.current);
|
||||
};
|
||||
}, [connectWebSocket, fetchNodes]);
|
||||
useEffect(() => {
|
||||
const logContainer = document.querySelector('.log-info');
|
||||
if (logContainer) {
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}
|
||||
}, [logs]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>节点记录信息</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>IP</th>
|
||||
<th>Last Heartbeat</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{nodes.map((node) => (
|
||||
<tr key={node[0]}>
|
||||
<td>{node[0]}</td>
|
||||
<td>{node[1]}</td>
|
||||
<td>{new Date(node[2] * 1000).toLocaleString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>日志信息</h2>
|
||||
<div
|
||||
className="log-info"
|
||||
style={{ height: '300px', overflowY: 'scroll', background: '#fff', padding: '10px' }}
|
||||
>
|
||||
{logs.map((log, index) => (
|
||||
<p key={index} style={{ margin: '5px 0' }}>{log}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebSocketComponent;
|
||||
11
frontend/src/index.css
Normal file
11
frontend/src/index.css
Normal file
@@ -0,0 +1,11 @@
|
||||
/* src/index.css */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
13
frontend/src/index.js
Normal file
13
frontend/src/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
10
frontend/src/setupProxy.js
Normal file
10
frontend/src/setupProxy.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
|
||||
module.exports = function(app) {
|
||||
app.use(
|
||||
createProxyMiddleware('/server', {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user