//标签页切换
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') {
startLogStreaming();
} else {
stopLogStreaming();
}
//如果切换到设置页,加载 .env 文件
if (tab === 'settings') {
loadEnvFile();
}
//如果切换到使用指南页,加载 README
if (tab === 'guide') {
loadGuide();
}
}
//日志流控制
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');
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');
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 {
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 = `
`;
}
}
//页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
//默认显示dashboard
switchTab('dashboard');
//加载初始数据
loadDashboardData();
//定期刷新仪表盘数据(每30秒)
setInterval(loadDashboardData, 30000);
});