paper-burner/admin/index.html.backup

338 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paper Burner X - 管理面板</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-gray-100">
<!-- 登录页面 -->
<div id="loginPage" class="min-h-screen flex items-center justify-center">
<div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
<h1 class="text-2xl font-bold mb-6 text-center">Paper Burner X 管理面板</h1>
<form id="loginForm" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">邮箱</label>
<input type="email" id="email" required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700">密码</label>
<input type="password" id="password" required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<button type="submit"
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
登录
</button>
</form>
<div id="loginError" class="mt-4 text-red-600 text-sm hidden"></div>
</div>
</div>
<!-- 管理面板主界面 -->
<div id="adminPanel" class="hidden min-h-screen">
<!-- 顶部导航 -->
<nav class="bg-white shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<h1 class="text-xl font-bold">Paper Burner X 管理面板</h1>
</div>
<div class="flex items-center space-x-4">
<span id="adminName" class="text-gray-700"></span>
<button onclick="logout()"
class="bg-gray-200 px-4 py-2 rounded-md hover:bg-gray-300">
退出登录
</button>
</div>
</div>
</div>
</nav>
<!-- 主内容区 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 统计卡片 -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div class="bg-white p-6 rounded-lg shadow">
<div class="text-gray-500 text-sm">总用户数</div>
<div id="totalUsers" class="text-3xl font-bold mt-2">0</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="text-gray-500 text-sm">活跃用户</div>
<div id="activeUsers" class="text-3xl font-bold mt-2">0</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="text-gray-500 text-sm">总文档数</div>
<div id="totalDocuments" class="text-3xl font-bold mt-2">0</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="text-gray-500 text-sm">今日处理</div>
<div id="documentsToday" class="text-3xl font-bold mt-2">0</div>
</div>
</div>
<!-- 选项卡导航 -->
<div class="bg-white rounded-lg shadow mb-4">
<div class="border-b border-gray-200">
<nav class="flex -mb-px">
<button onclick="switchTab('users')" id="tab-users"
class="tab-button px-6 py-3 border-b-2 border-blue-500 font-medium text-blue-600">
用户管理
</button>
<button onclick="switchTab('models')" id="tab-models"
class="tab-button px-6 py-3 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300">
模型配置
</button>
<button onclick="switchTab('system')" id="tab-system"
class="tab-button px-6 py-3 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300">
系统设置
</button>
</nav>
</div>
<!-- 用户管理 -->
<div id="content-users" class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">邮箱</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">姓名</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">角色</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">注册时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">操作</th>
</tr>
</thead>
<tbody id="usersList" class="bg-white divide-y divide-gray-200">
<!-- 用户列表将在这里动态加载 -->
</tbody>
</table>
</div>
</div>
<!-- 模型配置 -->
<div id="content-models" class="p-6 hidden">
<div class="mb-4">
<button onclick="addSourceSite()"
class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
添加自定义源站
</button>
</div>
<div id="sourceSitesList" class="space-y-4">
<!-- 源站配置将在这里动态加载 -->
</div>
</div>
<!-- 系统设置 -->
<div id="content-system" class="p-6 hidden">
<div class="space-y-6">
<div>
<h3 class="text-lg font-medium mb-4">系统配置</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">允许用户注册</label>
<select id="allowRegistration"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md">
<option value="true">是</option>
<option value="false">否</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">最大上传大小MB</label>
<input type="number" id="maxUploadSize" value="100"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md">
</div>
<button onclick="saveSystemSettings()"
class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
保存设置
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const API_BASE = window.location.origin + '/api';
let authToken = localStorage.getItem('admin_token');
// 初始化
document.addEventListener('DOMContentLoaded', () => {
if (authToken) {
checkAuth();
} else {
showLoginPage();
}
});
// 登录
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
try {
const response = await axios.post(`${API_BASE}/auth/login`, {
email,
password
});
if (response.data.success && response.data.user.role === 'ADMIN') {
authToken = response.data.token;
localStorage.setItem('admin_token', authToken);
showAdminPanel(response.data.user);
} else {
showError('您没有管理员权限');
}
} catch (error) {
showError('登录失败:' + (error.response?.data?.error || error.message));
}
});
function showError(message) {
const errorDiv = document.getElementById('loginError');
errorDiv.textContent = message;
errorDiv.classList.remove('hidden');
setTimeout(() => errorDiv.classList.add('hidden'), 5000);
}
async function checkAuth() {
try {
const response = await axios.get(`${API_BASE}/auth/me`, {
headers: { Authorization: `Bearer ${authToken}` }
});
if (response.data.user.role === 'ADMIN') {
showAdminPanel(response.data.user);
} else {
logout();
}
} catch (error) {
logout();
}
}
function showLoginPage() {
document.getElementById('loginPage').classList.remove('hidden');
document.getElementById('adminPanel').classList.add('hidden');
}
async function showAdminPanel(user) {
document.getElementById('loginPage').classList.add('hidden');
document.getElementById('adminPanel').classList.remove('hidden');
// 使用 textContent 而不是 innerHTML 来防止 XSS
const adminNameElement = document.getElementById('adminName');
adminNameElement.textContent = user.name || user.email;
await loadStats();
await loadUsers();
}
async function loadStats() {
try {
const response = await axios.get(`${API_BASE}/admin/stats`, {
headers: { Authorization: `Bearer ${authToken}` }
});
const stats = response.data;
document.getElementById('totalUsers').textContent = stats.totalUsers;
document.getElementById('activeUsers').textContent = stats.activeUsers;
document.getElementById('totalDocuments').textContent = stats.totalDocuments;
document.getElementById('documentsToday').textContent = stats.documentsToday;
} catch (error) {
console.error('Failed to load stats:', error);
}
}
// 辅助函数:安全地转义 HTML 以防止 XSS 攻击
function escapeHtml(text) {
if (!text) return '-';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async function loadUsers() {
try {
const response = await axios.get(`${API_BASE}/admin/users`, {
headers: { Authorization: `Bearer ${authToken}` }
});
const usersList = document.getElementById('usersList');
usersList.innerHTML = response.data.map(user => `
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm">${escapeHtml(user.email)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${escapeHtml(user.name)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${escapeHtml(user.role)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<span class="px-2 py-1 rounded-full text-xs ${user.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
${user.isActive ? '活跃' : '禁用'}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${new Date(user.createdAt).toLocaleDateString()}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button onclick="toggleUserStatus('${escapeHtml(user.id)}', ${!user.isActive})"
class="text-blue-600 hover:text-blue-900">
${user.isActive ? '禁用' : '启用'}
</button>
</td>
</tr>
`).join('');
} catch (error) {
console.error('Failed to load users:', error);
}
}
async function toggleUserStatus(userId, isActive) {
try {
await axios.put(`${API_BASE}/admin/users/${userId}/status`,
{ isActive },
{ headers: { Authorization: `Bearer ${authToken}` } }
);
await loadUsers();
} catch (error) {
alert('操作失败:' + error.message);
}
}
function switchTab(tab) {
// 更新选项卡样式
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('border-blue-500', 'text-blue-600');
btn.classList.add('border-transparent', 'text-gray-500');
});
document.getElementById(`tab-${tab}`).classList.add('border-blue-500', 'text-blue-600');
document.getElementById(`tab-${tab}`).classList.remove('border-transparent', 'text-gray-500');
// 切换内容
['users', 'models', 'system'].forEach(t => {
document.getElementById(`content-${t}`).classList.toggle('hidden', t !== tab);
});
}
function logout() {
localStorage.removeItem('admin_token');
authToken = null;
showLoginPage();
}
// 占位函数
function addSourceSite() {
alert('自定义源站管理功能开发中...');
}
function saveSystemSettings() {
alert('系统设置保存功能开发中...');
}
</script>
<script src="admin-enhanced.js"></script>
</body>
</html>