// 后台管理 JavaScript // 获取 Cookie function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); return null; } // API 请求封装 async function apiRequest(url, options = {}) { const token = getCookie('token'); const headers = { 'Content-Type': 'application/json', ...options.headers, }; if (token) { headers['Authorization'] = `Bearer ${token}`; } try { const response = await fetch(url, { ...options, headers, credentials: 'include', }); const data = await response.json(); if (!response.ok) { // 如果是401,跳转到登录页 if (response.status === 401) { window.location.href = '/admin/login'; return; } throw new Error(data.error || '请求失败'); } return data; } catch (error) { console.error('API Error:', error); throw error; } } // 加载当前用户信息 async function loadCurrentUser() { try { const data = await apiRequest('/admin/me'); const userEl = document.getElementById('current-user'); if (userEl && data.user) { userEl.textContent = `${data.user.username}${data.user.is_admin ? ' (管理员)' : ''}`; } } catch (error) { console.error('Load user error:', error); } } // 登出 document.getElementById('logout-btn')?.addEventListener('click', async () => { try { await apiRequest('/admin/logout', { method: 'POST' }); document.cookie = 'token=; path=/; max-age=0'; window.location.href = '/admin/login'; } catch (error) { console.error('Logout error:', error); // 即使失败也跳转 document.cookie = 'token=; path=/; max-age=0'; window.location.href = '/admin/login'; } }); // 页面导航 document.querySelectorAll('.nav-item').forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); const page = item.dataset.page; document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); item.classList.add('active'); document.getElementById(`page-${page}`).classList.add('active'); // 加载对应页面数据 if (page === 'routes') { loadRoutes(); } else if (page === 'logs') { loadLogs(); } else if (page === 'files') { loadFiles(); } else if (page === 'config') { loadConfig(); } else if (page === 'database') { loadDatabaseInfo(); } }); }); // 加载数据库信息 async function loadDatabaseInfo() { try { const data = await apiRequest('/admin/api/database'); const dbDetails = document.getElementById('db-details'); const osDetails = document.getElementById('os-details'); const dbConfig = document.getElementById('db-config'); const passwordSection = document.getElementById('database-password-section'); dbDetails.innerHTML = `

类型: ${data.database.type}

状态: ${data.database.status}

${data.database.file ? `

文件: ${data.database.file}

` : ''} ${data.database.cgo_support !== undefined ? `

CGO 支持: ${data.database.cgo_support ? '是' : '否'}

` : ''} `; osDetails.innerHTML = `

操作系统: ${data.os.os}

架构: ${data.os.arch}

`; // 加载数据库配置 try { const configData = await apiRequest('/admin/api/database/config'); dbConfig.innerHTML = `

类型: ${configData.config.type}

主机: ${configData.config.host}

端口: ${configData.config.port}

用户: ${configData.config.user}

数据库: ${configData.config.database}

已设置密码: ${configData.config.has_password ? '是' : '否'}

`; // 如果是 MySQL,显示密码修改界面 if (configData.config.type === 'mysql') { passwordSection.style.display = 'block'; } else { passwordSection.style.display = 'none'; } } catch (error) { console.error('Load database config error:', error); dbConfig.innerHTML = '

无法加载配置信息

'; } } catch (error) { console.error('Load database info error:', error); } } // 刷新数据库信息 document.getElementById('refresh-db-btn')?.addEventListener('click', loadDatabaseInfo); // 转换数据库 document.getElementById('convert-db-btn')?.addEventListener('click', async () => { const targetType = document.getElementById('target-db-type').value; if (!confirm(`确定要转换数据库类型吗?\n\n目标类型: ${targetType.toUpperCase()}\n\n此操作会导出当前数据并导入到新数据库。请确保已备份数据!`)) { return; } const resultEl = document.getElementById('convert-result'); resultEl.className = ''; resultEl.textContent = '正在转换...'; resultEl.style.display = 'block'; try { const result = await apiRequest('/admin/api/database/convert', { method: 'POST', body: JSON.stringify({ target_type: targetType, }), }); resultEl.className = 'success'; resultEl.textContent = result.message || '数据库转换成功!'; loadDatabaseInfo(); loadLogs(); } catch (error) { resultEl.className = 'error'; resultEl.textContent = '转换失败: ' + error.message; } }); // 更新数据库密码 document.getElementById('password-form')?.addEventListener('submit', async (e) => { e.preventDefault(); const currentPassword = document.getElementById('current-password').value; const newPassword = document.getElementById('new-password').value; const confirmPassword = document.getElementById('confirm-password').value; if (newPassword !== confirmPassword) { alert('新密码和确认密码不一致!'); return; } if (!confirm('确定要更新数据库 root 密码吗?\n\n更新后需要修改环境变量 DB_PASSWORD 并重启服务器!')) { return; } const resultEl = document.getElementById('password-result'); resultEl.className = ''; resultEl.textContent = '正在更新密码...'; resultEl.style.display = 'block'; try { const result = await apiRequest('/admin/api/database/password', { method: 'POST', body: JSON.stringify({ current_password: currentPassword, new_password: newPassword, confirm_password: confirmPassword, }), }); resultEl.className = 'success'; resultEl.textContent = result.message || '密码更新成功!'; // 清空表单 document.getElementById('password-form').reset(); loadLogs(); } catch (error) { resultEl.className = 'error'; resultEl.textContent = '更新失败: ' + error.message; } }); // 加载系统信息 async function loadSystemInfo() { try { const data = await apiRequest('/admin/api/system'); document.getElementById('stat-users').textContent = data.users; document.getElementById('stat-routes').textContent = data.routes; document.getElementById('stat-logs').textContent = data.logs; document.getElementById('stat-time').textContent = data.server_time; } catch (error) { console.error('Load system info error:', error); } } // 加载路由 async function loadRoutes() { try { const data = await apiRequest('/admin/api/routes'); const tbody = document.getElementById('routes-table-body'); if (!tbody) return; tbody.innerHTML = ''; if (data.routes && data.routes.length > 0) { data.routes.forEach(route => { const tr = document.createElement('tr'); tr.innerHTML = ` ${route.id} ${route.method} ${route.path} ${route.type} ${route.description || '-'} ${route.is_active ? '✅' : '❌'} `; tbody.appendChild(tr); }); } else { tbody.innerHTML = '暂无路由'; } } catch (error) { console.error('Load routes error:', error); const tbody = document.getElementById('routes-table-body'); if (tbody) { tbody.innerHTML = '加载失败: ' + error.message + ''; } } } // 添加路由 document.getElementById('add-route-btn')?.addEventListener('click', () => { document.getElementById('route-modal-title').textContent = '添加路由'; document.getElementById('route-form').reset(); document.getElementById('route-id').value = ''; document.getElementById('route-modal').classList.add('show'); }); // 编辑路由 window.editRoute = async function(id) { try { const data = await apiRequest('/admin/api/routes'); const route = data.routes.find(r => r.id === id); if (route) { document.getElementById('route-modal-title').textContent = '编辑路由'; document.getElementById('route-id').value = route.id; document.getElementById('route-method').value = route.method; document.getElementById('route-path').value = route.path; document.getElementById('route-type').value = route.type; document.getElementById('route-handler').value = route.handler; document.getElementById('route-description').value = route.description || ''; document.getElementById('route-active').checked = route.is_active; document.getElementById('route-order').value = route.order; document.getElementById('route-modal').classList.add('show'); } } catch (error) { console.error('Edit route error:', error); } }; // 删除路由 window.deleteRoute = async function(id) { if (!confirm('确定要删除这个路由吗?')) return; try { await apiRequest(`/admin/api/routes/${id}`, { method: 'DELETE' }); loadRoutes(); } catch (error) { alert('删除失败: ' + error.message); } }; // 保存路由 document.getElementById('route-save-btn')?.addEventListener('click', async () => { const id = document.getElementById('route-id').value; const routeData = { method: document.getElementById('route-method').value, path: document.getElementById('route-path').value, type: document.getElementById('route-type').value, handler: document.getElementById('route-handler').value, description: document.getElementById('route-description').value, is_active: document.getElementById('route-active').checked, order: parseInt(document.getElementById('route-order').value) || 0, }; try { if (id) { await apiRequest(`/admin/api/routes/${id}`, { method: 'PUT', body: JSON.stringify(routeData), }); } else { await apiRequest('/admin/api/routes', { method: 'POST', body: JSON.stringify(routeData), }); } document.getElementById('route-modal').classList.remove('show'); loadRoutes(); } catch (error) { alert('保存失败: ' + error.message); } }); // 关闭模态框 document.querySelectorAll('.modal-close, #route-cancel-btn').forEach(btn => { btn.addEventListener('click', () => { document.getElementById('route-modal').classList.remove('show'); }); }); // 加载日志 async function loadLogs() { try { const data = await apiRequest('/admin/api/logs?limit=100'); const logsEl = document.getElementById('logs-content'); if (!logsEl) return; logsEl.innerHTML = ''; if (data.logs && data.logs.length > 0) { data.logs.forEach(log => { const entry = document.createElement('div'); entry.className = 'log-entry'; entry.innerHTML = ` ${log.time || ''} ${log.level || 'INFO'} ${log.message || ''} `; logsEl.appendChild(entry); }); logsEl.scrollTop = logsEl.scrollHeight; } else { logsEl.innerHTML = '
暂无日志
'; } } catch (error) { console.error('Load logs error:', error); const logsEl = document.getElementById('logs-content'); if (logsEl) { logsEl.innerHTML = '
加载失败: ' + error.message + '
'; } } } // 刷新日志 document.getElementById('refresh-logs-btn')?.addEventListener('click', loadLogs); // 清空日志 document.getElementById('clear-logs-btn')?.addEventListener('click', () => { const logsEl = document.getElementById('logs-content'); if (logsEl) { logsEl.innerHTML = '
日志已清空
'; } }); // 加载文件 async function loadFiles() { const fileBrowser = document.getElementById('file-browser'); if (!fileBrowser) return; try { const data = await apiRequest('/admin/api/files?dir=public/downloads'); fileBrowser.innerHTML = ''; if (data.files && data.files.length > 0) { data.files.forEach(file => { const item = document.createElement('div'); item.className = 'file-item'; item.innerHTML = `
${file.name} ${file.is_dir ? '📁 目录' : `📄 ${formatBytes(file.size)}`} • ${file.mod_time || ''}
${!file.is_dir ? `` : ''} `; fileBrowser.appendChild(item); }); } else { fileBrowser.innerHTML = '
暂无文件
'; } } catch (error) { console.error('Load files error:', error); fileBrowser.innerHTML = '
加载失败: ' + error.message + '
'; } } document.getElementById('refresh-files-btn')?.addEventListener('click', loadFiles); // 读取文件 window.readFile = async function(filename) { const fullPath = `public/downloads/${filename}`; try { const data = await apiRequest(`/admin/api/file?path=${encodeURIComponent(fullPath)}`); // 显示文件内容在模态框中或新窗口 const content = data.content.substring(0, 5000) + (data.content.length > 5000 ? '\n\n... (内容过长,已截断)' : ''); alert('文件内容:\n\n' + content); } catch (error) { alert('读取文件失败: ' + error.message); } }; // 加载配置 async function loadConfig() { const file = document.getElementById('config-select').value; try { const response = await fetch(`/public/${file}`); const data = await response.json(); document.getElementById('config-editor').value = JSON.stringify(data, null, 2); } catch (error) { console.error('Load config error:', error); } } document.getElementById('load-config-btn')?.addEventListener('click', loadConfig); // 保存配置 document.getElementById('save-config-btn')?.addEventListener('click', async () => { const file = document.getElementById('config-select').value; const content = document.getElementById('config-editor').value; try { const jsonData = JSON.parse(content); const result = await apiRequest('/admin/api/config', { method: 'PUT', body: JSON.stringify({ file: file, content: jsonData, reload: false, // 仅保存 }), }); alert(result.message || '配置保存成功!'); } catch (error) { alert('保存失败: ' + error.message); } }); // 保存并立即加载配置 document.getElementById('save-reload-config-btn')?.addEventListener('click', async () => { const file = document.getElementById('config-select').value; const content = document.getElementById('config-editor').value; try { const jsonData = JSON.parse(content); const result = await apiRequest('/admin/api/config', { method: 'PUT', body: JSON.stringify({ file: file, content: jsonData, reload: true, // 保存并立即加载 }), }); alert(result.message || '配置已保存并立即生效!'); // 刷新日志以显示加载信息 if (document.getElementById('page-logs')?.classList.contains('active')) { loadLogs(); } } catch (error) { alert('保存失败: ' + error.message); } }); // 工具函数 function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } // 页面加载时初始化 document.addEventListener('DOMContentLoaded', () => { loadCurrentUser(); loadSystemInfo(); // 定期更新系统信息 setInterval(loadSystemInfo, 30000); // 每30秒更新一次 });