Files
PromdataPanel/public/index.html
2026-04-06 16:58:58 +08:00

670 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="LDNET-GA">
<title>LDNET-GA</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script>
// Prevent theme flicker
(function () {
const savedTheme = localStorage.getItem('theme');
const settings = window.SITE_SETTINGS || {};
const defaultTheme = settings.default_theme || 'dark';
let theme = savedTheme || defaultTheme;
if (theme === 'auto') {
theme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
if (theme === 'light') {
document.documentElement.classList.add('light-theme');
}
// Also apply title if available to prevent flicker
if (settings.page_name) {
document.title = settings.page_name;
}
})();
</script>
</head>
<body>
<!-- Animated Background -->
<div class="bg-grid"></div>
<div class="bg-glow bg-glow-1"></div>
<div class="bg-glow bg-glow-2"></div>
<div class="bg-glow bg-glow-3"></div>
<!-- App Container -->
<div id="app">
<!-- Header -->
<header class="header" id="header">
<div class="header-left">
<div class="logo">
<div id="logoIconContainer">
<svg class="logo-icon" id="logoSvg" viewBox="0 0 32 32" fill="none">
<rect x="2" y="2" width="28" height="28" rx="8" stroke="url(#logoGrad)" stroke-width="2.5" />
<path d="M8 22 L12 14 L16 18 L20 10 L24 16" stroke="url(#logoGrad)" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" fill="none" />
<circle cx="12" cy="14" r="2" fill="url(#logoGrad)" />
<circle cx="20" cy="10" r="2" fill="url(#logoGrad)" />
<defs>
<linearGradient id="logoGrad" x1="0" y1="0" x2="32" y2="32">
<stop offset="0%" stop-color="#6366f1" />
<stop offset="100%" stop-color="#06b6d4" />
</linearGradient>
</defs>
</svg>
</div>
<h1 class="logo-text" id="logoText">数据可视化展示大屏</h1>
</div>
</div>
<div class="header-right">
<div class="theme-switch-wrapper">
<label class="theme-switch" for="themeToggle">
<input type="checkbox" id="themeToggle" />
<div class="slider round">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="theme-icon sun-icon">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="theme-icon moon-icon" style="display: none;">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</div>
</label>
</div>
<div id="userSection">
<button class="btn btn-login" id="btnLogin">登录</button>
</div>
<button class="btn-settings" id="btnSettings" title="配置管理">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z">
</path>
</svg>
</button>
</div>
</header>
<!-- Main Dashboard -->
<main class="dashboard" id="dashboard">
<!-- Top Stat Cards -->
<section class="stat-cards">
<div class="stat-card stat-card-servers" id="cardServers">
<div class="stat-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="2" y="2" width="20" height="8" rx="2" />
<rect x="2" y="14" width="20" height="8" rx="2" />
<circle cx="6" cy="6" r="1" fill="currentColor" />
<circle cx="6" cy="18" r="1" fill="currentColor" />
</svg>
</div>
<div class="stat-card-content">
<span class="stat-card-label" id="totalServersLabel">服务器总数</span>
<span class="stat-card-value" id="totalServers">0</span>
</div>
</div>
<div class="stat-card stat-card-cpu" id="cardCpu">
<div class="stat-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="4" y="4" width="16" height="16" rx="2" />
<rect x="9" y="9" width="6" height="6" />
<line x1="9" y1="2" x2="9" y2="4" />
<line x1="15" y1="2" x2="15" y2="4" />
<line x1="9" y1="20" x2="9" y2="22" />
<line x1="15" y1="20" x2="15" y2="22" />
<line x1="2" y1="9" x2="4" y2="9" />
<line x1="2" y1="15" x2="4" y2="15" />
<line x1="20" y1="9" x2="22" y2="9" />
<line x1="20" y1="15" x2="22" y2="15" />
</svg>
</div>
<div class="stat-card-content">
<span class="stat-card-label">CPU 使用率</span>
<span class="stat-card-value" id="cpuPercent">0%</span>
<span class="stat-card-sub" id="cpuDetail">0 / 0 核心</span>
</div>
</div>
<div class="stat-card stat-card-mem" id="cardMem">
<div class="stat-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="3" y="3" width="18" height="18" rx="2" />
<path d="M7 7h4v4H7zM13 7h4v4h-4zM7 13h4v4H7zM13 13h4v4h-4z" />
</svg>
</div>
<div class="stat-card-content">
<span class="stat-card-label">内存使用率</span>
<span class="stat-card-value" id="memPercent">0%</span>
<span class="stat-card-sub" id="memDetail">0 / 0 GB</span>
</div>
</div>
<div class="stat-card stat-card-disk" id="cardDisk">
<div class="stat-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
</div>
<div class="stat-card-content">
<span class="stat-card-label">磁盘使用率</span>
<span class="stat-card-value" id="diskPercent">0%</span>
<span class="stat-card-sub" id="diskDetail">0 / 0 GB</span>
</div>
</div>
<div class="stat-card stat-card-bandwidth" id="cardBandwidth">
<div class="stat-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
</div>
<div class="stat-card-content">
<span class="stat-card-label">实时带宽 (MB/s ↑/↓)</span>
<div class="stat-card-value-group">
<span class="stat-card-value" id="totalBandwidthTx">0.00</span>
<span class="stat-card-separator">/</span>
<span class="stat-card-value" id="totalBandwidthRx">0.00</span>
</div>
</div>
</div>
</section>
<!-- Center Charts -->
<section class="charts-section">
<!-- Network Traffic 24h Chart -->
<div class="chart-card chart-card-wide" id="networkChart">
<div class="chart-card-header">
<div class="chart-header-left">
<h2 class="chart-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="chart-title-icon">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
网络流量趋势 (24h)
</h2>
</div>
<div class="chart-legend">
<span class="legend-item" id="legendRx" style="cursor: pointer;" title="点击切换 接收 (RX) 显示/隐藏"><span
class="legend-dot legend-rx"></span>接收 (RX)</span>
<span class="legend-item" id="legendTx" style="cursor: pointer;" title="点击切换 发送 (TX) 显示/隐藏"><span
class="legend-dot legend-tx"></span>发送 (TX)</span>
<span class="legend-item disabled" id="legendP95" style="cursor: pointer;" title="点击切换 P95 线显示/隐藏">
<span class="legend-dot legend-p95"></span>95计费 (<span id="p95LabelText">上行</span>)
</span>
</div>
</div>
<div class="chart-body">
<canvas id="networkCanvas"></canvas>
</div>
<div class="chart-footer">
<div class="traffic-stat">
<span class="traffic-label">24h 接收总量</span>
<span class="traffic-value" id="traffic24hRx">0 B</span>
</div>
<div class="traffic-stat">
<span class="traffic-label">24h 发送总量</span>
<span class="traffic-value" id="traffic24hTx">0 B</span>
</div>
<div class="traffic-stat traffic-stat-p95">
<span class="traffic-label">95计费 (上行)</span>
<span class="traffic-value" id="trafficP95">0 B/s</span>
</div>
<div class="traffic-stat traffic-stat-total">
<span class="traffic-label">24h 总流量</span>
<span class="traffic-value" id="traffic24hTotal">0 B</span>
</div>
<div class="traffic-stat traffic-stat-time">
<span class="traffic-label">当前时间</span>
<span class="traffic-value" id="footerTime">00:00:00</span>
</div>
</div>
</div>
<!-- Global Traffic 3D Globe -->
<div class="chart-card globe-card" id="globeCard">
<div class="chart-card-header">
<h2 class="chart-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="chart-title-icon">
<circle cx="12" cy="12" r="10" />
<path
d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
全球服务器分布
</h2>
<div class="chart-header-actions">
<button class="btn-icon" id="btnExpandGlobe" title="放大显示">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
style="width: 18px; height: 18px;">
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" />
</svg>
</button>
</div>
</div>
<div class="globe-body" id="globeContainer"></div>
<div class="chart-footer">
<div class="traffic-stat">
<span class="traffic-label">全球节点总数</span>
<span class="traffic-value" id="globeTotalNodes">0</span>
</div>
<div class="traffic-stat">
<span class="traffic-label">覆盖地区/国家</span>
<span class="traffic-value" id="globeTotalRegions">0</span>
</div>
<div class="traffic-stat">
<span class="traffic-label">实时活跃状态</span>
<span class="traffic-value" style="color: var(--accent-emerald);">Active</span>
</div>
</div>
</div>
</section>
<!-- Server List -->
<section class="server-list-section" id="serverListSection">
<div class="chart-card">
<div class="chart-card-header">
<h2 class="chart-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="chart-title-icon">
<rect x="2" y="2" width="20" height="8" rx="2" />
<rect x="2" y="14" width="20" height="8" rx="2" />
<circle cx="6" cy="6" r="1" fill="currentColor" />
<circle cx="6" cy="18" r="1" fill="currentColor" />
</svg>
服务器详情
</h2>
<div class="chart-header-right">
<div class="search-box">
<input type="search" id="serverSearchFilter" name="q-filter-server" placeholder="检索服务器名称..."
autocomplete="one-time-code" spellcheck="false">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="search-icon">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</div>
<button id="btnResetSort" class="btn-icon-sm" title="重置筛选与排序">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
<path d="M3 3v5h5"></path>
</svg>
</button>
<select id="sourceFilter" class="source-select">
<option value="all">所有数据源</option>
</select>
</div>
</div>
<div class="server-table-wrap">
<table class="server-table" id="serverTable">
<thead>
<tr>
<th class="sortable active" data-sort="up">状态 <span class="sort-icon"></span></th>
<th class="sortable" data-sort="job">Job / 实例 <span class="sort-icon"></span></th>
<th class="sortable" data-sort="source">数据源 <span class="sort-icon"></span></th>
<th class="sortable" data-sort="cpu">CPU <span class="sort-icon"></span></th>
<th class="sortable" data-sort="mem">内存 <span class="sort-icon"></span></th>
<th class="sortable" data-sort="disk">磁盘 <span class="sort-icon"></span></th>
<th class="sortable" data-sort="netRx">网络 ↓ <span class="sort-icon"></span></th>
<th class="sortable" data-sort="netTx">网络 ↑ <span class="sort-icon"></span></th>
</tr>
</thead>
<tbody id="serverTableBody">
<tr class="empty-row">
<td colspan="8">暂无数据 - 请先配置 Prometheus 数据源</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination-footer">
<div class="page-size-selector">
<span>每页显示</span>
<select id="pageSizeSelect" class="source-select">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span></span>
</div>
<div class="pagination-controls" id="paginationControls">
<!-- Pagination buttons will be injected here -->
</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="site-footer">
<div class="footer-content">
<div class="copyright">© <span id="copyrightYear"></span> LDNET-GA-Service. All rights reserved.</div>
<div class="filings">
<a href="https://beian.miit.gov.cn/" target="_blank" id="icpFilingDisplay" style="display: none;"></a>
<a href="http://www.beian.gov.cn/portal/registerSystemInfo" target="_blank" id="psFilingDisplay" style="display: none;">
<img src="https://www.beian.gov.cn/img/ghs.png" alt="公安备案" style="width: 14px; height: 14px; vertical-align: middle; margin-right: 2px;">
<span id="psFilingText"></span>
</a>
</div>
</div>
</footer>
<!-- Settings Modal -->
<div class="modal-overlay" id="settingsModal">
<div class="modal">
<div class="modal-header">
<div class="modal-tabs">
<button class="modal-tab active" data-tab="prom">数据源管理</button>
<button class="modal-tab" data-tab="site">大屏设置</button>
<button class="modal-tab" data-tab="latency">延迟线路管理</button>
<button class="modal-tab" data-tab="auth">账号安全</button>
</div>
<button class="modal-close" id="modalClose">&times;</button>
</div>
<div class="modal-body">
<!-- Prometheus Sources Tab -->
<div class="tab-content active" id="tab-prom">
<!-- Add Source Form -->
<div class="add-source-form" id="addSourceForm">
<h3>添加数据源</h3>
<div class="form-row">
<div class="form-group" style="flex: 0.8;">
<label for="sourceType">类型</label>
<select id="sourceType"
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary); outline: none;">
<option value="prometheus">Prometheus</option>
<option value="blackbox">Blackbox Exporter</option>
</select>
</div>
<div class="form-group" style="flex: 1;">
<label for="sourceName">名称</label>
<input type="text" id="sourceName" placeholder="例:生产环境" autocomplete="off">
</div>
<div class="form-group form-group-wide">
<label for="sourceUrl">URL 地址</label>
<input type="url" id="sourceUrl" placeholder="http://1.2.3.4:9090" autocomplete="off">
</div>
</div>
<div class="form-row">
<div class="form-group form-group-wide">
<label for="sourceDesc">描述 (可选)</label>
<input type="text" id="sourceDesc" placeholder="数据源描述" autocomplete="off">
</div>
<div class="form-group" id="serverSourceOption"
style="display: flex; align-items: flex-end; padding-bottom: 8px;">
<label
style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap;">
<input type="checkbox" id="isServerSource" checked
style="width: 16px; height: 16px; accent-color: var(--accent-indigo);">
<span>用于服务器展示</span>
</label>
</div>
<div class="form-actions">
<button class="btn btn-test" id="btnTest">测试连接</button>
<button class="btn btn-add" id="btnAdd">添加</button>
</div>
</div>
<div class="form-message" id="formMessage"></div>
</div>
<!-- Source List -->
<div class="source-list" id="sourceList">
<h3>已配置数据源</h3>
<div class="source-items" id="sourceItems">
<div class="source-empty">暂无数据源</div>
</div>
</div>
</div>
<!-- Site Settings Tab -->
<div class="tab-content" id="tab-site">
<div class="site-settings-form">
<h3>自定义大屏展示</h3>
<div class="form-group">
<label for="pageNameInput">页面名称 (浏览器标签页标题)</label>
<input type="text" id="pageNameInput" placeholder="例:运维监控大屏">
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="siteTitleInput">标题 (大屏左上角显示名称)</label>
<input type="text" id="siteTitleInput" placeholder="例:数据可视化展示大屏">
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="logoUrlInput">Logo URL (图片链接,为空则显示默认图标)</label>
<input type="url" id="logoUrlInput" placeholder="https://example.com/logo.png">
</div>
<div class="settings-section" style="margin-top: 25px; border-top: 1px solid var(--border-color); padding-top: 20px;">
<h4 style="font-size: 0.85rem; color: var(--accent-indigo); margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px;">界面外观 (Appearance)</h4>
<div class="form-group">
<label for="defaultThemeInput">色彩主题模式</label>
<select id="defaultThemeInput"
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary); width: 100%;">
<option value="auto">跟随系统主题 (Sync with OS)</option>
<option value="dark">强制深色模式 (Always Dark)</option>
<option value="light">强制浅色模式 (Always Light)</option>
</select>
<p style="font-size: 0.72rem; color: var(--text-muted); margin-top: 6px;">选择“跟随系统”后,应用将自动同步您操作系统或浏览器的黑暗/白天模式设置。</p>
</div>
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="show95BandwidthInput">24h趋势图默认显示 95计费线</label>
<select id="show95BandwidthInput"
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary);">
<option value="1">显示</option>
<option value="0">不显示</option>
</select>
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="p95TypeSelect">95带宽计费统计类型</label>
<select id="p95TypeSelect"
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary);">
<option value="tx">仅统计上行 (TX)</option>
<option value="rx">仅统计下行 (RX)</option>
<option value="both">统计上行+下行 (Sum)</option>
</select>
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="icpFilingInput">ICP 备案号 (如京ICP备12345678号)</label>
<input type="text" id="icpFilingInput" placeholder="请输入 ICP 备案号">
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="psFilingInput">公安备案号 (如:京公网安备 11010102000001号)</label>
<input type="text" id="psFilingInput" placeholder="请输入公安备案号">
</div>
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end;">
<button class="btn btn-add" id="btnSaveSiteSettings">保存基础设置</button>
</div>
<div class="form-message" id="siteSettingsMessage"></div>
</div>
</div>
<!-- Latency Routes Tab -->
<div class="tab-content" id="tab-latency">
<div class="latency-settings-form">
<h3>Blackbox 延迟连线管理</h3>
<div class="latency-routes-manager">
<!-- Add Route Form -->
<div class="add-route-mini-form"
style="background: rgba(255,255,255,0.02); padding: 15px; border-radius: 8px; margin-bottom: 20px; border: 1px solid var(--border-color);">
<div class="form-row">
<div class="form-group" style="flex: 1.5;">
<label>探测用服务器</label>
<select id="routeSourceSelect"
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary);">
<option value="">-- 选择数据源 --</option>
</select>
</div>
<div class="form-group">
<label>起航点</label>
<input type="text" id="routeSourceInput" placeholder="例China">
</div>
<div class="form-group">
<label>目的地</label>
<input type="text" id="routeDestInput" placeholder="例United States">
</div>
</div>
<div class="form-row" style="margin-top: 10px; align-items: flex-end;">
<div class="form-group" style="flex: 2;">
<label>Blackbox 探测目标 (IP 或 域名)</label>
<input type="text" id="routeTargetInput" placeholder="例1.1.1.1 或 google.com">
</div>
<div class="form-actions" style="padding-bottom: 0; display: flex; gap: 8px;">
<button class="btn btn-add" id="btnAddRoute" style="padding: 10px 24px;">添加线路</button>
<button class="btn btn-test" id="btnCancelEditRoute"
style="display: none; padding: 10px 15px; background: rgba(0,0,0,0.3);">取消</button>
</div>
</div>
</div>
<!-- Routes List -->
<div class="latency-routes-list-container">
<h4
style="font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase;; margin-bottom: 10px;">
已配置线路</h4>
<div id="latencyRoutesList" class="latency-routes-list"
style="display: flex; flex-direction: column; gap: 10px;">
<!-- Routes will be injected here -->
<div class="route-empty"
style="text-align: center; padding: 20px; color: var(--text-muted); font-size: 0.85rem; background: rgba(0,0,0,0.1); border-radius: 8px;">
暂无线路</div>
</div>
</div>
</div>
</div>
</div>
<!-- Account Security Tab -->
<div class="tab-content" id="tab-auth">
<div class="security-settings-form">
<h3>修改登录密码</h3>
<div class="form-group">
<label for="oldPassword">当前密码</label>
<input type="password" id="oldPassword" placeholder="请输入当前旧密码">
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="newPassword">新密码</label>
<input type="password" id="newPassword" placeholder="请输入要设置的新密码">
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="confirmNewPassword">确认新密码</label>
<input type="password" id="confirmNewPassword" placeholder="请再次确认新密码">
</div>
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end;">
<button class="btn btn-add" id="btnChangePassword">提交修改</button>
</div>
<div class="form-message" id="changePasswordMessage"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Server Detail Modal -->
<div class="modal-overlay" id="serverDetailModal">
<div class="modal" style="max-width: 800px; width: 95%;">
<div class="modal-header">
<div style="display: flex; flex-direction: column;">
<h2 id="serverDetailTitle" style="margin-bottom: 0;">服务器详情</h2>
</div>
<button class="modal-close" id="serverDetailClose">&times;</button>
</div>
<div class="modal-body" id="serverDetailBody" style="padding: 0;">
<div id="detailLoading" style="text-align: center; padding: 40px; display: none;">
<div class="dot dot-pulse"
style="display: inline-block; width: 12px; height: 12px; background: var(--accent-indigo);"></div>
<span style="margin-left: 10px; color: var(--text-secondary);">正在从数据源读取详情...</span>
</div>
<div class="detail-container" id="detailContainer">
<!-- Metric Items are injected here -->
<div class="detail-metrics-list" id="detailMetricsList"></div>
<div class="detail-partitions-container metric-item" id="detailPartitionsContainer" style="display: none;">
<div class="metric-item-header" id="partitionHeader">
<div class="metric-label-group">
<span class="metric-label">磁盘分区详情 (已挂载)</span>
<span class="metric-value" id="partitionSummary">读取中...</span>
</div>
<svg class="chevron-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<div class="metric-item-content" id="partitionContent">
<div class="detail-partitions-list" id="detailPartitionsList"></div>
</div>
</div>
<div class="detail-info-grid" id="detailInfoGrid">
<div class="info-item">
<span class="info-label">CPU 核心总数</span>
<span class="info-value" id="detailCpuCores">0 核心</span>
</div>
<div class="info-item">
<span class="info-label">物理内存总量</span>
<span class="info-value" id="detailMemTotal">0 GB</span>
</div>
<div class="info-item">
<span class="info-label">运行时间 (Uptime)</span>
<span class="info-value" id="detailUptime">0天 0小时</span>
</div>
<div class="info-item">
<span class="info-label">硬盘总量统计</span>
<span class="info-value" id="detailDiskTotal">0 GB</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Login Modal -->
<div class="modal-overlay" id="loginModalOverlay">
<div class="modal" style="max-width: 400px;">
<div class="modal-header">
<h2>用户登录</h2>
<button class="modal-close" id="closeLoginModal">&times;</button>
</div>
<div class="modal-body">
<form id="loginForm">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" placeholder="请输入用户名" required>
</div>
<div class="form-group" style="margin-top: 16px;">
<label for="password">密码</label>
<input type="password" id="password" placeholder="请输入密码" required>
</div>
<div id="loginError" style="color: var(--accent-rose); font-size: 0.8rem; margin-top: 10px; display: none;">
</div>
<button type="submit" class="btn btn-primary"
style="width: 100%; margin-top: 24px; background: var(--gradient-primary); color: white; border: none; padding: 12px; border-radius: 8px; cursor: pointer;">
</button>
</form>
</div>
</div>
</div>
<script src="/js/utils.js"></script>
<script src="/js/chart.js"></script>
<script src="/js/app.js"></script>
</body>
</html>