添加数据库修复脚本
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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">×</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>
|
||||
|
||||
171
public/js/app.js
171
public/js/app.js
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user