From 8a675923cdfc944aacd7d1e0b46b359a89d28555 Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Sat, 8 Aug 2026 21:33:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=B1=E8=B4=A5=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=B1=87=E6=80=BB=20-=20/api/summary=20+=20=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E9=A1=B5=E5=BC=82=E5=B8=B8=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 /api/summary:各状态计数 + 异常设备列表(last_error/任务/重试次数/时间) - 监控页新增"异常汇总"面板:显示失败/异常设备及错误信息,方便长期运行观察健康度 --- static/admin/monitor.js | 37 ++++++++++++++++++++++++++++++++++++ templates/admin/monitor.html | 10 ++++++++++ web_server.py | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/static/admin/monitor.js b/static/admin/monitor.js index 9a09846..699a6c9 100644 --- a/static/admin/monitor.js +++ b/static/admin/monitor.js @@ -364,6 +364,43 @@ async function loadMonitor(){ updateSelCount(); // 加载任务卡片 loadJobsForMonitor(); + // 加载异常汇总 + loadSummary(); + }catch(e){console.error(e);} +} + +// ===== 异常设备汇总 ===== +async function loadSummary(){ + try{ + const r=await apiGet('/api/summary'); + if(!r||!r.ok)return; + const counts=r.counts||{}; + const errors=r.errors||[]; + const errCount=(counts.error||0)+(counts.failed||0); + const badge=document.getElementById('summary-badge'); + const panel=document.getElementById('summary-panel'); + const empty=document.getElementById('summary-empty'); + if(badge)badge.textContent='· 异常 '+errCount+' · 运行 '+counts.running+' · 完成 '+counts.done; + if(!panel||!empty)return; + if(!errors.length){ + panel.style.display='none'; + empty.style.display='block'; + return; + } + panel.style.display='block'; + empty.style.display='none'; + const tb=document.getElementById('tb-summary'); + tb.innerHTML=errors.map(e=>{ + const st=e.status==='failed'?'失败':'异常'; + const up=e.updated?new Date(e.updated*1000).toLocaleTimeString():''; + return ''+ + ''+esc(e.serial)+''+(e.model?' '+esc(e.model)+'':'')+''+ + ''+st+''+ + ''+esc(e.task||'-')+''+ + ''+(e.attempt>0?e.attempt+'次':'-')+''+ + ''+esc(e.last_error||'-')+''+ + ''; + }).join(''); }catch(e){console.error(e);} } diff --git a/templates/admin/monitor.html b/templates/admin/monitor.html index 980f5df..64883d3 100644 --- a/templates/admin/monitor.html +++ b/templates/admin/monitor.html @@ -315,6 +315,16 @@ select.form-control{cursor:pointer}
+ +
异常汇总
+ + +
任务运行概况
diff --git a/web_server.py b/web_server.py index 0390012..af85996 100644 --- a/web_server.py +++ b/web_server.py @@ -28,6 +28,7 @@ from flask_login import (LoginManager, login_user, logout_user, from core.stf_client import STFClient from core.task_manager import TaskManager +from core.device_worker import get_all_worker_status from core.logger import get_logger, _LOG_DIR, _MODULE_FILES from core.models import db, init_db, User, DeviceGroup, TaskJob, CustomAction from core.apk_manager import ApkManager @@ -147,6 +148,40 @@ def api_health(): return jsonify({"ok": False, "status": "down", "error": str(e)}), 500 +# ================== API:失败任务汇总 ================== +@app.route("/api/summary") +@login_required +def api_summary(): + """失败/异常任务汇总:各状态计数 + 异常设备列表(last_error / 重试次数)。 + + 供监控页"异常汇总"面板使用,便于长期运行观察设备健康度。 + """ + try: + sts = get_all_worker_status() + counts = {"total": 0, "running": 0, "done": 0, + "error": 0, "failed": 0, "idle": 0} + errors = [] + for s in sts: + st = s.get("status", "idle") + counts["total"] += 1 + if st in counts: + counts[st] += 1 + if st in ("error", "failed") and s.get("last_error"): + errors.append({ + "serial": s.get("serial"), + "model": s.get("model", ""), + "status": st, + "last_error": s.get("last_error", ""), + "task": s.get("task_job", ""), + "attempt": s.get("attempt", 0), + "updated": s.get("last_heartbeat", 0), + }) + errors.sort(key=lambda x: x.get("updated", 0), reverse=True) + return jsonify({"ok": True, "counts": counts, "errors": errors[:50]}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 + + # ================== API:状态(监控大屏用)================== @app.route("/api/status") @login_required