diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 33fdb08..71a2de2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0", "react-scripts": "5.0.1", "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": { "version": "5.3.1", "resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -16207,6 +16217,38 @@ "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": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 58a5d6a..b860d37 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/frontend/src/App.css b/frontend/src/App.css index b77d9a8..e762e16 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,114 +1,135 @@ -@keyframes glow-border { - 0% { - box-shadow: inset 0 0 0 2px #00e6e6; - } - 25% { - box-shadow: inset 0 0 0 2px #00e6e6, 2px 0 0 0 #00e6e6; - } - 50% { - box-shadow: inset 0 0 0 2px #00e6e6, 2px 0 0 0 #00e6e6, 0 2px 0 0 #00e6e6; - } - 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; - } +/* App.css */ +body { + background-color: #f0f8ff; /* AliceBlue */ + color: #2f6c99; /* Navy */ + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + min-height: 100vh; /* Minimum height set to 100% of the viewport height */ } -body { - background-color: #1e1e1e; - color: #ffffff; - font-family: Arial, sans-serif; +.App { + display: flex; + flex-direction: column; + min-height: 100vh; /* Ensure the App component takes the full height */ } .App-header { - text-align: center; + background-color: #4abbe1; /*背景颜色*/ padding: 20px; + text-align: center; } -h1 { - color: #eff4f0; - font-size: 4em; - margin-bottom: 20px; +.App-content { + flex: 1; /* This makes the main content take up the remaining space */ + display: flex; + 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 { - color: #2196f3; - font-size: 2em; - margin-top: 20px; - margin-bottom: 10px; +.App-footer { + background-color: #4abbe1; /*背景颜色*/ + padding: 10px; + text-align: center; } -h3 { - color: #ff9800; - font-size: 1.75em; - margin-top: 15px; - margin-bottom: 10px; -} - -h4 { - color: #f44336; - font-size: 1.5em; - margin-top: 10px; - margin-bottom: 5px; +.glow { + color: #ffffff; /* White */ + 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; + font-size: 2.5em; /* 调整字体大小 */ } input, button { - margin: 10px 0; + margin: 20px 10px; padding: 10px; - border: none; + border: 1px solid #000080; /* Navy */ border-radius: 5px; -} - -input { - width: 80%; - max-width: 300px; -} - -button { - background-color: #2196f3; - color: #ffffff; cursor: pointer; + font-size: 1.2em; /* 调整字体大小 */ } button:hover { - background-color: #1976d2; + background-color: #104e8b; /* DodgerBlue Darker */ } -section { - background-color: #333333; - padding: 20px; - margin: 20px 0; - border-radius: 10px; - animation: glow-border 4s infinite; +nav ul { + list-style-type: none; + padding: 0; + margin: 20px 0; /* 增加上下间距 */ +} + +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 { - background-color: #444444; - color: #ffffff; + height: 200px; + overflow-y: scroll; + background-color: #ffffff; /* White */ padding: 10px; + border: 1px solid #b3d9ff; /* Light Blue Border */ border-radius: 5px; - animation: glow-border 4s infinite; + margin-top: 20px; } -ul { - list-style-type: none; - padding: 0; -} - -li { - background-color: #444444; +.log-entry { 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; + border: 1px solid #b3d9ff; /* Light Blue Border */ border-radius: 5px; + width: 100%; + max-width: 600px; } -.container { - display: flex; - justify-content: space-between; +.info-item { + margin-bottom: 10px; } -.left-panel, .right-panel { - width: 48%; +pre { + white-space: pre-wrap; /* Wrap long lines */ + word-wrap: break-word; /* Break long words */ } \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.js index 76bf8c2..dc6df9d 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,118 +1,38 @@ -import React, { useEffect, useState } from 'react'; -import axios from 'axios'; +import React from 'react'; +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 './App.css'; 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 ( -
{JSON.stringify(node)}
:here is nothing!
} -{JSON.stringify(heartbeat)}
:here is nothing!
} -here is nothing!
- )} -请输入节点 IP 地址以获取心跳状态信息:
+ setIp(e.target.value)} + placeholder="Enter node IP" + /> + + {heartbeat ? ( +这里没有任何信息!
+ )} +欢迎来到服务器管理页面。请选择一个操作:
+ +请输入节点 IP 地址以获取节点信息:
+ setIp(e.target.value)} + placeholder="Enter node IP" + /> + + {node ? ( +这里没有任何信息!
+ )} +请输入要获取的节点数量:
+ setCount(e.target.value)} + placeholder="Enter the number of nodes" + /> + + {nodesList.length > 0 ? ( +这里没有任何信息!
+ )} +{log}
- ))} +这里显示服务器的日志信息:
+