添加鉴权逻辑

This commit is contained in:
CN-JS-HuiBai
2026-04-04 17:49:00 +08:00
parent a2a477d3fb
commit 2bad8978a4
8 changed files with 474 additions and 57 deletions

View File

@@ -42,12 +42,21 @@
btnTest: document.getElementById('btnTest'),
btnAdd: document.getElementById('btnAdd'),
formMessage: document.getElementById('formMessage'),
sourceItems: document.getElementById('sourceItems')
sourceItems: document.getElementById('sourceItems'),
// Auth & Theme elements
themeToggle: document.getElementById('themeToggle'),
userSection: document.getElementById('userSection'),
btnLogin: document.getElementById('btnLogin'),
loginModal: document.getElementById('loginModalOverlay'),
closeLoginModal: document.getElementById('closeLoginModal'),
loginForm: document.getElementById('loginForm'),
loginError: document.getElementById('loginError')
};
// ---- State ----
let previousMetrics = null;
let networkChart = null;
let user = null; // Currently logged in user
// ---- Initialize ----
function init() {
@@ -58,6 +67,12 @@
updateClock();
setInterval(updateClock, 1000);
// Theme initialization
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.add('light-theme');
}
// Network chart
networkChart = new AreaChart(dom.networkCanvas);
@@ -70,11 +85,26 @@
dom.btnTest.addEventListener('click', testConnection);
dom.btnAdd.addEventListener('click', addSource);
// Auth & Theme listeners
dom.themeToggle.addEventListener('click', toggleTheme);
dom.btnLogin.addEventListener('click', openLoginModal);
dom.closeLoginModal.addEventListener('click', closeLoginModal);
dom.loginForm.addEventListener('submit', handleLogin);
dom.loginModal.addEventListener('click', (e) => {
if (e.target === dom.loginModal) closeLoginModal();
});
// Keyboard shortcut
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeSettings();
if (e.key === 'Escape') {
closeSettings();
closeLoginModal();
}
});
// Check auth status
checkAuthStatus();
// Start data fetching
fetchMetrics();
fetchNetworkHistory();
@@ -82,6 +112,88 @@
setInterval(fetchNetworkHistory, NETWORK_HISTORY_INTERVAL);
}
// ---- Theme Switching ----
function toggleTheme() {
const isLight = document.documentElement.classList.toggle('light-theme');
localStorage.setItem('theme', isLight ? 'light' : 'dark');
}
// ---- Auth Logic ----
async function checkAuthStatus() {
try {
const resp = await fetch('/api/auth/status');
const data = await resp.json();
if (data.authenticated) {
updateUserUI(data.username);
} else {
updateUserUI(null);
}
} catch (err) {
updateUserUI(null);
}
}
function updateUserUI(username) {
if (username) {
user = username;
dom.userSection.innerHTML = `
<div class="user-info">
<span class="username">${escapeHtml(username)}</span>
<button class="btn btn-logout" id="btnLogout">退出</button>
</div>
`;
document.getElementById('btnLogout').addEventListener('click', handleLogout);
} else {
user = null;
dom.userSection.innerHTML = `<button class="btn btn-login" id="btnLogin">登录</button>`;
document.getElementById('btnLogin').addEventListener('click', openLoginModal);
}
}
function openLoginModal() {
dom.loginModal.classList.add('active');
dom.loginError.style.display = 'none';
}
function closeLoginModal() {
dom.loginModal.classList.remove('active');
dom.loginForm.reset();
}
async function handleLogin(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
const resp = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await resp.json();
if (resp.ok) {
updateUserUI(data.username);
closeLoginModal();
} else {
dom.loginError.textContent = data.error || '登录失败';
dom.loginError.style.display = 'block';
}
} catch (err) {
dom.loginError.textContent = '服务器错误';
dom.loginError.style.display = 'block';
}
}
async function handleLogout() {
try {
await fetch('/api/auth/logout', { method: 'POST' });
updateUserUI(null);
} catch (err) {
console.error('Logout failed:', err);
}
}
// ---- Add SVG Gradient Defs ----
function addGaugeSvgDefs() {
const svgs = document.querySelectorAll('.gauge svg');
@@ -97,7 +209,7 @@
grad.setAttribute('id', gradients[i].id);
grad.setAttribute('x1', '0%');
grad.setAttribute('y1', '0%');
grad.setAttribute('x2', '100%');
grad.setAttribute('x1', '100%');
grad.setAttribute('y2', '100%');
gradients[i].colors.forEach((color, ci) => {
@@ -150,13 +262,13 @@
dom.diskDetail.textContent = `${formatBytes(data.disk.used)} / ${formatBytes(data.disk.total)}`;
// Bandwidth
dom.totalBandwidth.textContent = formatBandwidth(data.network.totalBandwidth);
dom.totalBandwidth.textContent = formatBandwidth(data.network.total || 0);
dom.bandwidthDetail.textContent = `${formatBandwidth(data.network.rx)}${formatBandwidth(data.network.tx)}`;
// 24h traffic
dom.traffic24hRx.textContent = formatBytes(data.traffic24h.rx);
dom.traffic24hTx.textContent = formatBytes(data.traffic24h.tx);
dom.traffic24hTotal.textContent = formatBytes(data.traffic24h.total);
dom.traffic24hTotal.textContent = formatBytes(data.traffic24h.total || (data.traffic24h.rx + data.traffic24h.tx));
// Update gauges
updateGauge(dom.gaugeCpuFill, dom.gaugeCpuValue, cpuPct);
@@ -186,10 +298,6 @@
const offset = CIRCUMFERENCE - (clamped / 100) * CIRCUMFERENCE;
fillEl.style.strokeDashoffset = offset;
valueEl.textContent = formatPercent(clamped);
// Change color based on usage
const color = getUsageColor(clamped);
// We keep gradient but could override for critical
}
// ---- Server Table ----
@@ -218,7 +326,10 @@
<td>
<span class="status-dot ${server.up ? 'status-dot-online' : 'status-dot-offline'}"></span>
</td>
<td style="color: var(--text-primary); font-weight: 500;">${escapeHtml(server.instance)}</td>
<td>
<div style="color: var(--text-primary); font-weight: 600; font-family: var(--font-sans);">${escapeHtml(server.job)}</div>
<div style="font-size: 0.65rem; color: var(--text-muted); opacity: 0.8;">${escapeHtml(server.instance)}</div>
</td>
<td>${escapeHtml(server.source)}</td>
<td>
<div class="usage-bar">
@@ -330,7 +441,12 @@
if (data.status === 'ok') {
showMessage(`连接成功Prometheus 版本: ${data.version}`, 'success');
} else {
showMessage(`连接失败: ${data.message}`, 'error');
if (response.status === 401) {
showMessage('请先登录', 'error');
openLoginModal();
} else {
showMessage(`连接失败: ${data.message || data.error}`, 'error');
}
}
} catch (err) {
showMessage(`连接失败: ${err.message}`, 'error');
@@ -342,6 +458,12 @@
// ---- Add Source ----
async function addSource() {
if (!user) {
showMessage('请先登录后操作', 'error');
openLoginModal();
return;
}
const name = dom.sourceName.value.trim();
const url = dom.sourceUrl.value.trim();
const description = dom.sourceDesc.value.trim();
@@ -367,12 +489,12 @@
dom.sourceUrl.value = '';
dom.sourceDesc.value = '';
loadSources();
// Refresh metrics immediately
fetchMetrics();
fetchNetworkHistory();
} else {
const err = await response.json();
showMessage(`添加失败: ${err.error}`, 'error');
if (response.status === 401) openLoginModal();
}
} catch (err) {
showMessage(`添加失败: ${err.message}`, 'error');
@@ -384,6 +506,11 @@
// ---- Delete Source ----
window.deleteSource = async function (id) {
if (!user) {
showMessage('请先登录后操作', 'error');
openLoginModal();
return;
}
if (!confirm('确定要删除这个数据源吗?')) return;
try {
@@ -392,6 +519,8 @@
loadSources();
fetchMetrics();
fetchNetworkHistory();
} else {
if (response.status === 401) openLoginModal();
}
} catch (err) {
console.error('Error deleting source:', err);
@@ -412,7 +541,7 @@
// ---- Escape HTML ----
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
div.textContent = text || '';
return div.innerHTML;
}
@@ -421,7 +550,7 @@
try {
const response = await fetch('/api/sources');
const sources = await response.json();
dom.sourceCount.textContent = `${sources.length} 个数据源`;
dom.sourceCount.textContent = `${Array.isArray(sources) ? sources.length : 0} 个数据源`;
} catch (err) {
// ignore
}