Files
YMhut-box-C-/server/update/public/js/admin.js
T
QWQLwToo 079ee4eaeb
build-winui / winui (push) Has been cancelled
Add server components
2026-06-26 13:28:09 +08:00

533 lines
19 KiB
JavaScript

// 后台管理 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 = `
<p><strong>类型:</strong> ${data.database.type}</p>
<p><strong>状态:</strong> ${data.database.status}</p>
${data.database.file ? `<p><strong>文件:</strong> ${data.database.file}</p>` : ''}
${data.database.cgo_support !== undefined ? `<p><strong>CGO 支持:</strong> ${data.database.cgo_support ? '是' : '否'}</p>` : ''}
`;
osDetails.innerHTML = `
<p><strong>操作系统:</strong> ${data.os.os}</p>
<p><strong>架构:</strong> ${data.os.arch}</p>
`;
// 加载数据库配置
try {
const configData = await apiRequest('/admin/api/database/config');
dbConfig.innerHTML = `
<p><strong>类型:</strong> ${configData.config.type}</p>
<p><strong>主机:</strong> ${configData.config.host}</p>
<p><strong>端口:</strong> ${configData.config.port}</p>
<p><strong>用户:</strong> ${configData.config.user}</p>
<p><strong>数据库:</strong> ${configData.config.database}</p>
<p><strong>已设置密码:</strong> ${configData.config.has_password ? '是' : '否'}</p>
`;
// 如果是 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 = '<p>无法加载配置信息</p>';
}
} 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 = `
<td>${route.id}</td>
<td><span class="badge">${route.method}</span></td>
<td>${route.path}</td>
<td>${route.type}</td>
<td>${route.description || '-'}</td>
<td>${route.is_active ? '✅' : '❌'}</td>
<td>
<button class="btn btn-secondary" onclick="editRoute(${route.id})">编辑</button>
<button class="btn btn-danger" onclick="deleteRoute(${route.id})">删除</button>
</td>
`;
tbody.appendChild(tr);
});
} else {
tbody.innerHTML = '<tr><td colspan="7" class="empty-state">暂无路由</td></tr>';
}
} catch (error) {
console.error('Load routes error:', error);
const tbody = document.getElementById('routes-table-body');
if (tbody) {
tbody.innerHTML = '<tr><td colspan="7" class="empty-state">加载失败: ' + error.message + '</td></tr>';
}
}
}
// 添加路由
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 = `
<span class="log-time">${log.time || ''}</span>
<span class="log-level ${log.level || 'INFO'}">${log.level || 'INFO'}</span>
<span class="log-message">${log.message || ''}</span>
`;
logsEl.appendChild(entry);
});
logsEl.scrollTop = logsEl.scrollHeight;
} else {
logsEl.innerHTML = '<div class="empty-state">暂无日志</div>';
}
} catch (error) {
console.error('Load logs error:', error);
const logsEl = document.getElementById('logs-content');
if (logsEl) {
logsEl.innerHTML = '<div class="empty-state">加载失败: ' + error.message + '</div>';
}
}
}
// 刷新日志
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 = '<div class="empty-state">日志已清空</div>';
}
});
// 加载文件
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 = `
<div>
<strong>${file.name}</strong>
<small style="display: block; color: #86868b;">
${file.is_dir ? '📁 目录' : `📄 ${formatBytes(file.size)}`}${file.mod_time || ''}
</small>
</div>
${!file.is_dir ? `<button class="btn btn-secondary" onclick="readFile('${file.name}')">查看</button>` : ''}
`;
fileBrowser.appendChild(item);
});
} else {
fileBrowser.innerHTML = '<div class="empty-state">暂无文件</div>';
}
} catch (error) {
console.error('Load files error:', error);
fileBrowser.innerHTML = '<div class="empty-state">加载失败: ' + error.message + '</div>';
}
}
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秒更新一次
});