feat: overhaul UI styling, improve templates, enhance services and tests
This commit is contained in:
+161
-77
@@ -1,8 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}管理日志 — HF Daily Papers{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% extends "base.html" %} {% block title %}管理日志 — HF Daily Papers{% endblock
|
||||
%} {% block content %}
|
||||
<div class="admin-logs-page">
|
||||
<h1 class="page-heading">📋 管理日志</h1>
|
||||
|
||||
@@ -34,21 +31,31 @@
|
||||
{% 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="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 %}
|
||||
{% 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>
|
||||
<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>
|
||||
@@ -90,15 +97,23 @@
|
||||
<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 %}
|
||||
{% 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>
|
||||
<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>
|
||||
@@ -116,16 +131,24 @@
|
||||
<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>
|
||||
<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-logs-page {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.admin-tabs {
|
||||
display: flex;
|
||||
@@ -144,22 +167,32 @@
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
transition:
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.admin-tab:hover { color: var(--accent); }
|
||||
.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; }
|
||||
.admin-tab-content {
|
||||
display: none;
|
||||
}
|
||||
.admin-tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Table ─────────────────────────────────────────────────────── */
|
||||
.admin-table-wrap { overflow-x: auto; }
|
||||
.admin-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
@@ -187,14 +220,29 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.admin-table tbody tr:hover { background: var(--bg); }
|
||||
.admin-table tbody tr:last-child td { border-bottom: none; }
|
||||
.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; }
|
||||
.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 {
|
||||
.task-badge,
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
@@ -202,15 +250,39 @@
|
||||
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; }
|
||||
.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; }
|
||||
.status-success {
|
||||
background: #e8f5e9;
|
||||
color: #388e3c;
|
||||
}
|
||||
.status-running {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
.status-failed {
|
||||
background: #fce4ec;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
/* ── Admin Actions ─────────────────────────────────────────────── */
|
||||
.admin-actions {
|
||||
@@ -254,46 +326,58 @@
|
||||
|
||||
/* ── 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; }
|
||||
.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 %}
|
||||
{% endblock %} {% block scripts %}
|
||||
<script>
|
||||
function adminAction(action) {
|
||||
const token = prompt('请输入 Admin Token:');
|
||||
if (!token) return;
|
||||
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);
|
||||
});
|
||||
}
|
||||
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');
|
||||
// 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 %}
|
||||
|
||||
Reference in New Issue
Block a user