feat: 失败任务汇总 - /api/summary + 监控页异常面板
- 新增 /api/summary:各状态计数 + 异常设备列表(last_error/任务/重试次数/时间) - 监控页新增"异常汇总"面板:显示失败/异常设备及错误信息,方便长期运行观察健康度
This commit is contained in:
@@ -364,6 +364,43 @@ async function loadMonitor(){
|
|||||||
updateSelCount();
|
updateSelCount();
|
||||||
// 加载任务卡片
|
// 加载任务卡片
|
||||||
loadJobsForMonitor();
|
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'?'<span class="label label-danger">失败</span>':'<span class="label label-warning">异常</span>';
|
||||||
|
const up=e.updated?new Date(e.updated*1000).toLocaleTimeString():'';
|
||||||
|
return '<tr>'+
|
||||||
|
'<td><strong>'+esc(e.serial)+'</strong>'+(e.model?' <span style="font-size:11px;color:#94a3b8">'+esc(e.model)+'</span>':'')+'</td>'+
|
||||||
|
'<td>'+st+'</td>'+
|
||||||
|
'<td>'+esc(e.task||'-')+'</td>'+
|
||||||
|
'<td>'+(e.attempt>0?e.attempt+'次':'<span class="text-muted">-</span>')+'</td>'+
|
||||||
|
'<td class="err-text" title="'+esc(e.last_error||'')+'">'+esc(e.last_error||'-')+'</td>'+
|
||||||
|
'</tr>';
|
||||||
|
}).join('');
|
||||||
}catch(e){console.error(e);}
|
}catch(e){console.error(e);}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -315,6 +315,16 @@ select.form-control{cursor:pointer}
|
|||||||
</table>
|
</table>
|
||||||
<div class="list-pager" id="pager-devices"></div>
|
<div class="list-pager" id="pager-devices"></div>
|
||||||
|
|
||||||
|
<!-- 异常设备汇总 -->
|
||||||
|
<div class="section-title" style="margin-top:8px">异常汇总 <span id="summary-badge" style="font-size:12px;font-weight:400;color:#94a3b8"></span></div>
|
||||||
|
<div id="summary-panel" style="display:none;margin-bottom:12px">
|
||||||
|
<table class="table">
|
||||||
|
<thead><tr><th>设备</th><th>状态</th><th>任务</th><th>重试</th><th>错误信息</th></tr></thead>
|
||||||
|
<tbody id="tb-summary"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="summary-empty" style="display:none" class="text-muted">无异常设备</div>
|
||||||
|
|
||||||
<div class="section-title" style="margin-top:8px">任务运行概况</div>
|
<div class="section-title" style="margin-top:8px">任务运行概况</div>
|
||||||
<div id="jobs-grid" class="jobs-grid"></div>
|
<div id="jobs-grid" class="jobs-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from flask_login import (LoginManager, login_user, logout_user,
|
|||||||
|
|
||||||
from core.stf_client import STFClient
|
from core.stf_client import STFClient
|
||||||
from core.task_manager import TaskManager
|
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.logger import get_logger, _LOG_DIR, _MODULE_FILES
|
||||||
from core.models import db, init_db, User, DeviceGroup, TaskJob, CustomAction
|
from core.models import db, init_db, User, DeviceGroup, TaskJob, CustomAction
|
||||||
from core.apk_manager import ApkManager
|
from core.apk_manager import ApkManager
|
||||||
@@ -147,6 +148,40 @@ def api_health():
|
|||||||
return jsonify({"ok": False, "status": "down", "error": str(e)}), 500
|
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:状态(监控大屏用)==================
|
# ================== API:状态(监控大屏用)==================
|
||||||
@app.route("/api/status")
|
@app.route("/api/status")
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
Reference in New Issue
Block a user