384 lines
10 KiB
HTML
384 lines
10 KiB
HTML
{% extends "base.html" %} {% block title %}管理日志 — HF Daily Papers{% endblock
|
|
%} {% block content %}
|
|
<div class="admin-logs-page">
|
|
<h1 class="page-heading">📋 管理日志</h1>
|
|
|
|
<!-- Tab 切换 -->
|
|
<div class="admin-tabs">
|
|
<button class="admin-tab active" data-tab="crawl-logs">抓取日志</button>
|
|
<button class="admin-tab" data-tab="delete-jobs">删除记录</button>
|
|
</div>
|
|
|
|
<!-- 抓取日志 Tab -->
|
|
<div class="admin-tab-content active" id="crawl-logs">
|
|
{% if crawl_logs %}
|
|
<div class="admin-table-wrap">
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>任务</th>
|
|
<th>状态</th>
|
|
<th>日期</th>
|
|
<th>发现</th>
|
|
<th>新增</th>
|
|
<th>开始时间</th>
|
|
<th>完成时间</th>
|
|
<th>错误</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for log in crawl_logs %}
|
|
<tr>
|
|
<td>{{ log.id }}</td>
|
|
<td>
|
|
<span class="task-badge task-{{ log.task }}">{{ log.task }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="status-badge status-{{ log.status }}">
|
|
{% if log.status == 'success' %}✓ 成功 {% elif log.status ==
|
|
'running' %}⟳ 运行中 {% elif log.status == 'failed' %}✗ 失败 {%
|
|
else %}{{ log.status }}{% endif %}
|
|
</span>
|
|
</td>
|
|
<td>{{ log.date or '-' }}</td>
|
|
<td>{{ log.papers_found or 0 }}</td>
|
|
<td>{{ log.papers_new or 0 }}</td>
|
|
<td class="time-cell">
|
|
{{ log.started_at.strftime('%m-%d %H:%M') if log.started_at else
|
|
'-' }}
|
|
</td>
|
|
<td class="time-cell">
|
|
{{ log.completed_at.strftime('%m-%d %H:%M') if log.completed_at
|
|
else '-' }}
|
|
</td>
|
|
<td class="error-cell" title="{{ log.error or '' }}">
|
|
{{ log.error[:80] + '...' if log.error and log.error|length > 80
|
|
else (log.error or '-') }}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<p>暂无抓取日志</p>
|
|
<p class="hint">通过管理接口触发抓取或总结后,日志将出现在这里。</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- 删除记录 Tab -->
|
|
<div class="admin-tab-content" id="delete-jobs">
|
|
{% if delete_jobs %}
|
|
<div class="admin-table-wrap">
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>起始日期</th>
|
|
<th>结束日期</th>
|
|
<th>包含笔记</th>
|
|
<th>论文数</th>
|
|
<th>状态</th>
|
|
<th>开始时间</th>
|
|
<th>完成时间</th>
|
|
<th>错误</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for job in delete_jobs %}
|
|
<tr>
|
|
<td>{{ job.id }}</td>
|
|
<td>{{ job.date_start }}</td>
|
|
<td>{{ job.date_end }}</td>
|
|
<td>{{ '是' if job.include_notes else '否' }}</td>
|
|
<td>{{ job.paper_count or 0 }}</td>
|
|
<td>
|
|
<span class="status-badge status-{{ job.status }}">
|
|
{% if job.status == 'success' %}✓ 成功 {% elif job.status ==
|
|
'running' %}⟳ 运行中 {% elif job.status == 'failed' %}✗ 失败 {%
|
|
else %}{{ job.status }}{% endif %}
|
|
</span>
|
|
</td>
|
|
<td class="time-cell">
|
|
{{ job.started_at.strftime('%m-%d %H:%M') if job.started_at else
|
|
'-' }}
|
|
</td>
|
|
<td class="time-cell">
|
|
{{ job.completed_at.strftime('%m-%d %H:%M') if job.completed_at
|
|
else '-' }}
|
|
</td>
|
|
<td class="error-cell" title="{{ job.error or '' }}">
|
|
{{ job.error[:80] + '...' if job.error and job.error|length > 80
|
|
else (job.error or '-') }}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<p>暂无删除记录</p>
|
|
<p class="hint">通过管理接口删除论文后,记录将出现在这里。</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- 管理操作区 -->
|
|
<div class="admin-actions">
|
|
<h2 class="admin-actions-title">管理操作</h2>
|
|
<div class="admin-action-buttons">
|
|
<button class="admin-action-btn" onclick="adminAction('crawl')">
|
|
🔄 抓取今天
|
|
</button>
|
|
<button class="admin-action-btn" onclick="adminAction('summarize')">
|
|
📝 批量总结
|
|
</button>
|
|
<button class="admin-action-btn" onclick="adminAction('cleanup')">
|
|
🧹 清理临时文件
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* ── Admin Logs ────────────────────────────────────────────────── */
|
|
.admin-logs-page {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.admin-tabs {
|
|
display: flex;
|
|
gap: 0;
|
|
border-bottom: 2px solid var(--border);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.admin-tab {
|
|
padding: 10px 24px;
|
|
border: none;
|
|
background: none;
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
color: var(--ink-light);
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -2px;
|
|
transition:
|
|
color 0.2s,
|
|
border-color 0.2s;
|
|
font-family: var(--font-sans);
|
|
}
|
|
|
|
.admin-tab:hover {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.admin-tab.active {
|
|
color: var(--accent);
|
|
border-bottom-color: var(--accent);
|
|
}
|
|
|
|
.admin-tab-content {
|
|
display: none;
|
|
}
|
|
.admin-tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
/* ── Table ─────────────────────────────────────────────────────── */
|
|
.admin-table-wrap {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.admin-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.85rem;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.admin-table th {
|
|
padding: 10px 12px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
color: var(--ink-light);
|
|
background: var(--bg);
|
|
border-bottom: 1px solid var(--border);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.admin-table td {
|
|
padding: 8px 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
color: var(--ink);
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.admin-table tbody tr:hover {
|
|
background: var(--bg);
|
|
}
|
|
.admin-table tbody tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.time-cell {
|
|
white-space: nowrap;
|
|
color: var(--ink-light);
|
|
}
|
|
.error-cell {
|
|
max-width: 200px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: #c62828;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* ── Badges ────────────────────────────────────────────────────── */
|
|
.task-badge,
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 3px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.task-crawl {
|
|
background: #e3f2fd;
|
|
color: #1565c0;
|
|
}
|
|
.task-summarize {
|
|
background: #f3e5f5;
|
|
color: #7b1fa2;
|
|
}
|
|
.task-cleanup {
|
|
background: #e8f5e9;
|
|
color: #2e7d32;
|
|
}
|
|
.task-delete {
|
|
background: #fce4ec;
|
|
color: #c62828;
|
|
}
|
|
.task-scheduler {
|
|
background: #fff3e0;
|
|
color: #e65100;
|
|
}
|
|
|
|
.status-success {
|
|
background: #e8f5e9;
|
|
color: #388e3c;
|
|
}
|
|
.status-running {
|
|
background: #e3f2fd;
|
|
color: #1976d2;
|
|
}
|
|
.status-failed {
|
|
background: #fce4ec;
|
|
color: #c62828;
|
|
}
|
|
|
|
/* ── Admin Actions ─────────────────────────────────────────────── */
|
|
.admin-actions {
|
|
margin-top: 32px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.admin-actions-title {
|
|
font-family: var(--font-body);
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin-bottom: 12px;
|
|
color: var(--ink);
|
|
}
|
|
|
|
.admin-action-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.admin-action-btn {
|
|
padding: 8px 18px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
color: var(--ink);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
font-family: var(--font-sans);
|
|
}
|
|
|
|
.admin-action-btn:hover {
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
box-shadow: 0 2px 8px var(--shadow);
|
|
}
|
|
|
|
/* ── Responsive ────────────────────────────────────────────────── */
|
|
@media (max-width: 640px) {
|
|
.admin-table {
|
|
font-size: 0.8rem;
|
|
}
|
|
.admin-table th,
|
|
.admin-table td {
|
|
padding: 6px 8px;
|
|
}
|
|
.admin-action-buttons {
|
|
flex-direction: column;
|
|
}
|
|
.admin-action-btn {
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %} {% block scripts %}
|
|
<script>
|
|
function adminAction(action) {
|
|
const token = prompt("请输入 Admin Token:");
|
|
if (!token) return;
|
|
|
|
const url = "/admin/" + action;
|
|
fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer " + token,
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((r) => r.json())
|
|
.then((data) => {
|
|
alert(JSON.stringify(data, null, 2));
|
|
location.reload();
|
|
})
|
|
.catch((err) => {
|
|
alert("请求失败: " + err.message);
|
|
});
|
|
}
|
|
|
|
// Tab 切换
|
|
document.querySelectorAll(".admin-tab").forEach((tab) => {
|
|
tab.addEventListener("click", () => {
|
|
document
|
|
.querySelectorAll(".admin-tab")
|
|
.forEach((t) => t.classList.remove("active"));
|
|
document
|
|
.querySelectorAll(".admin-tab-content")
|
|
.forEach((c) => c.classList.remove("active"));
|
|
tab.classList.add("active");
|
|
document.getElementById(tab.dataset.tab).classList.add("active");
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|