Files
daily-paper/app/templates/admin_dashboard.html
T
Rain-Bus c94ff48254 fix: PDF extraction bbox compatibility, update date formats, and bump max retries
- Fix bbox format detection in pdf_image_extractor (support Rect and tuple)
- Update date display format to include year (%Y-%m-%d) across templates
- Increase SUMMARY_MAX_RETRIES from 1 to 2 for better error recovery
- Widen date input field for better usability
2026-06-09 18:30:04 +08:00

198 lines
8.6 KiB
HTML
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.
{% extends "base.html" %}
{% block title %}管理仪表盘 — HF Daily Papers{% endblock %}
{% block content %}
<div class="admin-page">
{% set active = "dashboard" %}{% include "partials/admin_subnav.html" %}
<h1 class="page-heading">📊 系统状态</h1>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">{{ stats.total_papers }}</div>
<div class="stat-label">论文总数</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ stats.today_papers }}</div>
<div class="stat-label">今日新增</div>
</div>
<div class="stat-card">
<div class="stat-value {% if stats.pending_count > 0 %}stat-warn{% endif %}">
{{ stats.pending_count + stats.none_count }}
</div>
<div class="stat-label">待总结</div>
</div>
<div class="stat-card">
<div class="stat-value {% if stats.failed_count > 0 %}stat-danger{% endif %}">
{{ stats.failed_count }}
</div>
<div class="stat-label">总结失败</div>
</div>
</div>
<div class="admin-quick-actions">
<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>
<button class="admin-action-btn" onclick="refreshUpvotes()">👍 刷新投票</button>
</div>
<div class="admin-info-grid">
<div class="admin-info-card">
<h2 class="admin-info-title">🕐 调度器</h2>
<div class="admin-info-body">
<div class="info-row">
<span class="info-label">状态</span>
<span class="info-value">
{% if stats.scheduler_enabled %}
<span class="status-dot status-dot-on"></span> 运行中
{% else %}
<span class="status-dot status-dot-off"></span> 未启用
{% endif %}
</span>
</div>
<div class="info-row">
<span class="info-label">调度时间</span>
<span class="info-value">{{ stats.schedule_time }}{{ stats.timezone }}</span>
</div>
{% if stats.next_run %}
<div class="info-row">
<span class="info-label">下次执行</span>
<span class="info-value">{{ stats.next_run[:19] | replace('T', ' ') }}</span>
</div>
{% endif %}
<div class="info-row">
<span class="info-label">投票刷新</span>
<span class="info-value">每日自动刷新最近 {{ stats.upvote_refresh_days | default(7) }} 天</span>
</div>
{% if stats.active_locks %}
<div class="info-row">
<span class="info-label">活跃任务</span>
<span class="info-value">
{% for lock in stats.active_locks %}
<span class="task-badge task-{{ lock.task }}">{{ lock.task }}</span>
{% endfor %}
</span>
</div>
{% endif %}
<div class="info-row">
<span class="info-label"></span>
<button class="admin-action-btn admin-action-btn-sm" onclick="triggerPipeline()">
▶ 立即执行流水线
</button>
</div>
</div>
<div class="scheduler-history">
<h3 class="section-subtitle">执行历史</h3>
{% if scheduler_history %}
<div class="admin-table-wrap">
<table class="admin-table admin-table-compact">
<thead>
<tr><th>时间</th><th>状态</th><th>发现</th><th>新增</th><th>错误</th></tr>
</thead>
<tbody>
{% for log in scheduler_history %}
<tr>
<td class="time-cell">{{ log.started_at.strftime('%Y-%m-%d %H:%M') if log.started_at else '-' }}</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.papers_found or 0 }}</td>
<td>{{ log.papers_new or 0 }}</td>
<td class="error-cell" title="{{ log.error or '' }}">
{{ (log.error[:50] + '...') if log.error and log.error|length > 50 else (log.error or '-') }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="hint">暂无调度器执行记录。</p>
{% endif %}
</div>
</div>
<div class="admin-info-card">
<h2 class="admin-info-title">💾 存储概况</h2>
<div class="admin-info-body">
<div class="info-row"><span class="info-label">数据库</span><span class="info-value">{{ stats.db_size }}</span></div>
<div class="info-row"><span class="info-label">论文文件</span><span class="info-value">{{ stats.papers_size }}</span></div>
<div class="info-row"><span class="info-label">临时文件</span><span class="info-value">{{ stats.tmp_size }}</span></div>
</div>
<div class="summary-dist">
<h3 class="section-subtitle">总结状态分布</h3>
<div class="summary-dist-bars">
{% set total = stats.total_papers or 1 %}
{% set labels = {"done": "已完成", "pending": "待总结", "running": "运行中", "processing": "处理中", "failed": "失败", "permanent_failure": "永久失败", "none": "未开始"} %}
{% for st, cnt in stats.status_counts.items() %}
{% if cnt > 0 %}
<div class="dist-row">
<span class="dist-label">{{ labels.get(st, st) }}</span>
<div class="dist-bar-wrap"><div class="dist-bar dist-bar-{{ st }}" style="width: {{ (cnt / total * 100)|round(1) }}%"></div></div>
<span class="dist-count">{{ cnt }}</span>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
<div class="admin-section">
<h2 class="admin-section-title">📋 最近活动</h2>
{% if stats.recent_logs %}
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr><th>任务</th><th>状态</th><th>日期</th><th>发现</th><th>新增</th><th>开始时间</th><th>完成时间</th><th>错误</th></tr>
</thead>
<tbody>
{% for log in stats.recent_logs %}
<tr>
<td><span class="task-badge task-{{ log.task }}">{{ log.task }}</span></td>
<td><span class="status-badge status-{{ log.status }}">
{# djlint:off #}
{% if log.status == 'success' %}✓ 成功{% elif log.status == 'running' %}⟳ 运行中{% elif log.status == 'failed' %}✗ 失败{% else %}{{ log.status }}{% endif %}
{# djlint:on #}
</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('%Y-%m-%d %H:%M') if log.started_at else '-' }}</td>
<td class="time-cell">{{ log.completed_at.strftime('%Y-%m-%d %H:%M') if log.completed_at else '-' }}</td>
<td class="error-cell" title="{{ log.error or '' }}">
{{ (log.error[:60] + '...') if log.error and log.error|length > 60 else (log.error or '-') }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="empty-state">
<p>暂无活动日志</p>
<p class="hint">通过快捷操作触发任务后,日志将出现在这里。</p>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function triggerPipeline() {
fetch("/admin/trigger-pipeline", { method: "POST", headers: { "Content-Type": "application/json" } })
.then(r => { if (r.status===303||r.status===401) { window.location.href="/admin/login"; return; } return r.json(); })
.then(data => { if (data) showToast(data.error ? "❌ " + data.error.substring(0,200) : "✅ 流水线已触发"); })
.catch(err => showToast("❌ 请求失败"));
}
function refreshUpvotes() {
fetch("/admin/refresh-upvotes", { method: "POST", headers: { "Content-Type": "application/json" } })
.then(r => { if (r.status===303||r.status===401) { window.location.href="/admin/login"; return; } return r.json(); })
.then(data => { if (data) showToast(data.error ? "❌ " + data.error.substring(0,200) : `✅ 已刷新 ${data.updated || 0} 篇论文投票`); })
.catch(err => showToast("❌ 请求失败"));
}
</script>
{% endblock %}