新增基站测试模块
This commit is contained in:
parent
2590a76cfd
commit
3d02b7e170
4
cookies.txt
Normal file
4
cookies.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
555
frontend/assets/meituan-test.css
Normal file
555
frontend/assets/meituan-test.css
Normal file
@ -0,0 +1,555 @@
|
||||
/* 页面布局 - 单页面适配 */
|
||||
.meituan-test-page {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
height: calc(100vh - 80px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meituan-test-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 卡片布局 */
|
||||
.server-status-card {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.control-panel-card {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.self-test-card {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stations-card {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stations-card .card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 状态指示器 */
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-dot.online {
|
||||
background-color: #52c41a;
|
||||
box-shadow: 0 0 8px rgba(82, 196, 26, 0.5);
|
||||
}
|
||||
|
||||
.status-dot.offline {
|
||||
background-color: #ff4d4f;
|
||||
}
|
||||
|
||||
.status-dot.connecting {
|
||||
background-color: #faad14;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 服务器状态卡片 - 紧凑版 */
|
||||
.server-status-card.compact {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.server-status-card.compact .card-body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 信息网格 */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.info-item label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-item span {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 基站表格 */
|
||||
.stations-table-container {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.stations-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stations-table th,
|
||||
.stations-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.stations-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--surface);
|
||||
z-index: 10;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.online {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
border: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
.status-badge.offline {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ffccc7;
|
||||
}
|
||||
|
||||
/* 命令表单 */
|
||||
.command-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.command-form .form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.command-form label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.command-form .form-control {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.command-form .form-control:focus {
|
||||
outline: none;
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 命令输出 */
|
||||
.command-output {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.command-output h4 {
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.output-container {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.output-container pre {
|
||||
margin: 0;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* 日志容器 */
|
||||
.logs-container {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.logs-container pre {
|
||||
margin: 0;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* 卡片样式增强 */
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background-color: #17a2b8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 卡片布局 */
|
||||
.server-status-card {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.control-panel-card {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.self-test-card {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.stations-card {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
/* 控制面板样式 */
|
||||
.control-panel-card .card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control-row .form-control {
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 自检结果样式 */
|
||||
.self-test-card {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
}
|
||||
|
||||
.self-test-card .card-body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.test-results {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.test-module {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.test-module:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.module-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.module-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.module-status {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.module-status.testing {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.module-status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.module-status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.module-result {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.module-result.success {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.module-result.error {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.test-status {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 响应式布局 */
|
||||
@media (max-width: 1200px) {
|
||||
.meituan-test-content {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto auto 1fr;
|
||||
}
|
||||
|
||||
.server-status-card {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.control-panel-card {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.self-test-card {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.stations-card {
|
||||
grid-column: 1;
|
||||
grid-row: 4;
|
||||
}
|
||||
|
||||
.test-results {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.meituan-test-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stations-table-container {
|
||||
margin: 0 -10px;
|
||||
}
|
||||
}
|
||||
@ -2588,7 +2588,7 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{
|
||||
.topbar .user-avatar-btn:hover{background:rgba(79,140,255,0.1)}
|
||||
.topbar .user-avatar-img{width:32px;height:32px;border-radius:50%;object-fit:cover}
|
||||
.topbar .user-name-display{color:var(--text);font-size:14px;font-weight:500}
|
||||
.topbar .user-dropdown{position:absolute;top:100%;right:0;margin-top:8px;min-width:150px;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.3);z-index:1000;max-height:calc(100vh - 80px);overflow-y:auto}
|
||||
.topbar .user-dropdown{position:absolute;top:100%;left:0;margin-top:8px;min-width:150px;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.3);z-index:1000;max-height:calc(100vh - 80px);overflow-y:auto}
|
||||
.topbar .user-menu-container{position:relative}
|
||||
.topbar-icon-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:8px;color:var(--text-2);text-decoration:none;transition:all 0.2s ease}
|
||||
.topbar-icon-btn:hover{background:rgba(79,140,255,0.1);color:var(--text)}
|
||||
@ -2658,8 +2658,8 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{
|
||||
#app.trackit-layout > #sidebar .sidebar-actions .user-name-display{display:none}
|
||||
#app.trackit-layout > #sidebar .sidebar-actions .user-dropdown{top:auto;bottom:100%;margin-top:0;margin-bottom:8px}
|
||||
|
||||
#app.trackit-layout > #sidebar .sidebar-notification-card{position:relative;background:rgba(255,255,255,.78);border:1px solid rgba(226,232,240,.9);border-radius:18px;padding:12px 6px 10px;box-shadow:0 10px 30px rgba(15,23,42,.06);height:220px;display:flex;flex-direction:column;cursor:default;margin:0 -12px;width:calc(100% + 24px)}
|
||||
[data-theme="dark"] #app.trackit-layout > #sidebar .sidebar-notification-card{background:rgba(15,23,42,.25);border-color:rgba(255,255,255,.08);box-shadow:none}
|
||||
#app.trackit-layout > #sidebar .sidebar-notification-card{position:relative;background:rgba(255,255,255,.78);border:1px solid rgba(226,232,240,.9);border-radius:18px;padding:12px 6px 10px;box-shadow:0 10px 30px rgba(15,23,42,.06),0 0 0 2px rgba(0,0,0,.15);height:220px;display:flex;flex-direction:column;cursor:default;margin:0 -12px;width:calc(100% + 24px)}
|
||||
[data-theme="dark"] #app.trackit-layout > #sidebar .sidebar-notification-card{background:rgba(15,23,42,.25);border-color:rgba(255,255,255,.08);box-shadow:0 0 0 2px rgba(0,0,0,.4)}
|
||||
#app.trackit-layout > #sidebar .sidebar-notification-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:10px;padding:0 6px;cursor:pointer}
|
||||
#app.trackit-layout > #sidebar .sidebar-notification-title{font-size:12px;font-weight:800;color:var(--text);letter-spacing:.02em}
|
||||
#app.trackit-layout > #sidebar .sidebar-notification-right{display:flex;align-items:center;gap:10px}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
<link rel="stylesheet" href="./assets/styles.css?v=20251122" />
|
||||
<link rel="stylesheet" href="./assets/dashboard-enhancements.css" />
|
||||
<link rel="stylesheet" href="./assets/mod.css" />
|
||||
<link rel="stylesheet" href="./assets/meituan-test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- 初始加载提示 -->
|
||||
@ -171,6 +172,24 @@
|
||||
<a href="#/production" class="dropdown-item" data-route="production">时间记录</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topnav-item has-dropdown" data-menu="test">
|
||||
<span class="nav-left">
|
||||
<span class="nav-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14,2 14,8 20,8" />
|
||||
<line x1="16" y1="13" x2="8" y2="13" />
|
||||
<line x1="16" y1="17" x2="8" y2="17" />
|
||||
<polyline points="10,9 9,9 8,9" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="topnav-text">基站测试</span>
|
||||
</span>
|
||||
<span class="topnav-caret">▾</span>
|
||||
<div class="topnav-dropdown">
|
||||
<a href="#/test/meituan" class="dropdown-item" data-route="test-meituan">美团基站测试</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#/export" class="topnav-item" data-route="export">
|
||||
<span class="nav-left">
|
||||
<span class="nav-icon" aria-hidden="true">
|
||||
@ -314,6 +333,7 @@
|
||||
<script src="./js/components/export.js" defer></script>
|
||||
<script src="./js/components/settings.js" defer></script>
|
||||
<script src="./js/components/notifications.js" defer></script>
|
||||
<script src="./js/components/meituan-test.js" defer></script>
|
||||
<script src="./js/components/operations-log.js" defer></script>
|
||||
<script src="./js/components/product-intro.js" defer></script>
|
||||
<script src="./js/app.js" defer></script>
|
||||
|
||||
@ -45,7 +45,7 @@ const AIReport = (() => {
|
||||
|
||||
<!-- 思考过程 -->
|
||||
<div id="ai-report-thinking" style="display:none;flex:1;overflow:hidden;flex-direction:column">
|
||||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text);flex-shrink:0">🤔 AI思考过程</h4>
|
||||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text);flex-shrink:0;display:none">🤔 AI思考过程</h4>
|
||||
<div id="thinking-content" style="background:linear-gradient(135deg,#f8f9ff,#f0f2ff);padding:12px;border-radius:8px;border:1px solid #e0e5ff;flex:1;overflow-y:auto;font-size:12px;line-height:1.6">
|
||||
<!-- 思考过程内容 -->
|
||||
</div>
|
||||
@ -257,7 +257,7 @@ const AIReport = (() => {
|
||||
<div style="padding:4px 0">
|
||||
${data.thinking ? `
|
||||
<div style="margin-bottom:16px">
|
||||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px">🤔 AI思考过程</h4>
|
||||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;display:none">🤔 AI思考过程</h4>
|
||||
<div style="background:linear-gradient(135deg,#f8f9ff,#f0f2ff);padding:12px;border-radius:8px;border:1px solid #e0e5ff;font-size:12px;line-height:1.6">
|
||||
${data.thinking}
|
||||
</div>
|
||||
|
||||
424
frontend/js/components/meituan-test.js
Normal file
424
frontend/js/components/meituan-test.js
Normal file
@ -0,0 +1,424 @@
|
||||
// 美团基站测试组件
|
||||
Router.register('/test/meituan', async () => {
|
||||
const currentUser = await API.me().catch(() => null);
|
||||
if (!currentUser) {
|
||||
window.location.href = './login.html';
|
||||
return '';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="page-container meituan-test-page">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">美团基站测试</h2>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" id="connect-server-btn">
|
||||
连接服务器
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="refresh-btn">
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="meituan-test-content">
|
||||
<!-- 服务器状态 -->
|
||||
<div class="card server-status-card compact">
|
||||
<div class="card-header">
|
||||
<h3>服务器状态</h3>
|
||||
<span class="status-indicator" id="server-status">
|
||||
<span class="status-dot offline"></span>
|
||||
离线
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="info-row">
|
||||
<span class="info-label">服务器地址:</span>
|
||||
<span class="info-value">180.163.74.83:8888</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基站控制面板 -->
|
||||
<div class="card control-panel-card">
|
||||
<div class="card-header">
|
||||
<h3>基站控制</h3>
|
||||
<div class="card-actions">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="hide-offline" checked>
|
||||
<span>隐藏离线基站</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="control-row">
|
||||
<select class="form-control" id="station-select">
|
||||
<option value="">请选择在线基站</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" id="self-test-btn">
|
||||
开始自检
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自检结果 -->
|
||||
<div class="card self-test-card" id="self-test-card" style="display: none;">
|
||||
<div class="card-header">
|
||||
<h3>自检结果</h3>
|
||||
<div class="card-actions">
|
||||
<span class="test-status" id="test-status">检测中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="test-results">
|
||||
<div class="test-module" id="test-g">
|
||||
<div class="module-header">
|
||||
<span class="module-name">G模块</span>
|
||||
<span class="module-status" id="g-status">待检测</span>
|
||||
</div>
|
||||
<div class="module-result" id="g-result"></div>
|
||||
</div>
|
||||
<div class="test-module" id="test-d">
|
||||
<div class="module-header">
|
||||
<span class="module-name">D模块</span>
|
||||
<span class="module-status" id="d-status">待检测</span>
|
||||
</div>
|
||||
<div class="module-result" id="d-result"></div>
|
||||
</div>
|
||||
<div class="test-module" id="test-h">
|
||||
<div class="module-header">
|
||||
<span class="module-name">H模块</span>
|
||||
<span class="module-status" id="h-status">待检测</span>
|
||||
</div>
|
||||
<div class="module-result" id="h-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基站列表 -->
|
||||
<div class="card stations-card">
|
||||
<div class="card-header">
|
||||
<h3>在线基站列表</h3>
|
||||
<div class="card-actions">
|
||||
<input type="text" class="form-control" id="search-station" placeholder="搜索基站...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="stations-table-container">
|
||||
<table class="table stations-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>基站SN</th>
|
||||
<th>连接时间</th>
|
||||
<th>状态</th>
|
||||
<th>最后心跳</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="stations-tbody">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">暂无基站连接</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
// 美团基站测试逻辑
|
||||
(() => {
|
||||
let ws = null;
|
||||
let connected = false;
|
||||
let stations = new Map();
|
||||
let isInitialized = false;
|
||||
|
||||
// 初始化
|
||||
function initMeituanTest() {
|
||||
if (isInitialized) return;
|
||||
|
||||
// 等待DOM渲染完成
|
||||
const checkElements = () => {
|
||||
const statusEl = document.getElementById('server-status');
|
||||
if (statusEl) {
|
||||
isInitialized = true;
|
||||
setupEventListeners();
|
||||
connectServer();
|
||||
} else {
|
||||
setTimeout(checkElements, 50);
|
||||
}
|
||||
};
|
||||
|
||||
checkElements();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
isInitialized = false;
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
connected = false;
|
||||
}
|
||||
|
||||
// 监听路由变化
|
||||
Router.onAfterEach(async (path) => {
|
||||
if (path === '/test/meituan') {
|
||||
initMeituanTest();
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
function setupEventListeners() {
|
||||
// 连接服务器按钮
|
||||
const connectBtn = document.getElementById('connect-server-btn');
|
||||
if (connectBtn) {
|
||||
connectBtn.addEventListener('click', connectServer);
|
||||
}
|
||||
|
||||
// 刷新按钮
|
||||
const refreshBtn = document.getElementById('refresh-btn');
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
connectServer();
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索基站
|
||||
const searchInput = document.getElementById('search-station');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
filterStations(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 隐藏离线基站
|
||||
const hideOfflineCheckbox = document.getElementById('hide-offline');
|
||||
if (hideOfflineCheckbox) {
|
||||
hideOfflineCheckbox.addEventListener('change', () => {
|
||||
updateStationsList();
|
||||
updateStationSelect();
|
||||
});
|
||||
}
|
||||
|
||||
// 自检按钮
|
||||
const selfTestBtn = document.getElementById('self-test-btn');
|
||||
if (selfTestBtn) {
|
||||
selfTestBtn.addEventListener('click', startSelfTest);
|
||||
}
|
||||
}
|
||||
|
||||
function connectServer() {
|
||||
const statusEl = document.getElementById('server-status');
|
||||
|
||||
// 更新状态为连接中
|
||||
statusEl.innerHTML = '<span class="status-dot connecting"></span>连接中...';
|
||||
|
||||
// 获取服务器状态
|
||||
fetch('/api/meituan/server-status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
connected = true;
|
||||
statusEl.innerHTML = `<span class="status-dot ${data.status}"></span>${data.status === 'online' ? '在线' : '离线'}`;
|
||||
|
||||
// 获取基站列表
|
||||
loadStations();
|
||||
|
||||
// 定期刷新
|
||||
setInterval(() => {
|
||||
loadStations();
|
||||
}, 5000);
|
||||
} else {
|
||||
statusEl.innerHTML = '<span class="status-dot offline"></span>连接失败';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('连接失败:', error);
|
||||
statusEl.innerHTML = '<span class="status-dot offline"></span>连接失败';
|
||||
});
|
||||
}
|
||||
|
||||
function startLogPolling() {
|
||||
// 轮询获取服务器日志
|
||||
setInterval(async () => {
|
||||
try {
|
||||
// 这里应该调用实际的API获取日志
|
||||
// const logs = await API.getServerLogs();
|
||||
// updateLogs(logs);
|
||||
} catch (e) {
|
||||
console.error('获取日志失败:', e);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function loadStations() {
|
||||
fetch('/api/meituan/stations')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
stations.clear();
|
||||
data.stations.forEach(station => {
|
||||
stations.set(station.id, station);
|
||||
});
|
||||
updateStationsList();
|
||||
updateStationSelect();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取基站列表失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function updateStationsList() {
|
||||
const tbody = document.getElementById('stations-tbody');
|
||||
const hideOffline = document.getElementById('hide-offline')?.checked ?? true;
|
||||
|
||||
if (!tbody) return;
|
||||
|
||||
// 过滤基站
|
||||
let filteredStations = Array.from(stations.values());
|
||||
if (hideOffline) {
|
||||
filteredStations = filteredStations.filter(s => s.status === 'online');
|
||||
}
|
||||
|
||||
if (filteredStations.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center">${hideOffline ? '暂无在线基站' : '暂无基站连接'}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = filteredStations.map(station => {
|
||||
// 处理可能缺失的字段,适配实际数据格式
|
||||
const lastSeen = station.last_seen || station.lastHeartbeat || new Date();
|
||||
const connectTime = station.connect_time || station.connectTime || station.last_seen || new Date();
|
||||
const ip = station.ip || '10.8.0.x'; // 基站通常在内网
|
||||
const status = station.status || 'offline';
|
||||
|
||||
// 转换为Date对象
|
||||
const connectTimeDate = connectTime instanceof Date ? connectTime : new Date(connectTime);
|
||||
const lastSeenDate = lastSeen instanceof Date ? lastSeen : new Date(lastSeen);
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${station.id || '未知'}</td>
|
||||
<td>${connectTimeDate.toLocaleString()}</td>
|
||||
<td><span class="status-badge ${status}">${status === 'online' ? '在线' : '离线'}</span></td>
|
||||
<td>${lastSeenDate.toLocaleString()}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateStationSelect() {
|
||||
const select = document.getElementById('station-select');
|
||||
const hideOffline = document.getElementById('hide-offline')?.checked ?? true;
|
||||
|
||||
if (!select) return;
|
||||
|
||||
// 保存当前选中的值
|
||||
const currentValue = select.value;
|
||||
|
||||
// 过滤基站
|
||||
let filteredStations = Array.from(stations.values());
|
||||
if (hideOffline) {
|
||||
filteredStations = filteredStations.filter(s => s.status === 'online');
|
||||
}
|
||||
|
||||
select.innerHTML = '<option value="">请选择在线基站</option>' +
|
||||
filteredStations.map(station => {
|
||||
const ip = station.ip || '未知';
|
||||
return `<option value="${station.id}">${station.id}</option>`;
|
||||
}).join('');
|
||||
|
||||
// 恢复之前选中的值(如果仍然存在)
|
||||
if (currentValue && filteredStations.some(s => s.id === currentValue)) {
|
||||
select.value = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
function filterStations(keyword) {
|
||||
const rows = document.querySelectorAll('#stations-tbody tr');
|
||||
rows.forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
row.style.display = text.includes(keyword.toLowerCase()) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function startSelfTest() {
|
||||
const stationId = document.getElementById('station-select').value;
|
||||
|
||||
if (!stationId) {
|
||||
API.toast('请先选择基站', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示自检卡片
|
||||
const selfTestCard = document.getElementById('self-test-card');
|
||||
selfTestCard.style.display = 'block';
|
||||
|
||||
// 重置状态
|
||||
resetTestStatus();
|
||||
|
||||
// 更新状态
|
||||
document.getElementById('test-status').textContent = `正在检测基站 ${stationId}...`;
|
||||
|
||||
// 模拟自检过程
|
||||
simulateSelfTest(stationId);
|
||||
}
|
||||
|
||||
function resetTestStatus() {
|
||||
// 重置所有模块状态
|
||||
['g', 'd', 'h'].forEach(module => {
|
||||
document.getElementById(`${module}-status`).textContent = '待检测';
|
||||
document.getElementById(`${module}-status`).className = 'module-status';
|
||||
document.getElementById(`${module}-result`).textContent = '';
|
||||
document.getElementById(`${module}-result`).className = 'module-result';
|
||||
});
|
||||
}
|
||||
|
||||
function simulateSelfTest(stationId) {
|
||||
// 模拟G模块检测
|
||||
updateModuleStatus('g', 'testing', '检测中...');
|
||||
setTimeout(() => {
|
||||
const gResult = Math.random() > 0.2 ? '正常' : '异常';
|
||||
updateModuleStatus('g', gResult === '正常' ? 'success' : 'error', gResult === '正常' ? '正常' : '异常');
|
||||
|
||||
// 模拟D模块检测
|
||||
updateModuleStatus('d', 'testing', '检测中...');
|
||||
setTimeout(() => {
|
||||
const dResult = Math.random() > 0.2 ? '正常' : '异常';
|
||||
updateModuleStatus('d', dResult === '正常' ? 'success' : 'error', dResult === '正常' ? '正常' : '异常');
|
||||
|
||||
// 模拟H模块检测
|
||||
updateModuleStatus('h', 'testing', '检测中...');
|
||||
setTimeout(() => {
|
||||
const hResult = Math.random() > 0.2 ? '正常' : '异常';
|
||||
updateModuleStatus('h', hResult === '正常' ? 'success' : 'error', hResult === '正常' ? '正常' : '异常');
|
||||
|
||||
// 更新总体状态
|
||||
document.getElementById('test-status').textContent = '检测完成';
|
||||
}, 2000);
|
||||
}, 2000);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function updateModuleStatus(module, status, result) {
|
||||
const statusEl = document.getElementById(`${module}-status`);
|
||||
const resultEl = document.getElementById(`${module}-result`);
|
||||
|
||||
statusEl.textContent = status === 'testing' ? '检测中' : (status === 'success' ? '正常' : '异常');
|
||||
statusEl.className = `module-status ${status}`;
|
||||
|
||||
if (result) {
|
||||
resultEl.textContent = result;
|
||||
resultEl.className = `module-result ${status}`;
|
||||
}
|
||||
}
|
||||
})();
|
||||
187
server/app.py
187
server/app.py
@ -6192,6 +6192,193 @@ def add_cache_headers(response):
|
||||
return response
|
||||
|
||||
|
||||
# ===== 基站测试API =====
|
||||
import subprocess
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import re
|
||||
|
||||
# 存储基站连接信息
|
||||
base_stations = {}
|
||||
|
||||
@app.route('/api/meituan/server-status', methods=['GET'])
|
||||
def get_server_status():
|
||||
"""获取基站服务器状态"""
|
||||
try:
|
||||
# 检查服务器进程是否运行
|
||||
result = subprocess.run(['systemctl', 'is-active', 'basestation-server'],
|
||||
capture_output=True, text=True)
|
||||
is_active = result.stdout.strip() == 'active'
|
||||
|
||||
# 获取服务器日志并解析基站状态
|
||||
logs = []
|
||||
try:
|
||||
result = subprocess.run(['journalctl', '-u', 'basestation-server', '-n', '200', '--no-pager'],
|
||||
capture_output=True, text=True)
|
||||
if result.stdout:
|
||||
logs = result.stdout.strip().split('\n')
|
||||
# 解析日志更新基站状态
|
||||
parse_station_logs(logs)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 清理超过5分钟未活动的基站
|
||||
cleanup_stations()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'status': 'online' if is_active else 'offline',
|
||||
'logs': logs[-50:], # 只返回最近50条日志
|
||||
'station_count': len([s for s in base_stations.values() if s['status'] == 'online']) # 返回在线基站数
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
def parse_station_logs(logs):
|
||||
"""解析日志更新基站状态"""
|
||||
now = datetime.now()
|
||||
|
||||
# 先解析所有注册和断开事件
|
||||
for log in logs:
|
||||
# 解析基站注册
|
||||
if '基站' in log and '已注册' in log:
|
||||
match = re.search(r'基站 (\w+) 已注册', log)
|
||||
if match:
|
||||
station_id = match.group(1)
|
||||
# 提取时间戳
|
||||
log_time = extract_log_time(log)
|
||||
base_stations[station_id] = {
|
||||
'id': station_id,
|
||||
'status': 'online',
|
||||
'last_seen': log_time,
|
||||
'connect_time': log_time
|
||||
}
|
||||
|
||||
# 解析基站断开
|
||||
elif '基站' in log and '已断开' in log:
|
||||
match = re.search(r'基站 (\w+) 已断开', log)
|
||||
if match:
|
||||
station_id = match.group(1)
|
||||
if station_id in base_stations:
|
||||
base_stations[station_id]['status'] = 'offline'
|
||||
base_stations[station_id]['last_seen'] = extract_log_time(log)
|
||||
|
||||
# 然后处理心跳 - 假设最近的基站仍然在线
|
||||
# 找到最后注册的基站
|
||||
if base_stations:
|
||||
# 按注册时间排序,最近的在前
|
||||
sorted_stations = sorted(base_stations.values(),
|
||||
key=lambda x: x['connect_time'],
|
||||
reverse=True)
|
||||
|
||||
# 检查最近是否有心跳
|
||||
recent_time = now - timedelta(minutes=1) # 最近1分钟
|
||||
has_recent_heartbeat = False
|
||||
for log in logs:
|
||||
if 'heartbeat' in log:
|
||||
heartbeat_time = extract_log_time(log)
|
||||
if heartbeat_time > recent_time:
|
||||
has_recent_heartbeat = True
|
||||
break
|
||||
|
||||
# 如果最近有心跳,标记最新的基站为在线
|
||||
if has_recent_heartbeat and sorted_stations:
|
||||
latest_station = sorted_stations[0]
|
||||
base_stations[latest_station['id']]['last_seen'] = now
|
||||
base_stations[latest_station['id']]['status'] = 'online'
|
||||
|
||||
# 其他基站标记为离线(假设只有一个基站会发送心跳)
|
||||
for station in sorted_stations[1:]:
|
||||
if now - station['last_seen'] > timedelta(minutes=1):
|
||||
base_stations[station['id']]['status'] = 'offline'
|
||||
else:
|
||||
# 如果没有最近的心跳,所有基站都标记为离线
|
||||
for station in base_stations.values():
|
||||
if now - station['last_seen'] > timedelta(minutes=1):
|
||||
station['status'] = 'offline'
|
||||
|
||||
def extract_log_time(log_line):
|
||||
"""从日志行中提取时间戳"""
|
||||
try:
|
||||
# 日志格式:Jan 05 10:33:56 ...
|
||||
time_str = ' '.join(log_line.split()[:3])
|
||||
# 添加年份
|
||||
current_year = datetime.now().year
|
||||
time_with_year = f"{current_year} {time_str}"
|
||||
return datetime.strptime(time_with_year, "%Y %b %d %H:%M:%S")
|
||||
except:
|
||||
return datetime.now()
|
||||
|
||||
def cleanup_stations():
|
||||
"""清理超过5分钟未活动的基站"""
|
||||
now = datetime.now()
|
||||
to_remove = []
|
||||
|
||||
for station_id, station in base_stations.items():
|
||||
# 如果基站超过2分钟没有活动,标记为离线
|
||||
if now - station['last_seen'] > timedelta(minutes=2):
|
||||
station['status'] = 'offline'
|
||||
|
||||
# 可选:完全移除超过1小时离线的基站
|
||||
# for station_id, station in base_stations.items():
|
||||
# if station['status'] == 'offline' and now - station['last_seen'] > timedelta(hours=1):
|
||||
# to_remove.append(station_id)
|
||||
|
||||
for station_id in to_remove:
|
||||
del base_stations[station_id]
|
||||
|
||||
@app.route('/api/meituan/send-command', methods=['POST'])
|
||||
def send_station_command():
|
||||
"""向基站发送命令"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
station_id = data.get('station_id')
|
||||
command = data.get('command')
|
||||
|
||||
if not station_id or not command:
|
||||
return jsonify({'success': False, 'error': '参数不完整'})
|
||||
|
||||
# 这里需要连接到基站服务器发送命令
|
||||
# 暂时返回模拟响应
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'命令已发送到基站 {station_id}',
|
||||
'command': command
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
@app.route('/api/meituan/stations', methods=['GET'])
|
||||
def get_stations():
|
||||
"""获取基站列表"""
|
||||
try:
|
||||
# 先更新基站状态(通过解析日志)
|
||||
try:
|
||||
result = subprocess.run(['journalctl', '-u', 'basestation-server', '-n', '200', '--no-pager'],
|
||||
capture_output=True, text=True)
|
||||
if result.stdout:
|
||||
logs = result.stdout.strip().split('\n')
|
||||
parse_station_logs(logs)
|
||||
cleanup_stations()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 转换时间格式为字符串
|
||||
stations_list = []
|
||||
for station in base_stations.values():
|
||||
station_copy = station.copy()
|
||||
station_copy['last_seen'] = station['last_seen'].strftime('%Y-%m-%d %H:%M:%S')
|
||||
station_copy['connect_time'] = station['connect_time'].strftime('%Y-%m-%d %H:%M:%S')
|
||||
stations_list.append(station_copy)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'stations': stations_list
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🚀 启动服务器(已启用静态资源缓存)...")
|
||||
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', '5000')), threaded=True)
|
||||
|
||||
145
test-meituan-api.html
Normal file
145
test-meituan-api.html
Normal file
@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>美团基站测试 - API调试</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||
.card { border: 1px solid #ddd; padding: 20px; margin: 20px 0; border-radius: 8px; }
|
||||
button { padding: 10px 20px; margin: 5px; cursor: pointer; }
|
||||
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; }
|
||||
.status { padding: 10px; margin: 10px 0; border-radius: 4px; }
|
||||
.online { background: #d4edda; color: #155724; }
|
||||
.offline { background: #f8d7da; color: #721c24; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>美团基站测试 - API调试</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>服务器状态</h2>
|
||||
<button onclick="checkServerStatus()">检查服务器状态</button>
|
||||
<div id="server-status" class="status">点击按钮检查</div>
|
||||
<h3>服务器日志:</h3>
|
||||
<pre id="server-logs" style="max-height: 400px; overflow-y: auto;"></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>基站列表</h2>
|
||||
<button onclick="loadStations()">加载基站列表</button>
|
||||
<pre id="stations-list">点击按钮加载</pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>发送命令</h2>
|
||||
<input type="text" id="station-id" placeholder="基站ID (如: BS003)" style="width: 200px; padding: 5px;">
|
||||
<input type="text" id="command" placeholder="命令 (如: ping 8.8.8.8)" style="width: 300px; padding: 5px;">
|
||||
<button onclick="sendCommand()">发送命令</button>
|
||||
<pre id="command-result" style="margin-top: 10px;"></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function checkServerStatus() {
|
||||
const statusEl = document.getElementById('server-status');
|
||||
const logsEl = document.getElementById('server-logs');
|
||||
|
||||
statusEl.textContent = '检查中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/meituan/server-status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
statusEl.className = 'status online';
|
||||
statusEl.textContent = `服务器状态: ${data.status === 'online' ? '在线' : '离线'} (基站数: ${data.station_count})`;
|
||||
|
||||
if (data.logs && data.logs.length > 0) {
|
||||
logsEl.textContent = data.logs.join('\n');
|
||||
} else {
|
||||
logsEl.textContent = '暂无日志';
|
||||
}
|
||||
} else {
|
||||
statusEl.className = 'status offline';
|
||||
statusEl.textContent = `错误: ${data.error}`;
|
||||
}
|
||||
} catch (error) {
|
||||
statusEl.className = 'status offline';
|
||||
statusEl.textContent = `连接失败: ${error.message}`;
|
||||
console.error('错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStations() {
|
||||
const listEl = document.getElementById('stations-list');
|
||||
|
||||
listEl.textContent = '加载中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/meituan/stations');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (data.stations && data.stations.length > 0) {
|
||||
listEl.textContent = JSON.stringify(data.stations, null, 2);
|
||||
} else {
|
||||
listEl.textContent = '暂无基站连接';
|
||||
}
|
||||
} else {
|
||||
listEl.textContent = `错误: ${data.error}`;
|
||||
}
|
||||
} catch (error) {
|
||||
listEl.textContent = `加载失败: ${error.message}`;
|
||||
console.error('错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendCommand() {
|
||||
const stationId = document.getElementById('station-id').value;
|
||||
const command = document.getElementById('command').value;
|
||||
const resultEl = document.getElementById('command-result');
|
||||
|
||||
if (!stationId || !command) {
|
||||
alert('请输入基站ID和命令');
|
||||
return;
|
||||
}
|
||||
|
||||
resultEl.textContent = '发送中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/meituan/send-command', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
station_id: stationId,
|
||||
command: command
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
resultEl.textContent = `成功: ${data.message}`;
|
||||
} else {
|
||||
resultEl.textContent = `失败: ${data.error}`;
|
||||
}
|
||||
} catch (error) {
|
||||
resultEl.textContent = `发送失败: ${error.message}`;
|
||||
console.error('错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时自动检查状态
|
||||
window.onload = function() {
|
||||
console.log('页面加载完成,开始检查服务器状态...');
|
||||
checkServerStatus();
|
||||
// 每5秒刷新一次状态
|
||||
setInterval(() => {
|
||||
console.log('定时刷新服务器状态...');
|
||||
checkServerStatus();
|
||||
}, 5000);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user