830 lines
43 KiB
HTML
830 lines
43 KiB
HTML
<!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></title>
|
||
<link rel="icon" id="siteFavicon" href="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<script src="/vendor/echarts.min.js"></script>
|
||
<script>
|
||
// Prevent theme flicker
|
||
(function () {
|
||
const savedTheme = localStorage.getItem('theme');
|
||
const settings = window.SITE_SETTINGS || {};
|
||
const sanitizeAssetUrl = (url) => {
|
||
if (!url || typeof url !== 'string') return null;
|
||
const trimmed = url.trim();
|
||
if (!trimmed) return null;
|
||
return /^(https?:|data:image\/|\/)/i.test(trimmed) ? trimmed : null;
|
||
};
|
||
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 and favicon if available to prevent flicker
|
||
if (settings.page_name) {
|
||
document.title = settings.page_name;
|
||
}
|
||
|
||
const safeFaviconUrl = sanitizeAssetUrl(settings.favicon_url);
|
||
if (safeFaviconUrl) {
|
||
const link = document.getElementById('siteFavicon');
|
||
if (link) link.href = safeFaviconUrl;
|
||
}
|
||
|
||
// Advanced Anti-Flicker: Wait for header elements to appear
|
||
const observer = new MutationObserver(function(mutations, me) {
|
||
const logoText = document.getElementById('logoText');
|
||
const logoIcon = document.getElementById('logoIconContainer');
|
||
const header = document.getElementById('header');
|
||
|
||
if (logoText || logoIcon) {
|
||
// If we found either, apply what we have
|
||
if (logoText) {
|
||
const displayTitle = settings.title || settings.page_name || '数据可视化展示大屏';
|
||
logoText.textContent = displayTitle;
|
||
if (settings.show_page_name === 0) logoText.style.display = 'none';
|
||
}
|
||
|
||
if (logoIcon) {
|
||
const actualTheme = document.documentElement.classList.contains('light-theme') ? 'light' : 'dark';
|
||
const logoToUse = sanitizeAssetUrl((actualTheme === 'dark' && settings.logo_url_dark) ? settings.logo_url_dark : (settings.logo_url || null));
|
||
if (logoToUse) {
|
||
const img = document.createElement('img');
|
||
img.src = logoToUse;
|
||
img.alt = 'Logo';
|
||
img.className = 'logo-icon-img';
|
||
logoIcon.replaceChildren(img);
|
||
} else {
|
||
// Only if we REALLY have no logo URL, we show the default SVG fallback
|
||
// (But since it's already in HTML, we just don't touch it or we show it if we hid it)
|
||
const svg = logoIcon.querySelector('svg');
|
||
if (svg) svg.style.visibility = 'visible';
|
||
}
|
||
}
|
||
|
||
// Once found everything or we are past header, we are done
|
||
if (logoText && logoIcon) me.disconnect();
|
||
}
|
||
});
|
||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||
})();
|
||
</script>
|
||
<script>
|
||
// Global Error Logger for remote debugging
|
||
window.onerror = function(msg, url, line, col, error) {
|
||
var debugDiv = document.getElementById('js-debug-overlay');
|
||
if (!debugDiv) {
|
||
debugDiv = document.createElement('div');
|
||
debugDiv.id = 'js-debug-overlay';
|
||
debugDiv.style.cssText = 'position:fixed;top:0;left:0;width:100%;background:rgba(220,38,38,0.95);color:white;z-index:99999;padding:10px;font-family:monospace;font-size:12px;max-height:30vh;overflow:auto;pointer-events:none;';
|
||
document.body.appendChild(debugDiv);
|
||
}
|
||
debugDiv.innerHTML += '<div>[JS ERROR] ' + msg + ' at ' + line + ':' + col + '</div>';
|
||
return false;
|
||
};
|
||
</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" style="visibility: hidden;">
|
||
<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 class="chart-header-actions">
|
||
<button class="btn-icon" id="btnRefreshNetwork" title="刷新流量趋势">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 16px; height: 16px;">
|
||
<path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</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>
|
||
<th class="sortable" data-sort="traffic24h">24h 流量 <span class="sort-icon"></span></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="serverTableBody">
|
||
<tr class="empty-row">
|
||
<td colspan="9">暂无数据 - 请先配置 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="http://www.beian.gov.cn/portal/registerSystemInfo" target="_blank" id="psFilingDisplay" style="display: none;">
|
||
<span id="psFilingText"></span>
|
||
</a>
|
||
<span class="filing-sep"></span>
|
||
<a href="https://beian.miit.gov.cn/" target="_blank" id="icpFilingDisplay" style="display: none;"></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="security">安全设置</button>
|
||
<button class="modal-tab" data-tab="latency">延迟线路管理</button>
|
||
<button class="modal-tab" data-tab="auth">账号安全</button>
|
||
</div>
|
||
<button class="modal-close" id="modalClose">×</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>
|
||
<div class="form-row" id="serverSourceOption" style="margin-top: 4px;">
|
||
<div class="form-group form-group-wide">
|
||
<div class="source-options-clean-row">
|
||
<label class="source-option-item" title="将此数据源的服务器指标聚合到首页总览中">
|
||
<div class="switch-wrapper">
|
||
<input type="checkbox" id="isOverviewSource" checked class="switch-input">
|
||
<div class="switch-label"></div>
|
||
</div>
|
||
<span class="source-option-label">加入总览统计</span>
|
||
</label>
|
||
<label class="source-option-item" title="在服务器详情列表中显示此数据源的服务器">
|
||
<div class="switch-wrapper">
|
||
<input type="checkbox" id="isDetailSource" checked class="switch-input">
|
||
<div class="switch-label"></div>
|
||
</div>
|
||
<span class="source-option-label">加入详情展示</span>
|
||
</label>
|
||
</div>
|
||
<input type="checkbox" id="isServerSource" checked disabled style="display: none;">
|
||
</div>
|
||
</div>
|
||
<div class="form-row" style="margin-top: 8px;">
|
||
<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="showPageNameInput">是否显示左上角标题</label>
|
||
<select id="showPageNameInput"
|
||
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="1">显示 (Show)</option>
|
||
<option value="0">隐藏 (Hide)</option>
|
||
</select>
|
||
</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_light.png">
|
||
</div>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="logoUrlDarkInput">Logo URL (黑夜模式,可为空则使用默认)</label>
|
||
<input type="url" id="logoUrlDarkInput" placeholder="https://example.com/logo_dark.png">
|
||
</div>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="faviconUrlInput">Favicon URL (浏览器标签页图标)</label>
|
||
<input type="url" id="faviconUrlInput" placeholder="https://example.com/favicon.ico">
|
||
</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>
|
||
<option value="max">出入取大 (Max)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="psFilingInput">公安备案号 (如:京公网安备 11010102000001号)</label>
|
||
<input type="text" id="psFilingInput" placeholder="请输入公安备案号">
|
||
</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-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>
|
||
|
||
<!-- Security Settings Tab -->
|
||
<div class="tab-content" id="tab-security">
|
||
<div class="security-settings-form">
|
||
<h3>安全与隐私设置</h3>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="requireLoginForServerDetailsInput">服务器详情是否仅登录后可查看</label>
|
||
<select id="requireLoginForServerDetailsInput"
|
||
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="1">仅登录后可查看</option>
|
||
<option value="0">允许公开查看</option>
|
||
</select>
|
||
<p style="font-size: 0.72rem; color: var(--text-muted); margin-top: 6px;">开启后,未登录访客仍可看到大屏总览,但点击单台服务器时需要先登录。</p>
|
||
</div>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="showServerIpInput">是否在服务器详情中显示公网 IP</label>
|
||
<select id="showServerIpInput"
|
||
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="1">显示 (Show)</option>
|
||
<option value="0">隐藏 (Hide)</option>
|
||
</select>
|
||
<p style="font-size: 0.72rem; color: var(--text-muted); margin-top: 6px;">开启后,点击服务器详情时会显示该服务器的公网 IP 地址。</p>
|
||
</div>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="ipMetricNameInput">自定义 IP 采集指标 (可选)</label>
|
||
<input type="text" id="ipMetricNameInput" placeholder="例:node_network_address_info">
|
||
<p style="font-size: 0.72rem; color: var(--text-muted); margin-top: 6px;">如果您的 Prometheus 中有专门记录 IP 的指标,请在此输入。留空则尝试自动发现。</p>
|
||
</div>
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="ipLabelNameInput">IP 指标中的 Label 名称</label>
|
||
<input type="text" id="ipLabelNameInput" placeholder="默认:address">
|
||
</div>
|
||
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end;">
|
||
<button class="btn btn-add" id="btnSaveSecuritySettings">保存安全设置</button>
|
||
</div>
|
||
<div class="form-message" id="securitySettingsMessage"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Custom Detail Metrics Tab -->
|
||
<div class="tab-content" id="tab-details-metrics">
|
||
<div class="metrics-settings-form">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||
<h3 style="margin: 0;">服务器详情指标配置</h3>
|
||
<button class="btn btn-add" id="btnAddCustomMetric" style="padding: 6px 12px; font-size: 0.8rem;">
|
||
<i class="fas fa-plus"></i> 添加指标
|
||
</button>
|
||
</div>
|
||
|
||
<div id="customMetricsList" class="custom-metrics-list" style="max-height: 400px; overflow-y: auto; padding-right: 5px;">
|
||
<!-- Dynamic rows will be added here -->
|
||
</div>
|
||
|
||
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end;">
|
||
<button class="btn btn-add" id="btnSaveCustomMetrics">保存指标配置</button>
|
||
</div>
|
||
<div class="form-message" id="customMetricsMessage"></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">×</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">×</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>
|