添加数据库修复脚本

This commit is contained in:
CN-JS-HuiBai
2026-04-04 19:11:40 +08:00
parent a686977da6
commit 3823eeede2
6 changed files with 549 additions and 70 deletions

View File

@@ -71,6 +71,41 @@
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}
:root.light-theme .bg-grid {
background-image:
linear-gradient(rgba(99, 102, 241, 0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(99, 102, 241, 0.06) 1px, transparent 1px);
}
:root.light-theme .header {
background: rgba(255, 255, 255, 0.8);
}
:root.light-theme .stat-card {
background: #ffffff;
}
:root.light-theme .chart-card {
background: #ffffff;
}
:root.light-theme .chart-footer {
background: rgba(0, 0, 0, 0.02);
}
:root.light-theme .server-table th {
background: rgba(0, 0, 0, 0.03);
}
:root.light-theme .modal {
background: #ffffff;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
}
:root.light-theme .modal-overlay {
background: rgba(255, 255, 255, 0.4);
}
/* ---- Reset & Base ---- */
*, *::before, *::after {
margin: 0;
@@ -721,6 +756,68 @@ body {
-webkit-text-fill-color: transparent;
}
/* ---- Modal Tabs ---- */
.modal-tabs {
display: flex;
gap: 16px;
}
.modal-tab {
background: none;
border: none;
color: var(--text-muted);
font-family: var(--font-sans);
font-size: 0.95rem;
font-weight: 600;
padding: 8px 4px;
cursor: pointer;
position: relative;
transition: all 0.2s ease;
}
.modal-tab:hover {
color: var(--text-primary);
}
.modal-tab.active {
color: var(--text-primary);
}
.modal-tab.active::after {
content: '';
position: absolute;
bottom: -21px; /* Align with header border */
left: 0;
right: 0;
height: 2px;
background: var(--gradient-primary);
z-index: 1;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* ---- Logo Styling ---- */
#logoIconContainer {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.logo-icon-img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: var(--radius-sm);
}
.modal-close {
width: 32px;
height: 32px;

View File

@@ -23,19 +23,21 @@
<header class="header" id="header">
<div class="header-left">
<div class="logo">
<svg class="logo-icon" 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>
<h1 class="logo-text">数据可视化展示大屏</h1>
<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 class="header-meta">
<span class="server-count" id="serverCount">
@@ -48,7 +50,8 @@
<div class="header-right">
<div class="clock" id="clock"></div>
<button class="btn-icon theme-toggle" id="themeToggle" title="切换主题">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="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 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>
</button>
<div id="userSection">
<button class="btn btn-login" id="btnLogin">登录</button>
@@ -267,41 +270,77 @@
<div class="modal-overlay" id="settingsModal">
<div class="modal">
<div class="modal-header">
<h2>Prometheus 数据源管理</h2>
<div class="modal-tabs">
<button class="modal-tab active" data-tab="prom">数据源管理</button>
<button class="modal-tab" data-tab="site">大屏设置</button>
</div>
<button class="modal-close" id="modalClose">&times;</button>
</div>
<div class="modal-body">
<!-- Add Source Form -->
<div class="add-source-form" id="addSourceForm">
<h3>添加数据源</h3>
<div class="form-row">
<div class="form-group">
<label for="sourceName">名称</label>
<input type="text" id="sourceName" placeholder="例:生产环境" autocomplete="off">
<!-- 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">
<label for="sourceName">名称</label>
<input type="text" id="sourceName" placeholder="例:生产环境" autocomplete="off">
</div>
<div class="form-group form-group-wide">
<label for="sourceUrl">Prometheus URL</label>
<input type="url" id="sourceUrl" placeholder="http://prometheus.example.com:9090" autocomplete="off">
</div>
</div>
<div class="form-group form-group-wide">
<label for="sourceUrl">Prometheus URL</label>
<input type="url" id="sourceUrl" placeholder="http://prometheus.example.com:9090" autocomplete="off">
<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-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 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-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>
<!-- 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="form-group" style="margin-top: 15px;">
<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);">
<option value="dark">默认夜间模式</option>
<option value="light">默认白天模式</option>
</select>
</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>
</div>

View File

@@ -43,8 +43,21 @@
btnAdd: document.getElementById('btnAdd'),
formMessage: document.getElementById('formMessage'),
sourceItems: document.getElementById('sourceItems'),
// Site Settings
modalTabs: document.querySelectorAll('.modal-tab'),
tabContents: document.querySelectorAll('.tab-content'),
pageNameInput: document.getElementById('pageNameInput'),
siteTitleInput: document.getElementById('siteTitleInput'),
logoUrlInput: document.getElementById('logoUrlInput'),
btnSaveSiteSettings: document.getElementById('btnSaveSiteSettings'),
siteSettingsMessage: document.getElementById('siteSettingsMessage'),
logoText: document.getElementById('logoText'),
logoIconContainer: document.getElementById('logoIconContainer'),
defaultThemeInput: document.getElementById('defaultThemeInput'),
// Auth & Theme elements
themeToggle: document.getElementById('themeToggle'),
sunIcon: document.querySelector('.sun-icon'),
moonIcon: document.querySelector('.moon-icon'),
userSection: document.getElementById('userSection'),
btnLogin: document.getElementById('btnLogin'),
loginModal: document.getElementById('loginModalOverlay'),
@@ -67,11 +80,7 @@
updateClock();
setInterval(updateClock, 1000);
// Theme initialization
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.add('light-theme');
}
// Initial theme check (localStorage handled after site settings load to ensure priority)
// Network chart
networkChart = new AreaChart(dom.networkCanvas);
@@ -94,6 +103,17 @@
if (e.target === dom.loginModal) closeLoginModal();
});
// Tab switching
dom.modalTabs.forEach(tab => {
tab.addEventListener('click', () => {
const targetTab = tab.getAttribute('data-tab');
switchTab(targetTab);
});
});
// Site settings
dom.btnSaveSiteSettings.addEventListener('click', saveSiteSettings);
// Keyboard shortcut
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
@@ -108,6 +128,7 @@
// Start data fetching
fetchMetrics();
fetchNetworkHistory();
loadSiteSettings();
setInterval(fetchMetrics, REFRESH_INTERVAL);
setInterval(fetchNetworkHistory, NETWORK_HISTORY_INTERVAL);
}
@@ -115,7 +136,25 @@
// ---- Theme Switching ----
function toggleTheme() {
const isLight = document.documentElement.classList.toggle('light-theme');
localStorage.setItem('theme', isLight ? 'light' : 'dark');
const theme = isLight ? 'light' : 'dark';
localStorage.setItem('theme', theme);
updateThemeIcons(theme);
}
function applyTheme(theme) {
const isLight = theme === 'light';
document.documentElement.classList.toggle('light-theme', isLight);
updateThemeIcons(theme);
}
function updateThemeIcons(theme) {
if (theme === 'light') {
dom.sunIcon.style.display = 'none';
dom.moonIcon.style.display = 'block';
} else {
dom.sunIcon.style.display = 'block';
dom.moonIcon.style.display = 'none';
}
}
// ---- Auth Logic ----
@@ -381,6 +420,126 @@
function closeSettings() {
dom.settingsModal.classList.remove('active');
hideMessage();
hideSiteMessage();
}
// ---- Tab Switching ----
function switchTab(tabId) {
dom.modalTabs.forEach(tab => {
tab.classList.toggle('active', tab.getAttribute('data-tab') === tabId);
});
dom.tabContents.forEach(content => {
content.classList.toggle('active', content.id === `tab-${tabId}`);
});
}
// ---- Site Settings ----
async function loadSiteSettings() {
try {
const response = await fetch('/api/settings');
const settings = await response.json();
// Update inputs
dom.pageNameInput.value = settings.page_name || '';
dom.siteTitleInput.value = settings.title || '';
dom.logoUrlInput.value = settings.logo_url || '';
dom.defaultThemeInput.value = settings.default_theme || 'dark';
// Apply to UI
applySiteSettings(settings);
// Handle Theme Priority: localStorage > Site Default
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
applyTheme(savedTheme);
} else if (settings.default_theme) {
applyTheme(settings.default_theme);
} else {
applyTheme('dark'); // Fallback
}
} catch (err) {
console.error('Error loading site settings:', err);
}
}
function applySiteSettings(settings) {
if (settings.page_name) {
document.title = settings.page_name;
}
if (settings.title) {
dom.logoText.textContent = settings.title;
}
// Logo Icon
if (settings.logo_url) {
dom.logoIconContainer.innerHTML = `<img src="${escapeHtml(settings.logo_url)}" alt="Logo" class="logo-icon-img">`;
} else {
// Restore default SVG
dom.logoIconContainer.innerHTML = `
<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>
`;
}
}
async function saveSiteSettings() {
if (!user) {
showSiteMessage('请先登录后操作', 'error');
openLoginModal();
return;
}
const settings = {
page_name: dom.pageNameInput.value.trim(),
title: dom.siteTitleInput.value.trim(),
logo_url: dom.logoUrlInput.value.trim(),
default_theme: dom.defaultThemeInput.value
};
dom.btnSaveSiteSettings.disabled = true;
dom.btnSaveSiteSettings.textContent = '保存中...';
try {
const response = await fetch('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings)
});
if (response.ok) {
showSiteMessage('设置保存成功', 'success');
applySiteSettings(settings);
} else {
const err = await response.json();
showSiteMessage(`保存失败: ${err.error}`, 'error');
if (response.status === 401) openLoginModal();
}
} catch (err) {
showSiteMessage(`保存失败: ${err.message}`, 'error');
} finally {
dom.btnSaveSiteSettings.disabled = false;
dom.btnSaveSiteSettings.textContent = '保存设置';
}
}
function showSiteMessage(text, type) {
dom.siteSettingsMessage.textContent = text;
dom.siteSettingsMessage.className = `form-message ${type}`;
setTimeout(hideSiteMessage, 5000);
}
function hideSiteMessage() {
dom.siteSettingsMessage.className = 'form-message';
}
async function loadSources() {