Commit 2cbef105 authored by 郭阔's avatar 郭阔

ui(schedules): 上次运行徽标可点击,展开交付详情抽屉

- 徽标(绿/黄/红)带 ▾ 可点击,行下展开:成功 N 篇(标题链接原文)+
  失败 M 篇(标题+[失败类型] 原因)
- 抽屉互斥(排程/详情同时只开一个);数据复用 _latest_run_articles 助手
  (最近抓取页共用);修复 helper 误插装饰器与函数之间导致的 500
parent e5c5cc17
...@@ -1306,6 +1306,23 @@ def guide(): ...@@ -1306,6 +1306,23 @@ def guide():
return render_template("guide.html", body=body, raw=text) return render_template("guide.html", body=body, raw=text)
def _latest_run_articles(name):
"""源最近一次运行的文章明细(标题/链接/状态/失败原因),供最近抓取页与调度中心详情抽屉共用。"""
runs = get_history_runs(name, include_data=True)
if not runs:
return []
biz, _legacy = get_business_result(runs[0]["data"])
if not biz:
return []
return [{
"title": a.get("title") or a.get("url", ""),
"url": a.get("url", ""),
"status": a.get("status", ""),
"failure_type": a.get("failure_type") or "",
"failure_reason": a.get("failure_reason") or "",
} for a in biz.get("articles", [])]
@app.route("/latest") @app.route("/latest")
@require_auth @require_auth
def latest_content(): def latest_content():
...@@ -1426,6 +1443,9 @@ def schedule_board(): ...@@ -1426,6 +1443,9 @@ def schedule_board():
buckets[w].pop(0) if buckets.get(w) else "" buckets[w].pop(0) if buckets.get(w) else ""
for w in r["intent"]["runs"] for w in r["intent"]["runs"]
] ]
# 上次运行文章明细(点击徽标展开的详情抽屉用)
for r in rows:
r["last_articles"] = _latest_run_articles(r["name"])
return render_template("schedules.html", rows=rows, now=now, return render_template("schedules.html", rows=rows, now=now,
digest_next=_next_fire(digest_times, now), digest_next=_next_fire(digest_times, now),
digest_times=digest_times, digest_times=digest_times,
......
...@@ -9,8 +9,8 @@ function toggleRuns(sel) { ...@@ -9,8 +9,8 @@ function toggleRuns(sel) {
sp.style.display = parseInt(sp.dataset.i, 10) <= f ? '' : 'none'; sp.style.display = parseInt(sp.dataset.i, 10) <= f ? '' : 'none';
}); });
} }
function toggleEdit(name) { function toggleDrawer(id) {
var d = document.getElementById('edit-' + name); var d = document.getElementById(id);
var open = d.style.display !== 'none'; var open = d.style.display !== 'none';
document.querySelectorAll('tr.drawer').forEach(function (x) { x.style.display = 'none'; }); document.querySelectorAll('tr.drawer').forEach(function (x) { x.style.display = 'none'; });
if (!open) { d.style.display = ''; } if (!open) { d.style.display = ''; }
...@@ -36,6 +36,12 @@ function toggleEdit(name) { ...@@ -36,6 +36,12 @@ function toggleEdit(name) {
.tchip { background:#1e2230; border:1px solid #2a2d37; border-radius:4px; padding:2px 10px; font-family:monospace; color:#d4d4d4; } .tchip { background:#1e2230; border:1px solid #2a2d37; border-radius:4px; padding:2px 10px; font-family:monospace; color:#d4d4d4; }
.decho { color:#e8c34a; font-family:monospace; min-width:44px; } .decho { color:#e8c34a; font-family:monospace; min-width:44px; }
.dnote { color:#666; font-size:12px; margin-left:12px; } .dnote { color:#666; font-size:12px; margin-left:12px; }
.linkbadge { cursor:pointer; }
.linkbadge:hover { filter:brightness(1.3); text-decoration:underline dotted; }
ul.arts { margin:2px 0 8px 28px; padding:0; list-style:none; }
ul.arts li { padding:3px 0; display:flex; gap:8px; align-items:baseline; flex-wrap:wrap; }
ul.arts li .t { flex:1 1 auto; min-width:220px; }
.hint { color:#888; font-size:12px; margin:6px 0 2px; }
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
...@@ -108,14 +114,14 @@ function toggleEdit(name) { ...@@ -108,14 +114,14 @@ function toggleEdit(name) {
{{ r.last.mtime }}<br> {{ r.last.mtime }}<br>
{% set st = r.last.status or '' %} {% set st = r.last.status or '' %}
{% if '熔断' in st %}<span class="badge unknown">{{ st }}</span> {% if '熔断' in st %}<span class="badge unknown">{{ st }}</span>
{% elif '部分交付' in st or '⚠' in st %}<span class="badge warn">{{ st }}</span> {% elif '部分交付' in st or '⚠' in st %}<span class="badge warn linkbadge" onclick="toggleDrawer('last-{{ r.name }}')">{{ st }} ▾</span>
{% elif '成功' in st %}<span class="badge ok">{{ st }}</span> {% elif '成功' in st %}<span class="badge ok linkbadge" onclick="toggleDrawer('last-{{ r.name }}')">{{ st }} ▾</span>
{% elif st %}<span class="badge fail">{{ st }}</span>{% endif %} {% elif st %}<span class="badge fail linkbadge" onclick="toggleDrawer('last-{{ r.name }}')">{{ st }} ▾</span>{% endif %}
{% else %}<span style="color:#666">从未</span>{% endif %} {% else %}<span style="color:#666">从未</span>{% endif %}
</td> </td>
<td> <td>
<div class="opsbar"> <div class="opsbar">
<button type="button" class="btn btn-sm" onclick="toggleEdit('{{ r.name }}')">✎ 排程</button> <button type="button" class="btn btn-sm" onclick="toggleDrawer('edit-{{ r.name }}')">✎ 排程</button>
{% if r.enabled %} {% if r.enabled %}
<form method="POST" action="/run/{{ r.name }}" <form method="POST" action="/run/{{ r.name }}"
onsubmit="return confirm('立即采集 {{ r.name }} 一次?')"> onsubmit="return confirm('立即采集 {{ r.name }} 一次?')">
...@@ -176,7 +182,7 @@ function toggleEdit(name) { ...@@ -176,7 +182,7 @@ function toggleEdit(name) {
{% endfor %} {% endfor %}
<div class="drow" style="margin-left:28px"> <div class="drow" style="margin-left:28px">
<button type="submit" class="btn btn-primary">保存并重排</button> <button type="submit" class="btn btn-primary">保存并重排</button>
<button type="button" class="btn" onclick="toggleEdit('{{ r.name }}')">收起</button> <button type="button" class="btn" onclick="toggleDrawer('edit-{{ r.name }}')">收起</button>
</div> </div>
</form> </form>
<form method="POST" action="{{ url_for('schedule_time_set') }}" class="drow" <form method="POST" action="{{ url_for('schedule_time_set') }}" class="drow"
...@@ -191,6 +197,37 @@ function toggleEdit(name) { ...@@ -191,6 +197,37 @@ function toggleEdit(name) {
</div> </div>
</td> </td>
</tr> </tr>
<tr id="last-{{ r.name }}" class="drawer" style="display:none">
<td colspan="6">
{% set ok_arts = r.last_articles | selectattr('status', 'equalto', 'SUCCESS') | list %}
{% set bad_arts = r.last_articles | rejectattr('status', 'equalto', 'SUCCESS') | list %}
<div class="drawer-box">
<div class="drow">
<span class="dlabel">上次交付</span>
<span class="badge ok">成功 {{ ok_arts|length }} 篇</span>
{% if bad_arts %}<span class="badge fail">失败 {{ bad_arts|length }} 篇</span>{% endif %}
<span class="dnote">{{ r.last.mtime if r.last }} · 点击标题打开原文 · 再点徽标收起</span>
</div>
{% if ok_arts %}
<div class="hint" style="color:#6bbb6b;margin-left:28px">✓ 成功文章</div>
<ul class="arts">
{% for a in ok_arts %}
<li><span class="t">{% if a.url and a.url.startswith('http') %}<a class="source-link" href="{{ a.url }}" target="_blank" rel="noopener noreferrer">{{ a.title }}</a>{% else %}{{ a.title }}{% endif %}</span></li>
{% endfor %}
</ul>
{% endif %}
{% if bad_arts %}
<div class="hint" style="color:#bb6b6b;margin-left:28px">✗ 失败及原因</div>
<ul class="arts">
{% for a in bad_arts %}
<li><span class="t">{% if a.url and a.url.startswith('http') %}<a class="source-link" href="{{ a.url }}" target="_blank" rel="noopener noreferrer">{{ a.title }}</a>{% else %}{{ a.title }}{% endif %}</span>
<span style="color:#bb6b6b;font-size:12px">[{{ a.failure_type or 'UNKNOWN' }}] {{ a.failure_reason[:120] }}</span></li>
{% endfor %}
</ul>
{% endif %}
</div>
</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment