diff --git a/gateway/scripts/templates/dashboard.html b/gateway/scripts/templates/dashboard.html index e26057c..92b31b0 100644 --- a/gateway/scripts/templates/dashboard.html +++ b/gateway/scripts/templates/dashboard.html @@ -150,21 +150,107 @@ async function doAct(id,act){try{var r=await fetch('/api/agents/'+id+'/'+act,{me function tgL(id){if(L.has(id))L.delete(id);else L.add(id);var lp=qs('lp-'+id);if(lp)lp.className='lp'+(L.has(id)?' open':'');if(L.has(id))fL(id)} async function fL(id){var el=qs('lc-'+id);if(!el||!L.has(id))return;try{var r=await fetch('/api/agents/'+id+'/logs?lines=50'),d=await r.json();el.innerHTML=(d.lines||[]).map(function(l){return'
'+esc(l)+'
'}).join('')||'no log data';el.scrollTop=el.scrollHeight}catch(e){el.innerHTML='Failed'}} async function fHealth(){try{ + // Service metadata: label / layer / impact + var M={}; + M.ejabberd={n:'XMPP服务器',l:'通信层',i:'所有XMPP连接不可用'}; + M.xmpp_bot={n:'XMPP消息收发',l:'通信层',i:'所有Agent消息收发中断'}; + M.gateway_mohe={n:'Gateway-莫荷',l:'AI网关',i:'莫荷智能体不可用'}; + M.gateway_zhiwei={n:'Gateway-知微',l:'AI网关',i:'知微智能体不可用'}; + M.article_processor={n:'微信全文抓取',l:'辅助服务',i:'微信长文章无法读取'}; + M.dashboard={n:'管理门户',l:'辅助服务',i:'Dashboard无法访问'}; + M.watchdog={n:'进程看门狗',l:'辅助服务',i:'异常进程无自动恢复'}; + M['agents-health-check']={n:'健康检查(5min)',l:'定时任务',i:'5分钟级健康检测未运行'}; + M['agents-daily-health']={n:'日报健康检查(每日)',l:'定时任务',i:'日报统计未运行'}; + M['agents-todo-executor']={n:'待办执行器(10min)',l:'定时任务',i:'定时待办任务未执行'}; + var layerOrder=['通信层','AI网关','辅助服务','定时任务']; + var h=''; - try{var r=await fetch('/api/expected'),d=await r.json();var ok=0;for(var i=0;i'+ok+'/'+d.expected.length+''; - for(var i=0;i';} - h+='
服务期望实际状态
'+esc(e.name)+''+esc(e.expected)+''+esc(a)+''+(cmp?'OK':'MISMATCH')+'
';}catch(e){} - try{var r2=await fetch('/api/services'),d2=await r2.json();var ok2=0;h+='

服务健康 '+d2.services.length+' services

'; - for(var i=0;i';} - h+='
ServiceTypePortHealthWD
'+esc(s.name)+''+esc(s.type||'-')+''+(s.port||'-')+''+(a?'OK':'DOWN')+''+(s.watchdog?'Y':'N')+'
健康: '+ok2+'/'+d2.services.length+' | 看门狗: '+d2.services.filter(function(s){return s.watchdog}).length+'/'+d2.services.length+'
';}catch(e){} - try{var r3=await fetch('/api/monitor'),d3=await r3.json();h+='

监控数据

'; - if(d3.tier1&&d3.tier1.summary)h+='
'+d3.tier1.summary.ok+'/'+d3.tier1.summary.total+'
T1 健康
'; - if(d3.tier2&&d3.tier2.summary)h+='
'+d3.tier2.summary.ok+'/'+d3.tier2.summary.total+'
T2 日报
'; - if(d3.tasks&&d3.tasks.length){h+='

定时任务

';for(var i=0;i'+esc(t.name)+' '+esc(t.status)+'
';}h+='
';} - h+='';}catch(e){} + var all=await Promise.all([ + fetch('/api/services').then(function(r){return r.json()}).catch(function(){return{}}), + fetch('/api/expected').then(function(r){return r.json()}).catch(function(){return{}}), + fetch('/api/monitor').then(function(r){return r.json()}).catch(function(){return{}}) + ]); + var sD=all[0]||{},eD=all[1]||{},mD=all[2]||{}; + var svcs=sD.services||[],exp=eD.expected||[],act=eD.actual||{}; + + // --- Layer 1: 系统概览 --- + var hCount=svcs.filter(function(s){return s.health&&s.health.ok}).length; + var cDown=exp.filter(function(e){return e.critical&&e.expected.indexOf('running')===0&&act[e.name]!=='running'}).length; + var st=cDown>0?'critical':hCount
'+stL[st]+'
'+hCount+'/'+svcs.length+' 服务健康
'; + h+='
'+cDown+'
关键异常
'; + if(mD.tier1&&mD.tier1.summary){var t1=mD.tier1.summary;var t1c=t1.ok===t1.total?'var(--green)':'var(--red)'; + h+='
'+t1.ok+'/'+t1.total+'
T1监控
';} + h+=''; + + // --- Layer 2: 异常服务 --- + var issues=[]; + for(var i=0;i'+esc(iv.status)+''; + h+='
'+esc(iv.impact)+'
';} + h+='';} + + // --- Layer 3: 全部服务 (merge expected + services) --- + var layers={}; + var added={}; + for(var i=0;i'; + for(var j=0;j'; + h+=''+esc(it.label)+''; + if(it.port)h+=':'+it.port+''; + h+=''+(run?'运行中':esc(it.status))+''; + h+='';} + } + h+=''; + + // --- 定时任务状态 --- + if(mD.tasks&&mD.tasks.length){ + h+='

定时任务

'; + for(var i=0;i'; + h+=''+esc(m.n)+''; + h+=''+(tk?'正常':tn?'未部署':'异常')+'
';} + h+='';} + if(!h)h='No data'; - fill('health',h)}catch(e){fill('health','Error')}} + fill('health',h)}catch(e){fill('health','Error: '+esc(e.message)+'')}} async function fK(){try{var r=await fetch('/api/kanban'),d=await r.json();var tasks=d.tasks||[];var s=document.getElementById('kf-status');var a=document.getElementById('kf-assignee');var fs=s?s.value:'';var fa=a?a.value:''; if(tasks.length==0){fill('kanban','
No tasks
');return} var html='
';html+='';