添加鉴权逻辑
This commit is contained in:
157
public/js/app.js
157
public/js/app.js
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user