This commit is contained in:
12600k-rog-d4
2026-08-13 22:49:50 +08:00
parent 5e296bfe19
commit 2aaaa16ae2
24 changed files with 1415 additions and 327 deletions

View File

@@ -0,0 +1,126 @@
package stress
const reportHTMLTemplate = `<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>硬件压力测试报告</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#f1f5f9;color:#1e293b;padding:24px}
.wrap{max-width:1100px;margin:0 auto}
header{background:linear-gradient(135deg,#0f172a 0%,#1e3a5f 100%);color:#fff;border-radius:16px;padding:28px 32px;margin-bottom:24px;box-shadow:0 4px 20px rgba(15,23,42,.25)}
header .title{font-size:22px;font-weight:700;letter-spacing:.5px}
header .meta{display:flex;flex-wrap:wrap;gap:20px;margin-top:14px;font-size:13px;color:#cbd5e1}
header .meta b{color:#fff;font-weight:600}
.sysinfo{margin-top:14px;font-size:12px;color:#94a3b8;line-height:1.8}
.overall-row{display:flex;align-items:center;gap:16px;margin-top:14px}
.badge{display:inline-block;padding:5px 14px;border-radius:999px;font-size:13px;font-weight:700;color:#fff}
.summary{font-size:14px;color:#e2e8f0}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(480px,1fr));gap:20px}
.card{background:#fff;border-radius:14px;padding:22px;box-shadow:0 1px 4px rgba(15,23,42,.06);border-left:4px solid #cbd5e1}
.card.pass{border-left-color:#16a34a}
.card.fail{border-left-color:#dc2626}
.card.error{border-left-color:#f59e0b}
.card.skip{border-left-color:#9ca3af}
.card-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.card-title{font-size:16px;font-weight:700}
.card-sub{font-size:12px;color:#64748b;margin-top:4px}
.score-row{display:flex;align-items:baseline;gap:14px;margin:16px 0;padding:14px 16px;background:#f8fafc;border-radius:10px}
.score-val{font-size:34px;font-weight:800;color:#0f172a;font-variant-numeric:tabular-nums}
.score-unit{font-size:14px;font-weight:600;color:#334155}
.score-hint{font-size:12px;color:#94a3b8;margin-top:2px}
.metrics{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:14px}
.metric{display:flex;flex-direction:column;gap:2px;padding:8px 12px;background:#f1f5f9;border-radius:8px;min-width:88px}
.metric .m-label{font-size:11px;color:#64748b}
.metric .m-value{font-size:15px;font-weight:700;color:#0f172a}
.chart-box{position:relative;height:260px;margin-top:6px}
details.output{margin-top:14px;border-top:1px dashed #e2e8f0;padding-top:10px}
details.output summary{cursor:pointer;font-size:12px;color:#64748b;user-select:none}
details.output pre{white-space:pre-wrap;word-break:break-all;font-size:11px;color:#475569;background:#f8fafc;border-radius:8px;padding:10px;margin-top:8px;max-height:240px;overflow:auto}
.empty{grid-column:1/-1;text-align:center;color:#94a3b8;padding:40px;font-size:14px}
@media(max-width:560px){.grid{grid-template-columns:1fr}.score-val{font-size:28px}}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="title" id="title"></div>
<div class="meta">
<span>设备 <b id="ip"></b></span>
<span>测试时间 <b id="time"></b></span>
<span>总耗时 <b id="duration"></b></span>
</div>
<div class="sysinfo" id="sysinfo"></div>
<div class="overall-row">
<span class="badge" id="overall"></span>
<span class="summary" id="summary"></span>
</div>
</header>
<div class="grid" id="cards"></div>
</div>
<script>
var STATUS={pass:{t:'通过',c:'#16a34a'},fail:{t:'失败',c:'#dc2626'},skip:{t:'跳过',c:'#9ca3af'},error:{t:'错误',c:'#f59e0b'},untested:{t:'未测',c:'#94a3b8'}};
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
function fmt(v){if(v==null)return'-';if(typeof v==='number')return v.toLocaleString(undefined,{maximumFractionDigits:1});return String(v);}
function rgba(hex,a){var h=hex.replace('#','');var r=parseInt(h.substring(0,2),16),g=parseInt(h.substring(2,4),16),b=parseInt(h.substring(4,6),16);return 'rgba('+r+','+g+','+b+','+a+')';}
function seriesLabel(s){return s.unit?s.label+' ('+s.unit+')':s.label;}
function buildCard(card,idx){
var m=STATUS[card.status]||{t:card.status,c:'#64748b'};
var el=document.createElement('div');
el.className='card '+card.status;
var h='<div class="card-head"><span class="card-title">'+esc(card.label)+'</span>'+
'<span class="badge" style="background:'+m.c+'">'+m.t+'</span></div>'+
'<div class="card-sub">'+esc(card.name)+' · 耗时 '+esc(card.duration)+'</div>';
if(card.score>0){
h+='<div class="score-row"><div class="score-val">'+fmt(card.score)+'</div>'+
'<div><div class="score-unit">'+esc(card.score_unit)+'</div>'+
'<div class="score-hint">'+esc(card.score_hint)+'</div></div></div>';
}
if(card.metrics&&card.metrics.length){
h+='<div class="metrics">';
card.metrics.forEach(function(x){h+='<div class="metric"><span class="m-label">'+esc(x.label)+'</span><span class="m-value">'+esc(x.value)+'</span></div>';});
h+='</div>';
}
if(card.chart){h+='<div class="chart-box"><canvas id="c'+idx+'"></canvas></div>';}
if(card.output&&card.status!=='pass'){
h+='<details class="output"><summary>详细输出</summary><pre>'+esc(card.output)+'</pre></details>';
}
el.innerHTML=h;
return el;
}
function renderChart(canvas,chart){
var left=(chart.left_sets||[]).map(function(s){return{label:seriesLabel(s),data:s.data,borderColor:s.color,backgroundColor:rgba(s.color,.08),fill:true,tension:.3,yAxisID:'y'};});
var right=(chart.right_sets||[]).map(function(s){return{label:seriesLabel(s),data:s.data,borderColor:s.color,backgroundColor:rgba(s.color,.05),fill:false,tension:.3,yAxisID:'y1'};});
var scales={y:{type:'linear',position:'left',title:{display:true,text:chart.left_title},grid:{color:'#f1f5f9'}}};
if(chart.left_pct){scales.y.min=0;scales.y.max=100;}
scales.y1={type:'linear',position:'right',title:{display:true,text:chart.right_title},grid:{drawOnChartArea:false}};
new Chart(canvas,{type:'line',data:{labels:chart.labels,datasets:left.concat(right)},
options:{responsive:true,maintainAspectRatio:false,interaction:{intersect:false,mode:'index'},
plugins:{legend:{display:true,position:'bottom',labels:{boxWidth:12,font:{size:11}}}},scales:scales}});
}
var data=__DATA__;
document.getElementById('title').textContent=data.title;
document.getElementById('ip').textContent=data.ip;
document.getElementById('time').textContent=data.time;
document.getElementById('duration').textContent=data.duration;
document.getElementById('summary').textContent=data.summary;
var si=data.sys_info||{},sp=[];
if(si.cpu)sp.push('CPU: '+si.cpu);
if(si.cores)sp.push('核心: '+si.cores);
if(si.memory)sp.push('内存: '+si.memory);
if(si.kernel)sp.push('内核: '+si.kernel);
if(si.disk)sp.push('磁盘: '+si.disk);
document.getElementById('sysinfo').textContent=sp.join(' · ');
var om=STATUS[data.overall]||{t:data.overall,c:'#64748b'};
var ob=document.getElementById('overall');ob.textContent=om.t;ob.style.background=om.c;
var grid=document.getElementById('cards');
data.cards.forEach(function(card,idx){
grid.appendChild(buildCard(card,idx));
if(card.chart){var cv=document.getElementById('c'+idx);if(cv)renderChart(cv,card.chart);}
});
</script>
</body>
</html>`