init
This commit is contained in:
157
templates/admin_logs.html
Normal file
157
templates/admin_logs.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>日志审计 - 管理后台</title>
|
||||
<link rel="stylesheet" href="/static/css/ui-components.css?v=3">
|
||||
<link rel="stylesheet" href="/static/css/admin.css">
|
||||
</head>
|
||||
|
||||
<body class="admin-layout">
|
||||
<header class="admin-header">
|
||||
<div class="header-container">
|
||||
<a href="/admin/dashboard" class="brand">
|
||||
<span style="font-size: 1.5rem;">⚡</span> JieXi Admin
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="/admin/dashboard" class="nav-item">仪表板</a>
|
||||
<a href="/admin/users" class="nav-item">用户管理</a>
|
||||
<a href="/admin/apis" class="nav-item">接口管理</a>
|
||||
<a href="/admin/config" class="nav-item">系统配置</a>
|
||||
<a href="/admin/logs" class="nav-item active">日志审计</a>
|
||||
</nav>
|
||||
<div class="user-actions">
|
||||
<a href="/admin/profile" class="ui-btn ui-btn-secondary ui-btn-sm">账号设置</a>
|
||||
<button class="ui-btn ui-btn-secondary ui-btn-sm" onclick="logout()">退出登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-container">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">日志审计</h1>
|
||||
<div class="actions flex gap-2">
|
||||
<select id="platformFilter" class="ui-input" style="width: auto; margin-bottom: 0;"
|
||||
onchange="loadLogs(1)">
|
||||
<option value="">全部平台</option>
|
||||
<option value="douyin">抖音</option>
|
||||
<option value="tiktok">TikTok</option>
|
||||
<option value="bilibili">哔哩哔哩</option>
|
||||
</select>
|
||||
<select id="statusFilter" class="ui-input" style="width: auto; margin-bottom: 0;"
|
||||
onchange="loadLogs(1)">
|
||||
<option value="">全部状态</option>
|
||||
<option value="success">成功</option>
|
||||
<option value="failed">失败</option>
|
||||
<option value="queued">排队中</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui-card">
|
||||
<div class="table-container">
|
||||
<table id="logsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>时间</th>
|
||||
<th>用户ID</th>
|
||||
<th>IP地址</th>
|
||||
<th>平台</th>
|
||||
<th>视频链接</th>
|
||||
<th>状态</th>
|
||||
<th>响应时间</th>
|
||||
<th>错误信息</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="9" style="text-align: center; color: var(--text-muted);">加载中...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination mt-4 flex justify-between items-center" id="pagination"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="/static/js/ui-components.js"></script>
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
|
||||
async function loadLogs(page = 1) {
|
||||
const platform = document.getElementById('platformFilter').value;
|
||||
const status = document.getElementById('statusFilter').value;
|
||||
|
||||
let url = `/admin/api/logs?page=${page}&per_page=50`;
|
||||
if (platform) url += `&platform=${platform}`;
|
||||
if (status) url += `&status=${status}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const tbody = document.querySelector('#logsTable tbody');
|
||||
if (result.data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" style="text-align: center; padding: 2rem; color: var(--text-muted);">暂无日志记录</td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = result.data.map(log => {
|
||||
let badgeClass = 'badge-neutral';
|
||||
if (log.status === 'success') badgeClass = 'badge-success';
|
||||
else if (log.status === 'failed') badgeClass = 'badge-error';
|
||||
else if (log.status === 'queued') badgeClass = 'badge-warning';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>#${log.id}</td>
|
||||
<td class="text-sm text-muted">${new Date(log.created_at).toLocaleString('zh-CN')}</td>
|
||||
<td>${log.user_id || '<span class="text-muted">游客</span>'}</td>
|
||||
<td class="text-sm">${log.ip_address}</td>
|
||||
<td><span class="font-medium">${log.platform}</span></td>
|
||||
<td><button class="ui-btn ui-btn-secondary ui-btn-sm" onclick="copyUrl('${log.video_url}')" title="${log.video_url}">复制链接</button></td>
|
||||
<td><span class="badge ${badgeClass}">${log.status}</span></td>
|
||||
<td class="text-sm">${log.response_time ? (log.response_time / 1000).toFixed(2) + 's' : '-'}</td>
|
||||
<td class="text-sm text-muted" style="max-width: 150px; overflow: hidden; text-overflow: ellipsis;" title="${log.error_message || ''}">${log.error_message || '-'}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = `
|
||||
<button class="ui-btn ui-btn-secondary ui-btn-sm" ${page === 1 ? 'disabled' : ''} onclick="loadLogs(${page - 1})">上一页</button>
|
||||
<span class="text-sm text-muted">第 ${page} / ${result.pagination.pages} 页 (共 ${result.pagination.total} 条)</span>
|
||||
<button class="ui-btn ui-btn-secondary ui-btn-sm" ${page === result.pagination.pages ? 'disabled' : ''} onclick="loadLogs(${page + 1})">下一页</button>
|
||||
`;
|
||||
currentPage = page;
|
||||
}
|
||||
} catch (error) {
|
||||
UI.notify('加载失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function copyUrl(url) {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
UI.notify('链接已复制', 'success');
|
||||
}).catch(() => {
|
||||
UI.notify('复制失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await fetch('/admin/logout', { method: 'POST' });
|
||||
window.location.href = '/admin/login';
|
||||
} catch (error) {
|
||||
UI.notify('退出失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
loadLogs();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user