Compare commits
4 Commits
c64ccd57e3
...
e70cfdd857
Author | SHA1 | Date | |
---|---|---|---|
e70cfdd857 | |||
5397a2bb90 | |||
edab4a3950 | |||
043da6cb70 |
42
frontend/package-lock.json
generated
42
frontend/package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.27.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
}
|
}
|
||||||
@ -3484,6 +3485,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@remix-run/router": {
|
||||||
|
"version": "1.20.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.20.0.tgz",
|
||||||
|
"integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-babel": {
|
"node_modules/@rollup/plugin-babel": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||||
@ -16207,6 +16217,38 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.27.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.27.0.tgz",
|
||||||
|
"integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.20.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.27.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.27.0.tgz",
|
||||||
|
"integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.20.0",
|
||||||
|
"react-router": "6.27.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-scripts": {
|
"node_modules/react-scripts": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/react-scripts/-/react-scripts-5.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.27.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
@ -1,114 +1,135 @@
|
|||||||
@keyframes glow-border {
|
/* App.css */
|
||||||
0% {
|
body {
|
||||||
box-shadow: inset 0 0 0 2px #00e6e6;
|
background-color: #f0f8ff; /* AliceBlue */
|
||||||
}
|
color: #2f6c99; /* Navy */
|
||||||
25% {
|
font-family: Arial, sans-serif;
|
||||||
box-shadow: inset 0 0 0 2px #00e6e6, 2px 0 0 0 #00e6e6;
|
margin: 0;
|
||||||
}
|
padding: 0;
|
||||||
50% {
|
display: flex;
|
||||||
box-shadow: inset 0 0 0 2px #00e6e6, 2px 0 0 0 #00e6e6, 0 2px 0 0 #00e6e6;
|
flex-direction: column;
|
||||||
}
|
min-height: 100vh; /* Minimum height set to 100% of the viewport height */
|
||||||
75% {
|
|
||||||
box-shadow: inset 0 0 0 2px #00e6e6, 2px 0 0 0 #00e6e6, 0 2px 0 0 #00e6e6, -2px 0 0 0 #00e6e6;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: inset 0 0 0 2px #00e6e6, 2px 0 0 0 #00e6e6, 0 2px 0 0 #00e6e6, -2px 0 0 0 #00e6e6, 0 -2px 0 0 #00e6e6;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
.App {
|
||||||
background-color: #1e1e1e;
|
display: flex;
|
||||||
color: #ffffff;
|
flex-direction: column;
|
||||||
font-family: Arial, sans-serif;
|
min-height: 100vh; /* Ensure the App component takes the full height */
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-header {
|
.App-header {
|
||||||
text-align: center;
|
background-color: #4abbe1; /*背景颜色*/
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
.App-content {
|
||||||
color: #eff4f0;
|
flex: 1; /* This makes the main content take up the remaining space */
|
||||||
font-size: 4em;
|
display: flex;
|
||||||
margin-bottom: 20px;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background: linear-gradient(135deg, #e0f7fa, #80deea); /* 渐变背景颜色 */
|
||||||
|
border-radius: 0px; /* 圆角边框 */
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影效果 */
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
.App-footer {
|
||||||
color: #2196f3;
|
background-color: #4abbe1; /*背景颜色*/
|
||||||
font-size: 2em;
|
padding: 10px;
|
||||||
margin-top: 20px;
|
text-align: center;
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
.glow {
|
||||||
color: #ff9800;
|
color: #ffffff; /* White */
|
||||||
font-size: 1.75em;
|
text-shadow: 0 0 10px #00bfff, 0 0 20px #00bfff, 0 0 30px #00bfff, 0 0 40px #00bfff, 0 0 50px #00bfff, 0 0 60px #00bfff, 0 0 70px #00bfff;
|
||||||
margin-top: 15px;
|
font-size: 2.5em; /* 调整字体大小 */
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
color: #f44336;
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input, button {
|
input, button {
|
||||||
margin: 10px 0;
|
margin: 20px 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: none;
|
border: 1px solid #000080; /* Navy */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 80%;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: #2196f3;
|
|
||||||
color: #ffffff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 1.2em; /* 调整字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #1976d2;
|
background-color: #104e8b; /* DodgerBlue Darker */
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
nav ul {
|
||||||
background-color: #333333;
|
list-style-type: none;
|
||||||
padding: 20px;
|
padding: 0;
|
||||||
margin: 20px 0;
|
margin: 20px 0; /* 增加上下间距 */
|
||||||
border-radius: 10px;
|
}
|
||||||
animation: glow-border 4s infinite;
|
|
||||||
|
nav li {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: #ffffff; /* White */
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.5em; /* 调整字体大小 */
|
||||||
|
padding: 5px 19px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #2db5c391; /* DodgerBlue */
|
||||||
|
transition: background-color 0.3s, transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
background-color: #104e8b; /* DodgerBlue Darker */
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-info {
|
.log-info {
|
||||||
background-color: #444444;
|
height: 200px;
|
||||||
color: #ffffff;
|
overflow-y: scroll;
|
||||||
|
background-color: #ffffff; /* White */
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
border: 1px solid #b3d9ff; /* Light Blue Border */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
animation: glow-border 4s infinite;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
.log-entry {
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
background-color: #444444;
|
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid #b3d9ff; /* Light Blue Border */
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-timestamp {
|
||||||
|
color: #007acc; /* Blue */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-message {
|
||||||
|
color: #333333; /* Dark Gray */
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-info, .heartbeat-info, .nodes-list {
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #e6f7ff; /* Light Blue */
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
border: 1px solid #b3d9ff; /* Light Blue Border */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.info-item {
|
||||||
display: flex;
|
margin-bottom: 10px;
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-panel, .right-panel {
|
pre {
|
||||||
width: 48%;
|
white-space: pre-wrap; /* Wrap long lines */
|
||||||
|
word-wrap: break-word; /* Break long words */
|
||||||
}
|
}
|
@ -1,117 +1,37 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||||
|
import HomePage from './HomePage';
|
||||||
|
import NodePage from './NodePage';
|
||||||
|
import HeartbeatPage from './HeartbeatPage';
|
||||||
|
import NodesListPage from './NodesListPage';
|
||||||
import WebSocketComponent from './WebSocketComponent';
|
import WebSocketComponent from './WebSocketComponent';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
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');
|
|
||||||
setNodesList(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 (
|
return (
|
||||||
|
<Router>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
|
{/* Header 部分仅放置导航或标题 */}
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
<h1 className="glow">The server</h1>
|
|
||||||
<div className="container">
|
|
||||||
<div className="left-panel">
|
|
||||||
<section>
|
|
||||||
<h2 className="glow">get node</h2>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={ip}
|
|
||||||
onChange={(e) => setIp(e.target.value)}
|
|
||||||
placeholder="Enter node ip"
|
|
||||||
/>
|
|
||||||
<button onClick={handleFetchNode}>send</button>
|
|
||||||
{node ? <p>{JSON.stringify(node)}</p> : <p>here is nothing!</p>}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 className="glow">heartbeat</h2>
|
|
||||||
<button onClick={handleFetchHeartbeat}>Get heartbeat</button>
|
|
||||||
{heartbeat ? <p>{JSON.stringify(heartbeat)}</p> : <p>here is nothing!</p>}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 className="glow">nodes list</h2>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={count}
|
|
||||||
onChange={(e) => setCount(e.target.value)}
|
|
||||||
placeholder="Enter the number of nodes"
|
|
||||||
/>
|
|
||||||
<button onClick={handleFetchNodesList}>Get node list</button>
|
|
||||||
{nodesList.length > 0 ? (
|
|
||||||
<ul>
|
|
||||||
{nodesList.map((node, index) => (
|
|
||||||
<li key={index}>{JSON.stringify(node)}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<p>here is nothing!</p>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div className="right-panel">
|
|
||||||
<WebSocketComponent />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{/* Main 部分用来渲染路由的内容 */}
|
||||||
|
<main className="App-content">
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="/node" element={<NodePage />} />
|
||||||
|
<Route path="/heartbeat" element={<HeartbeatPage />} />
|
||||||
|
<Route path="/nodes-list" element={<NodesListPage />} />
|
||||||
|
<Route path="/logs" element={<WebSocketComponent />} />
|
||||||
|
</Routes>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Footer 部分 */}
|
||||||
|
<footer className="App-footer">
|
||||||
|
<p>© 2024 Server Application</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
55
frontend/src/HeartbeatPage.js
Normal file
55
frontend/src/HeartbeatPage.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
const HeartbeatPage = () => {
|
||||||
|
const [ip, setIp] = useState('');
|
||||||
|
const [heartbeat, setHeartbeat] = useState(null);
|
||||||
|
|
||||||
|
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 handleFetchHeartbeat = () => {
|
||||||
|
fetchHeartbeat(ip);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><Link to="/">Home</Link></li>
|
||||||
|
<li><Link to="/node">Get Node</Link></li>
|
||||||
|
<li><Link to="/heartbeat">Heartbeat</Link></li>
|
||||||
|
<li><Link to="/nodes-list">Nodes List</Link></li>
|
||||||
|
<li><Link to="/logs">Logs</Link></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<h2 className="glow">Heartbeat</h2>
|
||||||
|
<p>请输入节点 IP 地址以获取心跳状态信息:</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={ip}
|
||||||
|
onChange={(e) => setIp(e.target.value)}
|
||||||
|
placeholder="Enter node IP"
|
||||||
|
/>
|
||||||
|
<button onClick={handleFetchHeartbeat}>Get Heartbeat</button>
|
||||||
|
{heartbeat ? (
|
||||||
|
<div className="heartbeat-info">
|
||||||
|
<h3>Heartbeat Information:</h3>
|
||||||
|
<div className="info-item"><strong>Status:</strong> {heartbeat.status}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>这里没有任何信息!</p>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeartbeatPage;
|
21
frontend/src/HomePage.js
Normal file
21
frontend/src/HomePage.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import './App.css';
|
||||||
|
const HomePage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="glow">The server</h1>
|
||||||
|
<p>欢迎来到服务器管理页面。请选择一个操作:</p>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><Link to="/node">Get Node</Link></li>
|
||||||
|
<li><Link to="/heartbeat">Heartbeat</Link></li>
|
||||||
|
<li><Link to="/nodes-list">Nodes List</Link></li>
|
||||||
|
<li><Link to="/logs">Logs</Link></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
59
frontend/src/NodePage.js
Normal file
59
frontend/src/NodePage.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
const NodePage = () => {
|
||||||
|
const [ip, setIp] = useState('');
|
||||||
|
const [node, setNode] = useState(null);
|
||||||
|
|
||||||
|
const fetchNode = async (ip) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/server/get_node', { params: { ip } });
|
||||||
|
const nodeData = response.data;
|
||||||
|
const date = new Date(nodeData.current_time * 1000);
|
||||||
|
nodeData.current_time = date.toLocaleString();
|
||||||
|
setNode(nodeData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching node:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFetchNode = () => {
|
||||||
|
fetchNode(ip);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><Link to="/">Home</Link></li>
|
||||||
|
<li><Link to="/node">Get Node</Link></li>
|
||||||
|
<li><Link to="/heartbeat">Heartbeat</Link></li>
|
||||||
|
<li><Link to="/nodes-list">Nodes List</Link></li>
|
||||||
|
<li><Link to="/logs">Logs</Link></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<h2 className="glow">Get Node</h2>
|
||||||
|
<p>请输入节点 IP 地址以获取节点信息:</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={ip}
|
||||||
|
onChange={(e) => setIp(e.target.value)}
|
||||||
|
placeholder="Enter node IP"
|
||||||
|
/>
|
||||||
|
<button onClick={handleFetchNode}>Send</button>
|
||||||
|
{node ? (
|
||||||
|
<div className="node-info">
|
||||||
|
<h3>Node Information:</h3>
|
||||||
|
<div className="info-item"><strong>ID:</strong> {node.id}</div>
|
||||||
|
<div className="info-item"><strong>Current Time:</strong> {node.current_time}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>这里没有任何信息!</p>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodePage;
|
61
frontend/src/NodesListPage.js
Normal file
61
frontend/src/NodesListPage.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
const NodesListPage = () => {
|
||||||
|
const [count, setCount] = useState('');
|
||||||
|
const [nodesList, setNodesList] = useState([]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFetchNodesList = () => {
|
||||||
|
fetchNodesList(count);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><Link to="/">Home</Link></li>
|
||||||
|
<li><Link to="/node">Get Node</Link></li>
|
||||||
|
<li><Link to="/heartbeat">Heartbeat</Link></li>
|
||||||
|
<li><Link to="/nodes-list">Nodes List</Link></li>
|
||||||
|
<li><Link to="/logs">Logs</Link></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<h2 className="glow">Nodes List</h2>
|
||||||
|
<p>请输入要获取的节点数量:</p>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={count}
|
||||||
|
onChange={(e) => setCount(e.target.value)}
|
||||||
|
placeholder="Enter the number of nodes"
|
||||||
|
/>
|
||||||
|
<button onClick={handleFetchNodesList}>Get Node List</button>
|
||||||
|
{nodesList.length > 0 ? (
|
||||||
|
<div className="nodes-list">
|
||||||
|
<h3>Nodes Information:</h3>
|
||||||
|
<ul>
|
||||||
|
{nodesList.map((node, index) => (
|
||||||
|
<li key={index} className="info-item">
|
||||||
|
{node}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>这里没有任何信息!</p>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodesListPage;
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||||
|
import './App.css'; // 确保引入了样式文件
|
||||||
|
|
||||||
const WebSocketComponent = () => {
|
const WebSocketComponent = () => {
|
||||||
const [logs, setLogs] = useState([]);
|
const [logs, setLogs] = useState([]);
|
||||||
@ -16,7 +17,7 @@ const WebSocketComponent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
wsRef.current.onmessage = (event) => {
|
wsRef.current.onmessage = (event) => {
|
||||||
setLogs((prevLogs) => [...prevLogs, event.data]); // 直接加入收到的消息
|
setLogs((prevLogs) => [...prevLogs, JSON.parse(event.data)]); // 解析 JSON 数据
|
||||||
};
|
};
|
||||||
|
|
||||||
wsRef.current.onerror = (error) => {
|
wsRef.current.onerror = (error) => {
|
||||||
@ -50,14 +51,18 @@ const WebSocketComponent = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>The logs</h2>
|
<h2 className="glow">The logs</h2>
|
||||||
<div
|
<p>这里显示服务器的日志信息:</p>
|
||||||
className="log-info"
|
<div className="log-info">
|
||||||
style={{ height: '550px', overflowY: 'scroll', backgroundColor: 'rgb(32, 28, 28)', padding: '10px' }}
|
{logs.map((log, index) => {
|
||||||
>
|
const [timestamp, message] = log.message.split(' - ', 2);
|
||||||
{logs.map((log, index) => (
|
return (
|
||||||
<p key={index} style={{ margin: '5px 0' }}>{log}</p>
|
<div key={index} className="log-entry">
|
||||||
))}
|
<span className="log-timestamp">{timestamp}</span>
|
||||||
|
<span className="log-message"> - {message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
/* src/index.css */
|
html, body {
|
||||||
body {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%; /* Ensures the body takes the full height */
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100%; /* Makes sure the root element takes the full height of the viewport */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
ReactDOM.render(
|
||||||
const root = createRoot(container);
|
|
||||||
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>
|
</React.StrictMode>,
|
||||||
|
document.getElementById('root')
|
||||||
);
|
);
|
@ -8,6 +8,7 @@ cp -r gmssl/include include
|
|||||||
mkdir gmssl/build
|
mkdir gmssl/build
|
||||||
cd gmssl/build || exit
|
cd gmssl/build || exit
|
||||||
cmake ..
|
cmake ..
|
||||||
|
make
|
||||||
cd bin || exit
|
cd bin || exit
|
||||||
cp libgmssl.so libgmssl.so.3 libgmssl.so.3.1 ../../../lib
|
cp libgmssl.so libgmssl.so.3 libgmssl.so.3.1 ../../../lib
|
||||||
cp libsdf_dummy.so libsdf_dummy.so.3 libsdf_dummy.so.3.1 ../../../lib
|
cp libsdf_dummy.so libsdf_dummy.so.3 libsdf_dummy.so.3.1 ../../../lib
|
||||||
|
@ -28,9 +28,10 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 配置日志文件路径
|
# 配置日志文件路径
|
||||||
log_dir = "logs"
|
|
||||||
if not os.path.exists(log_dir):
|
log_dir = os.path.expanduser("~/tpre-python-logs")
|
||||||
os.makedirs(log_dir)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
log_file = os.path.join(log_dir, "server_logs.log")
|
log_file = os.path.join(log_dir, "server_logs.log")
|
||||||
|
|
||||||
# 全局日志配置
|
# 全局日志配置
|
||||||
@ -58,10 +59,10 @@ async def lifespan(_: FastAPI):
|
|||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
# 定义 frontend 的绝对路径
|
# 定义 frontend 的绝对路径
|
||||||
frontend_dir = os.path.join(current_dir, "..", "frontend")
|
frontend_dir = os.path.join(current_dir, "..", "frontend","build")
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
app.mount("/frontend", StaticFiles(directory="frontend/build"), name="frontend")
|
app.mount("/frontend", StaticFiles(directory=frontend_dir), name="frontend")
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user