61 lines
7.5 KiB
HTML
61 lines
7.5 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>FreeCAD Web 执行进度</title>
|
||
<style>
|
||
:root{font-family:system-ui,sans-serif;color:#dae4ee;background:#111923;color-scheme:dark}body{max-width:1050px;margin:32px auto;padding:0 20px;line-height:1.65}h1{font-size:25px}h2{font-size:19px;margin-top:0}.muted{color:#9babbc}section,details{background:#192532;border:1px solid #334657;border-radius:8px;padding:20px;margin:18px 0}.detail{white-space:pre-wrap;overflow-wrap:anywhere}li{overflow-wrap:anywhere}summary{cursor:pointer}a{color:#8ed9dd}.stale{color:#ffcc83}.tag{font-weight:600;color:#8ed9dd}#error{color:#ffb8ad}@media(max-width:600px){body{margin:18px auto;padding:0 12px}section,details{padding:14px}}
|
||
</style></head><body>
|
||
<h1>FreeCAD Web 执行进度</h1><p class="muted">每轮结束后自动更新结果;接续任务经调度确认后显示。本页每 5 秒刷新。</p>
|
||
<p id="heartbeat" class="muted">正在读取状态…</p><p id="error" role="status"></p>
|
||
<section id="change-section" hidden aria-label="增量巡检"><h2>输入变化与新故障巡检</h2><div id="change-watch"></div></section>
|
||
<section id="smoke-section" hidden aria-label="全面烟测"><h2>全面烟测 · 可接续</h2><div id="smoke"></div><p class="muted">烟测与历史开发轮次分别记录。通过数是检查组数量,不是全部功能完成率。</p></section>
|
||
<section aria-label="最近一轮结果"><h2>最近一轮结果</h2><div id="last">等待记录</div></section>
|
||
<section aria-label="下一轮任务"><h2>下一轮 / 当前接续任务</h2><div id="next">待调度确认</div></section>
|
||
<h2>最近轮次</h2><div id="history"></div>
|
||
<p class="muted">“已修改”不代表整项通过;未完成、阻塞和上游等待会如实保留。此页显示已有记录,不触发构建或验收。</p>
|
||
<p><a href="/">打开 FreeCAD 工作区</a> · <a href="/execution-status.json">查看结构化回执</a></p>
|
||
<script type="module">
|
||
const byId=id=>document.getElementById(id);
|
||
const element=(tag,text,cls)=>{const node=document.createElement(tag);node.textContent=text;if(cls)node.className=cls;return node;};
|
||
function renderRound(row){
|
||
const box=document.createElement('div');
|
||
box.append(element('p',`第 ${row.cycle} 轮 · ${row.module} · ${row.seconds} 秒`,'muted'),element('p',row.result,'tag'),element('p',row.detail,'detail'));
|
||
if(row.remaining&&row.remaining.trim()!==row.detail.trim())box.append(element('p',`剩余/阻塞:${row.remaining}`,'detail muted'));
|
||
if(row.firstPassIds?.length){const list=document.createElement('ul');for(const id of row.firstPassIds)list.append(element('li',id));box.append(list);}
|
||
if(row.evidence?.length){const detail=document.createElement('details');detail.append(element('summary','证据记录'));for(const item of row.evidence)detail.append(element('p',item.path,'detail'));box.append(detail);}
|
||
return box;
|
||
}
|
||
let version='';
|
||
async function update(){try{
|
||
const response=await fetch('/execution-status.json',{cache:'no-store'});if(!response.ok)throw new Error(`状态读取失败(${response.status})`);
|
||
const data=await response.json();if(data.schema!=='freecad-web-round-report/v1'||![2,3].includes(data.formatVersion))throw new Error('回执格式不正确');
|
||
const age=(Date.now()-Date.parse(data.heartbeatAt))/1000;const heartbeat=byId('heartbeat');
|
||
const waitingForGap=data.schedulerStatus==='waiting_for_confirmed_gap';
|
||
heartbeat.textContent=data.reportingActive===false?'自动逐轮回执将在当前业务回合结束、调度器重载后启用;以下为最近记录。':`调度状态:${waitingForGap?'等待已确认缺口,当前没有实施任务':data.schedulerStatus||'未知'} · 最近心跳:${data.heartbeatAt?new Date(data.heartbeatAt).toLocaleString('zh-CN'):'未知'}${age>60?' · 心跳已超过 60 秒,请核对服务':''}`;
|
||
heartbeat.className=age>60?'stale':'muted';byId('error').textContent='';
|
||
const contentVersion=JSON.stringify([data.lastCompleted,data.nextTask,data.rounds,data.schedulerStatus]);
|
||
if(version===contentVersion)return;version=contentVersion;
|
||
byId('last').replaceChildren(data.lastCompleted?renderRound(data.lastCompleted):element('p','尚无已结束轮次记录'));
|
||
const next=byId('next');next.replaceChildren();
|
||
if(data.nextTask){const task=data.nextTask;next.append(element('p',`调度已确认:第 ${task.cycle} 轮 · ${task.module}`,'tag'));const list=document.createElement('ul');for(const text of task.tasks)list.append(element('li',text));next.append(list);}
|
||
else next.append(element('p',waitingForGap?'当前没有可执行的已确认缺口;保留心跳,登记新问题后继续,不生成空轮次。':'待调度确认;选定后自动显示。'));
|
||
byId('history').replaceChildren(...[...(data.rounds||[])].reverse().map(row=>{const detail=document.createElement('details');detail.append(element('summary',`第 ${row.cycle} 轮 · ${row.module} · ${row.result}`),renderRound(row));return detail;}));
|
||
}catch(error){byId('error').textContent=String(error.message||error)+';保留上次记录,稍后自动重试。';}}
|
||
async function updateSmoke(){try{
|
||
const response=await fetch('/smoke-status.json',{cache:'no-store'});if(!response.ok)return;
|
||
const data=await response.json();if(data.schema!=='freecad-web-smoke-progress/v1')return;
|
||
byId('smoke-section').hidden=false;
|
||
const labels={running:'执行中',paused:'可接续',interrupted:'已中断,可接续',completed:'本轮烟测收口',completed_with_failures:'本轮结束,仍有未通过项'};
|
||
byId('smoke').replaceChildren(element('p',`${labels[data.status]||data.status} · 已记录 ${data.completed}/${data.total} 组 · 通过 ${data.passed} 组`,'tag'),
|
||
element('p',data.current?`当前:${data.current.module} · ${data.current.phase}`:'当前没有正在运行的烟测项'),
|
||
element('p',data.failed.length?`未通过/待定位:${data.failed.join('、')}`:'没有记录失败项','detail'),
|
||
element('p','累计结果包含已有通过证据和修复后的定向复测;不表示全部功能逐项重新验收。','muted'));
|
||
}catch{ /* Keep last successfully read smoke result. */ }}
|
||
async function updateChangeWatch(){try{
|
||
const response=await fetch('/change-watch-status.json',{cache:'no-store'});if(!response.ok)return;
|
||
const d=await response.json();byId('change-section').hidden=false;
|
||
const labels={no_changes:'本轮无变化,等待下一次检查',checking_changed_input:'正在检查变化项',check_finished:'变化项检查结束',repair_queued:'新故障已进入修复队列',waiting_for_build:'源码已变更,等待对应构建产物',waiting_for_active_work:'等待现有测试或构建结束',unchanged_failure_preserved:'保留同输入失败,不重复重试',needs_attention:'检查未完成,需查看证据',monitor_error:'巡检异常',stopping_timeout:'检查超时,正在收尾'};
|
||
byId('change-watch').replaceChildren(element('p',labels[d.status]||d.status,'tag'),element('p',`最近检查:${d.lastCheckedAt?new Date(d.lastCheckedAt).toLocaleString('zh-CN'):'尚未完成'} · 下次检查:${d.nextCheckAt?new Date(d.nextCheckAt*1000).toLocaleString('zh-CN'):'待安排'}`),element('p',`核对 ${d.checks||0} 组 · 未变化 ${d.unchanged||0} 组 · 新故障 ${d.newIssues||0} 项${d.active?' · 当前:'+d.active.check:''}`),element('p',d.error||'仅处理变化或具体故障,已完成移植直接复用。','muted'));
|
||
}catch{}}
|
||
await Promise.all([update(),updateSmoke(),updateChangeWatch()]);setInterval(()=>{void update();void updateSmoke();void updateChangeWatch();},5000);
|
||
</script></body></html>
|