//标签页切换 function switchTab(tab) { //更新侧边栏按钮状态 document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('bg-indigo-600', 'text-white'); btn.classList.add('hover:bg-slate-800', 'text-slate-400', 'hover:text-white'); }); const activeBtn = document.getElementById(`tab-${tab}`); if (activeBtn) { activeBtn.classList.add('bg-indigo-600', 'text-white'); activeBtn.classList.remove('hover:bg-slate-800', 'text-slate-400', 'hover:text-white'); } //切换内容区 document.querySelectorAll('.tab-content').forEach(content => { content.classList.add('hidden'); }); const activeContent = document.getElementById(`content-${tab}`); if (activeContent) { activeContent.classList.remove('hidden'); } //如果切换到日志页,开始实时加载 if (tab === 'logs') { checkAdminStatus().then(allowed => { if (allowed) { startLogStreaming(); } else { const container = document.getElementById('content-logs'); container.innerHTML = `
`; } }); return; } else { stopLogStreaming(); } //如果切换到设置页,加载 .env 文件 if (tab === 'settings') { checkAdminStatus().then(allowed => { if (allowed) { loadEnvFile(); } else { const container = document.getElementById('content-settings'); // 使用 iframe 嵌入错误页,保持侧边栏导航 container.innerHTML = `
`; } }); return; // 等待检查结果,暂不加载内容 } //如果切换到使用指南页,加载 README if (tab === 'guide') { loadGuide(); } } async function checkAdminStatus() { try { const res = await fetch('/api/me'); const data = await res.json(); return data.loggedIn && data.isAdmin; } catch (e) { console.error('Check admin status failed:', e); return false; } } //日志流控制 let logInterval = null; let lastLogSize = 0; async function startLogStreaming() { //立即加载一次 await loadLogs(); //每2秒刷新一次 logInterval = setInterval(loadLogs, 2000); } function stopLogStreaming() { if (logInterval) { clearInterval(logInterval); logInterval = null; } } async function loadLogs() { try { const res = await fetch('/api/logs'); const data = await res.json(); if (data.success) { const logViewer = document.getElementById('log-viewer'); document.getElementById('log-filename').textContent = data.filename || 'sync_service.log'; if (data.logs && data.logs.length > 0) { //只在日志有变化时更新 const newContent = data.logs.map((log, index) => `
${index + 1}${escapeHtml(log)}
` ).join(''); if (logViewer.innerHTML !== newContent) { logViewer.innerHTML = newContent; //自动滚动到底部 logViewer.scrollTop = logViewer.scrollHeight; } } else { logViewer.innerHTML = '
暂无日志
'; } } } catch (e) { console.error('加载日志失败:', e); } } //HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } //加载仪表盘数据 async function loadDashboardData() { try { const res = await fetch('/api/status'); if (res.status === 403) { document.getElementById('today-syncs').textContent = '无权限'; document.getElementById('repo-count').textContent = '无权限'; document.getElementById('error-count').textContent = '无权限'; document.getElementById('uptime').textContent = '系统运行时间: 无权限'; updateServiceStatus('unknown'); loadHistory(); // 也会处理 403 return; } const data = await res.json(); if (data.success) { //更新统计数据 document.getElementById('today-syncs').textContent = data.todaySyncs || '--'; document.getElementById('repo-count').textContent = data.repoCount || '--'; document.getElementById('error-count').textContent = (data.errorCount + data.fatalCount) || '--'; document.getElementById('uptime').textContent = data.uptime ? `系统运行时间: ${data.uptime}` : '加载中...'; //更新服务状态 updateServiceStatus(data.status); } //加载历史记录 loadHistory(); } catch (e) { console.error('加载仪表盘数据失败:', e); } } async function loadHistory() { try { const res = await fetch('/api/history'); if (res.status === 403) { const tbody = document.getElementById('history-table'); tbody.innerHTML = '无权限查看历史记录'; return; } const data = await res.json(); if (data.success && data.history) { const tbody = document.getElementById('history-table'); if (data.history.length === 0) { tbody.innerHTML = '暂无历史记录'; return; } tbody.innerHTML = data.history.map(day => ` ${day.date} ${day.syncs} ${day.errors} ${day.fatals} `).join(''); } } catch (e) { console.error('加载历史数据失败:', e); const tbody = document.getElementById('history-table'); tbody.innerHTML = '加载失败'; } } function updateServiceStatus(status) { const badge = document.getElementById('status-badge'); const statusText = document.getElementById('service-status'); if (status === 'running') { badge.className = 'px-3 py-1 rounded-full text-xs font-medium flex items-center border bg-emerald-50 text-emerald-600 border-emerald-200'; badge.innerHTML = ` 运行中 `; statusText.textContent = '运行中'; } else if (status === 'unknown') { badge.className = 'px-3 py-1 rounded-full text-xs font-medium flex items-center border bg-slate-100 text-slate-600 border-slate-200'; badge.innerHTML = ` 状态未知 `; statusText.textContent = '状态未知'; } else { badge.className = 'px-3 py-1 rounded-full text-xs font-medium flex items-center border bg-slate-100 text-slate-600 border-slate-200'; badge.innerHTML = ` 已停止 `; statusText.textContent = '已停止'; } } //控制机器人 async function controlBot(action) { try { const res = await fetch('/api/restart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action }) }); const data = await res.json(); if (data.success) { alert(`操作成功: ${data.message || action}`); //服务重启后延迟刷新页面 if (action === 'restart') { setTimeout(() => { location.reload(); }, 3000); } else { loadDashboardData(); } } else { alert(`操作失败: ${data.error}`); } } catch (e) { alert(`操作失败: ${e.message}`); } } //清空日志 async function clearLogs() { if (!confirm('确定要清空日志吗?此操作不可恢复。')) { return; } try { const res = await fetch('/api/logs/clear', { method: 'POST' }); const data = await res.json(); if (data.success) { alert('日志已清空'); if (document.getElementById('content-logs').classList.contains('hidden') === false) { loadLogs(); } } else { alert(`清空失败: ${data.error}`); } } catch (e) { alert(`清空失败: ${e.message}`); } } //刷新状态 function refreshStatus() { loadDashboardData(); } async function loadEnvFile() { const container = document.getElementById('envEditor'); try { const res = await fetch('/api/env'); const data = await res.json(); if (data.success) { const envContent = data.content; const envItems = parseEnvContent(envContent); renderEnvForm(envItems); } else { container.innerHTML = `
${data.error}
`; } } catch (e) { container.innerHTML = `
加载失败: ${e.message}
`; } } function parseEnvContent(content) { const lines = content.split('\n'); const items = []; for (const line of lines) { const trimmed = line.trim(); if (trimmed === '' || trimmed.startsWith('#')) { items.push({ type: 'comment', value: line }); } else if (trimmed.includes('=')) { const equalIndex = trimmed.indexOf('='); const key = trimmed.substring(0, equalIndex).trim(); const value = trimmed.substring(equalIndex + 1).trim(); items.push({ type: 'var', key, value }); } else { items.push({ type: 'comment', value: line }); } } return items; } function renderEnvForm(items) { const container = document.getElementById('envEditor'); container.innerHTML = items.map((item, index) => { if (item.type === 'comment') { return `
${escapeHtml(item.value)}
`; } else { const isSecret = item.key.toLowerCase().includes('token') || item.key.toLowerCase().includes('secret') || item.key.toLowerCase().includes('password') || item.key.toLowerCase().includes('pat'); return `
=
`; } }).join(''); } async function saveEnvFile() { const btn = document.getElementById('saveEnvBtn'); if (!confirm('确定要保存配置吗?保存后需要重启服务才能生效。')) { return; } btn.disabled = true; btn.textContent = '保存中...'; try { const content = buildEnvContent(); const res = await fetch('/api/env', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); const data = await res.json(); if (data.success) { alert(`保存成功!\n\n${data.message}`); loadEnvFile(); } else { alert(`保存失败: ${data.error}`); } } catch (e) { alert(`保存失败: ${e.message}`); } finally { btn.disabled = false; btn.textContent = '保存配置'; } } function buildEnvContent() { const container = document.getElementById('envEditor'); const lines = []; container.childNodes.forEach(node => { if (node.classList && node.classList.contains('text-slate-400')) { lines.push(node.textContent); } else if (node.classList && node.classList.contains('bg-slate-50')) { const input = node.querySelector('input'); const key = input.dataset.key; const value = input.value; lines.push(`${key}=${value}`); } }); return lines.join('\n'); } async function loadGuide() { const container = document.getElementById('guide-content'); try { const res = await fetch('/api/guide'); const data = await res.json(); if (data.success && data.content) { //使用marked.js渲染markdown if (typeof marked !== 'undefined') { container.innerHTML = marked.parse(data.content); } else { //如果marked未加载,显示原始文本 container.innerHTML = `
${data.content}
`; } } else { container.innerHTML = `

无法加载使用指南

${data.error || '未知错误'}

`; } } catch (e) { console.error('加载使用指南失败:', e); container.innerHTML = `

加载失败

${e.message}

`; } } //页面加载完成后初始化 document.addEventListener('DOMContentLoaded', () => { //默认显示dashboard switchTab('dashboard'); //加载初始数据 loadDashboardData(); //定期刷新仪表盘数据(每30秒) setInterval(loadDashboardData, 30000); });