chore(repo): initialize team collaboration repository
CI / Python 3.12 (push) Waiting to run
CI / Python 3.9 (push) Waiting to run

This commit is contained in:
2026-07-27 20:40:12 +08:00
commit c91a64fddb
109 changed files with 21121 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+188
View File
@@ -0,0 +1,188 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
@keyframes omPulse{0%,100%{opacity:1}50%{opacity:.3}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="assets" hint-size="100%,92px"></dc-import>
<div data-screen-label="资产中心" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:18px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">07 · ASSETS</div>
<h2 style="margin:2px 0 0;font-size:30px">资产中心</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">登记 · 绑定 · 禁用 · 审计 — 任何破坏性任务必须绑定允许的 DUT</div>
</div>
<span style="margin-left:auto"></span>
<button class="btn btn-primary" onClick="{{ openReg }}" style="gap:7px">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M5 12h14"></path><path d="M12 5v14"></path></svg>
登记资产
</button>
</div>
<div class="seg" style="background:var(--color-bg);margin-bottom:16px">
<label class="seg-opt"><input type="radio" name="atab" checked="{{ tHost }}" onChange="{{ setHost }}"><span>测试主机 6</span></label>
<label class="seg-opt"><input type="radio" name="atab" checked="{{ tDut }}" onChange="{{ setDut }}"><span>DUT 8</span></label>
<label class="seg-opt"><input type="radio" name="atab" checked="{{ tFw }}" onChange="{{ setFw }}"><span>固件包 6</span></label>
<label class="seg-opt"><input type="radio" name="atab" checked="{{ tTool }}" onChange="{{ setTool }}"><span>工具连接器 9</span></label>
<label class="seg-opt"><input type="radio" name="atab" checked="{{ tOob }}" onChange="{{ setOob }}"><span>带外控制器 6</span></label>
</div>
<sc-if value="{{ tHost }}" hint-placeholder-val="{{ false }}">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px;animation:omIn .35s cubic-bezier(.2,.7,.2,1) both" data-screen-label="资产·主机">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<table class="table">
<thead><tr><th>主机</th><th>平台 / OS</th><th>BIOS</th><th>Agent</th><th>心跳</th><th>带外配对</th><th>绑定工位</th><th>状态</th><th></th></tr></thead>
<tbody>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">HOST-A01</td><td style="font-size:12.5px">x86 台式 · Win11 22631</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">F26b</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.4.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">2s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">OOB-01</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-01</td><td><span class="tag tag-accent">运行中</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审计</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">HOST-A02</td><td style="font-size:12.5px">x86 台式 · Win11 22631</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">F26b</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.4.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">1s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">OOB-02</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-02</td><td><span class="tag tag-accent">运行中</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审计</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">HOST-L02</td><td style="font-size:12.5px">2U 服务器 · Ubuntu 22.04 · 6.5.0-41</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">2.1a</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.4.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#8a2e22">丢失 1m+</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">OOB-03</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-03</td><td><span style="font-size:10.5px;padding:3px 9px;background:var(--color-accent-200);color:var(--color-accent-800);animation:omPulse 1.2s infinite">恢复中 L3</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审计</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">HOST-L01</td><td style="font-size:12.5px">2U 服务器 · Ubuntu 22.04 · 6.5.0-41</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">2.1a</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.4.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">3s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">OOB-04</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-04</td><td><span class="tag tag-outline">空闲</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审计</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">HOST-A03</td><td style="font-size:12.5px">x86 台式 · Win11 22631</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">F26b</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.4.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">2s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">OOB-05</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-05</td><td><span class="tag tag-accent">已预约</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审计</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700;color:var(--color-neutral-600)">HOST-W02</td><td style="font-size:12.5px;color:var(--color-neutral-600)">x86 台式 · Win10 19045</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.8c</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.3.9</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px"></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">OOB-06 升级中</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-06</td><td><span style="font-size:10.5px;padding:3px 9px;border:1px solid var(--color-divider);color:var(--color-neutral-600);background:repeating-linear-gradient(135deg,rgba(29,31,32,.10) 0 2px,transparent 2px 5px)">维护</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审计</button></td></tr>
</tbody>
</table>
<div style="font-size:11px;color:var(--color-neutral-600)">主机镜像:golden-image-win11-v7 / ubuntu2204-v4 · 组策略固化(禁用快速启动/自动更新/睡眠劫持) · Agent 自愈:本地看门狗 + 双进程互监,崩溃 10s 内拉起且证据不丢</div>
</div>
</sc-if>
<sc-if value="{{ tDut }}" hint-placeholder-val="{{ true }}">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px;animation:omIn .35s cubic-bezier(.2,.7,.2,1) both" data-screen-label="资产·DUT">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<table class="table">
<thead><tr><th>序列号</th><th>型号 / 形态</th><th>当前固件</th><th style="width:130px">P/E 磨损</th><th>允许动作</th><th>绑定</th><th>生命周期</th><th></th></tr></thead>
<tbody>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-001</td><td style="font-size:12.5px">NV-E1T92 · 1.92TB U.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.3.0-rc2 · slot2</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:23%;background:var(--color-accent)"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px">23%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-accent-800);color:#f2f2f3;padding:2px 7px">破坏写</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px">刷写</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-01 · 运行中</td><td><span class="tag tag-outline">测试专用</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-002</td><td style="font-size:12.5px">NV-E1T92 · 1.92TB U.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.3.0-rc2 · slot2</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:31%;background:var(--color-accent)"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px">31%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-accent-800);color:#f2f2f3;padding:2px 7px">破坏写</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px">刷写</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-03 · 冻结</td><td><span class="tag tag-outline">测试专用</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-003</td><td style="font-size:12.5px">NV-E960 · 960GB M.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.2.1 · slot1</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:8%;background:var(--color-accent)"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px">8%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-accent-800);color:#f2f2f3;padding:2px 7px">破坏写</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px">刷写</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">未绑定</td><td><span class="tag tag-outline">测试专用</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-004</td><td style="font-size:12.5px">NV-E1T92 · 1.92TB U.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.2.1 · slot1</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:67%;background:var(--color-accent-700)"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px">67%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-accent-800);color:#f2f2f3;padding:2px 7px">破坏写</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-02 · 运行中</td><td><span class="tag tag-outline">测试专用</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-005</td><td style="font-size:12.5px">NV-E1T92 · 1.92TB U.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.1.9 · slot1</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:89%;background:#b03d2e"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:#8a2e22">89%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px;color:var(--color-neutral-600)">只读</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">未绑定</td><td><span style="font-size:10.5px;padding:2px 9px;border:1px solid #b03d2e;color:#8a2e22">寿命预警</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-006</td><td style="font-size:12.5px">NV-E1T92 · 1.92TB U.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.2.1 · slot1</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:12%;background:var(--color-accent)"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px">12%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-accent-800);color:#f2f2f3;padding:2px 7px">破坏写</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px">刷写</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-05 · 已预约</td><td><span class="tag tag-outline">测试专用</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">SAMPLE-007</td><td style="font-size:12.5px">NV-E3T84 · 3.84TB U.2 工程样</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">FW_3.3.0-rc3 · slot2</td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="height:100%;width:4%;background:var(--color-accent)"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px">4%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px">刷写</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px;color:var(--color-neutral-600)">只读</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">收货检验中</td><td><span class="tag tag-neutral">入库检</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700;color:var(--color-neutral-500)">SAMPLE-000</td><td style="font-size:12.5px;color:var(--color-neutral-500)">NV-E960 · 960GB M.2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-500)"></td><td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider);background:repeating-linear-gradient(135deg,rgba(29,31,32,.10) 0 2px,transparent 2px 5px)"></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">100%</span></div></td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px dashed var(--color-neutral-400);padding:2px 7px;color:var(--color-neutral-500)">禁用</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-500)"></td><td><span class="tag tag-neutral">已退役</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">履历</button></td></tr>
</tbody>
</table>
<div style="font-size:11px;color:var(--color-neutral-600)">危险任务需序列号 + BDF/设备路径 + 允许标签三信号一致才执行 · 命中系统盘立即拒绝</div>
</div>
</sc-if>
<sc-if value="{{ tFw }}" hint-placeholder-val="{{ false }}">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px;animation:omIn .35s cubic-bezier(.2,.7,.2,1) both" data-screen-label="资产·固件">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<table class="table">
<thead><tr><th>版本</th><th>SHA256</th><th>来源</th><th>兼容型号</th><th>刷写工具</th><th>审批</th><th></th></tr></thead>
<tbody>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">FW_3.3.0-rc3</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">9f2c…41aa</td><td style="font-size:12.5px">固件组 CI #4471</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">NV-E1T92 / E3T84</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">vendor_flash 2.4</td><td><span style="font-size:10.5px;padding:2px 9px;border:1px solid #b03d2e;color:#8a2e22">待审批</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">审批</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">FW_3.3.0-rc2</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">7d18…c930</td><td style="font-size:12.5px">固件组 CI #4402</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">NV-E1T92</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">vendor_flash 2.4</td><td><span class="tag tag-accent">已审批</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">详情</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">FW_3.3.0-rc1</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">55ab…0f77</td><td style="font-size:12.5px">固件组 CI #4359</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">NV-E1T92</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">vendor_flash 2.4</td><td><span class="tag tag-neutral">已归档</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">详情</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">FW_3.2.1</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">4bd1…08ce</td><td style="font-size:12.5px">发布库 · 量产基线</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">NV-E1T92 / E960</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">vendor_flash 2.4</td><td><span class="tag tag-accent">已审批 · 基线</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">详情</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">FW_3.1.9</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">e0c2…7b14</td><td style="font-size:12.5px">发布库</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">NV-E1T92 / E960</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">vendor_flash 2.3</td><td><span class="tag tag-neutral">已归档</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">详情</button></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">FW_2.9.4-eng</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1a9d…3c58</td><td style="font-size:12.5px">工程调试版</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">NV-E3T84</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">vendor_flash 2.4</td><td><span style="font-size:10.5px;padding:2px 9px;border:1px dashed var(--color-neutral-500);color:var(--color-neutral-700)">仅实验工位</span></td><td><button class="btn btn-ghost" style="font-size:11.5px">详情</button></td></tr>
</tbody>
</table>
<div style="font-size:11px;color:var(--color-neutral-600)">固件哈希在登记与刷写前双重校验 · 刷写命令只能来自签名模板</div>
</div>
</sc-if>
<sc-if value="{{ tTool }}" hint-placeholder-val="{{ false }}">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px;animation:omIn .35s cubic-bezier(.2,.7,.2,1) both" data-screen-label="资产·工具连接器">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<table class="table">
<thead><tr><th>连接器</th><th>接入方式</th><th>版本</th><th>risk_level</th><th>healthcheck</th><th>复用</th><th></th></tr></thead>
<tbody>
<tr><td style="font-size:13px;font-weight:500">nvme-cli</td><td><span class="tag tag-neutral">CLI</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">2.9.1 · conn 1.2</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px;color:var(--color-neutral-600)">只读+管理</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 4m 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">3 客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">fio</td><td><span class="tag tag-neutral">CLI</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">3.36 · conn 1.4</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-neutral-200);padding:2px 7px">写入</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 4m 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">3 客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">vendor_flash(私有刷写)</td><td><span class="tag tag-neutral">CLI</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">2.4 · conn 0.9</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-accent-800);color:#f2f2f3;padding:2px 7px">固件更新</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 12m 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">本客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr style="background:#f7e9e6">
<td style="font-size:13px;font-weight:500">MPTool 量产工具</td><td><span style="font-size:10.5px;padding:2px 9px;border:1px solid #b03d2e;color:#8a2e22">GUI-wrap</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">5.1 · pywinauto 0.3</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid #b03d2e;color:#8a2e22;padding:2px 7px">格式化+刷写</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#8a2e22">受控例外</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">本客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">风险预案</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">smartctl</td><td><span class="tag tag-neutral">CLI</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">7.4 · conn 1.1</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px;color:var(--color-neutral-600)">只读</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 4m 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">3 客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">PyNVMe3 执行器</td><td><span class="tag tag-accent">Python API</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">24.6 · conn 0.7β</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-neutral-200);padding:2px 7px">写入</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 1h 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">2 客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">SANBlaze REST 桥</td><td><span class="tag tag-accent">REST API</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">10.5 · conn 0.5β</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:var(--color-neutral-200);padding:2px 7px">写入+注错</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-500)">未接入设备</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1 客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">blktrace 采集</td><td><span class="tag tag-neutral">CLI</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.3 · conn 1.0</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px;color:var(--color-neutral-600)">只读</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 4m 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">2 客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
<tr><td style="font-size:13px;font-weight:500">私有日志抓取 log_dump</td><td><span class="tag tag-neutral">CLI</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1.8 · conn 0.9</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 7px;color:var(--color-neutral-600)">只读</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">✓ 9m 前</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">本客户</td><td><button class="btn btn-ghost" style="font-size:11.5px">配置</button></td></tr>
</tbody>
</table>
<div style="font-size:11px;color:var(--color-neutral-600)">连接器接口:discover / precheck / execute / collect / cancel / health / normalize_result · 输入输出受 JSON Schema 约束 · 复用率目标 &gt;60%</div>
</div>
</sc-if>
<sc-if value="{{ tOob }}" hint-placeholder-val="{{ false }}">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px;animation:omIn .35s cubic-bezier(.2,.7,.2,1) both" data-screen-label="资产·带外控制器">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<table class="table">
<thead><tr><th>控制器</th><th>硬件</th><th>固件</th><th>通道能力</th><th>独立心跳</th><th>配对主机</th><th>状态</th></tr></thead>
<tbody>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">OOB-01</td><td style="font-size:12.5px">RPi 5 + 光耦 ATX 板</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">oob-agent 0.9.4</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">PWR</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">RST</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">HDMI</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">温度</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">1s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">HOST-A01</td><td><span class="tag tag-accent">在线</span></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">OOB-02</td><td style="font-size:12.5px">RPi 5 + 光耦 ATX 板</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">oob-agent 0.9.4</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">PWR</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">RST</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">温度</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">1s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">HOST-A02</td><td><span class="tag tag-accent">在线</span></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">OOB-03</td><td style="font-size:12.5px">JetKVM + ATX 扩展</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">adapter 0.4</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">PWR</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">RST</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">KVM画面</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">1s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">HOST-L02</td><td><span style="font-size:10.5px;padding:3px 9px;background:var(--color-accent-200);color:var(--color-accent-800);animation:omPulse 1.2s infinite">执行 L3</span></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">OOB-04</td><td style="font-size:12.5px">PiKVM v4 + ATX</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">adapter 0.4</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">PWR</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">RST</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">KVM画面</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">串口</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">2s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">HOST-L01</td><td><span class="tag tag-accent">在线</span></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">OOB-05</td><td style="font-size:12.5px">RPi CM4 工业载板</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">oob-agent 0.9.4</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">PWR</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">RST</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">AC</span> <span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:2px 6px">温度</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">1s</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">HOST-A03</td><td><span class="tag tag-accent">在线</span></td></tr>
<tr><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700;color:var(--color-neutral-600)">OOB-06</td><td style="font-size:12.5px;color:var(--color-neutral-600)">RPi 5 + 光耦 ATX 板</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">0.9.4 → 0.9.5</td><td><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px dashed var(--color-neutral-400);padding:2px 6px;color:var(--color-neutral-500)">升级中</span></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px"></td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">HOST-W02</td><td><span style="font-size:10.5px;padding:3px 9px;border:1px solid var(--color-divider);color:var(--color-neutral-600);background:repeating-linear-gradient(135deg,rgba(29,31,32,.10) 0 2px,transparent 2px 5px)">维护</span></td></tr>
</tbody>
</table>
<div style="font-size:11px;color:var(--color-neutral-600)">带外控制器走独立网络与独立心跳,用于区分主机崩溃与网络故障 · 兼容任意商品 KVM/PDU 作执行器</div>
</div>
</sc-if>
<sc-if value="{{ regOpen }}" hint-placeholder-val="{{ false }}">
<div class="dialog-backdrop" style="z-index:60;animation:omIn .22s ease both">
<div class="dialog blueprint elev-lg" style="background:var(--color-bg)">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<span class="dialog-title">登记 DUT</span>
<div style="display:flex;flex-direction:column;gap:10px">
<div class="field"><label>序列号(将与 BDF/设备路径交叉校验)</label><input class="input" placeholder="S6XN…"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="field"><label>型号</label><select class="input"><option>NV-E1T92</option><option>NV-E3T84</option><option>NV-E960</option></select></div>
<div class="field"><label>生命周期</label><select class="input"><option>测试专用</option><option>入库检</option><option>只读观察</option></select></div>
</div>
<div class="field"><label>允许动作</label>
<div style="display:flex;gap:14px;font-size:13px;padding-top:2px">
<label style="display:flex;gap:6px;align-items:center;cursor:pointer"><input type="checkbox" checked style="accent-color:#5980a6">破坏性写入</label>
<label style="display:flex;gap:6px;align-items:center;cursor:pointer"><input type="checkbox" checked style="accent-color:#5980a6">固件刷写</label>
<label style="display:flex;gap:6px;align-items:center;cursor:pointer"><input type="checkbox" style="accent-color:#5980a6">格式化/Sanitize</label>
</div>
</div>
</div>
<div class="dialog-actions">
<button class="btn btn-secondary" onClick="{{ closeReg }}">取消</button>
<button class="btn btn-primary" onClick="{{ closeReg }}">登记并生成资产标签</button>
</div>
</div>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:820}}">
class Component extends DCLogic {
state = { tab: 'dut', regOpen: false };
renderVals() {
const t = this.state.tab;
const set = v => () => this.setState({ tab: v });
return {
tHost: t === 'host', tDut: t === 'dut', tFw: t === 'fw', tTool: t === 'tool', tOob: t === 'oob',
setHost: set('host'), setDut: set('dut'), setFw: set('fw'), setTool: set('tool'), setOob: set('oob'),
regOpen: this.state.regOpen,
openReg: () => this.setState({ regOpen: true }),
closeReg: () => this.setState({ regOpen: false })
};
}
}
</script>
</body>
</html>
+179
View File
@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="compare" hint-size="100%,92px"></dc-import>
<div data-screen-label="版本比较" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:16px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">05 · A/B COMPARE</div>
<h2 style="margin:2px 0 0;font-size:30px">版本比较</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">fw_ab_powerstate_regression · run-20260725-0003(A) ⇄ run-20260726-0007+复跑(B) · 报告生成于任务结束后 42s</div>
</div>
<span style="margin-left:auto"></span>
<button class="btn btn-secondary" style="font-size:12.5px">导出报告</button>
<button class="btn btn-primary" style="font-size:13px">生成缺陷单草稿 · 2</button>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:16px 18px;margin-bottom:18px;border-color:#b03d2e;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:12px">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#b03d2e" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
<div>
<div style="font-family:var(--font-heading);font-weight:600;font-size:20px;letter-spacing:.02em">执行摘要:不建议 FW_3.3.0-rc2 进入下一阶段</div>
<div style="font-size:13px;color:var(--color-neutral-800);margin-top:2px">1 个阻断问题(数据完整性) · 2 个新增失败簇 · 4k 随机写性能回退超出已审核阈值</div>
</div>
<span style="margin-left:auto;font-size:11px;padding:4px 12px;background:#b03d2e;color:#f7f4f2;font-family:var(--font-heading);font-weight:600;letter-spacing:.08em">GATE · 阻断</span>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;font-family:ui-monospace,Menlo,monospace;font-size:10.5px">
<span style="border:1px solid #b03d2e;color:#8a2e22;padding:3px 9px">阻断 FS-0074 · DUT_DATA_FAILURE · LBA 0x2E41_00000x2E41_3FFF 校验失败 ×2</span>
<a href="Evidence.dc.html" style="border:1px solid var(--color-accent-400);color:var(--color-accent-700);padding:3px 9px;text-decoration:none">证据 run-20260726-0007 →</a>
<a href="Failures.dc.html" style="border:1px solid var(--color-accent-400);color:var(--color-accent-700);padding:3px 9px;text-decoration:none">失败簇 FS-0091 →</a>
</div>
<div style="font-size:11.5px;color:var(--color-neutral-600)">发布决策由工程师作出 — 系统只提供门禁证据与风险提示 · 审阅人:__________ · 日期:2026-07-27</div>
</div>
<div style="display:flex;gap:10px;align-items:center;border:1px solid var(--color-divider);padding:9px 14px;margin-bottom:18px;font-size:12px;flex-wrap:wrap">
<span style="font-size:10px;letter-spacing:.12em;color:var(--color-neutral-600);font-weight:500">环境锁定 ENV LOCK</span>
<span style="color:var(--color-accent-700)">✓ 主机 HOST-A01/L02 同批</span>
<span style="color:var(--color-accent-700)">✓ BIOS F26b</span>
<span style="color:var(--color-accent-700)">✓ OS/驱动一致</span>
<span style="color:var(--color-accent-700)">✓ 工具链 nvme-cli 2.9.1 · fio 3.36</span>
<span style="color:var(--color-accent-700)">✓ 工作流 v3 同签名</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">两次测试可比 · 无环境漂移</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:18px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:16px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:700">FW_3.2.1</span>
<span class="tag tag-outline">A · 基线</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">run-20260725-0003 · WS-04</span>
</div>
<div style="display:flex;align-items:flex-end;gap:20px">
<div><span style="font-family:var(--font-heading);font-weight:600;font-size:52px;line-height:.95">98<span style="font-size:24px">%</span></span><div style="font-size:11px;color:var(--color-neutral-600);margin-top:2px">循环通过率</div></div>
<div style="font-size:12px;line-height:1.7;color:var(--color-neutral-800);font-family:ui-monospace,Menlo,monospace">100/100 完成 · 失败 2<br>自动恢复 1 · 人工介入 0</div>
</div>
<div style="height:14px;border:1px solid var(--color-divider);display:flex">
<div style="width:98%;background:var(--color-accent)" title="通过"></div>
<div style="width:1%;background:var(--color-accent-200)"></div>
<div style="width:1%;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px)"></div>
</div>
<div style="display:flex;gap:14px;font-size:10px;color:var(--color-neutral-600)"><span><span style="display:inline-block;width:9px;height:8px;background:var(--color-accent);vertical-align:-1px"></span> 通过 98</span><span><span style="display:inline-block;width:9px;height:8px;background:var(--color-accent-200);vertical-align:-1px"></span> 恢复后通过 1</span><span><span style="display:inline-block;width:9px;height:8px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);vertical-align:-1px"></span> INFRA 1</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:16px;gap:10px;border-color:var(--color-accent-600)">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:700">FW_3.3.0-rc2</span>
<span class="tag tag-accent">B · 候选</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">run-20260726-0007 + 复跑 · WS-03</span>
</div>
<div style="display:flex;align-items:flex-end;gap:20px">
<div><span style="font-family:var(--font-heading);font-weight:600;font-size:52px;line-height:.95">89<span style="font-size:24px">%</span></span><div style="font-size:11px;color:var(--color-neutral-600);margin-top:2px">循环通过率 <span style="color:#8a2e22">▼ 9pp</span></div></div>
<div style="font-size:12px;line-height:1.7;color:var(--color-neutral-800);font-family:ui-monospace,Menlo,monospace">97/100 完成 · 失败 11<br>自动恢复 4 · 人工介入 1</div>
</div>
<div style="height:14px;border:1px solid var(--color-divider);display:flex">
<div style="width:89%;background:var(--color-accent)"></div>
<div style="width:4%;background:var(--color-accent-200)"></div>
<div style="width:5%;background:var(--color-accent-800)"></div>
<div style="width:2%;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px)"></div>
</div>
<div style="display:flex;gap:14px;font-size:10px;color:var(--color-neutral-600)"><span><span style="display:inline-block;width:9px;height:8px;background:var(--color-accent);vertical-align:-1px"></span> 通过 89</span><span><span style="display:inline-block;width:9px;height:8px;background:var(--color-accent-200);vertical-align:-1px"></span> 恢复后通过 4</span><span><span style="display:inline-block;width:9px;height:8px;background:var(--color-accent-800);vertical-align:-1px"></span> DUT 失败 5</span><span><span style="display:inline-block;width:9px;height:8px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);vertical-align:-1px"></span> INFRA 2</span></div>
</div>
</div>
<h6 style="color:var(--color-neutral-700);margin:0 0 10px">问题变化 · ISSUE DELTA</h6>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:18px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px;border-color:#b03d2e">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><span class="card-kicker" style="color:#8a2e22">新增 NEW</span><span style="margin-left:auto;font-family:var(--font-heading);font-weight:600;font-size:24px;color:#8a2e22">2</span></div>
<div style="display:flex;flex-direction:column;gap:7px;font-size:11.5px">
<div style="border:1px solid var(--color-divider);padding:7px 9px"><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;background:#b03d2e;color:#f7f4f2;padding:1px 6px">阻断</span> <a href="Failures.dc.html">FS-0074</a> 数据校验失败 ×2<div style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;color:var(--color-neutral-500);margin-top:3px">DUT_DATA_FAILURE</div></div>
<div style="border:1px solid var(--color-divider);padding:7px 9px"><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;background:var(--color-accent-800);color:#f2f2f3;padding:1px 6px">DUT</span> <a href="Failures.dc.html">FS-0091</a> S4 唤醒掉盘 ×3<div style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;color:var(--color-neutral-500);margin-top:3px">DUT_ENUMERATION_FAILURE</div></div>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><span class="card-kicker">已修复 FIXED</span><span style="margin-left:auto;font-family:var(--font-heading);font-weight:600;font-size:24px;color:var(--color-accent-700)">5</span></div>
<div style="font-size:11.5px;line-height:1.8;color:var(--color-neutral-800)">FS-0031 冷启动超时<br>FS-0044 SMART 读取挂起<br>FS-0052 Trim 后延迟尖峰<br>FS-0058 唤醒后链路降速<br>FS-0063 日志页 CRC</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><span class="card-kicker">仍存在 PERSIST</span><span style="margin-left:auto;font-family:var(--font-heading);font-weight:600;font-size:24px">3</span></div>
<div style="font-size:11.5px;line-height:1.8;color:var(--color-neutral-800)">FS-0067 fio 参数兼容(SCRIPT)<br>FS-0071 高温限速告警<br>FS-0088 主机蓝屏(INFRA)</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><span class="card-kicker">频率/severity 变化</span><span style="margin-left:auto;font-family:var(--font-heading);font-weight:600;font-size:24px">1</span></div>
<div style="font-size:11.5px;line-height:1.7;color:var(--color-neutral-800)">FS-0071 高温限速<div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;margin-top:4px">A:4 次 → B:9 次 <span style="color:#8a2e22">▲ 偶发转频发</span></div><div style="font-size:10.5px;color:var(--color-neutral-600);margin-top:3px">建议纳入观察名单并触发热相关单变量验证</div></div>
</div>
</div>
<div style="display:grid;grid-template-columns:1.5fr 1fr;gap:18px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">性能 · 温度 · 恢复</h6>
<table class="table">
<thead><tr><th>指标</th><th style="text-align:right">FW_3.2.1 A</th><th style="text-align:right">FW_3.3.0-rc2 B</th><th style="text-align:right">Δ</th><th>判定</th></tr></thead>
<tbody>
<tr><td style="font-size:12.5px">顺序读 MB/s</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">7,012</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">7,048</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:var(--color-accent-700)">+0.5%</td><td><span class="tag tag-outline">PASS</span></td></tr>
<tr><td style="font-size:12.5px">顺序写 MB/s</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">6,388</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">6,341</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">-0.7%</td><td><span class="tag tag-outline">PASS</span></td></tr>
<tr><td style="font-size:12.5px">4k 随机读 IOPS</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">1,148k</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">1,131k</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">-1.5%</td><td><span class="tag tag-outline">PASS</span></td></tr>
<tr style="background:#f7e9e6">
<td style="font-size:12.5px;font-weight:600">4k 随机写 IOPS</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">238k</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">218k</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#8a2e22;font-weight:700">-8.2%</td><td><span style="font-size:10.5px;padding:2px 8px;background:#b03d2e;color:#f7f4f2;font-family:ui-monospace,Menlo,monospace">超阈值 -5%</span></td>
</tr>
<tr><td style="font-size:12.5px">P99 写延迟 µs</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">377</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">412</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#8a2e22">+9.3%</td><td><span style="font-size:10.5px;padding:2px 8px;border:1px solid var(--color-divider);color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace">观察</span></td></tr>
<tr><td style="font-size:12.5px">S4 唤醒枚举耗时 s</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">1.8</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">3.4 / 超时×3</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#8a2e22">+89%</td><td><span style="font-size:10.5px;padding:2px 8px;background:var(--color-accent-800);color:#f2f2f3;font-family:ui-monospace,Menlo,monospace">关联 FS-0091</span></td></tr>
<tr><td style="font-size:12.5px">SMART 温度峰值 °C</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">44.1</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">46.2</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">+2.1</td><td><span class="tag tag-outline">PASS</span></td></tr>
<tr><td style="font-size:12.5px">自动恢复次数</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">1</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px">4</td><td style="text-align:right;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#8a2e22">×4</td><td><span style="font-size:10.5px;padding:2px 8px;border:1px solid var(--color-divider);color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace">观察</span></td></tr>
</tbody>
</table>
<div style="font-size:11px;color:var(--color-neutral-600)">阈值来自已审核工作流 v3 · 全部指标可回链原始 metrics.json 与 fio 输出</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">下一步建议 · NEXT</h6>
<div style="display:flex;flex-direction:column;gap:9px;font-size:12.5px;line-height:1.6">
<div style="border:1px solid var(--color-divider);padding:9px 11px"><strong style="font-family:var(--font-heading);letter-spacing:.02em">最短复现</strong><div style="color:var(--color-neutral-800);margin-top:3px">fw_ab 复现包:S4 循环 ×20 · 固定 WS-03 + SAMPLE-002 · 预计 3.5h</div><button class="btn btn-ghost" style="font-size:12px;margin-top:4px;padding-inline:0">创建复现任务 →</button></div>
<div style="border:1px solid var(--color-divider);padding:9px 11px"><strong style="font-family:var(--font-heading);letter-spacing:.02em">单变量验证</strong><div style="color:var(--color-neutral-800);margin-top:3px">固定固件 B,更换主机 HOST-L01(排除 BIOS F26b 电源时序)</div></div>
<div style="border:1px solid var(--color-divider);padding:9px 11px"><strong style="font-family:var(--font-heading);letter-spacing:.02em">建议责任团队</strong><div style="color:var(--color-neutral-800);margin-top:3px">固件电源管理组(FS-0091/0074) · 测试平台组(FS-0067 脚本兼容)</div></div>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">缺陷草稿 · ISSUE DRAFT</h6>
<div style="border:1px solid var(--color-divider);padding:9px 11px;font-size:12px;line-height:1.6">
<div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-500)">issue-draft.md · 待审阅</div>
<div style="margin-top:4px"><strong>[FW_3.3.0-rc2] S4 唤醒后 PCIe 链路训练失败导致掉盘</strong></div>
<div style="color:var(--color-neutral-700);margin-top:3px">复现率 3/100 · 附 Evidence Bundle ×3 · 最短复现包已生成 · 引用 kernel.log / power-actions</div>
<div style="display:flex;gap:8px;margin-top:7px"><button class="btn btn-secondary" style="font-size:11.5px;padding:4px 10px">推送 Jira</button><button class="btn btn-secondary" style="font-size:11.5px;padding:4px 10px">推送禅道</button></div>
</div>
</div>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:1080}}"></script>
</body>
</html>
+143
View File
@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
@keyframes omPulse{0%,100%{opacity:1}50%{opacity:.3}}
</style>
</helmet>
<div style="font-family:var(--font-body);background:var(--color-bg);border-bottom:1px solid var(--color-divider)">
<sc-if value="{{ stopped }}" hint-placeholder-val="{{ false }}">
<div style="animation:omIn .3s ease both;display:flex;align-items:center;gap:12px;background:#b03d2e;color:#f7f4f2;padding:8px 20px;font-size:13px">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
<strong style="font-family:var(--font-heading);letter-spacing:.06em">实验室处于安全停止状态</strong>
<span style="opacity:.85">全部任务已冻结,现场证据已保留,自动重启被禁止 · 触发人 quan.zhang · {{ stopTime }}</span>
<button onClick="{{ releaseStop }}" style="margin-left:auto;background:transparent;border:1px solid rgba(247,244,242,.55);color:#f7f4f2;padding:4px 12px;font-family:var(--font-heading);font-size:12px;letter-spacing:.05em;cursor:pointer" style-hover="background:rgba(247,244,242,.14)">解除安全停止(需审批)</button>
</div>
</sc-if>
<div style="display:flex;align-items:center;gap:18px;padding:0 20px;height:56px">
<a href="Dashboard.dc.html" style-hover="color:var(--color-accent-700)" style="transition:color .15s ease;flex:none;white-space:nowrap;text-decoration:none;color:var(--color-text);display:flex;flex-direction:column;gap:0;line-height:1">
<span style="font-family:var(--font-heading);font-weight:600;font-size:19px;letter-spacing:.08em">STORAGE LABOS</span>
<span style="font-size:9px;letter-spacing:.22em;color:var(--color-accent-700);margin-top:3px">REGRESSION WORKER · 多工位控制台</span>
</a>
<span style="flex:none;width:1px;height:26px;background:var(--color-divider)"></span>
<nav style="display:flex;align-items:stretch;gap:2px;height:100%">
<sc-for list="{{ items }}" as="it" hint-placeholder-count="8">
<a href="{{ it.href }}" aria-current="{{ it.cur }}" style="{{ it.style }}" style-hover="background:var(--color-accent-100);color:var(--color-accent-700)">
<span style="font-size:13.5px;line-height:1.1">{{ it.label }}</span>
<span style="font-size:8px;letter-spacing:.14em;opacity:.55;margin-top:2px">{{ it.en }}</span>
</a>
</sc-for>
</nav>
<span style="margin-left:auto"></span>
<span class="tag tag-neutral" style="flex:none;white-space:nowrap;gap:6px"><span style="width:6px;height:6px;background:var(--color-accent);display:inline-block"></span>内网部署 · 深圳一号实验室</span>
<span style="flex:none;white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;color:var(--color-neutral-700);min-width:62px">{{ clock }}</span>
<button onClick="{{ openStop }}" style="flex:none;white-space:nowrap;display:inline-flex;align-items:center;gap:7px;background:#b03d2e;border:1px solid #8a2e22;color:#f7f4f2;padding:8px 14px;font-family:var(--font-heading);font-weight:600;font-size:13px;letter-spacing:.08em;cursor:pointer" style-hover="background:#9a3527">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon></svg>
紧急停止
</button>
</div>
<div style="display:flex;align-items:center;gap:16px;padding:5px 20px;border-top:1px solid var(--color-divider);background:var(--color-surface);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10.5px;color:var(--color-neutral-700);letter-spacing:.02em">
<span style="display:inline-flex;align-items:center;gap:5px;color:var(--color-accent-700)"><span style="width:6px;height:6px;border-radius:50%;background:var(--color-accent);animation:omPulse 2s infinite"></span>事件总线 LIVE</span>
<span>工位 5/6 在线</span>
<a href="LiveRun.dc.html" style="color:var(--color-accent-800);text-decoration:none" style-hover="color:var(--color-accent)">运行 2</a>
<span>恢复中 1</span>
<a href="Tasks.dc.html" style="color:inherit;text-decoration:none" style-hover="color:var(--color-accent-700)">队列 3</a>
<span style="width:1px;height:12px;background:var(--color-divider)"></span>
<span>UCR(7D) 96.8%</span>
<span>介入密度 1.8 次/百小时</span>
<span>自动恢复成功率 87.5%</span>
<span style="margin-left:auto">最近人工介入 14:32 · WS-03</span>
</div>
<sc-if value="{{ stopOpen }}" hint-placeholder-val="{{ false }}">
<div class="dialog-backdrop" style="z-index:60;animation:omIn .22s ease both">
<div class="dialog blueprint elev-lg" style="width:min(520px,100%);background:var(--color-bg)">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px;color:#b03d2e">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
<span class="dialog-title" style="letter-spacing:.04em">紧急停止 · EMERGENCY STOP</span>
</div>
<div class="dialog-body" style="opacity:1">
<p style="margin-bottom:8px">将对<strong>全部 6 个工位</strong>执行安全停止,该动作写入不可变审计日志:</p>
<ul style="margin:0;padding-left:18px;font-size:13.5px;line-height:1.7;color:var(--color-neutral-800)">
<li>停止 2 个运行中任务(run-20260727-0001 / 0002),在当前检查点冻结</li>
<li>中断 WS-03 正在执行的 L3 带外 Reset 恢复流程</li>
<li>保留全部现场证据,禁止任何自动重启与供电动作</li>
</ul>
<div style="margin-top:12px;padding:9px 12px;border:1px dashed #b03d2e;color:#8a2e22;font-size:12.5px;background:#f7e9e6">恢复运行需要实验室负责人审批。误触发将计入基础设施误报率。</div>
</div>
<div class="dialog-actions">
<button class="btn btn-secondary" onClick="{{ closeStop }}">取消</button>
<button onClick="{{ confirmStop }}" style="display:inline-flex;align-items:center;gap:7px;background:#b03d2e;border:1px solid #8a2e22;color:#f7f4f2;padding:8px 16px;font-family:var(--font-heading);font-weight:600;font-size:14px;letter-spacing:.06em;cursor:pointer" style-hover="background:#9a3527">确认全部停止</button>
</div>
</div>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:120},&quot;active&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;dashboard&quot;,&quot;tasks&quot;,&quot;live&quot;,&quot;evidence&quot;,&quot;compare&quot;,&quot;failures&quot;,&quot;assets&quot;,&quot;workflows&quot;],&quot;default&quot;:&quot;dashboard&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;导航&quot;}}">
class Component extends DCLogic {
state = { stopOpen: false, stopped: false, stopTime: '', clock: '' };
componentDidMount() {
this._t = setInterval(() => {
this.setState({ clock: new Date().toLocaleTimeString('zh-CN', { hour12: false }) });
}, 1000);
}
componentWillUnmount() { clearInterval(this._t); }
renderVals() {
const active = this.props.active ?? 'dashboard';
const base = {
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
flex: 'none', whiteSpace: 'nowrap',
padding: '0 11px', textDecoration: 'none', color: 'var(--color-neutral-800)',
fontFamily: 'var(--font-heading)', fontWeight: 600, letterSpacing: '.03em',
borderBottom: '2px solid transparent', borderTop: '2px solid transparent',
transition: 'color .15s ease, background .15s ease, border-color .15s ease'
};
const items = [
{ key: 'dashboard', href: 'Dashboard.dc.html', label: '总览', en: 'OVERVIEW' },
{ key: 'tasks', href: 'Tasks.dc.html', label: '任务中心', en: 'TASKS' },
{ key: 'live', href: 'LiveRun.dc.html', label: '实时运行', en: 'LIVE RUN' },
{ key: 'evidence', href: 'Evidence.dc.html', label: '证据浏览器', en: 'EVIDENCE' },
{ key: 'compare', href: 'Compare.dc.html', label: '版本比较', en: 'A/B COMPARE' },
{ key: 'failures', href: 'Failures.dc.html', label: '失败中心', en: 'FAILURES' },
{ key: 'assets', href: 'Assets.dc.html', label: '资产中心', en: 'ASSETS' },
{ key: 'workflows', href: 'Workflows.dc.html', label: '工作流模板', en: 'WORKFLOWS' }
].map(it => ({
...it,
cur: it.key === active ? 'page' : null,
style: it.key === active
? { ...base, color: 'var(--color-accent-700)', borderBottom: '2px solid var(--color-accent)', background: 'var(--color-accent-100)' }
: base
}));
return {
items,
clock: this.state.clock,
stopOpen: this.state.stopOpen,
stopped: this.state.stopped,
stopTime: this.state.stopTime,
openStop: () => this.setState({ stopOpen: true }),
closeStop: () => this.setState({ stopOpen: false }),
confirmStop: () => this.setState({ stopOpen: false, stopped: true, stopTime: new Date().toLocaleTimeString('zh-CN', { hour12: false }) }),
releaseStop: () => this.setState({ stopped: false })
};
}
}
</script>
</body>
</html>
+429
View File
@@ -0,0 +1,429 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
@keyframes omPulse{0%,100%{opacity:1}50%{opacity:.3}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="dashboard" hint-size="100%,92px"></dc-import>
<div data-screen-label="总览 Dashboard" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:20px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">01 · OVERVIEW</div>
<h2 style="margin:2px 0 0;font-size:30px">总览</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">深圳一号实验室 · 6 工位 · 遥测每 2s 刷新 · 2026-07-27</div>
</div>
<span style="margin-left:auto"></span>
<div class="seg" style="background:var(--color-bg)">
<label class="seg-opt"><input type="radio" name="dashv" checked="{{ vA }}" onChange="{{ setA }}"><span>A · 工位网格</span></label>
<label class="seg-opt"><input type="radio" name="dashv" checked="{{ vB }}" onChange="{{ setB }}"><span>B · 指标墙</span></label>
<label class="seg-opt"><input type="radio" name="dashv" checked="{{ vC }}" onChange="{{ setC }}"><span>C · 作战板</span></label>
</div>
</div>
<sc-if value="{{ vA }}" hint-placeholder-val="{{ true }}">
<div data-screen-label="总览 · 变体A 工位网格" style="animation:omIn .35s cubic-bezier(.2,.7,.2,1) both">
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-bottom:22px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:6px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<span class="card-kicker">UCR · 无人值守完成率(7D)</span>
<div style="display:flex;align-items:flex-end;gap:10px">
<span style="font-family:var(--font-heading);font-weight:600;font-size:44px;line-height:.95">96.8<span style="font-size:22px">%</span></span>
<svg width="60" height="18" viewBox="0 0 60 18" style="margin-bottom:4px"><polyline stroke-linejoin="round" stroke-linecap="round" points="0,10 10,8 20,12 30,5 40,6 50,2 60,7" fill="none" stroke="var(--color-accent)" stroke-width="1.5" style="stroke-dasharray:90 90;stroke-dashoffset:90;animation:omDraw 1s ease .2s both"></polyline><circle cx="60" cy="7" r="2.5" fill="var(--color-accent)" style="animation:omPulse 2.4s infinite"></circle></svg>
</div>
<span style="font-size:11.5px;color:var(--color-accent-700)">▲ 0.6pp vs 上周 · 目标 98%</span>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:6px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<span class="card-kicker">释放工时 · HOURS FREED</span>
<div style="display:flex;align-items:flex-end;gap:10px">
<span style="font-family:var(--font-heading);font-weight:600;font-size:44px;line-height:.95">41.5<span style="font-size:20px"> h/周</span></span>
<svg width="60" height="18" viewBox="0 0 60 18" style="margin-bottom:4px"><polyline stroke-linejoin="round" stroke-linecap="round" points="0,14 10,12 20,11 30,9 40,8 50,6 60,4" fill="none" stroke="var(--color-accent)" stroke-width="1.5" style="stroke-dasharray:90 90;stroke-dashoffset:90;animation:omDraw 1s ease .2s both"></polyline><circle cx="60" cy="4" r="2.5" fill="var(--color-accent)" style="animation:omPulse 2.4s infinite"></circle></svg>
</div>
<span style="font-size:11.5px;color:var(--color-neutral-700)">≈ 0.9 工程师 · ¥32万/年等值</span>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:6px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<span class="card-kicker">介入密度 · TOUCHES /100H · 主指标</span>
<div style="display:flex;align-items:flex-end;gap:10px">
<span style="font-family:var(--font-heading);font-weight:600;font-size:44px;line-height:.95">1.8</span>
<svg width="60" height="18" viewBox="0 0 60 18" style="margin-bottom:4px"><polyline stroke-linejoin="round" stroke-linecap="round" points="0,4 10,6 20,5 30,9 40,10 50,12 60,13" fill="none" stroke="var(--color-accent)" stroke-width="1.5" style="stroke-dasharray:90 90;stroke-dashoffset:90;animation:omDraw 1s ease .2s both"></polyline><circle cx="60" cy="13" r="2.5" fill="var(--color-accent)" style="animation:omPulse 2.4s infinite"></circle></svg>
</div>
<span style="font-size:11.5px;color:var(--color-accent-700)">▼ 0.4 vs 上周 · 口径:任何人工触碰</span>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:6px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<span class="card-kicker">自动恢复成功率 · RECOVERY</span>
<div style="display:flex;align-items:flex-end;gap:10px">
<span style="font-family:var(--font-heading);font-weight:600;font-size:44px;line-height:.95">87.5<span style="font-size:22px">%</span></span>
<svg width="60" height="18" viewBox="0 0 60 18" style="margin-bottom:4px"><polyline stroke-linejoin="round" stroke-linecap="round" points="0,12 10,9 20,11 30,7 40,8 50,5 60,6" fill="none" stroke="var(--color-accent)" stroke-width="1.5" style="stroke-dasharray:90 90;stroke-dashoffset:90;animation:omDraw 1s ease .2s both"></polyline><circle cx="60" cy="6" r="2.5" fill="var(--color-accent)" style="animation:omPulse 2.4s infinite"></circle></svg>
</div>
<span style="font-size:11.5px;color:var(--color-neutral-700)">28/32 · L1 62% · L2 21% · L3 17%</span>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 340px;gap:22px;align-items:start">
<div>
<h6 style="color:var(--color-neutral-700);margin-bottom:12px">工位状态 · WORKSTATIONS 6</h6>
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">WS-01</span>
<span class="text-muted" style="font-size:11.5px">HOST-A01 · Win11 22631</span>
<span style="margin-left:auto;display:inline-flex;align-items:center;gap:6px;background:var(--color-accent);color:#f2f2f3;font-size:10.5px;letter-spacing:.06em;padding:3px 9px"><span style="width:5px;height:5px;border-radius:50%;background:#f2f2f3;animation:omPulse 1.6s infinite"></span>RUNNING</span>
</div>
<div>
<div style="font-family:var(--font-heading);font-weight:600;font-size:16.5px">fw_ab_powerstate_regression</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-600)"><a href="LiveRun.dc.html" style="text-decoration:none">run-20260727-0001</a> · FW_3.3.0-rc2(B) · 休眠恢复循环</div>
</div>
<div>
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--color-neutral-700);margin-bottom:4px"><span>轮次 67 / 100</span><span>预计完成 23:40</span></div>
<div style="height:6px;border:1px solid var(--color-divider);position:relative"><div style="position:absolute;inset:0;width:67%;background-color:var(--color-accent);background-image:repeating-linear-gradient(90deg,rgba(242,242,243,.3) 0 6px,transparent 6px 14px);animation:omScan 1.1s linear infinite"></div><div style="position:absolute;top:-2px;bottom:-2px;left:25%;width:1px;background:var(--color-divider)"></div><div style="position:absolute;top:-2px;bottom:-2px;left:50%;width:1px;background:var(--color-divider)"></div><div style="position:absolute;top:-2px;bottom:-2px;left:75%;width:1px;background:var(--color-divider)"></div></div>
</div>
<div style="display:flex;gap:14px;font-size:11px;color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace"><span>DUT SAMPLE-001</span><span>{{ t01 }}°C</span><span style="color:var(--color-accent-700)">心跳 {{ hb01 }}</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">WS-02</span>
<span class="text-muted" style="font-size:11.5px">HOST-A02 · Win11 22631</span>
<span style="margin-left:auto;display:inline-flex;align-items:center;gap:6px;background:var(--color-accent);color:#f2f2f3;font-size:10.5px;letter-spacing:.06em;padding:3px 9px"><span style="width:5px;height:5px;border-radius:50%;background:#f2f2f3;animation:omPulse 1.6s infinite"></span>RUNNING</span>
</div>
<div>
<div style="font-family:var(--font-heading);font-weight:600;font-size:16.5px">fw_retention_72h</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-600)">run-20260727-0002 · FW_3.2.1(A) · 高温数据保持</div>
</div>
<div>
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--color-neutral-700);margin-bottom:4px"><span>51.2 / 72 h</span><span>预计完成 07-29 09:10</span></div>
<div style="height:6px;border:1px solid var(--color-divider);position:relative"><div style="position:absolute;inset:0;width:71%;background-color:var(--color-accent);background-image:repeating-linear-gradient(90deg,rgba(242,242,243,.3) 0 6px,transparent 6px 14px);animation:omScan 1.1s linear infinite"></div><div style="position:absolute;top:-2px;bottom:-2px;left:25%;width:1px;background:var(--color-divider)"></div><div style="position:absolute;top:-2px;bottom:-2px;left:50%;width:1px;background:var(--color-divider)"></div><div style="position:absolute;top:-2px;bottom:-2px;left:75%;width:1px;background:var(--color-divider)"></div></div>
</div>
<div style="display:flex;gap:14px;font-size:11px;color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace"><span>DUT SAMPLE-004</span><span>{{ t02 }}°C</span><span style="color:var(--color-accent-700)">心跳 {{ hb02 }}</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px;border-color:var(--color-accent-600)">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">WS-03</span>
<span class="text-muted" style="font-size:11.5px">HOST-L02 · Ubuntu 22.04</span>
<span style="margin-left:auto;display:inline-flex;align-items:center;gap:6px;background:var(--color-accent-200);color:var(--color-accent-800);font-size:10.5px;letter-spacing:.06em;padding:3px 9px;animation:omPulse 1.2s infinite">RECOVERING · L3</span>
</div>
<div>
<div style="font-family:var(--font-heading);font-weight:600;font-size:16.5px">fw_ab_powerstate_regression</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-600)"><a href="Evidence.dc.html" style="text-decoration:none">run-20260726-0007</a> · FW_3.3.0-rc2(B) · 轮次 41/100 冻结</div>
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:10.5px;font-family:ui-monospace,Menlo,monospace">
<span style="padding:2px 7px;border:1px solid var(--color-divider);color:var(--color-neutral-500);text-decoration:line-through">L1 软恢复</span>
<span style="color:var(--color-neutral-400)"></span>
<span style="padding:2px 7px;border:1px solid var(--color-divider);color:var(--color-neutral-500);text-decoration:line-through">L2 OS重启</span>
<span style="color:var(--color-neutral-400)"></span>
<span style="padding:2px 7px;background:var(--color-accent-800);color:#f2f2f3;animation:omPulse 1.2s infinite">L3 带外RESET</span>
<span style="color:var(--color-neutral-400)"></span>
<span style="padding:2px 7px;border:1px dashed var(--color-divider);color:var(--color-neutral-500)">L4 AC断电</span>
</div>
<div style="display:flex;gap:14px;font-size:11px;color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace"><span>DUT SAMPLE-002</span><span>43.8°C</span><span style="color:#8a2e22">心跳丢失 {{ lost03 }}</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">WS-04</span>
<span class="text-muted" style="font-size:11.5px">HOST-L01 · Ubuntu 22.04</span>
<span class="tag tag-outline" style="margin-left:auto;font-size:10.5px;letter-spacing:.06em">IDLE</span>
</div>
<div class="text-muted" style="font-size:13px;line-height:1.6">空闲,可接受调度。<br>上一任务 <a href="Compare.dc.html" style="text-decoration:none;font-family:ui-monospace,Menlo,monospace;font-size:11.5px">run-20260725-0003</a> · PASSED · 100/100 循环</div>
<div style="display:flex;gap:14px;font-size:11px;color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace"><span>DUT 未绑定</span><span>36.2°C</span><span style="color:var(--color-accent-700)">心跳 {{ hb04 }}</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">WS-05</span>
<span class="text-muted" style="font-size:11.5px">HOST-A03 · Win11 22631</span>
<span class="tag tag-accent" style="margin-left:auto;font-size:10.5px;letter-spacing:.06em">QUEUED</span>
</div>
<div>
<div style="font-family:var(--font-heading);font-weight:600;font-size:16.5px">fw_coldboot_matrix</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-600)">预约 今日 22:00 · 500 次冷启动 · FW_3.3.0-rc2(B)</div>
</div>
<div style="display:flex;gap:14px;font-size:11px;color:var(--color-neutral-700);font-family:ui-monospace,Menlo,monospace"><span>DUT SAMPLE-006</span><span>35.9°C</span><span style="color:var(--color-accent-700)">心跳 {{ hb05 }}</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.05) 0 2px,transparent 2px 7px)">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700;color:var(--color-neutral-600)">WS-06</span>
<span class="text-muted" style="font-size:11.5px">HOST-W02 · Win10 19045</span>
<span style="margin-left:auto;font-size:10.5px;letter-spacing:.06em;padding:3px 9px;border:1px solid var(--color-divider);color:var(--color-neutral-600);background:repeating-linear-gradient(135deg,rgba(29,31,32,.12) 0 2px,transparent 2px 5px)">OFFLINE</span>
</div>
<div class="text-muted" style="font-size:13px;line-height:1.6">维护窗口至 07-28 09:00<br>带外控制器 OOB-06 固件升级中</div>
<div style="display:flex;gap:14px;font-size:11px;color:var(--color-neutral-500);font-family:ui-monospace,Menlo,monospace"><span>DUT 已下线</span><span></span><span>心跳 —</span></div>
</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:18px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<h6 style="margin:0;color:var(--color-neutral-700)">需要人工处理</h6>
<span style="margin-left:auto;font-family:var(--font-heading);font-weight:600;font-size:18px;color:#8a2e22">3</span>
</div>
<div style="display:flex;flex-direction:column;gap:9px">
<div style="transition:background .15s ease,border-color .15s ease;display:flex;gap:9px;align-items:flex-start;border:1px solid var(--color-divider);padding:9px 10px" style-hover="background:var(--color-accent-100);border-color:var(--color-accent-400)">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 6px;border:1px dashed var(--color-neutral-500);color:var(--color-neutral-700);white-space:nowrap">UNKNOWN</span>
<div style="font-size:12.5px;line-height:1.5">run-20260726-0011 证据不足待分类<div><a href="Failures.dc.html" style="font-size:11.5px">FS-0102 去分类 →</a></div></div>
</div>
<div style="transition:background .15s ease,border-color .15s ease;display:flex;gap:9px;align-items:flex-start;border:1px solid var(--color-divider);padding:9px 10px" style-hover="background:var(--color-accent-100);border-color:var(--color-accent-400)">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 6px;background:#b03d2e;color:#f7f4f2;white-space:nowrap">阻断</span>
<div style="font-size:12.5px;line-height:1.5">FW_3.3.0-rc2 存在阻断问题,需研发确认<div><a href="Compare.dc.html" style="font-size:11.5px">FS-0074 查看对比 →</a></div></div>
</div>
<div style="transition:background .15s ease,border-color .15s ease;display:flex;gap:9px;align-items:flex-start;border:1px solid var(--color-divider);padding:9px 10px" style-hover="background:var(--color-accent-100);border-color:var(--color-accent-400)">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 6px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.12) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);color:var(--color-neutral-800);white-space:nowrap">维护</span>
<div style="font-size:12.5px;line-height:1.5">WS-06 维护窗口今日到期,确认恢复排产<div><a href="Assets.dc.html" style="font-size:11.5px">资产中心 →</a></div></div>
</div>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">最近恢复动作</h6>
<div style="display:flex;flex-direction:column;font-family:ui-monospace,Menlo,monospace;font-size:11px">
<div style="display:flex;gap:8px;padding:7px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:35</span><span>WS-03 L3 带外 Reset 已触发</span><span style="margin-left:auto;color:var(--color-accent-700);animation:omPulse 1.4s infinite">进行中</span></div>
<div style="display:flex;gap:8px;padding:7px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:33</span><span>WS-03 L2 OS 重启超时(120s)</span><span style="margin-left:auto;color:#8a2e22">失败</span></div>
<div style="display:flex;gap:8px;padding:7px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:32</span><span>WS-03 L1 Agent 软恢复无响应</span><span style="margin-left:auto;color:#8a2e22">失败</span></div>
<div style="display:flex;gap:8px;padding:7px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">02:31</span><span>WS-03 心跳丢失 → L3 Reset · 4m12s</span><span style="margin-left:auto;color:var(--color-accent-700)">成功</span></div>
<div style="display:flex;gap:8px;padding:7px 0"><span style="color:var(--color-neutral-500)">昨 23:04</span><span>WS-01 L1 进程清理 · 38s</span><span style="margin-left:auto;color:var(--color-accent-700)">成功</span></div>
</div>
<a href="Evidence.dc.html" style="font-size:11.5px">全部恢复记录 →</a>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">今日队列</h6>
<div style="display:flex;flex-direction:column;font-size:12.5px">
<div style="display:flex;gap:8px;padding:7px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="font-family:ui-monospace,Menlo,monospace;color:var(--color-neutral-500)">22:00</span><span>fw_coldboot_matrix · WS-05</span><span class="tag tag-accent" style="margin-left:auto">P1</span></div>
<div style="display:flex;gap:8px;padding:7px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="font-family:ui-monospace,Menlo,monospace;color:var(--color-neutral-500)">23:45</span><span>fw_ab 续跑 cycle 41 · WS-03</span><span class="tag tag-neutral" style="margin-left:auto">P2</span></div>
<div style="display:flex;gap:8px;padding:7px 0"><span style="font-family:ui-monospace,Menlo,monospace;color:var(--color-neutral-500)">明 06:00</span><span>incoming_qc_quick · WS-04</span><span class="tag tag-neutral" style="margin-left:auto">P3</span></div>
</div>
<a href="Tasks.dc.html" style="font-size:11.5px">任务中心 →</a>
</div>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ vB }}" hint-placeholder-val="{{ false }}">
<div data-screen-label="总览 · 变体B 指标墙" style="animation:omIn .35s cubic-bezier(.2,.7,.2,1) both">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:20px 24px;margin-bottom:20px;flex-direction:row;gap:36px;align-items:stretch">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="min-width:250px;display:flex;flex-direction:column;justify-content:center">
<span class="card-kicker">北极星 · NORTH STAR</span>
<div style="font-family:var(--font-heading);font-weight:600;font-size:84px;line-height:.95;margin:6px 0 4px">96.8<span style="font-size:36px">%</span></div>
<div style="font-size:14px;color:var(--color-neutral-800)">无人值守完成率 UCR · 近 7 天</div>
<div style="font-size:12px;color:var(--color-accent-700);margin-top:6px">▲ 0.6pp vs 上周 · 产品化目标 98%+</div>
</div>
<div style="flex:1;border-left:1px solid var(--color-divider);padding-left:32px">
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--color-neutral-600);margin-bottom:6px"><span>UCR 趋势 · 07-21 → 07-27</span><span style="font-family:ui-monospace,Menlo,monospace">计划任务 214 · 无人完成 207</span></div>
<svg width="100%" height="170" viewBox="0 0 700 170" preserveAspectRatio="none" style="display:block">
<line x1="40" y1="150" x2="660" y2="150" stroke="rgba(29,31,32,.16)" stroke-width="1"></line>
<line x1="40" y1="90" x2="660" y2="90" stroke="rgba(29,31,32,.10)" stroke-width="1"></line>
<line x1="40" y1="30" x2="660" y2="30" stroke="rgba(29,31,32,.10)" stroke-width="1"></line>
<text x="32" y="153" font-size="9" fill="#7a7a7d" text-anchor="end" font-family="Menlo,monospace">94</text>
<text x="32" y="93" font-size="9" fill="#7a7a7d" text-anchor="end" font-family="Menlo,monospace">96</text>
<text x="32" y="33" font-size="9" fill="#7a7a7d" text-anchor="end" font-family="Menlo,monospace">98</text>
<path d="M40,117 L143,78 L246,126 L349,54 L452,63 L555,27 L658,66 L658,150 L40,150 Z" fill="rgba(89,128,166,.13)"></path>
<polyline stroke-linejoin="round" stroke-linecap="round" points="40,117 143,78 246,126 349,54 452,63 555,27 658,66" fill="none" stroke="#5980a6" stroke-width="1.8" style="stroke-dasharray:700 700;stroke-dashoffset:700;animation:omDraw 1.2s ease .15s both"></polyline>
<g fill="#f2f2f3" stroke="#416180" stroke-width="1.5"><circle cx="40" cy="117" r="3"></circle><circle cx="143" cy="78" r="3"></circle><circle cx="246" cy="126" r="3"></circle><circle cx="349" cy="54" r="3"></circle><circle cx="452" cy="63" r="3"></circle><circle cx="555" cy="27" r="3"></circle><circle cx="658" cy="66" r="3.5" style="animation:omPulse 2.4s infinite"></circle></g>
<text x="40" y="166" font-size="9" fill="#7a7a7d" text-anchor="middle" font-family="Menlo,monospace">07-21</text>
<text x="246" y="166" font-size="9" fill="#7a7a7d" text-anchor="middle" font-family="Menlo,monospace">07-23</text>
<text x="452" y="166" font-size="9" fill="#7a7a7d" text-anchor="middle" font-family="Menlo,monospace">07-25</text>
<text x="658" y="166" font-size="9" fill="#7a7a7d" text-anchor="middle" font-family="Menlo,monospace">07-27</text>
</svg>
</div>
</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:20px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:4px"><i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i><span class="card-kicker">释放工时</span><span style="font-family:var(--font-heading);font-weight:600;font-size:32px;line-height:1">41.5 h/周</span><span style="font-size:11px;color:var(--color-neutral-700)">≈ 0.9 工程师 · ¥32万/年</span></div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:4px"><i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i><span class="card-kicker">介入密度</span><span style="font-family:var(--font-heading);font-weight:600;font-size:32px;line-height:1">1.8 /百h</span><span style="font-size:11px;color:var(--color-accent-700)">主指标 · 任何触碰计 1 次</span></div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:4px"><i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i><span class="card-kicker">自动恢复成功率</span><span style="font-family:var(--font-heading);font-weight:600;font-size:32px;line-height:1">87.5%</span><span style="font-size:11px;color:var(--color-neutral-700)">28/32 次失联</span></div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:4px"><i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i><span class="card-kicker">证据完整率</span><span style="font-family:var(--font-heading);font-weight:600;font-size:32px;line-height:1">100%</span><span style="font-size:11px;color:var(--color-neutral-700)">关键字段零丢失</span></div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:4px"><i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i><span class="card-kicker">基础设施误报率</span><span style="font-family:var(--font-heading);font-weight:600;font-size:32px;line-height:1">3.1%</span><span style="font-size:11px;color:var(--color-accent-700)">▼ 护栏指标,单独监控</span></div>
</div>
<div style="display:grid;grid-template-columns:1.2fr 1fr 1fr;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:12px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">版本风险 · FIRMWARE GATE</h6>
<div style="border:1px solid var(--color-divider);padding:12px">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">FW_3.3.0-rc2</span>
<span class="text-muted" style="font-size:11px">候选(B)</span>
<span style="margin-left:auto;font-size:10.5px;padding:3px 9px;background:#b03d2e;color:#f7f4f2;letter-spacing:.06em">暂缓发布</span>
</div>
<div style="display:flex;gap:8px;margin-top:10px;font-size:11px;font-family:ui-monospace,Menlo,monospace">
<span style="padding:3px 8px;background:#b03d2e;color:#f7f4f2">阻断 1</span>
<span style="padding:3px 8px;background:var(--color-accent-800);color:#f2f2f3">新增 2</span>
<span style="padding:3px 8px;border:1px solid var(--color-accent-400);color:var(--color-accent-700)">已修复 5</span>
<span style="padding:3px 8px;border:1px solid var(--color-divider);color:var(--color-neutral-700)">仍存在 3</span>
</div>
<div style="font-size:12px;margin-top:10px;color:var(--color-neutral-800)">阻断:FS-0074 数据校验失败(LBA 0x2E41xxxx) · <a href="Compare.dc.html" style="font-size:11.5px">查看 A/B 对比 →</a></div>
</div>
<div style="border:1px solid var(--color-divider);padding:12px;display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:13px;font-weight:700">FW_3.2.1</span>
<span class="text-muted" style="font-size:11px">基线(A)</span>
<span class="tag tag-outline" style="margin-left:auto">稳定基线</span>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">介入原因分布 · 30D</h6>
<div style="display:flex;flex-direction:column;gap:8px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px">
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>DUT_ENUMERATION_FAILURE</span><span>8</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:100%;background:var(--color-accent-800)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>TEST_SCRIPT_FAILURE</span><span>7</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:87%;background:var(--color-neutral-400)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>INFRA_NETWORK_FAILURE</span><span>6</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:75%;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>INFRA_HOST_OS_FAILURE</span><span>5</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:62%;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>INFRA_AGENT_FAILURE</span><span>4</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:50%;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>UNKNOWN</span><span>2</span></div><div style="height:8px;border:1px dashed var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:25%;background:var(--color-neutral-200)"></div></div></div>
</div>
<div style="display:flex;gap:12px;font-size:10px;color:var(--color-neutral-600);border-top:1px solid var(--color-divider);padding-top:8px"><span><span style="display:inline-block;width:10px;height:8px;background:var(--color-accent-800);vertical-align:-1px"></span> DUT 信号</span><span><span style="display:inline-block;width:10px;height:8px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);vertical-align:-1px"></span> INFRA 噪声</span></div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">工位利用率 · 7D</h6>
<div style="display:flex;flex-direction:column;gap:8px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px">
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>WS-01</span><span>92%</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:92%;background:var(--color-accent)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>WS-02</span><span>88%</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:88%;background:var(--color-accent)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>WS-03</span><span>61%</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:61%;background:var(--color-accent)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>WS-04</span><span>34%</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:34%;background:var(--color-accent)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>WS-05</span><span>12%</span></div><div style="height:8px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:12%;background:var(--color-accent)"></div></div></div>
<div><div style="display:flex;justify-content:space-between;margin-bottom:3px"><span>WS-06</span><span>0% 维护</span></div><div style="height:8px;border:1px solid var(--color-divider);background:repeating-linear-gradient(135deg,rgba(29,31,32,.10) 0 2px,transparent 2px 5px)"></div></div>
</div>
<div style="font-size:11px;color:var(--color-neutral-700);border-top:1px solid var(--color-divider);padding-top:8px">夜间(22:0008:00)利用率 74% · 无人值守时段占比 63%</div>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ vC }}" hint-placeholder-val="{{ false }}">
<div data-screen-label="总览 · 变体C 作战板" style="animation:omIn .35s cubic-bezier(.2,.7,.2,1) both;display:grid;grid-template-columns:216px 1fr 320px;gap:18px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:0">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0 0 8px;color:var(--color-neutral-700)">工位 RAIL</h6>
<div style="display:flex;gap:9px;align-items:center;padding:8px 2px;border-bottom:1px solid rgba(29,31,32,.08)"><span style="width:7px;height:7px;border-radius:50%;background:var(--color-accent);animation:omPulse 1.6s infinite"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700">WS-01</span><span style="font-size:10.5px;color:var(--color-neutral-600)">cycle 67/100</span><span style="margin-left:auto;font-size:9.5px;color:var(--color-accent-700)">RUN</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:8px 2px;border-bottom:1px solid rgba(29,31,32,.08)"><span style="width:7px;height:7px;border-radius:50%;background:var(--color-accent);animation:omPulse 1.6s infinite"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700">WS-02</span><span style="font-size:10.5px;color:var(--color-neutral-600)">51.2/72h</span><span style="margin-left:auto;font-size:9.5px;color:var(--color-accent-700)">RUN</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:8px 2px;border-bottom:1px solid rgba(29,31,32,.08)"><span style="width:7px;height:7px;border-radius:50%;border:1.5px solid var(--color-accent-700);background:var(--color-accent-200);animation:omPulse 1s infinite"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700">WS-03</span><span style="font-size:10.5px;color:var(--color-neutral-600)">L3 RESET</span><span style="margin-left:auto;font-size:9.5px;color:var(--color-accent-800)">RCVR</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:8px 2px;border-bottom:1px solid rgba(29,31,32,.08)"><span style="width:7px;height:7px;border-radius:50%;border:1px solid var(--color-neutral-500)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700">WS-04</span><span style="font-size:10.5px;color:var(--color-neutral-600)">空闲</span><span style="margin-left:auto;font-size:9.5px;color:var(--color-neutral-600)">IDLE</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:8px 2px;border-bottom:1px solid rgba(29,31,32,.08)"><span style="width:7px;height:7px;border-radius:50%;border:1px solid var(--color-accent-600);background:var(--color-accent-100)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700">WS-05</span><span style="font-size:10.5px;color:var(--color-neutral-600)">22:00 预约</span><span style="margin-left:auto;font-size:9.5px;color:var(--color-accent-700)">QUE</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:8px 2px"><span style="width:7px;height:7px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 1px,transparent 1px 3px);border:1px solid var(--color-neutral-500)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700;color:var(--color-neutral-600)">WS-06</span><span style="font-size:10.5px;color:var(--color-neutral-600)">维护</span><span style="margin-left:auto;font-size:9.5px;color:var(--color-neutral-500)">OFF</span></div>
<div style="border-top:1px solid var(--color-divider);margin-top:8px;padding-top:8px;font-size:10.5px;color:var(--color-neutral-600);font-family:ui-monospace,Menlo,monospace">UCR 96.8% · 介入 1.8/百h</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">运行中任务 · 2</h6>
<table class="table">
<thead><tr><th>RUN</th><th>工位</th><th>模板 / 固件</th><th style="width:150px">进度</th><th>心跳</th><th>ETA</th><th></th></tr></thead>
<tbody>
<tr>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:12px"><a href="LiveRun.dc.html" style="text-decoration:none">run-…0727-0001</a></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:12px">WS-01</td>
<td><div style="font-size:12.5px">fw_ab_powerstate_regression</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-600)">FW_3.3.0-rc2(B)</div></td>
<td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:67%;background-color:var(--color-accent);background-image:repeating-linear-gradient(90deg,rgba(242,242,243,.3) 0 6px,transparent 6px 14px);animation:omScan 1.1s linear infinite"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px">67/100</span></div></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">{{ hb01 }}</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">23:40</td>
<td><a class="btn btn-ghost" href="LiveRun.dc.html" style="font-size:12px">监视</a></td>
</tr>
<tr>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:12px">run-…0727-0002</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:12px">WS-02</td>
<td><div style="font-size:12.5px">fw_retention_72h</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-600)">FW_3.2.1(A)</div></td>
<td><div style="display:flex;align-items:center;gap:7px"><div style="flex:1;height:5px;border:1px solid var(--color-divider)"><div style="animation:omGrow .9s cubic-bezier(.2,.7,.2,1) both;height:100%;width:71%;background-color:var(--color-accent);background-image:repeating-linear-gradient(90deg,rgba(242,242,243,.3) 0 6px,transparent 6px 14px);animation:omScan 1.1s linear infinite"></div></div><span style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px">51/72h</span></div></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-accent-700)">{{ hb02 }}</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">07-29</td>
<td><a class="btn btn-ghost" href="LiveRun.dc.html" style="font-size:12px">监视</a></td>
</tr>
</tbody>
</table>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><h6 style="margin:0;color:var(--color-neutral-700)">队列与恢复 · 3</h6><a href="Tasks.dc.html" style="margin-left:auto;font-size:11.5px">任务中心 →</a></div>
<table class="table">
<thead><tr><th>优先级</th><th>任务</th><th>工位</th><th>计划时间</th><th>状态</th></tr></thead>
<tbody>
<tr><td><span class="tag tag-accent">P1</span></td><td style="font-size:12.5px">fw_coldboot_matrix · 500 次冷启动</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px">WS-05</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">今日 22:00</td><td><span class="tag tag-outline">已预约</span></td></tr>
<tr><td><span class="tag tag-neutral">P2</span></td><td style="font-size:12.5px">fw_ab 从检查点 cycle 41 续跑</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px">WS-03</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">恢复后自动</td><td><span style="font-size:10.5px;padding:3px 9px;background:var(--color-accent-200);color:var(--color-accent-800);animation:omPulse 1.4s infinite">等待恢复</span></td></tr>
<tr><td><span class="tag tag-neutral">P3</span></td><td style="font-size:12.5px">incoming_qc_quick · 来料抽检 12 盘</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:12px">WS-04</td><td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">明日 06:00</td><td><span class="tag tag-outline">已预约</span></td></tr>
</tbody>
</table>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">事件流 · EVENT BUS</h6>
<div style="display:flex;flex-direction:column;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;line-height:1.5">
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:35:12</span><span style="color:var(--color-accent-800);white-space:nowrap">[OOB]</span><span>WS-03 RESET_SENT · 光耦通道 CH2 · 脉冲 250ms</span></div>
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:33:40</span><span style="color:#8a2e22;white-space:nowrap">[RCVR]</span><span>WS-03 L2 超时,升级 L3(策略 esc-default-v2)</span></div>
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:32:29</span><span style="color:var(--color-neutral-600);white-space:nowrap">[EVID]</span><span>WS-03 死机快照已抓取:HDMI 帧 + 串口尾部 2KB</span></div>
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:32:07</span><span style="color:#8a2e22;white-space:nowrap">[HB]</span><span>WS-03 心跳丢失 &gt;30s,判定 HOST_UNREACHABLE</span></div>
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:28:51</span><span style="color:var(--color-accent-700);white-space:nowrap">[TEST]</span><span>WS-01 cycle 67 数据校验 PASS · 4k_mixed 300s</span></div>
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:25:03</span><span style="color:var(--color-accent-700);white-space:nowrap">[TEST]</span><span>WS-01 hibernate → resume 成功 · 唤醒耗时 11.2s</span></div>
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-500)">14:20:44</span><span style="color:var(--color-neutral-600);white-space:nowrap">[SYS]</span><span>WS-05 预检通过:磁盘/网络/OOB/工具版本/时钟同步</span></div>
<div style="display:flex;gap:8px;padding:6px 0"><span style="color:var(--color-neutral-500)">14:18:02</span><span style="color:var(--color-neutral-600);white-space:nowrap">[SYS]</span><span>FW_3.3.0-rc3.bin 已登记 · SHA256 9f2c…41aa · 待审批</span></div>
</div>
</div>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:900},&quot;defaultVariant&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;],&quot;default&quot;:&quot;a&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;布局&quot;},&quot;liveTicker&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;演示&quot;}}">
class Component extends DCLogic {
state = { variant: null, tick: 0 };
componentDidMount() {
this._t = setInterval(() => {
if (this.props.liveTicker ?? true) this.setState(s => ({ tick: s.tick + 1 }));
}, 2000);
}
componentWillUnmount() { clearInterval(this._t); }
renderVals() {
const v = this.state.variant ?? (this.props.defaultVariant ?? 'a');
const k = this.state.tick;
const hb = i => ((k * 2 + i * 3) % 7 + 1) + 's 前';
return {
vA: v === 'a', vB: v === 'b', vC: v === 'c',
setA: () => this.setState({ variant: 'a' }),
setB: () => this.setState({ variant: 'b' }),
setC: () => this.setState({ variant: 'c' }),
hb01: hb(0), hb02: hb(1), hb04: hb(2), hb05: hb(3),
lost03: (47 + k * 2) + 's',
t01: (42.5 + Math.sin(k / 2) * 0.4).toFixed(1),
t02: (45.1 + Math.cos(k / 3) * 0.3).toFixed(1)
};
}
}
</script>
</body>
</html>
+597
View File
@@ -0,0 +1,597 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<!-- @template name="Deck" description="A twenty-one-slide presentation starter: cover, contents, dividers, columns, quadrants, a data table, SVG charts and timeline, bleed imagery, a quote and a close, on the theme's tokens" -->
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css" />
<title>Industry — deck</title>
<script src="./ds-base.js"></script>
<script src="./image-slot.js"></script>
<style>
/* Template scaffolding — slide layout and projection-scale sizing only; every
color, font, radius and shadow comes from ../../styles.css. Change one
number here to re-size the whole deck.
THE WHOLE-GRID DECK (review rounds 4-5, replacing the containers-only
reading at Barron's pick): the modular grid the blueprint grammar
implies is drawn in full on every slide — the Grid Systems cover reading
of the same direction. Type registers baseline-ON-rule, display sizes
step up hard (Müller-Brockmann size contrast), photographs become grid
objects, and the system's "+" registration marks keep marking what is
measured. The field is EQUAL-MARGINED — 120px from all four stage
edges — and nearly every element seats on a DRAWN rule (each seat's
comment carries its derivation). */
deck-stage {
--type-display: 288px; /* 140 → 288: the grid variant's size contrast */
--type-title: 96px; /* 76 → 96 */
--type-subtitle: 40px;
--type-body: 34px;
--type-small: 28px;
--type-kicker: 24px;
--pad-x: 120px; /* round 2 (Barron): equal field margins — 120 on all four sides */
--page-foot-note: "Confidential — July 2026"; /* the deck-wide imprint text (page furniture; see the PAGE FURNITURE note below) */
--pad-top: 96px;
--pad-bottom: 120px;
--baseline: 48px; /* grid unit — one default-text line: the 34px Barlow body at its ~1.41
deck leading (a compact sans). The field's bottom rule is the 20th gridline (960); in
this variant the cover/close meta seats a half-step ABOVE it, on the 936 cell-bottom
rule (see .pin-bottom), and 960 itself stays the hard vertical budget: the bleed's
note ends exactly ON it, the reviewed deck's own footer convention. */
}
.slide {
display: flex; flex-direction: column;
isolation: isolate; /* the slide is its own stacking context, so the board's
negative z-index (below) floors it above the slide's OWN background but
beneath every in-flow and positioned child — without isolation a negative
z-index would drop the board behind the slide background entirely, and
with z-index:auto a positioned board paints ABOVE in-flow ink (CSS paint
order), the opposite of the drawn-grid intent */
background: var(--color-bg); color: var(--color-text);
font-family: var(--font-body);
text-wrap: pretty; /* inherited: better breaks, no orphans on wrapped blocks */
padding: var(--pad-top) var(--pad-x) var(--pad-bottom);
print-color-adjust: exact; -webkit-print-color-adjust: exact;
}
/* — the board: the whole modular grid, drawn — */
/* The variant's one big move. The field is equal-margined: 120px from every
stage edge (x 120..1800, y 120..960), so the frame reads symmetric and the
bottom rule IS the deck's established 960 footer/budget line. Columns:
four 384px modules with 48px gutters (pitch 432) — one baseline unit of
gutter, the Grid Systems cover's wider vertical doubling. Rows: seven
groups at pitch 120 (2.5 baseline units), each a 96px cell plus a 24px
caption row — the cover's doubled-line rhythm, every rule top on the
deck's 48/24px baseline lattice. Drawn as
border + four repeating gradients (origin border-box, so offsets count
from the field edge); zero-flow overlay like the reviewed deck's cells —
delete it and no baseline moves. Ink rules on paper; the dividers and
bleeds re-derive the rule color per ground (see below). Four "+" marks
register the field corners; content-anchored crosses stay on the
containers that keep them. */
.board {
--board-rule: color-mix(in srgb, var(--color-divider) 50%, transparent); /* review
round 10 (Barron, pre-ship): the grid fades by half — scoped here so the
chart grids and timeline line (data furniture on the same divider token)
keep their full ink. The table has no rules of its own — the board rules
it — so its ruling fades with the board, a deliberate part of the fade. */
position: absolute; z-index: -1; /* beneath all content — see .slide's isolation
note. The bleed slide overrides this: there the board deliberately lies ON
the photograph, beneath the scrim (see .bleed .board) */
left: var(--pad-x); top: calc(2.5 * var(--baseline));
width: calc(1680px + 1px); height: calc(17.5 * var(--baseline) + 1px);
border: 1px solid var(--board-rule, var(--color-divider));
background-origin: border-box; background-clip: border-box;
pointer-events: none;
background:
repeating-linear-gradient(to bottom, var(--board-rule, var(--color-divider)) 0 1px, transparent 1px 120px),
repeating-linear-gradient(to bottom, transparent 0 96px, var(--board-rule, var(--color-divider)) 96px 97px, transparent 97px 120px),
repeating-linear-gradient(to right, var(--board-rule, var(--color-divider)) 0 1px, transparent 1px 432px),
repeating-linear-gradient(to right, transparent 0 384px, var(--board-rule, var(--color-divider)) 384px 385px, transparent 385px 432px);
}
/* — shared pieces — */
/* Baseline grid: text sits on a 48px rhythm (--baseline) measured from the
slide's top edge. Snapped text blocks are trimmed to their cap and baseline
edges (text-box), so a block's bottom edge IS its last baseline and the next
block's first baseline lands N gridlines down with
margin-top: calc(N * var(--baseline) - 1cap). Compact rhythms (chart axes
and ticks) sit on the 24px half-step. Review the rhythm with ?baselines in
the URL, or press B (script in the helmet) — half-step lines draw
fainter than full ones. In this variant the board's rules are a subset of
the same lattice: row-group tops are full gridlines, cell bottoms are
half-steps, so "baseline on the rule" and "baseline on the grid" are one
statement. */
.kicker {
position: absolute; top: calc(2 * var(--baseline) - 1cap); /* baseline on the reviewed
deck's own kicker line (96) — now half a unit ABOVE the field's 120 top rule,
so the rule and its corner crosses stay clean */
left: var(--pad-x); text-box: trim-both cap alphabetic;
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: var(--type-kicker); letter-spacing: 0.08em; text-transform: uppercase;
color: var(--color-accent);
font-feature-settings: "tnum" 1;
}
.k-right { left: auto; right: var(--pad-x); } /* the cover/close kicker rides the
top rule flush right — the Grid Systems cover's author line */
.slide-title {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: var(--type-title); line-height: calc(2 * var(--baseline)); letter-spacing: 0; margin: 0;
margin-left: -0.052em; /* optical left alignment — measured for Barlow
Condensed SemiBold in the reviewed deck; em-scaled, so it holds at 96px */
text-box: trim-both cap alphabetic;
}
.pin-bottom { position: absolute; left: var(--pad-x); right: var(--pad-x); bottom: calc(var(--pad-bottom) + var(--baseline) / 2); } /* baseline
on the 936 caption rule — inside the field, clear of the 960 corner crosses;
the last caption row [936,960] is the imprint's band */
.meta { display: flex; align-items: baseline; gap: 18px; font-size: var(--type-kicker); color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.meta span { text-box: trim-both cap alphabetic; } /* baseline on the 936 caption rule — see .pin-bottom */
/* PAGE FURNITURE — on by default. To remove the page numbers and footer
text deck-wide, add data-no-page-furniture to the deck's <x-import>
mount (a one-line tweak — it renders on the <deck-stage>); to change
the text, edit --page-foot-note in
the deck's token block. The imprint sits where this board documents its
imprint's band: flush left on the axis, baseline ON the 936 caption rule
(the last caption row [936,960] of the field), at the meta row's scale
and 55% ink, the number tabular. Carried by the content slides (hero,
quote, split and the half-bleeds included — on the half-bleeds it
follows the copy half, because small muted type cannot pass the
type-over-photography rule) AND by the dividers: a blueprint sheet
carries its imprint on every ground, so the dividers keep it with the
ink re-derived to the page-corner marks' 55% paper (measured against
the steel ground in the render audit). The cover and close keep their
meta rows on the same rule instead, and the full bleed's photograph
owns its edges. Every slide still counts, so the printed number stays
truthful to position. */
deck-stage { counter-reset: page; }
.slide { counter-increment: page; }
.page-foot {
position: absolute; left: var(--pad-x); bottom: calc(var(--pad-bottom) + var(--baseline) / 2); /* baseline ON the 936 caption rule, like .pin-bottom */
font-size: var(--type-kicker);
color: color-mix(in srgb, var(--color-text) 55%, transparent);
text-box: trim-both cap alphabetic; /* bottom edge = baseline */
}
.page-foot::before { content: counter(page) " · "; font-feature-settings: "tnum" 1; }
.page-foot::after { content: var(--page-foot-note); }
deck-stage[data-no-page-furniture] .page-foot, deck-stage[data-no-page-furniture] .page-head { display: none !important; }
.divider .page-foot { color: color-mix(in srgb, var(--color-bg) 55%, transparent); } /* the imprint survives the steel sheet in the page corners' paper-alpha ink */
.half-bleed:not(.flip) .page-foot { left: 984px; } /* the imprint follows the copy half, seated on column 3's rule — type never sits on the photograph (deck-templates.md law) */
/* — cover and close — */
/* The Grid Systems cover, in industry's inks: the full board behind a
display line seated baseline-ON-rule (row-group 5's top, y 600), with the
description split into two caption columns under it — a short left cell
on the axis, the long cell on column 2 — first baselines ON the 696
caption rule, clear of the display's descenders (~55px below 600). The
kicker rides flush right above the field; the meta sits on the last
caption rule (936), inside the field. Most of the grid stays empty on
purpose. */
.cover { justify-content: flex-start; }
.cover .rise { position: absolute; inset: 0 var(--pad-x); }
.display {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: var(--type-display); line-height: calc(6 * var(--baseline)); letter-spacing: 0; margin: 0;
margin-left: -0.052em; /* optical left alignment, em-scaled — see .slide-title */
margin-top: calc(12.5 * var(--baseline) - 1cap); /* baseline exactly on the group-5 top rule (600) */
text-box: trim-both cap alphabetic;
}
.cover-sub { font-size: var(--type-small); line-height: var(--baseline); margin: 0;
position: absolute; left: 0; max-width: 768px;
top: calc(14.5 * var(--baseline) - 1cap); /* first baseline ON the 696 caption rule —
clear of the 288px display's descenders (~55px below 600) */
text-box: trim-both cap alphabetic; }
.cover-sub-a { left: 0; max-width: 360px; }
.cover-sub-b { left: 432px; max-width: 816px; } /* column 2's left edge (x 552) */
/* — contents — */
/* Contents as the cover's caption rows: no panel, no added rules — the
board IS the ruling. Each entry's baseline sits ON a caption rule
(456 / 576 / 696 / 816), the 24px caption row beneath reading as the
doubled line of the reference cover. Numbers hold the axis; labels start
on column 2 (x 552) — secondary starts in this deck always sit on a
column edge. */
.toc { position: absolute; inset: 0 var(--pad-x); font-size: var(--type-subtitle); }
.toc-row { position: absolute; left: 0; right: 0; }
.toc-row > span { text-box: trim-both cap alphabetic; position: absolute; top: 0; }
.toc-row > .toc-label { left: 432px; }
.toc-num { font-size: var(--type-kicker); color: var(--color-accent); font-feature-settings: "tnum" 1; }
.toc-row { top: calc(9.5 * var(--baseline)); } /* baseline on the 456 caption rule; the rows
re-seat below via nth-child so markup stays the reviewed deck's */
.toc-row:nth-child(2) { top: calc(12 * var(--baseline)); }
.toc-row:nth-child(3) { top: calc(14.5 * var(--baseline)); }
.toc-row:nth-child(4) { top: calc(17 * var(--baseline)); }
.toc-row > span { top: calc(-1cap); } /* each span trims to its cap; the row's
top IS the rule, so the baseline lands on it */
/* — section dividers — */
/* Dividers keep their own ground — the deep steel blueprint sheet — and the
variant draws the board through it in paper-alpha hairlines: the section
break is a fresh sheet of the same grid. Ghost numeral and title seat
baseline-ON-rule like everything else (ghost on 600, title on 816), both
flush on the axis; the "+" page corners keep their registration spots.
Contrast re-derived for the steel ground: paper type 12.6:1, ghost at
~2.2:1 (a drawing showing through the sheet), board rules decorative at
9% paper (halved in round 10's pre-ship fade). */ /* seats: ghost baseline ON the group-5 top rule (600),
title ON the 816 caption rule — see each declaration */
.divider { justify-content: flex-start; background: var(--color-accent-900); color: var(--color-bg); }
.divider .board { --board-rule: color-mix(in srgb, var(--color-bg) 9%, transparent); } /* half of the pre-round-10 18% — the fade keeps the per-ground ratio */
.divider .board > .corner { color: color-mix(in srgb, var(--color-bg) 55%, transparent); }
.divider .rise { position: absolute; inset: 0 var(--pad-x); }
.page-corner {
position: absolute; width: 15px; height: 15px;
color: color-mix(in srgb, var(--color-bg) 55%, transparent);
}
.page-corner::before, .page-corner::after { content: ""; position: absolute; background: currentColor; }
.page-corner::before { left: 7px; top: 0; width: 1px; height: 100%; }
.page-corner::after { top: 7px; left: 0; width: 100%; height: 1px; }
.page-corner.tl { top: 41px; left: 41px; }
.page-corner.tr { top: 41px; right: 41px; }
.page-corner.bl { bottom: 41px; left: 41px; }
.page-corner.br { bottom: 41px; right: 41px; }
/* — registration crosses (the cell-corner grammar, kept) — */
.cell { position: absolute; border: 1px solid var(--color-divider); pointer-events: none; }
.cell > .corner, .board > .corner { position: absolute; width: 15px; height: 15px; color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.cell > .corner::before, .cell > .corner::after,
.board > .corner::before, .board > .corner::after { content: ""; position: absolute; background: currentColor; }
.cell > .corner::before, .board > .corner::before { left: 7px; top: 0; width: 1px; height: 100%; }
.cell > .corner::after, .board > .corner::after { top: 7px; left: 0; width: 100%; height: 1px; }
.cell > .corner.tl, .board > .corner.tl { top: -8px; left: -8px; }
.cell > .corner.tr, .board > .corner.tr { top: -8px; right: -8px; }
.cell > .corner.bl, .board > .corner.bl { bottom: -8px; left: -8px; }
.cell > .corner.br, .board > .corner.br { bottom: -8px; right: -8px; }
/* -8px = the 7px arm offset + the 1px border: absolute offsets resolve from
the PADDING box, inside the border — each cross centers exactly on its
rule intersection, the reviewed deck's convention. */
.ghost-num {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 480px; line-height: 1; letter-spacing: 0;
color: var(--color-accent-700);
position: absolute; top: calc(12.5 * var(--baseline) - 1cap); /* baseline on the
group-5 top rule (600) */
text-box: trim-both cap alphabetic;
margin-left: -0.036em; /* ghosts lead with "0" — optical value from the reviewed deck, em-scaled */
}
.divider .slide-title { position: absolute; top: calc(17 * var(--baseline) - 1cap); } /* baseline on the 816 caption rule */
/* — content slides — */
.content { padding-top: calc(5 * var(--baseline)); /* title baseline on the 5th gridline —
row 2's top rule (240), clear of the pinned kicker row */ }
.content > .slide-title, .content > .chart-head { margin-top: -1cap; } /* cap-trimmed: the baseline, not the cap top, sits on the rule */
.content > .bullets { margin-top: calc(2.5 * var(--baseline) - 1cap); } /* first baseline on
the group-3 top rule (360), one row group below the title's */
.content > figure { margin-top: calc(2 * var(--baseline)); } /* svg top on the 336 caption
rule; the plots' axis line (svg y 360) lands ON the 696 caption rule */
.content > .table { margin-top: calc(0.5 * var(--baseline)); } /* table top at 264: the 96px
header row closes its baseline ON the group-3 top rule (360) */
.content > .chart-caption { margin-top: calc(2 * var(--baseline) - 1cap); }
.bullets { display: flex; flex-direction: column; max-width: 1220px; margin: 0; padding: 0; list-style: none; font-size: var(--type-body); }
.bullets li { position: relative; line-height: var(--baseline); text-box: trim-both cap alphabetic; }
.bullets li + li { margin-top: calc(2.5 * var(--baseline) - 1cap); } /* one thought per
row group: single-line bullets seat on successive group-top rules
(360 / 480 / 600) — the variant's pitch (the reviewed deck: every other
gridline) */
.bullets li::before, .bullets li::after { content: ""; position: absolute; background: var(--color-accent); }
.bullets li::before { left: -37px; top: calc(0.5cap - 8px); width: 2px; height: 16px; }
.bullets li::after { left: -44px; top: calc(0.5cap - 1px); width: 16px; height: 2px; }
/* — column layouts — */
/* Columns are the board's own bays: the two-column setting takes two
modules per column (x 120 and 984, 816px each, the drawn 48px gutter
between), and the three-column setting takes one module apiece (x 120,
552, 984) leaving module 4 DELIBERATELY empty — the Grid Systems cover's
empty-cell statement; on a four-module board three equal full-width
columns cannot seat on drawn rules, so the grid wins and the measure
narrows (a module runs ≈21 characters of condensed head, ≈26 of small
note — notes, not essays). No rules are added: the board is the ruling.
Seats: head baselines ON the group-3 top rule (360), note baselines ON
the 456 caption rule, wrapped lines riding the 48px lattice below. */
.cols { display: grid; column-gap: var(--baseline); margin-top: calc(2.5 * var(--baseline)); /* container top ON the group-3 rule; the cap term lives on the h3, where it resolves in the heading face (the two-family decks' -1cap lesson) */ }
.cols-2 { grid-template-columns: 816px 816px; }
.cols-3 { grid-template-columns: repeat(3, 384px); }
.cols h3 { font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: var(--type-subtitle); line-height: var(--baseline); margin: 0;
margin-top: -1cap; /* baseline ON the group-3 top rule (360); 1cap in the h3's own face */
text-box: trim-both cap alphabetic; }
.cols p { font-size: var(--type-small); line-height: var(--baseline); margin: 0;
margin-top: calc(2 * var(--baseline) - 1cap); /* first baseline ON the 456 caption rule */
text-box: trim-both cap alphabetic; }
/* — quadrants — */
/* The board IS the matrix — no division is drawn because every division
is already there: the vertical axis is the columns 2/3 gutter (center
960), the horizontal axis is the group-5 top rule (600). Cells are
two-module x two-row-group bays: heads seat their baselines ON the band
top rules (360 upper, 600 lower — the lower heads hang from the crossing
rule itself, the way every seated element does), one-liners ON the
caption rules below (456 / 696), wraps on the lattice. The axis pair
seats ON the group-7 top rule (840), left label on the axis, right label
flush right (the cover kicker's k-right precedent), one row group clear
of the 936 imprint. Left column = lower x; the y reading (top row
smaller work) is carried by the cell copy — a y pair has no seat that
doesn't fight the imprint band or the axis, so the horizontal pair does
the labeling (the shared-spec minimum). */
.quad { display: grid; grid-template-columns: 816px 816px; column-gap: var(--baseline);
grid-template-rows: calc(5 * var(--baseline)) calc(5 * var(--baseline));
margin-top: calc(2.5 * var(--baseline)); /* matrix top = the group-3 top rule (360) */ }
.quad h3 { font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: var(--type-subtitle); line-height: var(--baseline); margin: 0;
margin-top: -1cap; /* baseline ON the cell's band-top rule (360 / 600) */
text-box: trim-both cap alphabetic; }
.quad p { font-size: var(--type-small); line-height: var(--baseline); margin: 0;
margin-top: calc(2 * var(--baseline) - 1cap); /* one-liner ON the caption rule (456 / 696) */
text-box: trim-both cap alphabetic; }
.quad-axis { display: flex; justify-content: space-between;
margin-top: -1cap; /* label baselines ON the group-7 top rule (840) */
font-size: var(--type-kicker); letter-spacing: 0.08em; text-transform: uppercase;
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.quad-axis span { text-box: trim-both cap alphabetic; }
/* — data table (component class at projection scale) — */
/* The board rules the table: the component's own hairlines are suppressed
(a variant style call — no rules are drawn twice) and the rows take the
board's 120px row groups ONE-PER-ROW, each row's text baseline ON its
caption rule (456 / 576 / 696 / 816), the header on the group-3 top rule
(360). Columns seat on the grid columns — Role on the axis, Value on
column 2 (552), ground on column 3 (984), Use on column 4 (1416) — and
the swatch hangs LEFT of the axis like every other mark, its bottom edge
on the row's rule. */
.slide .table { max-width: 1680px; width: 1680px; font-size: 29px; font-feature-settings: "tnum" 1;
table-layout: fixed; border-collapse: collapse; }
/* Column plan: three 432px columns — one grid module each — and the rest
to Use. Carried on the header row's cells (table-layout: fixed reads row
one) rather than a <colgroup>: the Design Component pipeline parses the
template outside a real <table> context, where col elements are dropped,
and a fixed-layout table without them would fall back to equal columns. */
.slide .table th:nth-child(-n+3) { width: 432px; }
.slide .table thead tr { height: calc(2 * var(--baseline)); }
.slide .table tbody tr { height: calc(2.5 * var(--baseline)); } /* one board row group per data row */
.slide .table th, .slide .table td {
text-box: trim-both cap alphabetic; text-align: left; font-weight: 400;
border: none; background: none; vertical-align: bottom;
padding: 0 24px calc(0.5 * var(--baseline)) 0;
}
.slide .table th { font-size: var(--type-kicker); }
.slide .table thead th { padding-bottom: 0; } /* bottom-anchored: the header row closes ON the
group-3 top rule (360), each data row's LAST baseline sits 24px above its row's bottom —
on the caption rules 456 / 576 / 696 / 816 — and a wrapped cell grows upward into
its own cell instead of past the field (the vertical budget, kept by construction) */
.slide .table tbody tr:hover { background: none; }
.slide .table th:first-child, .slide .table td:first-child { padding-left: 0; position: relative; }
.slide .table th:last-child, .slide .table td:last-child { padding-right: 0; }
.slide .table .swatch { position: absolute; left: -72px; bottom: calc(0.5 * var(--baseline)); } /* hangs
left of the axis like every mark; bottom edge ON the row's rule */
.swatch { display: inline-block; width: 48px; height: 48px; border-radius: 0; /* square-corner
doctrine; 48px = one grid unit, bottom edge on the row's rule */ box-shadow: var(--shadow-sm); }
/* — hero figure — */
/* The variant's loudest size contrast: the figure at 432px, flush on the
axis, baseline ON the group-5 top rule (600); the caption moves to column
3 (x 984), its first baseline ON the 696 caption rule — the number and
its note read corner-to-corner across the empty grid. */
.hero { justify-content: flex-start; }
.hero-num {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 432px; line-height: 1; letter-spacing: 0; margin: 0;
color: var(--color-accent);
position: absolute; left: var(--pad-x); top: calc(12.5 * var(--baseline) - 1cap);
text-box: trim-both cap alphabetic;
margin-left: -0.010em; /* leads with the near-bearingless "1" — reviewed value, em-scaled */
}
.hero-caption { font-size: var(--type-small); line-height: var(--baseline); margin: 0;
position: absolute; left: 984px; top: calc(14.5 * var(--baseline) - 1cap); max-width: 816px;
text-box: trim-both cap alphabetic; }
/* — charts (static SVG on the tokens; no script, no chart library) — */
/* The plots seat their anatomy ON drawn rules — svg top at 336 (a caption
rule), the x-axis ON the 696 caption rule, and the line chart's 10k
gridlines (120px apart, the row-group pitch) phased by the axis onto the
drawn caption rules (576 / 456). Bar x-geometry is derived for the 1680
field. The board's column rules pass behind the plots — the drawing
stays on its grid. */
.chart { display: block; width: 100%; height: auto; overflow: visible; }
.chart text {
font-family: var(--font-body); font-size: 24px;
fill: color-mix(in srgb, var(--color-text) 55%, transparent);
font-feature-settings: "tnum" 1;
}
.chart .grid, .chart .axis { stroke: var(--color-divider); stroke-width: 1; }
.chart .bar { fill: none; stroke: var(--color-neutral-600); stroke-width: 1; }
.chart .bar-hi { fill: var(--color-accent); stroke: none; }
.chart .val { fill: color-mix(in srgb, var(--color-text) 70%, transparent); }
.chart .ref { stroke: var(--color-accent-700); stroke-width: 1; stroke-dasharray: 8 8; }
.chart .ref-label { fill: var(--color-accent-700); }
.chart .hi-label { fill: var(--color-accent-700); }
.chart .series-a, .key .series-a { stroke: var(--color-accent); stroke-width: 3; fill: none; }
.chart .series-b, .key .series-b { stroke: var(--color-neutral-800); stroke-width: 3; fill: none; stroke-dasharray: 10 8; }
.chart-caption { font-size: 25px; line-height: var(--baseline); max-width: 88ch; margin: 0; text-box: trim-both cap alphabetic; }
.chart-head { display: flex; align-items: baseline; justify-content: space-between;
font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: var(--type-title); }
.chart-legend { display: flex; align-items: baseline; gap: 16px; font-family: var(--font-body); font-weight: 400; font-size: var(--type-kicker); color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.chart-legend span { text-box: trim-both cap alphabetic; }
.chart-legend .key { width: 60px; height: 4px; overflow: visible; position: relative; top: -7px; }
.chart-legend .key:not(:first-child) { margin-left: 24px; }
.chart .tl-line { stroke: var(--color-divider); stroke-width: 1; }
.chart .tl-node { stroke: var(--color-neutral-600); stroke-width: 1.5; }
.chart .tl-node-hi-fill { fill: var(--color-accent); }
.chart .tl-node-hi { stroke: var(--color-bg); stroke-width: 1.5; }
.chart .tl-date { fill: color-mix(in srgb, var(--color-text) 55%, transparent); letter-spacing: 0.08em; }
.chart .tl-date-hi { fill: var(--color-accent-700); letter-spacing: 0.08em; }
.chart .tl-label { fill: var(--color-text); font-size: 30px; font-family: var(--font-heading); font-weight: var(--font-heading-weight); }
/* The timeline figure drops to the mid-field so its line sits ON the
group-4 top rule (480): svg top at 384, dates above, labels below, the
notes' baselines landing exactly ON the group-5 top rule (600). Node
pitch is 360 — milestone positions are data, not grid furniture. The
column-seated alternative (pitch 432, nodes on 120/552/984/1416/1800,
re-checked for the round-2 geometry) stays rejected: the fifth
milestone's ~340px notes would either overflow the stage or,
right-anchored, collide with the fourth's — and mixed anchoring
re-breaks the reviewed one-left-edge-per-milestone rule. */
.fig-timeline { margin-top: calc(3 * var(--baseline)) !important; }
/* — image slide — */
/* The photograph becomes a grid object: columns 3-4 by row-groups 2-7
exactly (x 984..1800, y 240..936), the blueprint frame's "+" marks seated
on board intersections. The copy keeps the axis in columns 1-2. */
.split { flex-direction: row; align-items: flex-start; gap: 0; padding-top: calc(5 * var(--baseline)); }
.split-copy { flex: none; width: 768px; display: flex; flex-direction: column; }
.split-copy .slide-title { margin-top: -1cap; }
.split-copy .note { font-size: var(--type-small); line-height: var(--baseline); margin: calc(2.5 * var(--baseline) - 1cap) 0 0;
text-box: trim-both cap alphabetic; max-width: 768px; }
.split-figure { position: absolute; left: 984px; top: calc(5 * var(--baseline)); width: 816px; height: calc(14.5 * var(--baseline)); margin: 0; }
.split-figure image-slot { width: 100%; height: 100%; } /* the grid-object box (columns 3-4 ×
row-groups 2-7) does the framing; cover is the component's own default */
.split-figure > .corner { pointer-events: none; } /* the registration crosses overlap the
slot by a few px — decorative marks must not eat its hover/drop events
(image-slot usage contract); scoped here rather than in styles.css, whose
.corner serves the whole system */
/* — full-bleed image slide — */
/* The board is drawn OVER the photograph in paper-alpha — the blueprint
laid onto the image — and UNDER the scrim, so the rules fade out exactly
where the type needs its measured ground. Type treatment unchanged from
the reviewed deck (it passes the type-over-photography rule): full-ink
note, kicker stepped to accent-700. */
.bleed .board { --board-rule: color-mix(in srgb, var(--color-bg) 13%, transparent); /* half of the pre-round-10 25%, rounded to a whole step */
z-index: auto; /* the one slide where the board rides ABOVE its photograph:
DOM-order painting — after the positioned figure, before the positioned
scrim — so photo → board → scrim → copy stack as documented */ }
.bleed .board > .corner { color: color-mix(in srgb, var(--color-bg) 55%, transparent); }
.bleed-figure { position: absolute; inset: 0; margin: 0; }
.bleed-figure image-slot { width: 100%; height: 100%; } /* framing is the component's (fit=cover default) */
.bleed-scrim {
position: absolute; inset: 0; pointer-events: none; /* the slot beneath is interactive (hover controls, drops) — overlays must not eat its events (image-slot usage contract) */
background:
linear-gradient(to top,
color-mix(in srgb, var(--color-bg) 98%, transparent),
color-mix(in srgb, var(--color-bg) 95%, transparent) 24%,
color-mix(in srgb, var(--color-bg) 60%, transparent) 42%,
transparent 66%),
linear-gradient(to bottom,
color-mix(in srgb, var(--color-bg) 92%, transparent),
color-mix(in srgb, var(--color-bg) 80%, transparent) 12%,
transparent 26%);
}
.bleed-copy { position: absolute; left: var(--pad-x); right: var(--pad-x); bottom: var(--pad-bottom); pointer-events: none; } /* text over the slot — must not eat its hover/drop events */
.bleed .kicker { color: var(--color-accent-700); pointer-events: none; /* sits on the photograph — same usage contract as the scrim */ }
.bleed-copy .note {
font-size: var(--type-small); line-height: var(--baseline); max-width: 56ch;
margin: calc(2 * var(--baseline) - 1cap) 0 0;
text-box: trim-both cap alphabetic;
color: var(--color-text);
} /* bottom-anchored block: the note's LAST baseline sits ON the field's 960
bottom rule; its earlier baselines (864/912) and the title's (768) ride the
48px lattice, not drawn rules — over a photograph the scrim, not the board,
is the ground (the board fades out beneath it) */
/* — half-bleed image slides — */
/* The photograph takes grid columns, not a raw half-stage: it bleeds to
three edges and stops at the gutter (base: columns 1-2, inner edge on
the 936 gutter rule; .flip mirrors to columns 3-4, starting exactly on
column 3's rule at 984), and the copy starts on a column rule — x 984
(column 3) for the base, the axis for the mirror. The board draws first,
the photograph covers its half. */
.half-bleed { padding-top: calc(5 * var(--baseline)); }
.half-bleed > .slide-title { margin-top: -1cap; max-width: 816px; }
.half-bleed .note {
font-size: var(--type-small); line-height: var(--baseline); max-width: 816px;
margin: calc(2 * var(--baseline) - 1cap) 0 0;
text-box: trim-both cap alphabetic;
}
.half-bleed .bullets { margin-top: calc(2.5 * var(--baseline) - 1cap); max-width: 816px; }
.half-bleed .bullets li + li { margin-top: calc(4 * var(--baseline) - 1cap); } /* items here
run up to two lines: the 4-unit gap from a two-line item's last baseline seats
each bullet's FIRST baseline back on a group-top rule (360 / 600 / 840), and a
one-line item simply leaves more air before the next */
.half-bleed-figure { position: absolute; inset: 0 auto 0 0; width: 936px; margin: 0; }
.half-bleed-figure image-slot { width: 100%; height: 100%; } /* framing is the component's (fit=cover default) */
.half-bleed:not(.flip) > .slide-title { margin-left: calc(864px - 0.052em); } /* column 3 (x 984), preserving the optical shift */
.half-bleed:not(.flip) .note,
.half-bleed:not(.flip) .bullets { margin-left: 864px; }
.half-bleed:not(.flip) .kicker { left: 984px; }
.half-bleed.flip .half-bleed-figure { inset: 0 0 0 auto; width: 936px; }
/* With the photograph ending on the 936 gutter rule and the copy on column
3 (984), the 48px gutter is the hanging margin: the crosses center on the
gutter's own centerline (x 960) with 16px of paper on either side —
"whatever sits behind them" now means the gutter itself. */
.half-bleed:not(.flip) .bullets li::before { left: -25px; }
.half-bleed:not(.flip) .bullets li::after { left: -32px; }
/* — quote — */
/* Flush on the axis, airy MB leading: 64px poster lines on a two-unit
(96px) leading, first baseline ON the group-3 top rule (360; the deck's
copy sets in two lines, 360/456 — top rule and caption rule); the
attribution drops to the 696 caption rule — see the figcaption note. */
.quote { justify-content: flex-start; }
.quote .rise { position: absolute; inset: 0 var(--pad-x); }
.quote blockquote {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 64px; line-height: calc(2 * var(--baseline)); letter-spacing: 0;
max-width: 1200px; margin: 0;
margin-top: calc(7.5 * var(--baseline) - 1cap); /* first baseline on the group-3 top rule (360) */
text-box: trim-both cap alphabetic;
text-indent: -0.316em; /* hang the opening “ (measured advance in the Condensed 600) */
}
.quote figcaption { font-size: var(--type-small); margin: 0;
position: absolute; top: calc(14.5 * var(--baseline) - 1cap); /* baseline on the 696
caption rule — a full row-group below the quote's two-line setting (360/456),
chosen for air over the nearer 576 rule; re-seat if the quote re-wraps */
text-box: trim-both cap alphabetic;
text-indent: -0.885em; /* hang the leading em-dash + space (measured advance in Barlow Regular) */ }
/* Review aid: the baseline grid drawn as a hairline under every --baseline
step. Debug-only — toggled by ?baselines or the B key; not part of the look. */
deck-stage[data-baselines] .slide::after {
content: ""; position: absolute; inset: 0; pointer-events: none;
background:
repeating-linear-gradient(to bottom,
color-mix(in srgb, var(--color-accent) 35%, transparent) 0 1px,
transparent 1px var(--baseline)),
repeating-linear-gradient(to bottom,
transparent 0 calc(var(--baseline) / 2),
color-mix(in srgb, var(--color-accent) 16%, transparent) calc(var(--baseline) / 2) calc(var(--baseline) / 2 + 1px),
transparent calc(var(--baseline) / 2 + 1px) var(--baseline));
}
/* Entrance — end-state is the base style; animate from hidden, gated on the
active slide and the motion preference, so print and reduced-motion are safe. */
@media (prefers-reduced-motion: no-preference) {
[data-deck-active] .rise { animation: rise 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
@keyframes rise { from { opacity: 0; transform: translateY(14px); } }
}
</style>
<script>
// Review aid: baseline-grid overlay — add ?baselines to the URL, or press B to toggle.
// This runs from <helmet>, before the deck renders, so the stage is looked up when it is
// needed instead of captured up front; the window flag keeps a second evaluation of
// this helmet script (a recreated script node) from binding the B key twice.
(() => {
if (window.__deckBaselineAid) return;
window.__deckBaselineAid = true;
const stage = () => document.querySelector('deck-stage');
if (new URLSearchParams(location.search).has('baselines')) {
let tries = 600; // wait out the deck's first render (up to ~10s of frames)
const arm = () => { const s = stage(); if (s) s.setAttribute('data-baselines', ''); else if (tries-- > 0) requestAnimationFrame(arm); };
arm();
}
addEventListener('keydown', (e) => {
const t = e.target;
if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return; // typing guard, as in deck-stage's own keys
if ((e.key === 'b' || e.key === 'B') && !e.metaKey && !e.ctrlKey && !e.altKey) stage()?.toggleAttribute('data-baselines');
});
})();
</script>
</helmet>
<x-import component-from-global-scope="deck-stage" from="./deck-stage.js" width="1920" height="1080" hint-size="100%,100%">
</x-import>
</x-dc>
</body>
</html>
+272
View File
@@ -0,0 +1,272 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
@keyframes omPulse{0%,100%{opacity:1}50%{opacity:.3}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="evidence" hint-size="100%,92px"></dc-import>
<div data-screen-label="证据浏览器" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:16px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">04 · EVIDENCE</div>
<h2 style="margin:2px 0 0;font-size:30px">证据浏览器</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">失败时间线 · Evidence Bundle · 每个结论回链到原始证据</div>
</div>
<span style="margin-left:auto"></span>
<button class="btn btn-secondary" style="font-size:12.5px">创建复现任务</button>
<button class="btn btn-secondary" style="font-size:12.5px">生成缺陷草稿</button>
<button class="btn btn-primary" style="gap:7px">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline stroke-linejoin="round" stroke-linecap="round" points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
下载证据包 · 247 MB
</button>
</div>
<div style="display:grid;grid-template-columns:290px 1fr 330px;gap:18px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<h6 style="margin:0;color:var(--color-neutral-700)">失败记录 · 近 7 天</h6>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">3 / 214</span>
</div>
<input class="input" placeholder="搜索 run / 指纹 / 状态码…" style="font-size:12.5px;min-height:32px">
<div style="display:flex;flex-direction:column;gap:8px">
<sc-for list="{{ failures }}" as="f" hint-placeholder-count="3">
<div onClick="{{ f.pick }}" style="{{ f.itemStyle }}">
<div style="display:flex;align-items:center;gap:7px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px;font-weight:700">{{ f.run }}</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:9.5px;color:var(--color-neutral-500)">{{ f.when }}</span>
</div>
<div style="margin:5px 0 4px"><span style="{{ f.tagStyle }}">{{ f.code }}</span></div>
<div style="font-size:12px;line-height:1.45;color:var(--color-neutral-800)">{{ f.summary }}</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500);margin-top:4px">{{ f.meta }}</div>
</div>
</sc-for>
</div>
<div style="display:flex;gap:10px;font-size:10px;color:var(--color-neutral-600);border-top:1px solid var(--color-divider);padding-top:8px;flex-wrap:wrap">
<span><span style="display:inline-block;width:9px;height:8px;background:var(--color-accent-800);vertical-align:-1px"></span> DUT_*</span>
<span><span style="display:inline-block;width:9px;height:8px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);vertical-align:-1px"></span> INFRA_*</span>
<span><span style="display:inline-block;width:9px;height:8px;border:1px dashed var(--color-neutral-500);vertical-align:-1px"></span> UNKNOWN</span>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:700">{{ selRun }}</span>
<span style="{{ selTagStyle }}">{{ selCode }}</span>
<span class="tag tag-outline" style="font-size:10px">指纹 {{ selFs }}</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-600)">{{ selMeta }}</span>
</div>
<div style="font-size:13.5px;color:var(--color-neutral-800)">{{ selHeadline }}</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-700)">
<span style="border:1px solid var(--color-divider);padding:2px 8px">HOST-L02 · Ubuntu 22.04 · 6.5.0-41</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">BIOS F26b</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">FW_3.3.0-rc2 · slot 2</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">nvme-cli 2.9.1 · fio 3.36</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">workflow v3 · sig 8e02…c4</span>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:2px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0 0 10px;color:var(--color-neutral-700)">事件时间线 · TIMELINE</h6>
<div style="display:flex;flex-direction:column">
<sc-for list="{{ events }}" as="ev" hint-placeholder-count="8">
<div style="display:flex;gap:12px;position:relative;padding-bottom:14px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-500);width:58px;text-align:right;padding-top:2px;flex:none">{{ ev.t }}</span>
<div style="display:flex;flex-direction:column;align-items:center;flex:none;width:14px">
<span style="{{ ev.dotStyle }}"></span>
<span style="{{ ev.lineStyle }}"></span>
</div>
<div style="flex:1;margin-top:-2px">
<div style="display:flex;align-items:center;gap:8px">
<span style="{{ ev.laneStyle }}">{{ ev.lane }}</span>
<span style="{{ ev.titleStyle }}">{{ ev.title }}</span>
</div>
<div style="font-size:12px;color:var(--color-neutral-700);margin-top:2px;line-height:1.5">{{ ev.detail }}</div>
<sc-if value="{{ ev.links }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;gap:6px;margin-top:5px;flex-wrap:wrap">
<sc-for list="{{ ev.linkList }}" as="lk" hint-placeholder-count="2">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);padding:2px 8px;cursor:pointer">{{ lk }}</span>
</sc-for>
</div>
</sc-if>
</div>
</div>
</sc-for>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<h6 style="margin:0;color:var(--color-neutral-700)">AI 摘要 · 仅引用证据,不做最终判断</h6>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-600)">置信度 0.82 · 本地模型</span>
</div>
<div style="font-size:13px;line-height:1.7;color:var(--color-neutral-800)">S4 唤醒后 DUT 未完成链路训练即被访问:kernel 在唤醒后 3s 内未出现 nvme 重枚举日志 <span style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);padding:1px 6px">kernel.log:L1042-1067</span>,而基线固件 A 平均 1.8s 完成 <span style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);padding:1px 6px">metrics.json#resume_enum</span>。与 FS-0091 簇内 11 次历史失败特征一致(均为 S4 唤醒 + rc 系列固件)。</div>
<div style="font-size:12px;color:var(--color-neutral-700);border:1px dashed var(--color-divider);padding:8px 10px">尚不能排除:主板 BIOS F26b 的 S4 电源时序差异(建议单变量验证:同盘同固件换 HOST-L01 复跑 20 循环)。</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:6px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:8px">
<h6 style="margin:0;color:var(--color-neutral-700)">EVIDENCE BUNDLE</h6>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">run-20260726-0007/</span>
</div>
<div style="display:flex;flex-direction:column;font-family:ui-monospace,Menlo,monospace;font-size:11px">
<sc-for list="{{ tree }}" as="n" hint-placeholder-count="12">
<div onClick="{{ n.toggle }}" style="{{ n.rowStyle }}">
<span style="{{ n.chevStyle }}">{{ n.chev }}</span>
<span>{{ n.name }}</span>
<sc-if value="{{ n.flag }}" hint-placeholder-val="{{ false }}">
<span style="font-size:8.5px;background:var(--color-accent-800);color:#f2f2f3;padding:1px 5px;letter-spacing:.06em">关键</span>
</sc-if>
<span style="margin-left:auto;color:var(--color-neutral-500);font-size:10px">{{ n.size }}</span>
</div>
</sc-for>
</div>
<div style="font-size:10.5px;color:var(--color-neutral-600);border-top:1px solid var(--color-divider);padding-top:8px;line-height:1.6">manifest 校验通过 · 42 文件 · SHA256 全部一致<br>生成组件 evidence-bus 0.9.4 · 不可覆盖</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">恢复过程 · RECOVERY.JSON</h6>
<div style="display:flex;flex-direction:column;font-family:ui-monospace,Menlo,monospace;font-size:10.5px">
<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">L1 Agent 软恢复</span><span style="color:#8a2e22">FAIL · 60s</span></div>
<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">L2 OS 通道重启</span><span style="color:#8a2e22">FAIL · 120s</span></div>
<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">L3 带外 Reset</span><span style="color:var(--color-accent-700)">OK · 2m17s</span></div>
<div style="display:flex;justify-content:space-between;padding:4px 0"><span style="color:var(--color-neutral-600)">总耗时 / 介入</span><span>4m12s · 无人</span></div>
</div>
<a href="LiveRun.dc.html" style="font-size:11.5px">回放当时的实时视图 →</a>
</div>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:980}}">
class Component extends DCLogic {
state = { sel: 0, open: { environment: true, device: false, host: true, test: false, oob: true, recovery: false, summary: true } };
renderVals() {
const mono = { fontFamily: 'ui-monospace,Menlo,monospace' };
const tagOf = code => {
if (code.startsWith('DUT')) return { ...mono, fontSize: '10px', padding: '2px 8px', background: 'var(--color-accent-800)', color: '#f2f2f3', letterSpacing: '.03em' };
if (code.startsWith('INFRA')) return { ...mono, fontSize: '10px', padding: '2px 8px', background: 'repeating-linear-gradient(135deg,rgba(29,31,32,.12) 0 2px,transparent 2px 5px)', border: '1px solid var(--color-divider)', color: 'var(--color-neutral-800)', letterSpacing: '.03em' };
if (code === 'UNKNOWN') return { ...mono, fontSize: '10px', padding: '2px 8px', border: '1px dashed var(--color-neutral-500)', color: 'var(--color-neutral-700)', letterSpacing: '.03em' };
return { ...mono, fontSize: '10px', padding: '2px 8px', background: 'var(--color-neutral-200)', color: 'var(--color-neutral-800)', letterSpacing: '.03em' };
};
const F = [
{ run: 'run-20260726-0007', when: '昨日 02:29', code: 'DUT_ENUMERATION_FAILURE', fs: 'FS-0091', summary: 'S4 唤醒后 DUT 未枚举,链路未训练', meta: 'WS-03 · cycle 41/100 · FW_3.3.0-rc2', headline: '休眠唤醒后 nvme0n1 未出现;L1/L2 恢复失败,L3 带外 Reset 后设备恢复,任务在检查点冻结。' },
{ run: 'run-20260726-0011', when: '昨日 11:47', code: 'UNKNOWN', fs: 'FS-0102', summary: '校验超时且证据不足,等待工程师分类', meta: 'WS-02 · cycle 12/50 · FW_3.2.1', headline: 'verify_hash 超时,但主机、DUT 与网络信号均正常 — 证据不足,已标记待人工分类。' },
{ run: 'run-20260725-0018', when: '07-25 23:12', code: 'INFRA_HOST_OS_FAILURE', fs: 'FS-0088', summary: '蓝屏 DRIVER_POWER_STATE_FAILURE', meta: 'WS-01 · cycle 88/100 · FW_3.3.0-rc2', headline: 'Windows 蓝屏(电源状态驱动),判定为基础设施故障,不计入 DUT 结论;L4 AC 循环后恢复。' }
];
const failures = F.map((f, i) => ({
...f,
pick: () => this.setState({ sel: i }),
tagStyle: tagOf(f.code),
itemStyle: {
border: i === this.state.sel ? '1px solid var(--color-accent-600)' : '1px solid var(--color-divider)',
background: i === this.state.sel ? 'var(--color-accent-100)' : 'transparent',
padding: '10px 11px', cursor: 'pointer', transition: 'border-color .15s ease, background .15s ease'
}
}));
const sel = F[this.state.sel];
const EV = {
0: [
{ t: '02:28:19', lane: 'TEST', kind: 'n', title: 'cycle 41 · host.hibernate → S4', detail: '前序 40 循环全部 PASS · 检查点 cycle 40 已写入', links: null },
{ t: '02:29:44', lane: 'HOST', kind: 'n', title: '唤醒完成 · resume 11.8s', detail: '唤醒源 RTC · Agent 心跳恢复正常', links: null },
{ t: '02:29:47', lane: 'DUT', kind: 'f', title: 'dut.verify_enumeration 失败', detail: 'nvme0n1 未出现 · PCIe 链路未完成训练 · 重试 3 次无效', links: ['host/kernel.log', 'host/device-enumeration.json'] },
{ t: '02:29:48', lane: 'EVID', kind: 'e', title: '失败现场取证(恢复之前)', detail: 'HDMI 帧 ×3 · dmesg 尾部 · SMART 不可读已记录 · lspci 快照', links: ['oob/screenshots/', 'device/error-log.json'] },
{ t: '02:30:21', lane: 'RCVR', kind: 'f', title: 'L1 Agent 软恢复 → 失败', detail: 'PCIe rescan 无效 · 设备节点仍缺失 (60s)', links: null },
{ t: '02:31:07', lane: 'HB', kind: 'f', title: '主机心跳丢失', detail: 'Agent 停止上报 · ping 不可达 · 判定 HOST_UNREACHABLE', links: ['oob/heartbeat.ndjson'] },
{ t: '02:31:40', lane: 'RCVR', kind: 'f', title: 'L2 OS 通道重启 → 失败', detail: 'SSH 超时 120s · 按策略 esc-default-v2 升级', links: null },
{ t: '02:33:02', lane: 'OOB', kind: 'r', title: 'L3 带外 Reset 已执行', detail: '光耦 CH2 · 脉冲 250ms · 供电动作已留痕', links: ['oob/power-actions.ndjson'] },
{ t: '02:35:19', lane: 'HOST', kind: 'r', title: '主机上线 · 指纹一致', detail: 'Agent 自启 · BIOS/OS/驱动与任务登记一致', links: null },
{ t: '02:35:24', lane: 'DUT', kind: 'r', title: 'DUT 重新枚举成功', detail: 'Gen4 x4 · 1.8s · SMART 正常 · 数据区未写入', links: ['device/smart-health.json'] },
{ t: '02:35:31', lane: 'SYS', kind: 'c', title: '检查点对账 · 任务冻结', detail: '物理状态 = cycle 41 起点 · 等待复跑决策(不自动重试破坏性步骤)', links: null },
{ t: '02:36:02', lane: 'SYS', kind: 'n', title: '失败指纹 FS-0091 已生成', detail: '与 11 个历史运行聚类 · 均为 S4 唤醒 + rc 系列固件', links: ['summary/failure-signature.json'] }
],
1: [
{ t: '11:47:12', lane: 'TEST', kind: 'f', title: 'data.verify_hash 超时', detail: '300s 未返回 · fio 进程存活 · IO 无错误计数', links: ['test/stderr.log'] },
{ t: '11:47:13', lane: 'EVID', kind: 'e', title: '现场取证', detail: '进程树 · 资源占用 · SMART 正常 · 网络正常', links: ['host/system.log'] },
{ t: '11:48:40', lane: 'RCVR', kind: 'r', title: 'L1 终止校验进程并重跑 → 通过', detail: '重跑一次即 PASS · 无法稳定复现', links: null },
{ t: '11:50:02', lane: 'SYS', kind: 'c', title: '标记 UNKNOWN · 等待分类', detail: '证据不足 · 建议补充 blktrace 后纳入观察名单', links: ['summary/ai-summary.md'] }
],
2: [
{ t: '23:12:44', lane: 'HOST', kind: 'f', title: 'Windows 蓝屏', detail: 'DRIVER_POWER_STATE_FAILURE (0x9F) · 冷启动循环第 88 轮', links: ['oob/screenshots/'] },
{ t: '23:12:45', lane: 'EVID', kind: 'e', title: 'HDMI 蓝屏帧留证', detail: 'OS 不可达状态下由带外控制器抓取', links: ['oob/screenshots/'] },
{ t: '23:14:10', lane: 'OOB', kind: 'r', title: 'L4 AC 电源循环', detail: 'L3 Reset 无效 → 整机断电 · 安全间隔 30s', links: ['oob/power-actions.ndjson'] },
{ t: '23:18:02', lane: 'SYS', kind: 'c', title: '判定 INFRA_HOST_OS_FAILURE', detail: '不计入 DUT 结论 · 计入基础设施误报率监控', links: ['summary/failure-signature.json'] }
]
}[this.state.sel];
const laneColors = { HB: '#8a2e22', OOB: 'var(--color-accent-800)', RCVR: '#8a2e22', TEST: 'var(--color-accent-700)', SYS: 'var(--color-neutral-600)', EVID: 'var(--color-neutral-600)', DUT: 'var(--color-accent-800)', HOST: 'var(--color-neutral-700)' };
const events = EV.map((e, i) => {
let dot = { width: '9px', height: '9px', borderRadius: '50%', border: '1.5px solid var(--color-neutral-400)', background: 'var(--color-bg)', flex: 'none', marginTop: '4px' };
if (e.kind === 'f') dot = { ...dot, border: '1.5px solid #b03d2e', background: '#f7e9e6' };
if (e.kind === 'r') dot = { ...dot, border: 'none', background: 'var(--color-accent)' };
if (e.kind === 'c') dot = { ...dot, borderRadius: 0, border: 'none', background: 'var(--color-accent-800)' };
if (e.kind === 'e') dot = { ...dot, border: '1.5px dashed var(--color-neutral-500)' };
return {
...e,
linkList: e.links || [],
dotStyle: dot,
lineStyle: { width: '1px', flex: 1, background: i === EV.length - 1 ? 'transparent' : 'var(--color-divider)', marginTop: '3px' },
laneStyle: { ...mono, fontSize: '9.5px', color: laneColors[e.lane] || 'var(--color-neutral-600)', letterSpacing: '.06em', minWidth: '34px' },
titleStyle: { fontSize: '13px', fontWeight: e.kind === 'f' || e.kind === 'r' ? 600 : 500, color: e.kind === 'f' ? '#8a2e22' : 'var(--color-text)' }
};
});
const DIRS = [
{ id: 'root', name: 'manifest.json', size: '4 KB', file: true, depth: 0 },
{ id: 'environment', name: 'environment/', size: '4 项', kids: [['host.json', '2 KB'], ['dut.json', '2 KB'], ['firmware.json', '1 KB'], ['tool_versions.json', '3 KB']] },
{ id: 'timeline', name: 'timeline/events.ndjson', size: '1.2 MB', file: true, depth: 0 },
{ id: 'device', name: 'device/', size: '4 项', kids: [['identify.json', '18 KB'], ['smart-health.json', '9 KB'], ['error-log.json', '22 KB'], ['telemetry.bin', '64 MB']] },
{ id: 'host', name: 'host/', size: '3 项', kids: [['system.log', '8.4 MB'], ['kernel.log', '2.1 MB', true], ['device-enumeration.json', '11 KB', true]] },
{ id: 'test', name: 'test/', size: '4 项', kids: [['stdout.log', '96 MB'], ['stderr.log', '2 KB'], ['metrics.json', '340 KB'], ['hashes.json', '88 KB']] },
{ id: 'oob', name: 'oob/', size: '4 项', kids: [['heartbeat.ndjson', '420 KB', true], ['power-actions.ndjson', '2 KB', true], ['temperature.csv', '180 KB'], ['screenshots/ ×9', '38 MB', true]] },
{ id: 'recovery', name: 'recovery/recovery.json', size: '6 KB', file: true, depth: 0, flag: true },
{ id: 'summary', name: 'summary/', size: '3 项', kids: [['failure-signature.json', '3 KB', true], ['ai-summary.md', '5 KB'], ['issue-draft.md', '4 KB']] }
];
const tree = [];
for (const d of DIRS) {
const isOpen = !!this.state.open[d.id];
tree.push({
name: d.name, size: d.size, flag: !!d.flag,
chev: d.file ? '·' : (isOpen ? '▾' : '▸'),
chevStyle: { width: '12px', color: d.file ? 'var(--color-neutral-400)' : 'var(--color-accent-700)', flex: 'none' },
toggle: d.file ? null : () => this.setState(s => ({ open: { ...s.open, [d.id]: !s.open[d.id] } })),
rowStyle: { display: 'flex', alignItems: 'center', gap: '6px', padding: '4px 4px', cursor: d.file ? 'default' : 'pointer', borderBottom: '1px solid rgba(29,31,32,.06)' }
});
if (!d.file && isOpen) for (const [nm, sz, flag] of d.kids) tree.push({
name: nm, size: sz, flag: !!flag, chev: '', toggle: null,
chevStyle: { width: '12px', flex: 'none' },
rowStyle: { display: 'flex', alignItems: 'center', gap: '6px', padding: '3px 4px 3px 22px', color: 'var(--color-neutral-700)', borderBottom: '1px solid rgba(29,31,32,.04)' }
});
}
return {
failures, events, tree,
selRun: sel.run, selCode: sel.code, selFs: sel.fs, selMeta: sel.meta, selHeadline: sel.headline,
selTagStyle: tagOf(sel.code)
};
}
}
</script>
</body>
</html>
+181
View File
@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="failures" hint-size="100%,92px"></dc-import>
<div data-screen-label="失败中心" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:16px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">06 · FAILURES</div>
<h2 style="margin:2px 0 0;font-size:30px">失败中心</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">跨任务聚类 · 失败指纹由稳定字段构成,工程师可拆分/合并/命名,结果反哺规则</div>
</div>
<span style="margin-left:auto"></span>
<input class="input" placeholder="搜索指纹 / 日志特征 / run…" style="width:240px;font-size:12.5px;min-height:34px">
<div class="seg" style="background:var(--color-bg)">
<label class="seg-opt"><input type="radio" name="fcat" checked="{{ cAll }}" onChange="{{ setAll }}"><span>全部 7</span></label>
<label class="seg-opt"><input type="radio" name="fcat" checked="{{ cDut }}" onChange="{{ setDut }}"><span>DUT_* 3</span></label>
<label class="seg-opt"><input type="radio" name="fcat" checked="{{ cInfra }}" onChange="{{ setInfra }}"><span>INFRA_* 2</span></label>
<label class="seg-opt"><input type="radio" name="fcat" checked="{{ cScript }}" onChange="{{ setScript }}"><span>SCRIPT 1</span></label>
<label class="seg-opt"><input type="radio" name="fcat" checked="{{ cUnk }}" onChange="{{ setUnk }}"><span>UNKNOWN 1</span></label>
</div>
</div>
<div style="display:flex;gap:16px;align-items:center;border:1px solid var(--color-divider);padding:8px 14px;margin-bottom:16px;font-size:10.5px;color:var(--color-neutral-700);flex-wrap:wrap">
<span style="font-size:10px;letter-spacing:.12em;color:var(--color-neutral-600)">视觉语言</span>
<span><span style="display:inline-block;width:10px;height:9px;background:var(--color-accent-800);vertical-align:-1px"></span> DUT_* 实心 — 真实信号,计入版本结论</span>
<span><span style="display:inline-block;width:10px;height:9px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.35) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);vertical-align:-1px"></span> INFRA_* 斜线 — 基础设施噪声,单独监控误报率</span>
<span><span style="display:inline-block;width:10px;height:9px;background:var(--color-neutral-300);vertical-align:-1px"></span> SCRIPT 灰 — 脚本/参数问题</span>
<span><span style="display:inline-block;width:10px;height:9px;border:1px dashed var(--color-neutral-500);vertical-align:-1px"></span> UNKNOWN 虚线 — 证据不足,等待分类</span>
<span><span style="display:inline-block;width:10px;height:9px;background:#b03d2e;vertical-align:-1px"></span> 阻断 — 门禁级</span>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:0;gap:0;overflow:visible">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:grid;grid-template-columns:30px 92px 240px 1fr 120px 130px 150px 110px;gap:10px;align-items:center;padding:9px 14px;border-bottom:1px solid var(--color-divider);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--color-neutral-600)">
<span></span><span>指纹</span><span>状态码</span><span>摘要</span><span>次数 · 14D</span><span>首次 / 最近</span><span>关联版本</span><span>状态</span>
</div>
<sc-for list="{{ rows }}" as="r" hint-placeholder-count="7">
<div>
<div onClick="{{ r.toggle }}" style="{{ r.rowStyle }}">
<span style="{{ r.chevStyle }}">{{ r.chev }}</span>
<span style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700">{{ r.id }}</span>
<span><span style="{{ r.tagStyle }}">{{ r.code }}</span></span>
<span style="font-size:13px">{{ r.summary }}<sc-if value="{{ r.blocking }}" hint-placeholder-val="{{ false }}"><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;background:#b03d2e;color:#f7f4f2;padding:1px 6px;margin-left:8px;vertical-align:1px">阻断</span></sc-if></span>
<span style="display:flex;align-items:flex-end;gap:8px"><span style="font-family:var(--font-heading);font-weight:600;font-size:18px;line-height:1">{{ r.count }}</span><span style="display:flex;align-items:flex-end;gap:2px;height:16px">
<sc-for list="{{ r.bars }}" as="b" hint-placeholder-count="7"><span style="{{ b.s }}"></span></sc-for>
</span></span>
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-600);line-height:1.5">{{ r.first }}<br>{{ r.last }}</span>
<span style="display:flex;gap:4px;flex-wrap:wrap">
<sc-for list="{{ r.vers }}" as="v" hint-placeholder-count="2"><span style="font-family:ui-monospace,Menlo,monospace;font-size:9.5px;border:1px solid var(--color-divider);padding:1px 6px;color:var(--color-neutral-700)">{{ v }}</span></sc-for>
</span>
<span><span style="{{ r.stTagStyle }}">{{ r.status }}</span></span>
</div>
<sc-if value="{{ r.open }}" hint-placeholder-val="{{ false }}">
<div style="display:grid;grid-template-columns:1.3fr 1fr 1fr;gap:16px;padding:14px 16px 16px 54px;background:var(--color-neutral-100);border-bottom:1px solid var(--color-divider)">
<div>
<div style="font-size:10px;letter-spacing:.1em;color:var(--color-neutral-600);margin-bottom:7px">指纹特征字段 · SIGNATURE</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.9;color:var(--color-neutral-800)">
<sc-for list="{{ r.feats }}" as="ft" hint-placeholder-count="4"><div style="display:flex;gap:10px"><span style="color:var(--color-neutral-500);min-width:88px">{{ ft.k }}</span><span>{{ ft.v }}</span></div></sc-for>
</div>
</div>
<div>
<div style="font-size:10px;letter-spacing:.1em;color:var(--color-neutral-600);margin-bottom:7px">关联运行 · {{ r.count }} 次</div>
<div style="display:flex;flex-direction:column;gap:5px">
<sc-for list="{{ r.runs }}" as="rn" hint-placeholder-count="3"><a href="Evidence.dc.html" style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);padding:3px 8px;text-decoration:none;width:fit-content">{{ rn }}</a></sc-for>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:8px">
<div>
<div style="font-size:10px;letter-spacing:.1em;color:var(--color-neutral-600);margin-bottom:7px">最短复现 · REPRO</div>
<div style="font-size:12px;line-height:1.6;border:1px solid var(--color-divider);padding:8px 10px;background:var(--color-bg)">{{ r.repro }}</div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-primary" style="font-size:12px;padding:5px 12px">生成缺陷草稿</button>
<button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">创建复现任务</button>
<button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">合并…</button>
<button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">拆分</button>
</div>
</div>
</div>
</sc-if>
</div>
</sc-for>
</div>
<div style="display:flex;gap:16px;margin-top:12px;font-size:11px;color:var(--color-neutral-600)">
<span>聚类由归一化结构字段计算签名,语义模型仅辅助合并建议</span>
<span style="margin-left:auto">30 天:去重后 7 簇 ← 43 次原始失败 · 重复问题合并率 84%</span>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:880}}">
class Component extends DCLogic {
state = { cat: 'all', openId: 'FS-0091' };
renderVals() {
const mono = { fontFamily: 'ui-monospace,Menlo,monospace' };
const tagOf = code => {
if (code.startsWith('DUT')) return { ...mono, fontSize: '10px', padding: '2px 8px', background: 'var(--color-accent-800)', color: '#f2f2f3' };
if (code.startsWith('INFRA')) return { ...mono, fontSize: '10px', padding: '2px 8px', background: 'repeating-linear-gradient(135deg,rgba(29,31,32,.12) 0 2px,transparent 2px 5px)', border: '1px solid var(--color-divider)', color: 'var(--color-neutral-800)' };
if (code === 'UNKNOWN') return { ...mono, fontSize: '10px', padding: '2px 8px', border: '1px dashed var(--color-neutral-500)', color: 'var(--color-neutral-700)' };
return { ...mono, fontSize: '10px', padding: '2px 8px', background: 'var(--color-neutral-200)', color: 'var(--color-neutral-800)' };
};
const stOf = st => {
const base = { fontSize: '10.5px', padding: '2px 9px', letterSpacing: '.02em' };
if (st === '已建单') return { ...base, background: 'var(--color-accent-100)', color: 'var(--color-accent-800)' };
if (st === '待建单') return { ...base, border: '1px solid #b03d2e', color: '#8a2e22' };
if (st === '待分类') return { ...base, border: '1px dashed var(--color-neutral-500)', color: 'var(--color-neutral-700)' };
if (st === '已修复') return { ...base, border: '1px solid var(--color-accent-400)', color: 'var(--color-accent-700)' };
return { ...base, background: 'var(--color-neutral-200)', color: 'var(--color-neutral-800)' };
};
const C = [
{ id: 'FS-0074', cat: 'dut', code: 'DUT_DATA_FAILURE', summary: '4k 混合负载后 LBA 区间校验失败', blocking: true, count: 2, hist: [0,0,0,0,0,1,1], first: '首 07-26', last: '近 昨日', vers: ['3.3.0-rc2'], status: '待建单',
feats: [{k:'step',v:'data.verify_hash'},{k:'lba_range',v:'0x2E41_00000x2E41_3FFF'},{k:'pattern',v:'写入后休眠,唤醒读校验 mismatch'},{k:'smart',v:'无介质错误计数增长'},{k:'data_loss',v:'true · 门禁级'}],
runs: ['run-20260726-0007 · cycle 41', 'run-20260726-0009 · cycle 17'], repro: '写入 32G 校验集 → S4 → 唤醒读校验;固定 SAMPLE-002,预计 45min ×10 循环' },
{ id: 'FS-0091', cat: 'dut', code: 'DUT_ENUMERATION_FAILURE', summary: 'S4 唤醒后掉盘,链路未训练', blocking: false, count: 12, hist: [0,1,1,2,2,3,3], first: '首 07-18', last: '近 昨日', vers: ['3.3.0-rc1', '3.3.0-rc2'], status: '已建单',
feats: [{k:'step',v:'dut.verify_enumeration'},{k:'kernel',v:'nvme nvme0: Device not ready; aborting reset'},{k:'power_state',v:'S4 resume · RTC 唤醒'},{k:'env',v:'HOST-L02 · BIOS F26b · 4247°C'},{k:'recovery',v:'L3 Reset 后 100% 恢复'}],
runs: ['run-20260726-0007 · cycle 41', 'run-20260725-0021 · cycle 63', 'run-20260722-0004 · cycle 12', '… 另 9 次'], repro: 'S4 循环 ×20 · 固定 WS-03 + SAMPLE-002 · 复现率 ~15% · 预计 3.5h' },
{ id: 'FS-0071', cat: 'dut', code: 'DUT_PERFORMANCE_REGRESSION', summary: '高温限速触发,4k 写长尾退化', blocking: false, count: 9, hist: [1,0,1,1,2,2,2], first: '首 07-12', last: '近 今日', vers: ['3.2.1', '3.3.0-rc2'], status: '观察',
feats: [{k:'step',v:'io.run_profile 4k_mixed'},{k:'threshold',v:'P99 > 基线 +8%'},{k:'temp_window',v:'46°C 以上触发'},{k:'freq',v:'A:4 → B:9 · 偶发转频发'}],
runs: ['run-20260727-0001 · cycle 33', 'run-20260726-0007 · cycle 08'], repro: '恒温箱 45°C · 4k_mixed 30min ×5 · 观察限速计数器' },
{ id: 'FS-0088', cat: 'infra', code: 'INFRA_HOST_OS_FAILURE', summary: 'Windows 蓝屏 DRIVER_POWER_STATE_FAILURE', blocking: false, count: 5, hist: [1,1,0,1,1,0,1], first: '首 07-08', last: '近 07-25', vers: ['跨版本'], status: '处理中',
feats: [{k:'bugcheck',v:'0x9F · 冷启动循环期间'},{k:'scope',v:'跨 3 工位,与固件版本无关'},{k:'suspect',v:'主机 NIC 驱动电源管理'},{k:'guard',v:'不计入 DUT 结论'}],
runs: ['run-20260725-0018 · cycle 88', 'run-20260721-0002 · cycle 41'], repro: 'golden image 更新 NIC 驱动后回归 ×50 冷启动' },
{ id: 'FS-0059', cat: 'infra', code: 'INFRA_NETWORK_FAILURE', summary: '交换机端口抖动导致 Agent 误报失联', blocking: false, count: 3, hist: [1,1,1,0,0,0,0], first: '首 06-30', last: '近 07-21', vers: ['—'], status: '已修复',
feats: [{k:'signal',v:'OOB 心跳正常 + Agent 断连'},{k:'fix',v:'更换 SW-02 端口 6 网线'},{k:'lesson',v:'已加入判定规则:双路径交叉验证'}],
runs: ['run-20260721-0006'], repro: '—' },
{ id: 'FS-0067', cat: 'script', code: 'TEST_SCRIPT_FAILURE', summary: 'fio 3.36 参数不兼容私有 profile', blocking: false, count: 9, hist: [2,2,1,1,1,1,1], first: '首 07-02', last: '近 今日', vers: ['—'], status: '处理中',
feats: [{k:'stderr',v:'unrecognized option --write_hist_log'},{k:'owner',v:'测试平台组'},{k:'workaround',v:'连接器已降级参数集'}],
runs: ['run-20260727-0002 · precheck'], repro: 'fio --version 校验加入 precheck,连接器 healthcheck 拦截' },
{ id: 'FS-0102', cat: 'unknown', code: 'UNKNOWN', summary: 'verify_hash 超时但各信号正常', blocking: false, count: 1, hist: [0,0,0,0,0,0,1], first: '首 昨日', last: '近 昨日', vers: ['3.2.1'], status: '待分类',
feats: [{k:'signal',v:'主机/DUT/网络均正常'},{k:'evidence_gap',v:'缺 blktrace · 已加入下次采集'},{k:'action',v:'等待工程师分类 → 反哺规则'}],
runs: ['run-20260726-0011 · cycle 12'], repro: '纳入观察名单 · 同参数复跑 ×5 后再判' }
];
const cat = this.state.cat;
const rows = C.filter(c => cat === 'all' || c.cat === cat).map(c => {
const open = this.state.openId === c.id;
const maxH = Math.max(...c.hist, 1);
return {
...c, open,
toggle: () => this.setState({ openId: open ? null : c.id }),
chev: open ? '▾' : '▸',
chevStyle: { color: 'var(--color-accent-700)', fontSize: '11px' },
tagStyle: tagOf(c.code), stTagStyle: stOf(c.status),
bars: c.hist.map(h => ({ s: { width: '5px', height: Math.max(2, (h / maxH) * 16) + 'px', background: h ? (c.cat === 'infra' ? 'repeating-linear-gradient(135deg,rgba(29,31,32,.4) 0 1px,transparent 1px 3px)' : c.cat === 'dut' ? 'var(--color-accent-700)' : 'var(--color-neutral-400)') : 'var(--color-neutral-200)', border: h && c.cat === 'infra' ? '1px solid var(--color-divider)' : 'none' } })),
rowStyle: {
display: 'grid', gridTemplateColumns: '30px 92px 240px 1fr 120px 130px 150px 110px', gap: '10px', alignItems: 'center',
padding: '11px 14px', cursor: 'pointer', transition: 'background .15s ease',
borderBottom: '1px solid rgba(29,31,32,.08)',
background: open ? 'var(--color-accent-100)' : 'transparent',
borderLeft: c.blocking ? '3px solid #b03d2e' : '3px solid transparent'
}
};
});
const set = c => () => this.setState({ cat: c });
return {
rows,
cAll: cat === 'all', cDut: cat === 'dut', cInfra: cat === 'infra', cScript: cat === 'script', cUnk: cat === 'unknown',
setAll: set('all'), setDut: set('dut'), setInfra: set('infra'), setScript: set('script'), setUnk: set('unknown')
};
}
}
</script>
</body>
</html>
+450
View File
@@ -0,0 +1,450 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
@keyframes omPulse{0%,100%{opacity:1}50%{opacity:.3}}
@keyframes omBlink{0%,49%{opacity:1}50%,100%{opacity:.15}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="live" hint-size="100%,92px"></dc-import>
<div data-screen-label="实时运行" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:16px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">03 · LIVE RUN</div>
<h2 style="margin:2px 0 0;font-size:30px">实时运行</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">{{ hostName }} · 只在必须时人工接管</div>
</div>
<span style="margin-left:auto"></span>
<button class="btn btn-secondary" onClick="{{ startDemo }}" disabled="{{ demoBusy }}" style="gap:7px">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
演示:注入主机失联
</button>
<div class="seg" style="background:var(--color-bg)">
<label class="seg-opt"><input type="radio" name="lrv" checked="{{ vA }}" onChange="{{ setA }}"><span>A · 控制室三栏</span></label>
<label class="seg-opt"><input type="radio" name="lrv" checked="{{ vB }}" onChange="{{ setB }}"><span>B · 时间线泳道</span></label>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;flex-direction:row;align-items:center;gap:20px;padding:12px 16px;margin-bottom:18px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:14px;font-weight:700">{{ runId }}</div>
<div style="font-size:11.5px;color:var(--color-neutral-600)">fw_ab_powerstate_regression v3 · 已审核模板</div>
</div>
<span style="width:1px;height:30px;background:var(--color-divider)"></span>
<div style="font-size:12px;line-height:1.5;color:var(--color-neutral-800)"><span class="text-muted">固件</span> {{ firmwareLabel }}<br><span class="text-muted">DUT</span> {{ dutLabel }}</div>
<span style="width:1px;height:30px;background:var(--color-divider)"></span>
<div style="font-size:12px;line-height:1.5;color:var(--color-neutral-800)"><span class="text-muted">开始</span> {{ startText }}<br><span class="text-muted">检查点</span> {{ checkpointText }}</div>
<span style="width:1px;height:30px;background:var(--color-divider)"></span>
<div style="flex:1;min-width:180px">
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--color-neutral-700);margin-bottom:4px"><span>轮次 {{ cycle }} / {{ target }}</span><span>由控制平面实时投影</span></div>
<div style="height:6px;border:1px solid var(--color-divider);position:relative"><div style="{{ progStyle }}"></div></div>
</div>
<span style="{{ badgeStyle }}">{{ badgeText }}</span>
<button class="btn btn-secondary" onClick="{{ togglePause }}" disabled="{{ pauseDisabled }}" style="font-size:12.5px">{{ pauseLabel }}</button>
<button class="btn btn-secondary" disabled="{{ true }}" style="font-size:12.5px" title="真实 Agent 接入后开放">人工接管</button>
<button onClick="{{ estop }}" disabled="{{ stopDisabled }}" style="display:inline-flex;align-items:center;gap:6px;background:transparent;border:1px solid #b03d2e;color:#8a2e22;padding:7px 12px;font-family:var(--font-heading);font-weight:600;font-size:12.5px;letter-spacing:.05em;cursor:pointer" style-hover="background:#f7e9e6">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon></svg>
停止本工位
</button>
</div>
<sc-if value="{{ estopped }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;align-items:center;gap:12px;background:#b03d2e;color:#f7f4f2;padding:10px 16px;margin-bottom:18px;font-size:13px">
<strong style="font-family:var(--font-heading);letter-spacing:.05em">{{ hostName }} 已安全停止</strong>
<span style="opacity:.85">任务已安全停止,现场与检查点已保留 · 已写入不可变审计事件</span>
<button onClick="{{ resumeEstop }}" style="margin-left:auto;background:transparent;border:1px solid rgba(247,244,242,.55);color:#f7f4f2;padding:4px 12px;font-family:var(--font-heading);font-size:12px;cursor:pointer" style-hover="background:rgba(247,244,242,.14)">新建演示任务</button>
</div>
</sc-if>
<sc-if value="{{ vA }}" hint-placeholder-val="{{ true }}">
<div data-screen-label="实时运行 · 变体A 控制室" style="display:grid;grid-template-columns:300px 1fr 300px;gap:18px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:4px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0 0 8px;color:var(--color-neutral-700)">步骤执行器 · WORKFLOW</h6>
<div style="display:flex;gap:9px;align-items:center;padding:6px 0;font-size:12.5px"><span style="width:16px;height:16px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);display:inline-flex;align-items:center;justify-content:center;font-size:10px"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">lab.precheck</span><span style="margin-left:auto;font-size:10px;color:var(--color-neutral-500)">09:12</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:6px 0;font-size:12.5px"><span style="width:16px;height:16px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);display:inline-flex;align-items:center;justify-content:center;font-size:10px"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">vendor.flash_firmware B</span><span style="margin-left:auto;font-size:10px;color:var(--color-neutral-500)">09:14</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:6px 0;font-size:12.5px"><span style="width:16px;height:16px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);display:inline-flex;align-items:center;justify-content:center;font-size:10px"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">host.reboot · 版本确认</span><span style="margin-left:auto;font-size:10px;color:var(--color-neutral-500)">09:18</span></div>
<div style="border:1px solid var(--color-divider);padding:8px 10px;margin:6px 0 2px">
<div style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--color-neutral-700);margin-bottom:6px"><span style="font-family:ui-monospace,Menlo,monospace">loop · repeat {{ target }}</span><span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;color:var(--color-accent-700)">cycle {{ cycle }}</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:5px 0;font-size:12px"><span style="width:14px;height:14px;border:1px solid var(--color-accent-400);color:var(--color-accent-700);display:inline-flex;align-items:center;justify-content:center;font-size:9px"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11px">nvme.collect_baseline</span></div>
<div style="{{ ioRowStyle }}"><span style="{{ ioDotStyle }}"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11px;font-weight:700">io.run_profile 4k_mixed</span><span style="margin-left:auto;font-size:10px">{{ ioNote }}</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:5px 0;font-size:12px;color:var(--color-neutral-500)"><span style="width:14px;height:14px;border:1px dashed var(--color-divider)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11px">host.hibernate</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:5px 0;font-size:12px;color:var(--color-neutral-500)"><span style="width:14px;height:14px;border:1px dashed var(--color-divider)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11px">host.wait_resume</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:5px 0;font-size:12px;color:var(--color-neutral-500)"><span style="width:14px;height:14px;border:1px dashed var(--color-divider)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11px">dut.verify_enumeration</span></div>
<div style="display:flex;gap:9px;align-items:center;padding:5px 0;font-size:12px;color:var(--color-neutral-500)"><span style="width:14px;height:14px;border:1px dashed var(--color-divider)"></span><span style="font-family:ui-monospace,Menlo,monospace;font-size:11px">data.verify_hash</span></div>
</div>
<div style="border:1px dashed #b03d2e;padding:8px 10px;margin-top:6px">
<div style="font-size:10px;letter-spacing:.1em;color:#8a2e22;margin-bottom:5px">ON_FAILURE</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-700);line-height:1.8">evidence.capture_all<br>recovery.escalate<br>failure.classify</div>
</div>
<div style="display:flex;gap:14px;font-size:10.5px;color:var(--color-neutral-600);margin-top:8px;font-family:ui-monospace,Menlo,monospace"><span>PASS 66</span><span>FAIL 0</span><span>自动恢复 1</span><span>介入 0</span></div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:0;gap:0;overflow:hidden">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--color-divider)">
<h6 style="margin:0;color:var(--color-neutral-700)">实时日志流 · EVENT LOG</h6>
<span style="margin-left:auto;display:inline-flex;gap:6px;font-size:10px;font-family:ui-monospace,Menlo,monospace">
<span style="padding:2px 8px;background:var(--color-accent);color:#f2f2f3">ALL</span>
<span style="padding:2px 8px;border:1px solid var(--color-divider);color:var(--color-neutral-600)">CMD</span>
<span style="padding:2px 8px;border:1px solid var(--color-divider);color:var(--color-neutral-600)">OOB</span>
<span style="padding:2px 8px;border:1px solid var(--color-divider);color:var(--color-neutral-600)">WARN</span>
</span>
</div>
<div ref="{{ logRef }}" style="height:330px;overflow-y:auto;padding:10px 14px;background:var(--color-neutral-100);font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.75">
<sc-for list="{{ logs }}" as="ln" hint-placeholder-count="8">
<div style="animation:omIn .3s ease both;display:flex;gap:10px"><span style="color:var(--color-neutral-500);white-space:nowrap">{{ ln.t }}</span><span style="{{ ln.laneStyle }}">{{ ln.lane }}</span><span style="{{ ln.textStyle }}">{{ ln.text }}</span></div>
</sc-for>
</div>
</div>
<div style="display:grid;grid-template-columns:1.15fr 1fr;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><h6 style="margin:0;color:var(--color-neutral-700)">HDMI 采集画面</h6><span style="margin-left:auto;font-size:10px;font-family:ui-monospace,Menlo,monospace;color:{{ hdmiColor }}">{{ hdmiNote }}</span></div>
<div style="height:186px;border:1px solid var(--color-divider)">
<x-import component-from-global-scope="image-slot" from="./image-slot.js" id="livehdmi" shape="rect" hint-size="100%,186px" placeholder="HDMI 采集帧 — 拖入主机画面截图"></x-import>
</div>
<div style="font-size:10.5px;color:var(--color-neutral-600);font-family:ui-monospace,Menlo,monospace">采集卡 CAP-01 · 1080p@5fps · OS 未启动/蓝屏时仍可留证</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">遥测 · TELEMETRY</h6>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:11px">
<div style="border:1px solid var(--color-divider);padding:8px 10px"><div class="text-muted" style="font-size:10px;letter-spacing:.08em">HOST 心跳</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:700;color:{{ hbColor }};margin-top:2px;animation:{{ hbAnim }}">{{ hbText }}</div></div>
<div style="border:1px solid var(--color-divider);padding:8px 10px"><div class="text-muted" style="font-size:10px;letter-spacing:.08em">DUT 温度</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:700;margin-top:2px">{{ temp }}°C</div></div>
<div style="border:1px solid var(--color-divider);padding:8px 10px"><div class="text-muted" style="font-size:10px;letter-spacing:.08em">设备枚举</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700;color:var(--color-accent-700);margin-top:4px">nvme0n1 · Gen4 x4</div></div>
<div style="border:1px solid var(--color-divider);padding:8px 10px"><div class="text-muted" style="font-size:10px;letter-spacing:.08em">供电 · OOB</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700;margin-top:4px">AC ON · ATX ON</div></div>
</div>
<div>
<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--color-neutral-600);margin-bottom:3px"><span>温度 30min</span><span>阈值 55°C</span></div>
<svg width="100%" height="36" viewBox="0 0 240 36" preserveAspectRatio="none"><line x1="0" y1="8" x2="240" y2="8" stroke="rgba(29,31,32,.14)" stroke-dasharray="3 3"></line><polyline stroke-linejoin="round" stroke-linecap="round" points="0,26 20,25 40,27 60,24 80,22 100,23 120,20 140,21 160,19 180,22 200,20 220,21 240,20" fill="none" stroke="#5980a6" stroke-width="1.5"></polyline></svg>
</div>
</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px;border-color:{{ ladderBorder }}">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><h6 style="margin:0;color:var(--color-neutral-700)">分级恢复阶梯 · ESCALATION</h6><span style="margin-left:auto;font-size:10px;font-family:ui-monospace,Menlo,monospace;color:var(--color-neutral-600)">esc-default-v2</span></div>
<div style="display:flex;flex-direction:column">
<sc-for list="{{ ladder }}" as="lv" hint-placeholder-count="5">
<div style="{{ lv.rowStyle }}">
<span style="{{ lv.numStyle }}">{{ lv.num }}</span>
<div style="flex:1">
<div style="font-size:12.5px;font-weight:500">{{ lv.name }}</div>
<div style="font-size:10.5px;color:var(--color-neutral-600)">{{ lv.desc }}</div>
</div>
<span style="{{ lv.stateStyle }}">{{ lv.state }}</span>
</div>
</sc-for>
</div>
<div style="font-size:10.5px;color:var(--color-neutral-600);border-top:1px solid var(--color-divider);padding-top:8px">恢复前先取证 · 多次失败进入安全停止,禁止无限重启</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">本轮关键指标 · CYCLE {{ cycle }}</h6>
<div style="display:flex;flex-direction:column;font-family:ui-monospace,Menlo,monospace;font-size:11px">
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">4k_mixed IOPS</span><span>218,430</span></div>
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">P99 延迟</span><span>412 µs</span></div>
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">唤醒耗时</span><span>11.2 s</span></div>
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid rgba(29,31,32,.08)"><span style="color:var(--color-neutral-600)">SMART 温度峰值</span><span>46°C</span></div>
<div style="display:flex;justify-content:space-between;padding:5px 0"><span style="color:var(--color-neutral-600)">校验哈希</span><span style="color:var(--color-accent-700)">一致 ✓</span></div>
</div>
<a href="Evidence.dc.html" style="font-size:11.5px">打开本任务证据 →</a>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ vB }}" hint-placeholder-val="{{ false }}">
<div data-screen-label="实时运行 · 变体B 时间线泳道">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px;margin-bottom:16px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><h6 style="margin:0;color:var(--color-neutral-700)">执行时间线 · 14:20 → NOW</h6><span style="margin-left:auto;font-size:10.5px;font-family:ui-monospace,Menlo,monospace;color:var(--color-neutral-600)">每格 2 分钟 · cycle 6667</span></div>
<div style="display:grid;grid-template-columns:64px 1fr;row-gap:6px;font-family:ui-monospace,Menlo,monospace;font-size:10px">
<div style="color:var(--color-neutral-600);padding-top:5px">TEST</div>
<div style="position:relative;height:26px;border:1px solid var(--color-divider);background:repeating-linear-gradient(90deg,transparent 0 calc(100%/9 - 1px),rgba(29,31,32,.07) calc(100%/9 - 1px) calc(100%/9))">
<div style="position:absolute;top:4px;bottom:4px;left:2%;width:24%;background:var(--color-accent-200);border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap">io 4k_mixed</div>
<div style="position:absolute;top:4px;bottom:4px;left:27%;width:9%;background:var(--color-accent-200);border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap">hib</div>
<div style="position:absolute;top:4px;bottom:4px;left:37%;width:7%;background:var(--color-accent-200);border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap">res</div>
<div style="position:absolute;top:4px;bottom:4px;left:45%;width:6%;background:var(--color-accent-200);border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 4px;overflow:hidden;white-space:nowrap">枚举</div>
<div style="position:absolute;top:4px;bottom:4px;left:52%;width:10%;background:var(--color-accent-200);border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap">verify</div>
<div style="position:absolute;top:4px;bottom:4px;left:64%;width:26%;background-color:var(--color-accent);background-image:repeating-linear-gradient(90deg,rgba(242,242,243,.3) 0 6px,transparent 6px 14px);animation:omScan 1.1s linear infinite;border:1px solid var(--color-accent-700);color:#f2f2f3;display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap;animation:omPulse 2s infinite">cycle 67 · io 4k_mixed RUNNING</div>
</div>
<div style="color:var(--color-neutral-600);padding-top:5px">HOST</div>
<div style="position:relative;height:26px;border:1px solid var(--color-divider)">
<div style="position:absolute;top:4px;bottom:4px;left:2%;width:24%;border:1px solid var(--color-divider);color:var(--color-neutral-700);display:flex;align-items:center;padding:0 6px;white-space:nowrap;overflow:hidden">ONLINE · hb 1-3s</div>
<div style="position:absolute;top:4px;bottom:4px;left:27%;width:17%;background:repeating-linear-gradient(135deg,rgba(29,31,32,.14) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);color:var(--color-neutral-700);display:flex;align-items:center;padding:0 6px;white-space:nowrap;overflow:hidden">S4 休眠</div>
<div style="position:absolute;top:4px;bottom:4px;left:45%;width:45%;border:1px solid var(--color-divider);color:var(--color-neutral-700);display:flex;align-items:center;padding:0 6px;white-space:nowrap;overflow:hidden">ONLINE · resume 11.2s</div>
</div>
<div style="color:var(--color-neutral-600);padding-top:5px">DUT</div>
<div style="position:relative;height:26px;border:1px solid var(--color-divider)">
<div style="position:absolute;top:4px;bottom:4px;left:2%;width:42%;border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 6px;white-space:nowrap;overflow:hidden">nvme0n1 Gen4 x4 · 42-46°C</div>
<div style="position:absolute;top:4px;bottom:4px;left:45%;width:3%;background:var(--color-accent-800)"></div>
<div style="position:absolute;top:4px;bottom:4px;left:49%;width:41%;border:1px solid var(--color-accent-400);color:var(--color-accent-800);display:flex;align-items:center;padding:0 6px;white-space:nowrap;overflow:hidden">重新枚举 1.8s · SMART 正常</div>
</div>
<div style="color:var(--color-neutral-600);padding-top:5px">OOB</div>
<div style="position:relative;height:26px;border:1px solid var(--color-divider)">
<div style="position:absolute;top:4px;bottom:4px;left:2%;width:88%;border:1px dashed var(--color-divider);color:var(--color-neutral-500);display:flex;align-items:center;padding:0 6px;white-space:nowrap;overflow:hidden">待命 · 心跳监测中 · 无供电动作</div>
</div>
</div>
<div style="position:relative;height:0"><div style="position:absolute;right:9.5%;top:-118px;width:1px;height:118px;background:#b03d2e;animation:omBlink 1.2s infinite"></div></div>
<div style="display:flex;gap:14px;font-size:10px;color:var(--color-neutral-600)"><span><span style="display:inline-block;width:10px;height:8px;background:var(--color-accent-200);border:1px solid var(--color-accent-400);vertical-align:-1px"></span> 测试步骤</span><span><span style="display:inline-block;width:10px;height:8px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.14) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);vertical-align:-1px"></span> 电源状态</span><span><span style="display:inline-block;width:10px;height:8px;background:var(--color-accent-800);vertical-align:-1px"></span> 链路事件</span><span style="color:#8a2e22">│ 播放头 NOW</span></div>
</div>
<div style="display:grid;grid-template-columns:1fr 300px;gap:16px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:0;gap:0">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="padding:10px 14px;border-bottom:1px solid var(--color-divider)"><h6 style="margin:0;color:var(--color-neutral-700)">日志尾部 · TAIL</h6></div>
<div ref="{{ logRefB }}" style="height:260px;overflow-y:auto;padding:10px 14px;background:var(--color-neutral-100);font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.75">
<sc-for list="{{ logs }}" as="ln" hint-placeholder-count="8">
<div style="animation:omIn .3s ease both;display:flex;gap:10px"><span style="color:var(--color-neutral-500);white-space:nowrap">{{ ln.t }}</span><span style="{{ ln.laneStyle }}">{{ ln.lane }}</span><span style="{{ ln.textStyle }}">{{ ln.text }}</span></div>
</sc-for>
</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px;gap:10px;border-color:{{ ladderBorder }}">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">恢复阶梯</h6>
<div style="display:flex;flex-direction:column">
<sc-for list="{{ ladder }}" as="lv" hint-placeholder-count="5">
<div style="{{ lv.rowStyle }}">
<span style="{{ lv.numStyle }}">{{ lv.num }}</span>
<div style="flex:1"><div style="font-size:12px;font-weight:500">{{ lv.name }}</div></div>
<span style="{{ lv.stateStyle }}">{{ lv.state }}</span>
</div>
</sc-for>
</div>
</div>
</div>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:950},&quot;defaultVariant&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;a&quot;,&quot;b&quot;],&quot;default&quot;:&quot;a&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;布局&quot;}}">
class Component extends DCLogic {
state = {
variant: null, phase: 'running', runState: 'IDLE', estopped: false, demoBusy: false,
runId: '', hostName: 'WS-05', dutLabel: 'NV-E1T92', firmwareLabel: '候选固件',
startText: '尚未开始', checkpointText: '尚无', cycle: 0, target: 8, progress: 0, hbAge: 2,
logs: [
{ t: '14:20:44', lane: 'SYS', tone: 'mut', text: 'cycle 66 完成 · 检查点已写入 (state_hash 7c1f…9d02)' },
{ t: '14:21:52', lane: 'TEST', tone: 'ok', text: 'cycle 67 开始 · nvme.collect_baseline PASS' },
{ t: '14:22:10', lane: 'CMD', tone: 'mut', text: 'fio --profile 4k_mixed --runtime 300 --dut /dev/nvme0n1' },
{ t: '14:25:03', lane: 'TEST', tone: 'ok', text: 'hibernate → resume 成功 · 唤醒 11.2s · 唤醒源 RTC' },
{ t: '14:25:11', lane: 'DUT', tone: 'ok', text: 'verify_enumeration PASS · nvme0n1 Gen4 x4 · 1.8s' },
{ t: '14:28:51', lane: 'TEST', tone: 'ok', text: 'data.verify_hash PASS · 128 files · sha256 一致' },
{ t: '14:29:02', lane: 'CMD', tone: 'mut', text: 'io.run_profile 4k_mixed 启动 · duration 300s · QD32' },
{ t: '14:30:15', lane: 'HB', tone: 'mut', text: 'Agent 心跳正常 · CPU 34% · MEM 41% · 磁盘余量 812G' }
]
};
_log(lane, tone, text) {
const t = new Date().toLocaleTimeString('zh-CN', { hour12: false });
this.setState(s => ({ logs: [...s.logs, { t, lane, tone, text }] }));
}
_scroll() {
for (const r of [this._ref, this._refB]) if (r && r.current) r.current.scrollTop = r.current.scrollHeight;
}
componentDidMount() {
this._ref = React.createRef(); this._refB = React.createRef();
this.forceUpdate();
this._syncRun();
this._poll = setInterval(() => this._syncRun(), 650);
this._hb = setInterval(() => {
if (this.state.phase === 'running' && !this.state.estopped) this.setState({ hbAge: (this.state.hbAge % 4) + 1 });
}, 2000);
}
componentWillUnmount() { clearInterval(this._hb); clearInterval(this._poll); }
componentDidUpdate() { this._scroll(); }
_lane(event) {
if (event.kind.includes('RECOVERY')) return 'RCVR';
if (event.kind.includes('HEARTBEAT')) return 'HB';
if (event.source === 'oob') return 'OOB';
if (event.kind.includes('EVIDENCE')) return 'EVID';
if (event.kind.includes('LOOP')) return 'TEST';
if (event.kind.includes('DUT')) return 'DUT';
return 'SYS';
}
_tone(event) {
if (event.severity === 'CRITICAL' || event.severity === 'ERROR') return 'bad';
if (event.severity === 'WARNING') return 'warn';
if (event.kind.includes('SUCCEEDED') || event.kind.includes('COMPLETED') || event.kind === 'SAFETY_APPROVED') return 'ok';
return 'mut';
}
_phase(run, events) {
if (run.state !== 'RECOVERING') return run.state === 'COMPLETED' ? 'completed' : 'running';
const latest = [...events].reverse().find(e => e.kind === 'RECOVERY_STARTED');
const level = latest && latest.payload ? latest.payload.level : '';
if (level.startsWith('L3')) return 'l3';
if (level.startsWith('L2')) return 'l2';
if (level.startsWith('L1')) return 'l1';
return 'lost';
}
async _syncRun() {
const runId = this.state.runId || localStorage.getItem('flashops.activeRunId');
if (!runId) return;
try {
const [runResponse, eventResponse] = await Promise.all([
fetch('/api/v1/runs/' + encodeURIComponent(runId)),
fetch('/api/v1/runs/' + encodeURIComponent(runId) + '/events')
]);
if (!runResponse.ok || !eventResponse.ok) return;
const run = await runResponse.json();
const events = await eventResponse.json();
const logs = events.map(event => ({
t: new Date(event.ts).toLocaleTimeString('zh-CN', { hour12: false }),
lane: this._lane(event),
tone: this._tone(event),
text: '#' + event.seq + ' · ' + event.message
}));
this.setState({
runId: run.id,
hostName: (run.environment && run.environment.host) || 'WS-05',
dutLabel: run.environment ? ((run.environment.dut_serial || '') + ' · ' + (run.environment.dut_model || '')).trim() : '未绑定',
firmwareLabel: run.firmware_b_version ? ('FW_' + run.firmware_b_version + ' (B) 候选') : '候选固件',
startText: run.started_at ? new Date(run.started_at).toLocaleTimeString('zh-CN', { hour12: false }) : '等待调度',
checkpointText: run.checkpoint && run.checkpoint.loop_index ? ('cycle ' + run.checkpoint.loop_index) : '尚无',
runState: run.state,
cycle: run.loop_done,
target: run.loop_target,
progress: run.progress,
phase: this._phase(run, events),
estopped: run.state === 'ABORTED',
demoBusy: !['COMPLETED', 'ABORTED', 'FROZEN', 'REJECTED'].includes(run.state),
logs
});
} catch (_) {}
}
async startDemo() {
if (this.state.demoBusy || this.state.estopped) return;
this.setState({ demoBusy: true, estopped: false, logs: [] });
try {
const response = await fetch('/api/v1/runs/demo', { method: 'POST' });
if (!response.ok) throw new Error('创建演示任务失败');
const run = await response.json();
localStorage.setItem('flashops.activeRunId', run.id);
this.setState({ runId: run.id, cycle: 0, target: run.loop_target, progress: 0 });
this._syncRun();
} catch (error) {
this.setState({ demoBusy: false });
this._log('SYS', 'bad', error.message || '控制平面不可用');
}
}
async emergencyStop() {
if (!this.state.runId || this.state.estopped) return;
try {
await fetch('/api/v1/runs/' + encodeURIComponent(this.state.runId) + '/emergency-stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ actor: 'console-user', reason: '控制台紧急停止' })
});
this._syncRun();
} catch (_) {}
}
async togglePause() {
if (!this.state.runId || !['RUNNING', 'PAUSED'].includes(this.state.runState)) return;
const action = this.state.runState === 'PAUSED' ? 'resume' : 'pause';
try {
await fetch('/api/v1/runs/' + encodeURIComponent(this.state.runId) + '/' + action, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ actor: 'console-user', reason: action === 'pause' ? '控制台暂停' : '控制台继续' })
});
this._syncRun();
} catch (_) {}
}
renderVals() {
const s = this.state;
const mono = { fontFamily: 'ui-monospace,Menlo,monospace' };
const laneColors = { HB: '#8a2e22', OOB: 'var(--color-accent-800)', RCVR: '#8a2e22', TEST: 'var(--color-accent-700)', CMD: 'var(--color-neutral-600)', SYS: 'var(--color-neutral-600)', EVID: 'var(--color-neutral-600)', DUT: 'var(--color-accent-800)' };
const toneColors = { ok: 'var(--color-accent-800)', bad: '#8a2e22', warn: '#8a2e22', mut: 'var(--color-neutral-700)', dut: 'var(--color-accent-800)' };
const logs = s.logs.map(l => ({
...l,
laneStyle: { color: laneColors[l.lane] || 'var(--color-neutral-600)', whiteSpace: 'nowrap', minWidth: '38px' },
textStyle: { color: toneColors[l.tone] || 'var(--color-text)' }
}));
const recovering = ['lost', 'l1', 'l2', 'l3', 'online'].includes(s.phase);
const phaseIdx = { running: -1, lost: 0, l1: 0, l2: 1, l3: 2, online: 2 }[s.phase];
const results = {
running: [], lost: ['active'], l1: ['active'], l2: ['fail', 'active'], l3: ['fail', 'fail', 'active'], online: ['fail', 'fail', 'ok']
}[s.phase] || [];
const defs = [
{ num: 'L1', name: 'Agent 软恢复', desc: '终止异常进程 · 清理资源' },
{ num: 'L2', name: 'OS 通道重启', desc: 'SSH / WinRM 远程软重启' },
{ num: 'L3', name: '带外 Reset', desc: '光耦短按主板 Reset' },
{ num: 'L4', name: 'AC 电源循环', desc: '整机断电 · 安全间隔后上电' },
{ num: 'L5', name: '安全停止', desc: '保留现场 · 禁止无限重启' }
];
const ladder = defs.map((d, i) => {
const r = results[i];
let state = '待命', stateStyle = { ...mono, fontSize: '10px', color: 'var(--color-neutral-500)' };
let rowStyle = { display: 'flex', gap: '10px', alignItems: 'center', padding: '8px 6px', borderBottom: i < 4 ? '1px solid rgba(29,31,32,.08)' : 'none', transition: 'background .25s ease' };
let numStyle = { ...mono, width: '26px', height: '22px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: '10.5px', border: '1px dashed var(--color-divider)', color: 'var(--color-neutral-500)', transition: 'background .25s ease, border-color .25s ease, color .25s ease' };
if (r === 'active') { state = '执行中'; stateStyle = { ...mono, fontSize: '10px', color: 'var(--color-accent-700)', animation: 'omPulse 1.2s infinite' }; numStyle = { ...numStyle, border: 'none', background: 'var(--color-accent-800)', color: '#f2f2f3' }; rowStyle = { ...rowStyle, background: 'var(--color-accent-100)' }; }
if (r === 'fail') { state = '失败'; stateStyle = { ...mono, fontSize: '10px', color: '#8a2e22' }; numStyle = { ...numStyle, border: '1px solid #b03d2e', color: '#8a2e22' }; }
if (r === 'ok') { state = '成功'; stateStyle = { ...mono, fontSize: '10px', color: 'var(--color-accent-700)' }; numStyle = { ...numStyle, border: 'none', background: 'var(--color-accent)', color: '#f2f2f3' }; }
return { ...d, state, stateStyle, rowStyle, numStyle };
});
let badgeText = 'RUNNING', badgeStyle = { display: 'inline-flex', alignItems: 'center', gap: '6px', background: 'var(--color-accent)', color: '#f2f2f3', fontSize: '11px', letterSpacing: '.06em', padding: '5px 12px', fontFamily: 'var(--font-heading)', fontWeight: 600, transition: 'background .25s ease, color .25s ease' };
if (recovering) { badgeText = 'RECOVERING'; badgeStyle = { ...badgeStyle, background: 'var(--color-accent-200)', color: 'var(--color-accent-800)', animation: 'omPulse 1.2s infinite' }; }
if (s.runState === 'QUEUED' || s.runState === 'PREFLIGHT') { badgeText = s.runState; badgeStyle = { ...badgeStyle, background: 'var(--color-neutral-600)' }; }
if (s.runState === 'COMPLETED') { badgeText = 'COMPLETED · PASS'; badgeStyle = { ...badgeStyle, background: 'var(--color-accent-700)' }; }
if (s.runState === 'REJECTED' || s.runState === 'FROZEN') { badgeText = s.runState; badgeStyle = { ...badgeStyle, background: '#b03d2e' }; }
if (s.estopped) { badgeText = 'EMERGENCY_STOP'; badgeStyle = { ...badgeStyle, background: '#b03d2e' }; }
const hbLost = recovering && s.phase !== 'online';
const vv = s.variant ?? (this.props.defaultVariant ?? 'a');
return {
vA: vv === 'a', vB: vv === 'b',
setA: () => this.setState({ variant: 'a' }), setB: () => this.setState({ variant: 'b' }),
startDemo: () => this.startDemo(), demoBusy: s.demoBusy,
togglePause: () => this.togglePause(),
pauseLabel: s.runState === 'PAUSED' ? '继续' : '暂停',
pauseDisabled: !['RUNNING', 'PAUSED'].includes(s.runState),
stopDisabled: !['QUEUED', 'PREFLIGHT', 'RUNNING', 'RECOVERING', 'PAUSED'].includes(s.runState),
estop: () => this.emergencyStop(),
resumeEstop: () => { this.setState({ estopped: false, demoBusy: false, runId: '' }, () => this.startDemo()); },
estopped: s.estopped,
runId: s.runId || '尚未创建真实任务',
hostName: s.hostName,
dutLabel: s.dutLabel,
firmwareLabel: s.firmwareLabel,
startText: s.startText,
checkpointText: s.checkpointText,
cycle: s.cycle,
target: s.target,
progStyle: { position: 'absolute', inset: 0, width: s.progress + '%', transition: 'width .4s ease', backgroundColor: s.estopped ? '#b03d2e' : 'var(--color-accent)', backgroundImage: s.estopped ? 'none' : 'repeating-linear-gradient(90deg,rgba(242,242,243,.3) 0 6px,transparent 6px 14px)', animation: s.estopped ? 'none' : 'omScan 1.1s linear infinite' },
badgeText, badgeStyle,
logs, logRef: this._ref, logRefB: this._refB, ladder,
ladderBorder: recovering ? 'var(--color-accent-600)' : 'var(--color-divider)',
hbText: s.estopped ? '已冻结' : (hbLost ? '丢失 ' + (s.phase === 'lost' ? '34s' : '2m+') : s.hbAge + 's 前'),
hbColor: hbLost ? '#8a2e22' : 'var(--color-accent-700)',
hbAnim: hbLost ? 'omPulse 1.2s infinite' : 'none',
temp: '43.' + ((s.hbAge * 3) % 9),
hdmiNote: hbLost ? '死机帧已冻结留证' : 'LIVE · 5fps',
hdmiColor: hbLost ? '#8a2e22' : 'var(--color-accent-700)',
ioRowStyle: { display: 'flex', gap: '9px', alignItems: 'center', padding: '5px 0', fontSize: '12px', color: recovering ? 'var(--color-neutral-500)' : 'var(--color-text)' },
ioDotStyle: { width: '14px', height: '14px', background: recovering ? 'transparent' : 'var(--color-accent)', border: recovering ? '1px dashed var(--color-divider)' : 'none', animation: recovering ? 'none' : 'omPulse 1.6s infinite' },
ioNote: recovering ? '冻结,等待恢复' : '178s / 300s'
};
}
}
</script>
</body>
</html>
+264
View File
@@ -0,0 +1,264 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
@keyframes omPulse{0%,100%{opacity:1}50%{opacity:.3}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="tasks" hint-size="100%,92px"></dc-import>
<div data-screen-label="任务中心" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:18px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">02 · TASKS</div>
<h2 style="margin:2px 0 0;font-size:30px">任务中心</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">创建 · 队列 · 预约 · 破坏性任务必须绑定允许的 DUT 序列号</div>
</div>
<span style="margin-left:auto"></span>
<span class="tag tag-neutral">运行 2</span>
<span class="tag tag-neutral">队列 3</span>
<span class="tag tag-neutral">今日完成 4</span>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:0;gap:0;margin-bottom:20px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px;padding:12px 18px;border-bottom:1px solid var(--color-divider)">
<h5 style="margin:0;letter-spacing:.03em">创建任务 · NEW RUN</h5>
<span class="text-muted" style="font-size:11.5px">固件 A/B 循环回归</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">工作流签名 8e02…c4 · 已审核 v3</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1.15fr">
<div style="padding:16px 18px;border-right:1px solid var(--color-divider)">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:12px"><span style="font-family:var(--font-heading);font-weight:600;font-size:22px;color:var(--color-accent-700)">01</span><span style="font-size:10.5px;letter-spacing:.14em;color:var(--color-neutral-600)">资产绑定</span></div>
<div class="field" style="margin-bottom:10px"><label>测试主机</label>
<select class="input" style="font-size:13px"><option>WS-05 · HOST-A03 · Win11 22631</option><option>WS-04 · HOST-L01 · Ubuntu 22.04</option></select>
</div>
<div class="field" style="margin-bottom:10px"><label>DUT(被测设备)</label>
<select class="input" style="font-size:13px"><option>SAMPLE-006 · NV-E1T92 · 1.92TB U.2</option><option>SAMPLE-003 · NV-E960 · 960GB M.2</option></select>
</div>
<div style="border:1px solid var(--color-divider);padding:8px 10px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;line-height:1.8;color:var(--color-neutral-700)">SN S6XNNA0T609 · 允许破坏性写入<br>P/E 磨损 12% · 生命周期 测试专用<br>带外 OOB-05 已配对 ✓</div>
</div>
<div style="padding:16px 18px;border-right:1px solid var(--color-divider)">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:12px"><span style="font-family:var(--font-heading);font-weight:600;font-size:22px;color:var(--color-accent-700)">02</span><span style="font-size:10.5px;letter-spacing:.14em;color:var(--color-neutral-600)">固件 A / B</span></div>
<div class="field" style="margin-bottom:10px"><label>固件 A · 基线</label>
<select class="input" style="font-size:13px"><option>FW_3.2.1 · 已审批</option><option>FW_3.1.9 · 已审批</option></select>
</div>
<div class="field" style="margin-bottom:10px"><label>固件 B · 候选</label>
<select class="input" style="font-size:13px"><option>FW_3.3.0-rc3 · 待审批</option><option>FW_3.3.0-rc2 · 已审批</option></select>
</div>
<div style="border:1px solid var(--color-divider);padding:8px 10px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;line-height:1.8;color:var(--color-neutral-700)">A sha256 4bd1…08ce ✓<br>B sha256 9f2c…41aa ✓<br><span style="color:#8a2e22">B 尚未审批 — 启动前需固件负责人批准</span></div>
</div>
<div style="padding:16px 18px;border-right:1px solid var(--color-divider)">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:12px"><span style="font-family:var(--font-heading);font-weight:600;font-size:22px;color:var(--color-accent-700)">03</span><span style="font-size:10.5px;letter-spacing:.14em;color:var(--color-neutral-600)">模板与参数</span></div>
<div class="field" style="margin-bottom:10px"><label>工作流模板(已审核)</label>
<select class="input" style="font-size:13px"><option>fw_ab_powerstate_regression · v3</option><option>fw_coldboot_matrix · v2</option><option>fw_retention_72h · v1</option></select>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px">
<div class="field"><label>循环次数</label><input class="input" value="100" style="font-size:13px"></div>
<div class="field"><label>检查点间隔</label><select class="input" style="font-size:13px"><option>每循环</option><option>每 5 循环</option></select></div>
</div>
<div class="field" style="margin-bottom:10px"><label>失败重试策略</label>
<select class="input" style="font-size:13px"><option>esc-default-v2 · 分级恢复,破坏性步骤不自动重试</option><option>conservative · 任何失败即安全停止</option></select>
</div>
<div class="field"><label>报告接收人</label><input class="input" value="fw-team@corp, lab-lead@corp" style="font-size:13px"></div>
</div>
<div style="padding:16px 18px;background:var(--color-neutral-100)">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:12px"><span style="font-family:var(--font-heading);font-weight:600;font-size:22px;color:var(--color-accent-700)">04</span><span style="font-size:10.5px;letter-spacing:.14em;color:var(--color-neutral-600)">安全与预检</span></div>
<label style="display:flex;gap:9px;align-items:flex-start;font-size:12.5px;margin-bottom:8px;cursor:pointer"><input type="checkbox" checked style="accent-color:#5980a6;margin-top:2px"><span>允许破坏性写入(覆盖数据区)<br><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-600)">allowed_dut_serials: ["S6XNNA0T609"]</span></span></label>
<label style="display:flex;gap:9px;align-items:center;font-size:12.5px;margin-bottom:12px;cursor:pointer"><input type="checkbox" checked style="accent-color:#5980a6"><span>系统盘保护已启用 · 非白名单序列号拒绝写入</span></label>
<div style="border:1px solid var(--color-divider);background:var(--color-bg);padding:10px 12px;margin-bottom:12px">
<div style="display:flex;align-items:center;margin-bottom:6px"><span style="font-size:10px;letter-spacing:.12em;color:var(--color-neutral-600)">环境预检 · PRECHECK</span><span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:{{ pcColor }}">{{ pcNote }}</span></div>
<div style="display:flex;flex-direction:column;font-family:ui-monospace,Menlo,monospace;font-size:11px">
<sc-for list="{{ checks }}" as="ck" hint-placeholder-count="6">
<div style="display:flex;gap:8px;align-items:center;padding:3.5px 0"><span style="{{ ck.iconStyle }}">{{ ck.icon }}</span><span style="{{ ck.txtStyle }}">{{ ck.name }}</span><span style="margin-left:auto;color:var(--color-neutral-500);font-size:10px">{{ ck.note }}</span></div>
</sc-for>
</div>
</div>
<sc-if value="{{ started }}" hint-placeholder-val="{{ false }}">
<div style="border:1px solid var(--color-accent-600);background:var(--color-accent-100);color:var(--color-accent-800);padding:9px 12px;font-size:12.5px;margin-bottom:10px">✓ 已入队 <span style="font-family:ui-monospace,Menlo,monospace">{{ startedRun }}</span> · WS-05 · 正在执行 · <a href="LiveRun.dc.html">跟踪 →</a></div>
</sc-if>
<div style="display:flex;gap:10px">
<button class="btn btn-secondary" onClick="{{ runPrecheck }}" disabled="{{ pcBusy }}" style="flex:1">运行预检</button>
<button class="btn btn-primary" onClick="{{ start }}" disabled="{{ startDisabled }}" style="flex:1.4;gap:7px">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polygon points="6 3 20 12 6 21 6 3"></polygon></svg>
启动 / 预约任务
</button>
</div>
</div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><h6 style="margin:0;color:var(--color-neutral-700)">队列 · QUEUE 3</h6><span style="margin-left:auto;font-size:10.5px;color:var(--color-neutral-500)">调度策略:优先级 + 维护窗口避让</span></div>
<table class="table">
<thead><tr><th></th><th>任务</th><th>工位 / DUT</th><th>计划</th><th>状态</th><th></th></tr></thead>
<tbody>
<tr>
<td><span class="tag tag-accent">P1</span></td>
<td><div style="font-size:12.5px">fw_coldboot_matrix · 500 次冷启动</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">FW_3.3.0-rc2(B)</div></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-05 · SAMPLE-006</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">今日 22:00</td>
<td><span class="tag tag-outline">已预约</span></td>
<td><button class="btn btn-ghost" style="font-size:11.5px">取消</button></td>
</tr>
<tr>
<td><span class="tag tag-neutral">P2</span></td>
<td><div style="font-size:12.5px">fw_ab 从检查点续跑 · cycle 41→100</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">FW_3.3.0-rc2(B)</div></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-03 · SAMPLE-002</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">恢复后自动</td>
<td><span style="font-size:10.5px;padding:3px 9px;background:var(--color-accent-200);color:var(--color-accent-800);animation:omPulse 1.4s infinite">等待恢复</span></td>
<td><button class="btn btn-ghost" style="font-size:11.5px">改期</button></td>
</tr>
<tr>
<td><span class="tag tag-neutral">P3</span></td>
<td><div style="font-size:12.5px">incoming_qc_quick · 来料抽检 12 盘</div><div style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">批次 B2607-11</div></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">WS-04 · 批量</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">明日 06:00</td>
<td><span class="tag tag-outline">已预约</span></td>
<td><button class="btn btn-ghost" style="font-size:11.5px">改期</button></td>
</tr>
</tbody>
</table>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center"><h6 style="margin:0;color:var(--color-neutral-700)">最近完成 · RECENT</h6><a href="Compare.dc.html" style="margin-left:auto;font-size:11.5px">全部报告 →</a></div>
<table class="table">
<thead><tr><th>RUN</th><th>模板</th><th>结果</th><th>循环</th><th>介入</th><th>报告</th></tr></thead>
<tbody>
<tr>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">run-…0726-0007</td>
<td style="font-size:12.5px">fw_ab_powerstate</td>
<td><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 8px;background:var(--color-accent-800);color:#f2f2f3">FAILED_DUT</span></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">97/100</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">1</td>
<td><a href="Evidence.dc.html" style="font-size:12px">证据</a> · <a href="Compare.dc.html" style="font-size:12px">A/B</a></td>
</tr>
<tr>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">run-…0726-0011</td>
<td style="font-size:12.5px">fw_retention_72h 段2</td>
<td><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 8px;border:1px dashed var(--color-neutral-500);color:var(--color-neutral-700)">UNKNOWN</span></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">50/50</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">0</td>
<td><a href="Evidence.dc.html" style="font-size:12px">证据</a></td>
</tr>
<tr>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">run-…0725-0003</td>
<td style="font-size:12.5px">fw_ab_powerstate</td>
<td><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 8px;border:1px solid var(--color-accent-400);color:var(--color-accent-700)">PASSED</span></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">100/100</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">0</td>
<td><a href="Compare.dc.html" style="font-size:12px">A/B</a></td>
</tr>
<tr>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11.5px">run-…0725-0018</td>
<td style="font-size:12.5px">fw_coldboot_matrix</td>
<td><span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;padding:2px 8px;background:repeating-linear-gradient(135deg,rgba(29,31,32,.12) 0 2px,transparent 2px 5px);border:1px solid var(--color-divider);color:var(--color-neutral-800)">FAILED_INFRA</span></td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">88/100</td>
<td style="font-family:ui-monospace,Menlo,monospace;font-size:11px">0</td>
<td><a href="Evidence.dc.html" style="font-size:12px">证据</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:920}}">
class Component extends DCLogic {
state = { checks: Array(6).fill('idle'), pc: 'idle', started: false, startedRun: '', error: '' };
_names = [
{ name: 'DUT 序列号白名单绑定', note: 'S6XNNA0T609' },
{ name: '系统盘 / 分区保护', note: '启动盘已排除' },
{ name: '磁盘空间 · 日志目录', note: '812G 可用' },
{ name: '带外控制器 OOB-05', note: 'Power/Reset/温度' },
{ name: '工具链版本 · 许可证', note: 'fio 3.36 · nvme-cli 2.9.1' },
{ name: '时间同步 · 网络', note: 'NTP 偏差 4ms' }
];
async runPrecheck() {
if (this.state.pc === 'running') return;
this.setState({ checks: Array(6).fill('run'), pc: 'running', started: false, error: '' });
try {
const response = await fetch('/api/v1/preflight', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}'
});
if (!response.ok) throw new Error('预检服务返回 ' + response.status);
const result = await response.json();
this._names = result.checks.map(c => ({ name: c.name, note: c.note }));
this.setState({
checks: result.checks.map(c => c.passed ? 'ok' : 'bad'),
pc: result.passed ? 'done' : 'failed'
});
} catch (error) {
this.setState({ checks: Array(6).fill('bad'), pc: 'failed', error: error.message || 'API 不可用' });
}
}
async start() {
if (this.state.pc !== 'done' || this.state.started) return;
this.setState({ started: true, error: '' });
try {
const response = await fetch('/api/v1/runs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ loops: 8, inject_failure: true, created_by: 'console' })
});
if (!response.ok) throw new Error('创建任务失败: ' + response.status);
const run = await response.json();
localStorage.setItem('flashops.activeRunId', run.id);
this.setState({ startedRun: run.id });
} catch (error) {
this.setState({ started: false, error: error.message || '任务创建失败' });
}
}
renderVals() {
const mono = { fontFamily: 'ui-monospace,Menlo,monospace' };
const checks = this._names.map((n, i) => {
const st = this.state.checks[i];
return {
...n,
icon: st === 'ok' ? '✓' : st === 'bad' ? '×' : st === 'run' ? '●' : '·',
iconStyle: { width: '14px', textAlign: 'center', color: st === 'ok' ? 'var(--color-accent-700)' : st === 'bad' ? '#8a2e22' : st === 'run' ? 'var(--color-accent)' : 'var(--color-neutral-400)', animation: st === 'run' ? 'omPulse .8s infinite' : 'none' },
txtStyle: { color: st === 'idle' ? 'var(--color-neutral-500)' : 'var(--color-text)' }
};
});
const pc = this.state.pc;
return {
checks,
pcBusy: pc === 'running',
pcNote: pc === 'idle' ? '未运行' : pc === 'running' ? '检查中…' : pc === 'done' ? '6/6 通过 · API 已确认' : (this.state.error || '存在阻断项'),
pcColor: pc === 'done' ? 'var(--color-accent-700)' : 'var(--color-neutral-500)',
startDisabled: pc !== 'done' || this.state.started,
started: this.state.started,
startedRun: this.state.startedRun || '正在分配…',
runPrecheck: () => this.runPrecheck(),
start: () => this.start()
};
}
}
</script>
</body>
</html>
+239
View File
@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="workflows" hint-size="100%,92px"></dc-import>
<div data-screen-label="工作流模板" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
<div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:18px">
<div>
<div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">08 · WORKFLOWS</div>
<h2 style="margin:2px 0 0;font-size:30px">工作流模板</h2>
<div class="text-muted" style="font-size:12.5px;margin-top:2px">SOP 结构化 · 版本化 · 审批与签名 — 运行时禁止拼接未审核的危险命令</div>
</div>
<span style="margin-left:auto"></span>
<button class="btn btn-secondary" style="font-size:12.5px">从 SOP 文档草拟(AI)</button>
<button class="btn btn-primary" style="font-size:13px">新建模板</button>
</div>
<div style="display:grid;grid-template-columns:330px 1fr;gap:18px;align-items:start">
<div style="display:flex;flex-direction:column;gap:10px">
<sc-for list="{{ list }}" as="tp" hint-placeholder-count="4">
<div onClick="{{ tp.pick }}" tabindex="0" style="{{ tp.itemStyle }}">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:12.5px;font-weight:700">{{ tp.name }}</span>
<span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">{{ tp.ver }}</span>
</div>
<div style="font-size:12px;color:var(--color-neutral-700);margin:4px 0 6px">{{ tp.desc }}</div>
<div style="display:flex;gap:6px;align-items:center">
<span style="{{ tp.stStyle }}">{{ tp.status }}</span>
<sc-for list="{{ tp.risks }}" as="rk" hint-placeholder-count="2"><span style="font-family:ui-monospace,Menlo,monospace;font-size:9px;background:var(--color-accent-800);color:#f2f2f3;padding:1px 6px">{{ rk }}</span></sc-for>
<span style="margin-left:auto;font-size:10px;color:var(--color-neutral-500)">{{ tp.updated }}</span>
</div>
</div>
</sc-for>
<div style="border:1px dashed var(--color-divider);padding:11px 12px;font-size:11.5px;color:var(--color-neutral-600);line-height:1.6">每接入一套真实 SOP,就沉淀为可复用模板与连接器 · 模板参数化,严格控制定制边界</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 18px;gap:10px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:center;gap:10px">
<h4 style="margin:0;letter-spacing:.01em">{{ selName }}</h4>
<span style="{{ selStStyle }}">{{ selStatus }}</span>
<span style="margin-left:auto"></span>
<button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">复制为新版本</button>
<button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">导出 YAML</button>
<button class="btn btn-primary" disabled="{{ selPublished }}" style="font-size:12px;padding:5px 14px">提交审批</button>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-700)">
<span style="border:1px solid var(--color-divider);padding:2px 8px">{{ selVer }} · workflow_version 1</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">签名 {{ selSig }}</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">来源 {{ selSource }}</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">检查点:每循环</span>
<span style="border:1px solid var(--color-divider);padding:2px 8px">恢复策略 esc-default-v2</span>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 340px;gap:16px;align-items:start">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:4px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0 0 8px;color:var(--color-neutral-700)">步骤定义 · STEPS</h6>
<sc-for list="{{ steps }}" as="st" hint-placeholder-count="10">
<div style="{{ st.rowStyle }}">
<span style="{{ st.numStyle }}">{{ st.num }}</span>
<div style="flex:1">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:600">{{ st.name }}</span>
<sc-if value="{{ st.risk }}" hint-placeholder-val="{{ false }}"><span style="font-family:ui-monospace,Menlo,monospace;font-size:9px;background:var(--color-accent-800);color:#f2f2f3;padding:1px 6px">{{ st.risk }}</span></sc-if>
</div>
<div style="font-size:11.5px;color:var(--color-neutral-600);margin-top:1px">{{ st.note }}</div>
</div>
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500);white-space:nowrap">{{ st.meta }}</span>
</div>
</sc-for>
<div style="border:1px dashed #b03d2e;padding:10px 12px;margin-top:10px">
<div style="font-size:10px;letter-spacing:.12em;color:#8a2e22;margin-bottom:6px">ON_FAILURE · 失败处理管道</div>
<div style="display:flex;gap:8px;align-items:center;font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-800);flex-wrap:wrap">
<span style="border:1px solid var(--color-divider);padding:3px 9px;background:var(--color-bg)">evidence.capture_all</span>
<span style="color:var(--color-neutral-400)"></span>
<span style="border:1px solid var(--color-divider);padding:3px 9px;background:var(--color-bg)">recovery.escalate</span>
<span style="color:var(--color-neutral-400)"></span>
<span style="border:1px solid var(--color-divider);padding:3px 9px;background:var(--color-bg)">failure.classify</span>
<span style="font-size:10px;color:var(--color-neutral-600);margin-left:4px">取证先于恢复 · 破坏性步骤不自动重试</span>
</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:8px;border-color:var(--color-accent-600)">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">安全声明 · SAFETY</h6>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;line-height:1.9;background:var(--color-neutral-100);border:1px solid var(--color-divider);padding:9px 11px;color:var(--color-neutral-800)">safety:<br>&nbsp;&nbsp;allowed_dut_serials: {{ selSerials }}<br>&nbsp;&nbsp;destructive_write: {{ selDestr }}<br>&nbsp;&nbsp;require_approval: true<br>&nbsp;&nbsp;system_disk_guard: enforce<br>variables:<br>&nbsp;&nbsp;cycles: {{ selCycles }}<br>&nbsp;&nbsp;firmware: "${firmware}"</div>
<div style="font-size:11px;color:var(--color-neutral-600)">Format / Sanitize / 刷写只能来自签名模板,参数受 Schema 限制</div>
</div>
<div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:8px">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<h6 style="margin:0;color:var(--color-neutral-700)">审批记录 · AUDIT</h6>
<div style="display:flex;flex-direction:column">
<sc-for list="{{ approvals }}" as="ap" hint-placeholder-count="4">
<div style="display:flex;gap:10px;padding:5px 0;border-bottom:1px solid rgba(29,31,32,.06);font-size:11.5px">
<span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500);min-width:44px">{{ ap.t }}</span>
<span style="{{ ap.dotStyle }}"></span>
<span style="flex:1">{{ ap.what }}</span>
<span style="color:var(--color-neutral-600)">{{ ap.who }}</span>
</div>
</sc-for>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:880}}">
class Component extends DCLogic {
state = { sel: 0 };
renderVals() {
const mono = { fontFamily: 'ui-monospace,Menlo,monospace' };
const stOf = st => {
const b = { fontSize: '10px', padding: '2px 9px', letterSpacing: '.04em' };
if (st === '已发布') return { ...b, background: 'var(--color-accent-100)', color: 'var(--color-accent-800)' };
if (st === '审批中') return { ...b, border: '1px solid var(--color-accent-600)', color: 'var(--color-accent-700)' };
return { ...b, border: '1px dashed var(--color-neutral-500)', color: 'var(--color-neutral-700)' };
};
const T = [
{ name: 'fw_ab_powerstate_regression', ver: 'v3', status: '已发布', risks: ['破坏写', '刷写'], desc: '固件 A/B 休眠/唤醒 100 循环回归 — 首个落地 SOP', updated: '07-15', sig: '8e02…c4', source: 'SOP-FW-012(客户脱敏版)', serials: '["SAMPLE-001","SAMPLE-002"]', destr: 'true', cycles: '100',
steps: [
{ num: '01', name: 'lab.precheck', note: '安全预检 · 环境指纹 · 序列号/系统盘校验', meta: 'timeout 120s', risk: null, lvl: 0 },
{ num: '02', name: 'vendor.flash_firmware', note: 'image: ${firmware} · 哈希校验 · slot 激活', meta: 'timeout 300s · retry 1', risk: '固件更新', lvl: 0 },
{ num: '03', name: 'host.reboot', note: 'wait_for agent_online · 版本确认', meta: 'timeout 600s', risk: null, lvl: 0 },
{ num: 'L', name: 'loop · repeat ${cycles}', note: '检查点:每循环写入,失败从安全点续跑', meta: 'cycles 100', risk: null, lvl: -1 },
{ num: '·1', name: 'nvme.collect_baseline', note: 'SMART / Error Log / Telemetry 基线', meta: '30s', risk: null, lvl: 1 },
{ num: '·2', name: 'io.run_profile', note: 'profile 4k_mixed · duration 300s · QD32', meta: '写入', risk: '破坏写', lvl: 1 },
{ num: '·3', name: 'host.hibernate', note: 'S4 · 断言电源状态达成', meta: 'timeout 180s', risk: null, lvl: 1 },
{ num: '·4', name: 'host.wait_resume', note: 'RTC 唤醒 · Agent 上线', meta: 'timeout 300s', risk: null, lvl: 1 },
{ num: '·5', name: 'dut.verify_enumeration', note: '设备节点 · 链路宽度/速率断言', meta: 'retry 3', risk: null, lvl: 1 },
{ num: '·6', name: 'data.verify_hash', note: '校验集 sha256 对比 · 失败范围定位', meta: '120s', risk: null, lvl: 1 }
],
approvals: [
{ t: '07-15', what: 'v3 审核通过并发布 · 签名 8e02…c4', who: '李工 · 验证组长', kind: 'ok' },
{ t: '07-12', what: 'v3 提交审批:唤醒断言加严(+链路速率)', who: 'quan.zhang', kind: 'n' },
{ t: '07-08', what: 'v2 修订:循环 50→100 · 检查点改每循环', who: 'quan.zhang', kind: 'n' },
{ t: '06-30', what: 'v1 由 SOP-FW-012 草拟(AI 初稿+人工修订)', who: 'quan.zhang', kind: 'n' }
] },
{ name: 'fw_retention_72h', ver: 'v1', status: '审批中', risks: ['破坏写'], desc: '高温 72h 数据保持,分段校验', updated: '07-24', sig: '待签名', source: 'SOP-FW-019', serials: '["SAMPLE-004"]', destr: 'true', cycles: '3 段 ×24h',
steps: [
{ num: '01', name: 'lab.precheck', note: '含温箱通道检查', meta: 'timeout 120s', risk: null, lvl: 0 },
{ num: '02', name: 'io.write_retention_set', note: '写入 1.2TB 校验集 · 记录 LBA 映射', meta: '≈70min', risk: '破坏写', lvl: 0 },
{ num: 'L', name: 'loop · repeat 3', note: '每 24h 一段,段间校验', meta: '72h', risk: null, lvl: -1 },
{ num: '·1', name: 'chamber.hold_temp', note: '55°C 恒温 24h · 温度证据采集', meta: '24h', risk: null, lvl: 1 },
{ num: '·2', name: 'data.verify_hash', note: '分段读校验 · 错误率统计', meta: '90min', risk: null, lvl: 1 }
],
approvals: [
{ t: '07-24', what: 'v1 提交审批 · 等待验证组长', who: 'quan.zhang', kind: 'n' },
{ t: '07-22', what: 'v1 由 SOP-FW-019 草拟', who: 'quan.zhang', kind: 'n' }
] },
{ name: 'fw_coldboot_matrix', ver: 'v2', status: '已发布', risks: ['刷写'], desc: '500 次冷启动 · 枚举与启动时序断言', updated: '07-10', sig: '5fd0…9a', source: 'SOP-FW-007', serials: '["SAMPLE-006"]', destr: 'false', cycles: '500',
steps: [
{ num: '01', name: 'lab.precheck', note: '安全预检 · OOB AC 通道确认', meta: 'timeout 120s', risk: null, lvl: 0 },
{ num: 'L', name: 'loop · repeat 500', note: '冷启动矩阵', meta: 'cycles 500', risk: null, lvl: -1 },
{ num: '·1', name: 'oob.power_cycle', note: '整机 AC 断电 · 安全间隔 30s', meta: 'OOB 动作', risk: null, lvl: 1 },
{ num: '·2', name: 'host.wait_boot', note: 'POST → OS → Agent 上线计时', meta: 'timeout 600s', risk: null, lvl: 1 },
{ num: '·3', name: 'dut.verify_enumeration', note: '枚举耗时断言 <5s', meta: 'retry 0', risk: null, lvl: 1 }
],
approvals: [
{ t: '07-10', what: 'v2 审核通过并发布', who: '李工 · 验证组长', kind: 'ok' },
{ t: '07-06', what: 'v2 修订:加启动时序断言', who: 'quan.zhang', kind: 'n' }
] },
{ name: 'incoming_qc_quick', ver: 'v5', status: '草稿', risks: [], desc: '来料抽检 30min 快检(只读+短写)', updated: '今日', sig: '未签名', source: '内部模板', serials: '["批量·入库检标签"]', destr: 'false', cycles: '1',
steps: [
{ num: '01', name: 'lab.precheck', note: '批量扫码 · 序列号登记比对', meta: 'timeout 120s', risk: null, lvl: 0 },
{ num: '02', name: 'nvme.identify_baseline', note: 'Identify / SMART / 固件版本核对', meta: '60s', risk: null, lvl: 0 },
{ num: '03', name: 'io.short_stress', note: '5min 混合负载 · 温升记录', meta: '5min', risk: '破坏写', lvl: 0 },
{ num: '04', name: 'report.batch_summary', note: '批次基线对比 · 标签打印', meta: '30s', risk: null, lvl: 0 }
],
approvals: [
{ t: '今日', what: 'v5 草稿修订中(批次基线阈值)', who: 'quan.zhang', kind: 'n' }
] }
];
const sel = T[this.state.sel];
const list = T.map((tp, i) => ({
...tp,
pick: () => this.setState({ sel: i }),
stStyle: stOf(tp.status),
itemStyle: {
border: i === this.state.sel ? '1px solid var(--color-accent-600)' : '1px solid var(--color-divider)',
background: i === this.state.sel ? 'var(--color-accent-100)' : 'transparent',
padding: '11px 13px', cursor: 'pointer', transition: 'border-color .15s ease, background .15s ease'
}
}));
const steps = sel.steps.map((st, i) => {
const isLoop = st.lvl === -1;
return {
...st,
rowStyle: {
animation: 'omIn .4s cubic-bezier(.2,.7,.2,1) both', animationDelay: (i * 45) + 'ms',
display: 'flex', gap: '12px', alignItems: 'flex-start', padding: '8px 6px',
borderBottom: '1px solid rgba(29,31,32,.07)',
marginLeft: st.lvl === 1 ? '26px' : 0,
borderLeft: st.lvl === 1 ? '2px solid var(--color-accent-300)' : isLoop ? '2px solid var(--color-accent)' : '2px solid transparent',
paddingLeft: '12px',
background: isLoop ? 'var(--color-accent-100)' : 'transparent'
},
numStyle: { ...mono, minWidth: '24px', fontSize: '10.5px', color: isLoop ? 'var(--color-accent-800)' : 'var(--color-neutral-500)', paddingTop: '2px', fontWeight: isLoop ? 700 : 400 }
};
});
const approvals = sel.approvals.map(ap => ({
...ap,
dotStyle: { width: '8px', height: '8px', borderRadius: '50%', flex: 'none', marginTop: '4px', background: ap.kind === 'ok' ? 'var(--color-accent)' : 'var(--color-bg)', border: ap.kind === 'ok' ? 'none' : '1.5px solid var(--color-neutral-400)' }
}));
return {
list, steps, approvals,
selName: sel.name, selVer: sel.ver, selStatus: sel.status, selStStyle: stOf(sel.status),
selSig: sel.sig, selSource: sel.source, selSerials: sel.serials, selDestr: sel.destr, selCycles: sel.cycles,
selPublished: sel.status === '已发布'
};
}
}
</script>
</body>
</html>
@@ -0,0 +1,152 @@
{
"plugins": [
"react",
"import"
],
"rules": {
"react/forbid-elements": [
"warn",
{
"forbid": []
}
],
"no-restricted-imports": [
"warn",
{
"patterns": []
}
],
"no-restricted-syntax": [
"warn",
{
"selector": "Literal[value=/#[0-9a-fA-F]{3,8}\\b/]",
"message": "Raw hex color — use a design-system color token via var()."
},
{
"selector": "Literal[value=/\\b\\d+px\\b/]",
"message": "Raw px value — use a design-system spacing token via var()."
},
{
"selector": "Literal[value=/font-family\\s*:\\s*(?!['\\\"]?(?:Barlow|Barlow Condensed))/i]",
"message": "Font not provided by the design system. Available: Barlow, Barlow Condensed."
}
]
},
"overrides": [
{
"files": [
"**/index.js"
],
"rules": {
"no-restricted-imports": "off"
}
}
],
"x-omelette": {
"components": {},
"tokens": [
"--color-accent",
"--color-accent-100",
"--color-accent-2",
"--color-accent-2-100",
"--color-accent-2-200",
"--color-accent-2-300",
"--color-accent-2-400",
"--color-accent-2-500",
"--color-accent-2-600",
"--color-accent-2-700",
"--color-accent-2-800",
"--color-accent-2-900",
"--color-accent-200",
"--color-accent-300",
"--color-accent-400",
"--color-accent-500",
"--color-accent-600",
"--color-accent-700",
"--color-accent-800",
"--color-accent-900",
"--color-bg",
"--color-divider",
"--color-neutral-100",
"--color-neutral-200",
"--color-neutral-300",
"--color-neutral-400",
"--color-neutral-500",
"--color-neutral-600",
"--color-neutral-700",
"--color-neutral-800",
"--color-neutral-900",
"--color-surface",
"--color-text",
"--font-body",
"--font-heading",
"--font-heading-weight",
"--radius-lg",
"--radius-md",
"--radius-sm",
"--shadow-lg",
"--shadow-md",
"--shadow-sm",
"--space-1",
"--space-2",
"--space-3",
"--space-4",
"--space-6",
"--space-8"
],
"tokenKinds": {
"--color-bg": "color",
"--color-surface": "color",
"--color-text": "font",
"--color-accent": "color",
"--color-accent-2": "color",
"--color-divider": "color",
"--color-neutral-100": "color",
"--color-neutral-200": "color",
"--color-neutral-300": "color",
"--color-neutral-400": "color",
"--color-neutral-500": "color",
"--color-neutral-600": "color",
"--color-neutral-700": "color",
"--color-neutral-800": "color",
"--color-neutral-900": "color",
"--color-accent-100": "color",
"--color-accent-200": "color",
"--color-accent-300": "color",
"--color-accent-400": "color",
"--color-accent-500": "color",
"--color-accent-600": "color",
"--color-accent-700": "color",
"--color-accent-800": "color",
"--color-accent-900": "color",
"--color-accent-2-100": "color",
"--color-accent-2-200": "color",
"--color-accent-2-300": "color",
"--color-accent-2-400": "color",
"--color-accent-2-500": "color",
"--color-accent-2-600": "color",
"--color-accent-2-700": "color",
"--color-accent-2-800": "color",
"--color-accent-2-900": "color",
"--font-heading": "font",
"--font-heading-weight": "font",
"--font-body": "font",
"--space-1": "spacing",
"--space-2": "spacing",
"--space-3": "spacing",
"--space-4": "spacing",
"--space-6": "spacing",
"--space-8": "spacing",
"--radius-sm": "radius",
"--radius-md": "radius",
"--radius-lg": "radius",
"--shadow-sm": "shadow",
"--shadow-md": "shadow",
"--shadow-lg": "shadow"
},
"fontFamilies": [
"Barlow",
"Barlow Condensed"
]
}
}
@@ -0,0 +1,11 @@
/* @ds-bundle: {"format":4,"namespace":"Industry_indust","components":[],"sourceHashes":{},"inlinedExternals":[],"unexposedExports":[]} */
(() => {
const __ds_ns = (window.Industry_indust = window.Industry_indust || {});
const __ds_scope = {};
(__ds_ns.__errors = __ds_ns.__errors || []);
})();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,82 @@
# Industry design system
Industry is a wireframe: steel-blue on a light technical ground, Barlow Condensed headings over Barlow, a modular grid, and cards, figures and buttons framed as blueprint objects — square-cornered, hairline-bordered, with "+" registration marks at the corners. Cards and figures stay transparent line drawings; the primary button is the one solid object on the board, an accent fill that keeps the square corners and the marks. Photography is duotoned into the steel accent and icons are thin-stroke.
## How to use this
- Link the one stylesheet from every page — `<link rel="stylesheet" href="styles.css">` (adjust the relative path) — and take every color, font, spacing, radius and shadow from its variables (`var(--color-*)`, `var(--font-*)`, `var(--space-*)`, `var(--radius-*)`, `var(--shadow-*)`). Never hard-code a hex, a font name or a px value the tokens already carry.
- Build with the classes below rather than inventing parallel ones; the component pages are plain HTML, so view source and copy the markup.
- `templates/` holds starting points a consuming project can copy whole.
- The whole system was derived from `theme.json`. To change the look, edit the tokens at the top of `styles.css` — every page, the thumbnail and this guide read from them — and keep `theme.json` and the written guidance in step so they don't drift from what the CSS actually does.
## Direction
Modular grid layouts — content in equal-width cells, strong horizontal and vertical rhythm, visible structure. Cards, buttons and major sections are wireframe objects: square-cornered, thin-bordered, with `+` crosshair corner marks (the `.blueprint` class + four `<i class="corner tl/tr/bl/br">` children) — never soft filled rounded blocks. Images and figures get the same treatment: square, hairline-framed and marked, never rounded or clipped. Wrap hero and inline images in the `.duotone` class — they are desaturated and washed in the accent, like a screen print that re-colors with the theme.
## Color
A light ground (`--color-bg` #f2f2f3) with `--color-text` #1d1f20 and a single accent #5980a6 (this is a mono scheme: no second accent was chosen — the `--color-accent-2-*` variables carry a machine-derived stand-in kept only so both sets resolve; treat them as one role). Each role carries a 100900 tonal ramp (`--color-neutral-100``--color-accent-2-900`) generated in OKLCH on a shared perceptual lightness scale, so the same step of any ramp has the same visual weight. Use the light steps (100300) for tinted fills, hovers and subtle borders, 500 as the role's base, and the dark steps (700900) for text on tinted fills and for pressed states; prefer ramp steps over ad-hoc `color-mix()`. For elevation use `--shadow-sm/md/lg` (already tuned to the ground) rather than ad-hoc box-shadows.
## Type
Barlow Condensed for headings over Barlow for body text, loaded as `--font-heading` / `--font-body`. Density 0.85× and radius 4px are already baked into the `--space-*` / `--radius-*` scales — use the variables, not raw numbers.
## Icons
Use Lucide icons (https://lucide.dev), at stroke-width 1.5 for a lighter, more technical look throughout.
## Interaction states
Interactive states are themed, never browser defaults: give every interactive element a `:hover` tint and a pressed state from the accent ramp (one step past the base — `--color-accent-600` on a light ground, `--color-accent-400` on a dark one, or a `color-mix()` tint for outlined/ghost variants), and style keyboard focus with `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }` — never leave the default blue focus ring.
## Components
| Class | What it is | Shown in |
| --- | --- | --- |
| `.btn` with `.btn-primary`, `.btn-secondary`, `.btn-ghost`, `.btn-icon`, `.btn-block` | Actions — the primary is a solid accent fill | components/buttons.html |
| `.tag` with `.tag-accent`, `.tag-accent-2`, `.tag-neutral`, `.tag-outline` | Small labels tinted from the ramps (mono palette: accent-2 reads the same as accent) | components/buttons.html |
| `.field` + `label`, `.input`, `.radio` + `.dot`, `.seg` + `.seg-opt` | Form fields and choices on native elements — no script | components/forms.html |
| `.card` with `.card-kicker`, `.card-title`, `.card-body`, `.card-meta`; `.elev-sm/md/lg` | Transparent, hairline-bordered cards with corner registration marks | components/cards.html |
| `.nav` + `.nav-brand` | The header bar | components/navigation.html |
| `.table` | Data tables with themed header and row rules | components/table.html |
| `.dialog-backdrop` + `.dialog` (+ `.dialog-title/-body/-actions`) | A modal at the top elevation | components/dialog.html |
| `.hr` | A horizontal rule — present, but this system prefers whitespace; avoid it | — |
| `.blueprint` + four `<i class="corner tl/tr/bl/br">` children | The wireframe frame every card, figure and primary button wears | components/cards.html |
| `.duotone` | The image wrapper — every content photograph goes through it | foundations/image.html |
States are built in: hovers and pressed states come from the accent ramp, keyboard focus is the 2px accent `:focus-visible` ring, `::selection` is an accent tint, and disabled controls drop to 45% opacity. Don't restyle them per page. The accent-to-ground pair is tuned to at least 3:1 — enough for icons, large text and interface chrome, not for body copy — so for paragraph-size text in the accent use a deep ramp step (`--color-accent-700` on this ground) rather than the accent itself.
## Do
- Frame cards, figures and primary buttons as blueprint objects: the `.blueprint` class plus four `<i class="corner …">` marks.
- Keep the grid visible — equal cells, strong horizontal and vertical rhythm.
- Condense headings (Barlow Condensed) and keep body copy in Barlow.
- Duotone photographs with the `.duotone` wrapper so they take the accent.
## Don't
- Do not round cards, figures or buttons, and do not give cards or figures a surface fill — they are line drawings (the solid accent primary button is the one deliberate exception).
- Do not drop the registration marks from a framed element.
- Do not use thick icon strokes; the set is Lucide at 1.5.
- Do not add decorative color beyond the steel accent. The accent's own deep step (`--color-accent-900`) may carry a full field where the deck's section dividers use it — steel as ground, type reversed to paper. (The landing's numbers sit on a drawn spec-sheet plate on the paper ground instead — its own grammar, not a field.)
## Files
- `styles.css` — the only stylesheet: the token sheet (`:root` variables, ramps, base type) plus the component layer. Link it from every page.
- `readme.md` — this guide.
- `theme.json` — the parameters these files were derived from (a machine-readable record of the theme).
- `thumbnail.html` — the project cover (brand mark + swatches).
- `foundations/type.html` — the type scale and the heading/body pairing at real sizes.
- `foundations/color.html` — color roles and the 100-900 tonal ramps, with usage notes.
- `foundations/layout.html` — the spacing scale, the grid and how edges are drawn.
- `foundations/icons.html` — the icon set at interface sizes, inline and in buttons.
- `foundations/image.html` — how photographs and figures are treated.
- `components/buttons.html` — buttons, icon buttons and tags in every variant and state.
- `components/forms.html` — text fields, radios and the segmented control on native elements.
- `components/cards.html` — content cards and the elevation steps.
- `components/navigation.html` — the header bar pattern.
- `components/table.html` — a data table with the themed header and row rules.
- `components/dialog.html` — a modal over its backdrop at the top elevation.
- `theme.html` — the theme's parameters rendered as a reference sheet.
- `templates/landing/` — a starter page consuming the system the intended way (`index.html`, its `ds-base.js` loader, and the vendored `image-slot.js` its photograph mounts).
- `assets/photo.jpg` — the reference photograph the imagery page treats.
@@ -0,0 +1,285 @@
/* Industry — design-system tokens and component classes. This file is the source of truth for the system's look; retune it here and see readme.md. */
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;700&family=Barlow+Condensed:wght@400;600&display=swap');
:root {
--color-bg: #f2f2f3;
--color-surface: #e9e9ea;
--color-text: #1d1f20;
--color-accent: #5980a6;
--color-accent-2: #728fab;
--color-divider: color-mix(in srgb, #1d1f20 16%, transparent);
/* Tonal ramps — generated in OKLCH on one shared lightness scale, so the
same step of any role matches the others in visual value. */
--color-neutral-100: #f5f5f8;
--color-neutral-200: #e7e7ea;
--color-neutral-300: #d4d4d7;
--color-neutral-400: #b7b7ba;
--color-neutral-500: #98989b;
--color-neutral-600: #7a7a7d;
--color-neutral-700: #5d5d60;
--color-neutral-800: #424244;
--color-neutral-900: #2b2b2d;
--color-accent-100: #eef6ff;
--color-accent-200: #d6ebff;
--color-accent-300: #b5d9fd;
--color-accent-400: #94bce3;
--color-accent-500: #749dc4;
--color-accent-600: #597ea3;
--color-accent-700: #416180;
--color-accent-800: #2c455d;
--color-accent-900: #1d2d3d;
--color-accent-2-100: #eef6ff;
--color-accent-2-200: #d6ebff;
--color-accent-2-300: #bdd8f2;
--color-accent-2-400: #9ebbd8;
--color-accent-2-500: #7e9cb8;
--color-accent-2-600: #627d98;
--color-accent-2-700: #486077;
--color-accent-2-800: #314457;
--color-accent-2-900: #1f2d3a;
--font-heading: "Barlow Condensed", system-ui, sans-serif;
--font-heading-weight: 600;
--font-body: "Barlow", system-ui, sans-serif;
--space-1: 3.4px;
--space-2: 6.8px;
--space-3: 10.2px;
--space-4: 13.6px;
--space-6: 20.4px;
--space-8: 27.2px;
--radius-sm: 2px;
--radius-md: 4px;
--radius-lg: 7px;
/* Elevation — derived from the ground: soft ink-tinted shadows on a
light theme, a hairline edge + ambient darkness on a dark one. */
--shadow-sm: 0 1px 2px color-mix(in srgb, #2b2b2d 14%, transparent);
--shadow-md: 0 3px 10px color-mix(in srgb, #2b2b2d 16%, transparent);
--shadow-lg: 0 12px 32px color-mix(in srgb, #2b2b2d 22%, transparent);
}
body {
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }
.blueprint {
position: relative;
border: 1px solid var(--color-divider);
border-radius: 0;
}
/* The overlay image treatments (halftone, duotone) clip their overlay
(overflow:hidden); a blueprint wrapper draws its registration marks
outside the box, so when both classes share a wrapper the frame must
win. */
.blueprint.halftone, .blueprint.plate, .blueprint.duotone { overflow: visible; }
.blueprint > .corner {
position: absolute; width: 11px; height: 11px;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.blueprint > .corner::before, .blueprint > .corner::after {
content: ""; position: absolute; background: currentColor;
}
.blueprint > .corner::before { left: 5px; top: 0; width: 1px; height: 100%; }
.blueprint > .corner::after { top: 5px; left: 0; width: 100%; height: 1px; }
.blueprint > .corner.tl { top: -6px; left: -6px; }
.blueprint > .corner.tr { top: -6px; right: -6px; }
.blueprint > .corner.bl { bottom: -6px; left: -6px; }
.blueprint > .corner.br { bottom: -6px; right: -6px; }
.duotone{position:relative;overflow:hidden}
.duotone::after{content:"";position:absolute;inset:0;pointer-events:none;
background:var(--color-accent);mix-blend-mode:color}
/* ══════════════════════════════════════════════════════════════════════════
Components — built with the tokens above. Plain CSS
on plain HTML: no JavaScript, no build step. Each class is documented in
readme.md and demonstrated in foundations/ and components/.
══════════════════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; }
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 13px; }
h6 { letter-spacing: 0.08em; text-transform: uppercase; }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
figcaption {
font-size: 11px; margin-top: var(--space-1);
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }
/* — rules — */
.hr {
height: 1px; border: 0; margin: var(--space-4) 0;
background: var(--color-divider);
}
/* — buttons — */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
cursor: pointer; text-decoration: none;
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 14px; line-height: 1.2; color: var(--color-text); /* matches the .input's 14px —
the pair sits side by side in sign-up rows */
background: transparent; border: 1px solid transparent;
padding: var(--space-2) calc(var(--space-3) * 1.2);
border-radius: var(--radius-md);
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); }
/* — forms — */
.field > label {
display: block; font-size: 12px; margin-bottom: 5px;
color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input {
width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
background: var(--color-surface);
border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.radio input, .seg-opt input {
position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
width: 16px; height: 16px; flex: none; border-radius: 50%;
border: 1.5px solid var(--color-divider);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
border-color: var(--color-accent); background: var(--color-accent);
box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.seg {
display: inline-flex; overflow: hidden;
border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.seg-opt {
display: inline-flex; align-items: center; gap: 6px;
padding: 7px 12px; font-size: 13px; cursor: pointer;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked) { background: var(--color-accent); color: var(--color-bg); }
.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }
/* — cards — */
.card {
display: flex; flex-direction: column; gap: var(--space-2);
padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 17px; line-height: 1.2;
}
.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; }
.card-meta {
display: flex; align-items: center; gap: 6px; font-size: 11px;
color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }
/* — tags — */
.tag {
display: inline-flex; align-items: center; font-size: 11px;
letter-spacing: 0.02em; padding: 3px 10px;
border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }
/* — navigation — */
.nav {
display: flex; align-items: center; gap: var(--space-4);
padding: var(--space-3) var(--space-4);
border-bottom: none;
}
.nav-brand {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 18px; margin-right: auto;
}
.nav a { color: inherit; text-decoration: none; font-size: 14px; }
.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); }
/* — tables — */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
color: color-mix(in srgb, var(--color-text) 60%, transparent);
padding: var(--space-2); border-bottom: 1px solid var(--color-divider);
}
.table td {
padding: var(--space-2);
border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
/* — dialog — */
.dialog-backdrop {
position: fixed; inset: 0; display: grid; place-items: center;
padding: var(--space-4);
background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}
.dialog {
width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
padding: var(--space-4); border-radius: var(--radius-lg);
background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 20px;
}
.dialog-body { font-size: 14px; opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }
/* — blueprint frame: components are wireframe objects (see .blueprint
and .corner above) — square, transparent, hairline-bordered — */
.card, .btn, .input, .tag, .seg, .dialog { border-radius: 0; }
.card, .dialog { background: transparent; border: 1px solid var(--color-divider); }
.btn { border: 1px solid var(--color-divider); }
.btn-primary { border-color: var(--color-accent); }
.btn-ghost { border-color: transparent; }
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
// templates/deck/ds-base.js — loads this design system's component bundle
// into the deck. The system stylesheet is deliberately NOT loaded here: the
// deck is a Design Component whose <helmet> links ../../styles.css ahead of
// the deck's own <style>, so the deck's rules win equal-specificity ties in
// every load path — a script-injected sheet would land after that <style>
// and silently outrank it, live and in published artifacts alike. The window
// flag makes the load one-shot: a Design Component evaluates helmet scripts
// twice when the page is opened directly (once from the parsed document,
// once from the runtime's recreated helmet copy), and the bundle must not
// run twice. In a consuming project, point base at the bound _ds/<folder>
// tree relative to this page — one line to edit.
(() => {
const base = '_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719';
if (window['__dsBundle:' + base]) return;
window['__dsBundle:' + base] = true;
const s = document.createElement('script');
s.src = base + '/_ds_bundle.js';
s.onerror = () => console.error('ds-base.js: failed to load ' + s.src + ' — if this is a consuming project, point the base line in ds-base.js at the bound _ds/<folder> tree relative to this page (e.g. _ds/<folder> at the project root, ../_ds/<folder> one level down); in a fresh design system this can just mean the bundle is not compiled yet');
document.head.appendChild(s);
})();
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,983 @@
FLASHOPS REGRESSION
存储固件回归测试值守机器人
产品立项、开发架构、竞品差异化与中长期规划方案
核心判断 不要重新发明一套 NVMe 协议测试仪。先把客户现有脚本、商用测试平台和测试主机统一接管,替代工程师反复刷版本、守机器、救卡死、收日志、写报告的机械劳动。
1 个真实 SOP第一枪只替代一段真实工作
100 次循环证明无需人工通宵值守
0 次错盘危险动作必须确定性防护
1 套证据包失败可复现、可追责、可比较
暂定产品名:FlashOps Regression
上位平台:Storage LabOS(未来品牌架构)
文档版本:V1.0|公开资料核查截止:2026 年 7 月 27 日
用途:合作讨论、产品立项、技术拆解、首轮 PoC 与开发团队输入
# 执行摘要
FlashOps Regression 的产品本质不是“更懂 NVMe 的 AI”,而是“存储实验室的数字执行员工”。它位于专业测试工具与工程师之间,负责固件版本流转、测试编排、长时间值守、测试机自救、故障现场冻结、跨工具证据归集、失败重跑、版本 A/B 对比及报告生成。
项目的第一原则是保留客户既有投入:ULINK、OakGate、SANBlaze、Quarch、NI TestStand、PyNVMe3、fio、nvme-cli 以及企业私有脚本都不应被替换,而应被纳入统一的适配器体系。现有厂商公开能力已经覆盖深度协议验证、性能、合规、功耗及故障注入;本项目应避开正面竞争,把差异化建立在跨工具无人值守闭环上。[1]-[10]
推荐立项方式 先找到一份真实且高频的固件回归 SOP,用一台测试主机、一块允许破坏性测试的 SSD、两个固件版本和一套带外控制器,完成 100 次循环的无人值守 PoC。PoC 的成败不看页面数量,而看人工触碰次数是否显著下降。
## 一页结论
项目项
建议结论
首要客户
中型 SSD / 模组厂、主控与固件团队、工业存储厂商;已有大量脚本但平台碎片化。
首要场景
固件 A/B 回归:刷固件、重启/休眠/I/O 循环、卡死恢复、证据留存、报告与缺陷单。
产品主打
不推翻现有工具;机器卡死能自救;失败自动形成证据包;版本差异一夜出结论。
MVP 边界
单测试节点、单 DUT、Windows/Linux Agent、基础带外控制、客户脚本接入、A/B 报告。
不做事项
不自研 PCIe Gen5 高速背板;不重写协议合规套件;不让大模型直接执行破坏性命令;不承诺自动批准固件发布。
北极星指标
每 100 次回归中,工程师必须亲自触碰测试机的次数。
未来方向
多节点企业版 → 故障签名与最短复现 → 兼容性循环测试柜 → Storage LabOS。
# 目录
01 项目背景与问题定义
02 产品定位与核心价值
03 目标客户、岗位与典型场景
04 端到端业务流程
05 产品与技术总体架构
06 软件开发方案
07 硬件与带外控制方案
08 AI 与故障智能方案
09 MVP 范围、开发里程碑与验收
10 竞品格局与差异化战略
11 商业化与首批客户策略
12 数据壁垒与长期护城河
13 未来产品规划
14 团队配置、投入顺序与治理
15 风险清单与应对策略
16 立即行动清单
附录 A 示例工作流
附录 B Evidence Bundle 字段
附录 C 失败分类体系
附录 D 公开资料来源
# 01 项目背景与问题定义
先区分“需要专家判断的工作”与“可以被机器稳定执行的工作”。
## 1.1 存储验证中的真实矛盾
存储固件测试并不缺少专业工具。真正长期消耗组织成本的,是工具之间缺少统一控制、测试任务需要人工值守、失败后证据散落、环境恢复依赖个人经验,以及同类失败被重复分析。工程师往往不是把时间花在“定义测试与判断根因”上,而是花在“让测试能够持续跑下去”上。
## 1.2 工作拆解:专家劳动与执行劳动
工作类型
典型任务
是否应由首版产品替代
专家劳动
定义覆盖范围、制定通过标准、决定发布门禁、最终根因判断
否。产品提供证据和建议,保留人工决策。
执行劳动
刷固件、登记序列号、配置环境、循环重启、等待、收日志、重跑、填表
是。首版核心替代对象。
半结构化劳动
归并相似失败、筛选关键日志、比较版本差异、生成复现步骤
逐步替代。规则优先,AI 辅助。
## 1.3 五个高频行业痛点
痛点
现场表现
直接损失
工具碎片化
脚本、商用平台、命令行、仪器和 Excel 各自独立
重复集成、上下文丢失、难以规模化。
无人值守不完整
脚本能跑,但蓝屏、掉线、卡死后仍需人工处理
夜间和周末机器利用率低。
故障留证不足
只有 Fail、退出码或零散日志,没有统一时间线
工程师无法快速复现,争议增加。
基础设施误报
网络、系统更新、磁盘满、Agent 崩溃被误判为 DUT 故障
产生大量无效缺陷,降低自动化可信度。
报告仍靠人工
版本、环境、日志、截图和结论手动拼装
测试结束不等于结论产出,发布周期继续被拖延。
问题定义 如何在不替换客户现有测试能力的前提下,把一次固件回归任务从“需要工程师持续守着”变成“工程师下达任务后即可离开,第二天获得可信结论与完整证据”?
## 1.4 技术可行性基础
NVMe 标准与生态已经提供错误日志、SMART、Telemetry、Persistent Event Log、设备自检等可采集信号;NVM Express 将 nvme-cli 列为 Linux 管理 NVMe SSD 的标准开源工具,并支持厂商插件。[11][12] Linux 内核还提供系统化的 NVMe 故障注入机制,可用于构建测试基础设施自身的验证场景。[13] 因此首版无需从芯片底层重新造轮子,而应优先完成编排、留证、恢复和安全控制。
# 02 产品定位与核心价值
定位必须避开“又一套测试仪”,形成可以与现有平台共存的控制层。
## 2.1 产品定义
一句话定义 固件提交后,系统自动完成版本刷写、回归执行、测试机自救、故障现场冻结、失败复现、A/B 对比与报告提单。
### 产品类别
存储实验室执行智能体(Storage Lab Execution Agent)与控制平面(Control Plane)。它不是 EDA,不是协议分析仪,不是单一测试套件,也不是聊天机器人。
### 核心产品承诺
工程师配置一次任务后,可以离开测试现场。
测试主机或 Agent 故障时,系统通过带外通道自行恢复。
每个失败自动生成结构化证据包与统一时间线。
已有脚本和商用测试平台继续使用,不要求客户重写。
危险动作由确定性策略控制,AI 无权直接越过安全门禁。
## 2.2 六个核心卖点
主打点
客户听得懂的表达
产品实现
真正无人值守
不是“能自动开始”,而是“异常后还能自己继续”。
心跳、状态机、软恢复、Reset、整机断电、检查点续跑。
故障现场自动冻结
失败不再只剩一行报错。
统一时间线、日志快照、设备状态、画面、温度、恢复过程。
不推翻已有工具
客户原来的测试投入全部保留。
Adapter SDK、CLI/REST/Python/PowerShell 接口、工具封装。
A/B 版本直接出结论
不用人工翻目录比较固件差异。
环境指纹锁定、基线比较、失败签名、回归门禁。
基础设施与 DUT 故障分离
减少自动化制造出来的垃圾缺陷。
故障分类规则、探针交叉验证、Agent/OOB 双通道。
AI 有用但受控
AI 做归并、解释和建议,不直接删盘或刷错固件。
结构化工具调用、安全策略、证据引用、人工发布门禁。
## 2.3 产品边界
首版明确要做
首版明确不做
固件 A/B 回归任务编排
自研 NVMe 协议合规测试套件
Windows/Linux Agent 与客户脚本执行
PCIe Gen5/Gen6 高速信号背板
主机带外自救与简单传感
裸 NAND、FTL、主控固件开发
设备/环境指纹与安全绑定
AI 自动批准固件发布
Evidence Bundle 与自动报告
替代最终专业根因分析
规则驱动的失败分类与重跑
第一版同时支持 NVMe/SATA/eMMC/UFS
# 03 目标客户、岗位与典型场景
首批客户应当“有真实痛点、有现成脚本、但没有完整平台”。
## 3.1 理想客户画像(ICP
优先级
客户类型
典型现状
切入理由
A
中型 SSD / 存储模组厂
5—50 个测试节点,脚本与工具分散,依赖关键工程师
痛点高、决策链较短、可快速拿到真实 SOP。
A
主控或固件创业团队
研发迭代快,但验证平台不完整
愿意以效率换取工具,适合作为设计合作伙伴。
A
工业存储厂商
客户现场问题多,回归和复现耗时
更看重稳定性与留证,不只看协议合规。
B
存储品牌与 OEM 验证团队
商用设备与自研脚本并存
可通过“统一控制层”切入。
C
拥有成熟自研 SLT 的头部厂商
已有调度、追溯和大数据平台
早期进入难,优先作为未来集成或特定模块客户。
## 3.2 主要使用角色
角色
当前最烦的工作
产品价值
验证工程师
守长测、救机器、收证据、重跑
从执行员回到验证设计和判断。
固件工程师
拿到的缺陷信息不完整,无法复现
获得最短复现路径和完整环境。
测试主管
无法统一了解机器利用率与失败类型
看见任务进度、故障分布与产能。
质量/项目经理
版本是否可发布依赖口头汇总
获得可审计的版本门禁报告。
实验室管理员
设备、主机、固件和脚本版本混乱
统一资产、环境和权限治理。
## 3.3 首版典型场景:固件 A/B 回归
选择 DUT、旧固件 A、新固件 B 与固定测试环境。
系统校验序列号、介质状态和破坏性测试授权。
先运行固件 A 的基线任务,再刷入固件 B。
按相同条件执行 100 次重启、休眠/唤醒、I/O 与数据校验循环。
出现卡死、蓝屏、掉盘或 Agent 失联时,带外控制器自动恢复。
每次失败生成 Evidence Bundle,并按失败签名归并。
输出 A/B 通过率、故障类型、性能回归、恢复行为与发布建议草稿。
## 3.4 后续可复用场景
返修问题复现:将客户现场症状转换为可重复执行的验证流程。
长时间稳定性:跨夜、跨周执行,异常后自动续跑。
电源状态回归:冷启动、热重启、休眠、休眠恢复、整机断电。
固件发布门禁:新版本必须通过指定的 Golden Suite。
多设备并行:同一固件在多块 DUT 上运行并比较离群样品。
# 04 端到端业务流程
目标是形成“准备—执行—异常—结果”的闭环,而不是堆叠命令。
图 1 端到端无人值守闭环
## 4.1 任务准备阶段
资产绑定:测试主机、DUT 序列号、固件包、主板/BIOS、OS、驱动、测试工具版本。
安全检查:确认 DUT 位于允许破坏性测试的白名单,禁止对系统盘执行 Format/Sanitize。
环境指纹:测试前生成不可变快照,保证 A/B 对比条件一致。
资源锁:同一 DUT、测试主机、仪器和电源模块不能被并发占用。
测试计划:明确循环次数、超时、重试、恢复策略、通过门槛和停止条件。
## 4.2 自动执行阶段
调用厂商刷固件工具,并验证刷写后的版本、槽位与激活状态。
执行客户现有 Python、Shell、PowerShell、fio、nvme-cli、PyNVMe3 或商用平台命令。
持续采集 Agent 心跳、系统日志、设备日志、温度、测试输出与画面状态。
在每个关键步骤创建检查点,保证恢复后可以安全续跑。
记录所有命令的模板、参数、发起者、时间与返回状态,满足审计要求。
## 4.3 异常处置阶段
异常类型
识别信号
默认处置
测试脚本失败
进程退出、断言失败、结果文件缺失
保存上下文,按策略重试或标记脚本失败。
Agent 失联
心跳超时,但带外控制器在线
先软恢复,再 Reset,最后整机断电。
主机 OS 故障
蓝屏/内核崩溃/启动失败
保存画面与串口,带外重启并等待恢复。
DUT 未枚举
OS 与 PCIe/NVMe 探针均未发现设备
记录掉盘证据,执行限定次数恢复。
数据完整性失败
哈希/读回比较不一致
立即冻结现场,停止覆盖性动作,进入高优先级。
基础设施故障
网络、存储空间、电源控制器或测试工具异常
分类为 INFRA_FAILURE,不进入 DUT 缺陷统计。
## 4.4 结果闭环阶段
自动生成 Evidence Bundle,保证关键字段完整且可追溯。
对相似失败进行签名与聚类,避免同一问题重复提单。
比较固件 A/B 在通过率、掉盘、数据错误、延迟、温度及恢复行为上的差异。
生成工程报告、管理摘要和 Jira/禅道缺陷草稿。
将已确认问题沉淀为回归用例,持续扩大 Golden Suite。
# 05 产品与技术总体架构
架构要保证“主机挂了,系统仍然有眼睛、有手、有记忆”。
图 2 FlashOps Regression 总体架构
## 5.1 五层架构
层级
核心模块
职责
交互与集成层
Web 控制台、Open API、CLI、企业系统连接器
任务配置、状态查看、报告、CI/CD 与缺陷系统接入。
控制平面
工作流引擎、调度器、资产中心、策略中心
编排任务、资源锁、权限、安全门禁、检查点与版本比较。
执行平面
Windows/Linux Host Agent、工具适配器
执行命令、采集日志、管理进程、调用客户工具。
带外平面
树莓派/工业控制器、Reset/Power/HDMI/传感器
主机失联后仍可观察和恢复,形成第二控制通道。
证据与智能层
事件总线、对象存储、指标库、规则引擎、AI
统一时间线、失败分类、聚类、解释、报告与复现建议。
## 5.2 关键设计原则
控制面与数据面分离:任务状态不能只存在于测试主机本地。
双通道观测:Host Agent 与带外控制器相互校验,避免单点误判。
事件溯源:关键状态变化以事件追加方式记录,方便重建完整时间线。
适配器优先:任何测试工具都通过标准 Adapter 接口接入。
默认本地化部署:固件、日志和厂商私有信息不离开客户网络。
安全默认拒绝:未绑定 DUT、未授权命令、未通过预检时不执行危险动作。
## 5.3 核心领域对象
对象
关键字段
用途
DUT
型号、序列号、容量、固件、BDF、健康信息、标签
防错盘、资产追踪与版本比较。
Test Host
主板、BIOS、CPU、OS、内核、驱动、Agent 版本
固定环境与兼容性归因。
Firmware Artifact
版本、哈希、签名、适用型号、刷写工具
确保固件包可信且可审计。
Workflow
步骤、参数、循环、条件、超时、恢复策略
将 SOP 变成可执行状态机。
Run
环境快照、输入、状态、事件、结果、操作者
一次完整测试任务。
Failure Signature
错误码、日志特征、步骤、设备状态、恢复结果
聚类、去重与复现。
Evidence Bundle
日志、指标、截图、命令、版本、时间线
缺陷分析与审计交付。
# 06 软件开发方案
MVP 先做可靠状态机和执行代理,不先做复杂微服务。
## 6.1 推荐技术栈
模块
MVP 推荐
演进建议
前端
Next.js + React + TypeScript + ECharts
后续增加实验室拓扑、实时画面与低代码流程编辑器。
控制平面 API
FastAPI + Python
规模化后拆分调度、资产、报告和连接器服务。
任务执行
显式状态机 + PostgreSQL 持久化;Redis 队列
任务复杂度上升后评估 Temporal 等持久工作流引擎。
Host Agent
Python/Go 混合;Windows Service / systemd
关键执行与心跳可逐步迁移到 Go,提高单文件部署与稳定性。
实时通信
WebSocket / Server-Sent Events
多节点后引入 NATS 或 Kafka 作为事件总线。
数据存储
PostgreSQL + MinIO/本地对象存储
高频指标增加 ClickHouse 或时序库。
部署
Docker Compose 私有化单机部署
企业版支持 K3s/Kubernetes 与离线升级。
说明:技术栈是工程建议,不构成必须绑定;首版优先减少运维复杂度。
## 6.2 Host Agent 必备能力
注册与身份:每个 Agent 使用唯一证书或密钥,绑定测试主机资产。
命令执行:支持白名单模板、参数校验、工作目录隔离、超时与进程树终止。
日志采集:stdout/stderr、Windows Event Log、dmesg/journal、NVMe 日志、工具输出。
设备探针:周期检查 DUT 枚举、BDF、固件版本、SMART、温度及链路状态。
心跳与健康:上报 CPU、内存、磁盘空间、网络、Agent 状态和任务阶段。
检查点:每个步骤完成后持久化输出,重启后可安全恢复。
自更新:企业内网签名包升级,支持回滚。
## 6.3 工作流引擎
工作流必须是确定性的结构化配置,而不是让大模型自由生成并直接执行命令。核心语义包括:步骤、循环、条件、并行、超时、重试、补偿动作、检查点、资源锁和安全级别。
能力
MVP
企业版
步骤类型
命令、脚本、HTTP、等待、设备检查、重启
仪器控制、子流程、人工审批、动态矩阵。
循环与条件
固定次数、通过/失败分支
基于指标和风险的自适应循环。
恢复
从最近安全检查点继续
跨节点迁移、测试环境重建。
版本管理
YAML/JSON + Git 哈希
可视化编辑、审批、发布与回滚。
资源管理
主机/DUT 独占锁
仪器池、许可证池、多实验室调度。
## 6.4 Adapter SDK
适配器是产品扩张的关键。统一接口应至少包含 discover、precheck、execute、collect、cancel、health 和 normalize_result。每个工具的原生输出被转换为统一事件与结果模型。
class ToolAdapter: def discover(self, context) -> Capabilities: ... def precheck(self, context) -> CheckResult: ... def execute(self, step, context) -> ExecutionHandle: ... def collect(self, handle) -> EvidenceArtifact: ... def cancel(self, handle) -> None: ... def health(self) -> HealthStatus: ... def normalize_result(self, raw) -> UnifiedResult: ...
## 6.5 首版页面结构
页面
关键内容
任务中心
创建任务、队列、实时进度、阶段、预计剩余循环、紧急停止。
资产中心
测试主机、DUT、固件包、工具版本、带外控制器、标签。
工作流中心
SOP 模板、版本、参数、危险级别、审批记录。
实时运行
命令、心跳、设备状态、日志流、画面、温度、恢复动作。
失败中心
失败签名、相似运行、时间线、证据包、复现建议。
版本对比
固件 A/B 通过率、问题类型、性能和稳定性差异。
报告中心
工程报告、管理摘要、缺陷单、导出与审计。
## 6.6 安全架构
控制点
要求
DUT 绑定
危险任务必须绑定序列号、BDF/设备路径与允许测试标签,多信号一致才执行。
命令模板
Format、Sanitize、固件刷写等命令只能来自签名模板,参数受 schema 限制。
系统盘保护
预检检测挂载点、启动盘和分区,命中系统盘立即拒绝。
权限分级
查看、执行、编辑流程、批准危险动作、发布门禁分离。
审计
记录操作者、审批者、命令模板版本、输入参数、结果和撤销动作。
紧急停止
Web、物理按钮与带外控制器均可触发,停止后进入安全状态。
# 07 硬件与带外控制方案
硬件的价值是“主机死了以后系统仍能观察和操作”,而不是做一个漂亮盒子。
## 7.1 MVP 硬件拓扑
部件
建议形态
首版作用
测试主机
标准 x86 Windows/Linux 台式机或服务器
提供原生 M.2/U.2 接口,运行真实测试负载。
带外控制器
树莓派 5 / CM4 或工业 Linux 控制器
独立网络、心跳、Power/Reset、HDMI 采集、传感。
确定性 MCU
可选 RP2040/STM32
执行看门狗、精确按键脉冲和紧急停止。
电源控制
首版控制整机 AC/ATX;DUT 独立断电优先采购成熟模块
避免早期陷入高速信号与供电时序。
画面采集
USB HDMI 采集卡
在 Agent 未启动或蓝屏时仍可留证。
环境传感
温度传感器;后续电压/电流
关联温度与故障,验证测试环境。
## 7.2 恢复阶梯
通过 Agent 执行优雅停止或软重启。
通过操作系统远程管理通道执行重启。
带外控制器触发主板 Reset。
带外控制器模拟 ATX 长按关机并重新开机。
控制整机 AC 断电并等待安全间隔后上电。
超过限定次数仍失败则冻结现场并停止自动恢复。
## 7.3 为什么不在首版自研 M.2 Gen5 电源夹层板
直接位于 PCIe Gen5 数据路径的硬件会引入信号完整性、连接器、时钟、复位、电源时序和热设计风险。ULINK 的 PSPA+ Gen5 已经公开支持 DUT 独立供电控制、电流/电压测量、PERST#、CLKREQ#、PLN#/PLA#、PWDIS# 与 SMBusQuarch 也提供面向 SSD/PCIe 的电源和故障注入模块。[3][8] 首版应优先适配成熟模块,在软件闭环和客户需求被验证后再决定是否自研。
## 7.4 硬件演进路线
阶段
硬件范围
判断门槛
H0 PoC
树莓派 + 继电器/智能电源 + ATX/Reset + HDMI + 温度
证明自动恢复和留证。
H1 工程样机
隔离型 Power/Reset HAT、看门狗、物理急停、标准线束
至少 3 个客户场景反复需要。
H2 工业控制器
金属机箱、宽温、电气保护、远程升级、模块化端口
进入客户实验室长期运行。
H3 DUT 电源模块
只在有硬件团队和明确接口需求后开发
商业模块成本或能力成为规模化瓶颈。
H4 多节点机柜
统一供电、网络、画面、环境与资产管理
扩展至兼容性循环测试柜。
# 08 AI 与故障智能方案
先建立可解释的规则与数据结构,再让 AI 提升工程师阅读和决策效率。
## 8.1 AI 的正确位置
AI 可以做
AI 不可以直接做
把自然语言 SOP 草拟为结构化流程
不经规则校验直接执行命令
归并几万条日志并提取关键时间点
凭空判断某块 SSD 必然损坏
根据证据给出根因候选排序
独立批准固件发布
生成工程报告、管理摘要和缺陷单
自由生成 Format/Sanitize/刷固件参数
推荐下一轮验证变量与复现步骤
绕过权限、DUT 绑定和安全门禁
## 8.2 三层智能架构
层级
方法
职责
第一层:确定性规则
阈值、状态机、签名、环境一致性校验
判断设备是否掉线、数据是否错误、恢复是否成功、故障属于哪一大类。
第二层:统计与异常检测
变化点、基线偏离、聚类、离群检测
发现性能退化、异常个体和重复失败。
第三层:大模型
RAG、结构化工具调用、证据引用
解释日志、生成摘要、建议复现、撰写报告。
## 8.3 失败签名
失败签名不应只使用错误码,而应组合:工作流步骤、主机状态、DUT 枚举状态、关键日志模板、错误码、温度/延迟变化、恢复结果、固件和环境指纹。签名用于聚类、去重、相似问题检索和版本回归判断。
failure_signature = hash( workflow_step, normalized_error_codes, log_templates, host_state, dut_enumeration_state, data_integrity_state, recovery_outcome, environment_fingerprint)
## 8.4 自动复现策略
固定所有环境变量,按原流程完整重跑一次,确认偶发性。
裁剪为失败前后的最小步骤区间。
每轮只改变一个变量,例如 APST、队列深度、负载、温度或电源状态。
使用二分与风险优先策略减少组合数量。
输出最短复现流程、触发概率、反例条件和所需证据。
## 8.5 AI 输出规范
每条推断必须关联具体 Run ID、时间点和证据文件。
使用“已证实 / 高概率 / 待验证 / 无足够证据”四级置信表达。
区分事实、推断和建议,不把建议写成结论。
对涉及数据完整性、固件发布和安全动作的建议必须要求人工确认。
模型不可访问未授权固件内容或跨客户数据。
# 09 MVP 范围、开发里程碑与验收
第一版只证明:一条真实固件回归 SOP 可以在无人值守条件下完成。
## 9.1 MVP 必须具备
Epic
MVP 功能
资产与安全
测试主机、DUT、固件包登记;序列号绑定;系统盘保护;危险命令白名单。
工作流
YAML/JSON 流程;循环、条件、超时、重试、检查点;任务队列。
Host Agent
Windows/Linux 至少完成一个主平台;命令执行、日志采集、心跳、设备探针。
带外恢复
Power/Reset/AC 控制、恢复阶梯、画面或状态留证。
测试接入
客户私有刷固件工具 + 现有回归脚本;fio/nvme-cli 作为通用工具。
证据与报告
统一事件时间线、Evidence Bundle、固件 A/B 对比、HTML/PDF/缺陷草稿。
运营监控
实时任务状态、人工触碰记录、无人值守完成率、基础设施故障统计。
## 9.2 12 周建议开发计划
阶段
周期
核心输出
退出条件
D0 真实流程发现
第 1—2 周
脱敏 SOP、样例日志、固件 A/B、DUT 与验收标准
每一步输入、输出、危险动作和失败处理均明确。
D1 单机执行闭环
第 3—4 周
控制台、基础 Agent、命令执行、日志与资产绑定
可稳定运行一次完整流程。
D2 循环与恢复
第 5—6 周
状态机、检查点、100 次循环、软重启与 Reset
Agent 重启后任务状态不丢失。
D3 带外与证据
第 7—8 周
带外控制、画面/温度、统一时间线、Evidence Bundle
模拟主机失联后可自动恢复并留证。
D4 A/B 与分类
第 9—10 周
环境指纹、A/B 报告、失败分类、基础去重
同一问题能够被识别为同类。
D5 客户 PoC
第 11—12 周
真实 100 次回归、结果复盘、ROI 数据
达到验收指标并形成下一版需求。
## 9.3 MVP 验收指标
指标
建议目标
说明
人工触碰次数
相对人工流程下降 ≥ 70%
记录每次必须到现场或远程人工干预。
正常任务完成率
100 次循环中正常路径完成率 ≥ 99%
排除人为断电等故障注入。
自动恢复成功率
对预设主机失联场景 ≥ 95%
软恢复、Reset、AC 断电阶梯。
证据包完整率
关键字段完整 ≥ 95%
版本、环境、日志、时间线、恢复动作。
基础设施误报率
< 5%
不把 Agent/网络/磁盘满误判为 DUT 故障。
危险动作安全
0 次错盘、0 次系统盘破坏
任何一次发生即判定 PoC 失败。
A/B 结论时间
测试结束后 10 分钟内产出初版报告
不含专家最终签字。
## 9.4 MVP 暂缓功能
可视化低代码流程设计器;先用受控 YAML/JSON。
大规模多租户与云端 SaaS;先做客户内网单租户。
自研 DUT 独立电源板;先接商业模块或控制整机电源。
复杂机器学习预测;先做规则、签名和基础聚类。
全接口支持;首版锁定一种 SSD 形态与一套真实环境。
# 10 竞品格局与差异化战略
竞品强项已经很深,正确策略是成为它们之上的统一执行与运营层。
竞品分析基于公开官网资料,核查截止 2026-07-27;“未强调”不等于竞品完全不具备。
图 3 基于公开定位的竞品示意图
## 10.1 竞品类别
类别
代表产品
公开能力重点
本方案关系
深度存储测试平台
ULINK DriveMaster、OakGate SVF/Enduro、SANBlaze SBExpress
协议/命令、合规、性能、数据完整性、功耗、故障注入、多 DUT、自动化 API。[1]-[7]
优先集成,不正面替代。
物理层与电源故障设备
Quarch
热插拔、电源循环、功耗与复杂故障注入自动化。[8]
作为带外执行设备接入。
通用测试管理平台
NI TestStand
多语言模块、序列、并行、资源调度、报告、部署,并已加入 Nigel AI。[10]
在存储垂直场景做更快开箱与更深证据模型。
可编程测试框架
PyNVMe3
Python API、专用驱动、现成脚本,可在普通 x86/Ubuntu 上规模部署。[9]
作为低成本执行器和开发者生态。
企业自研平台
Jenkins/Python/数据库/继电器
高度定制、成本可控
最现实的隐形竞品;用可靠性与产品化降低维护负担。
## 10.2 ULINK
ULINK DriveMaster 10 NVMe 公开支持 NVMe 2.0、协议与命令验证、回归和数据完整性测试、功耗测量、来料验证及多种侧带信号测试;其 NVMe Regression Suite 包含随机命令电源循环、数据比较电源循环、全盘扫描、MD5、JEDEC 工作负载等。PSPA+ Gen5 可在不关闭系统的情况下控制 DUT 供电,并支持电流/电压测量及多种侧带信号。[1][2][3]
与 ULINK 的差异 不与其争夺协议测试深度。FlashOps 接管固件包、测试站、客户私有脚本、DriveMaster 任务、卡死恢复、跨工具证据、失败去重与版本发布闭环。
## 10.3 OakGate
OakGate 的公开产品由 Enduro 客户端、Linux 测试 appliance、SVF/Enduro 软件以及可扩展多盘位 enclosure 构成;软件覆盖验证、性能、协议、功耗、自动化、报告和 REST/Python API,并具有内置自动化、协议分析、错误注入和数据验证能力。[4][5]
与 OakGate 的差异 OakGate 是强大的专业测试环境。FlashOps 的价值是把 OakGate 与客户其他 PC、脚本、固件工具、缺陷系统和带外恢复统一到一个任务与证据模型中。
## 10.4 SANBlaze
SANBlaze SBExpress-RM5 公开定位为 16 盘位 Gen5 NVMe 验证系统,支持合规、读写比较、错误注入、自定义命令、多盘控制、槽位供电、功耗测量、热插拔、温度监控,并提供 REST、XML/CLI 与 Python 接口。[6][7]
与 SANBlaze 的差异 SANBlaze 已具备成熟自动化和 API,因此更适合作为 FlashOps 的高端执行器。FlashOps 重点提供实验室级资源编排、主机自救、工具间证据合并、失败签名和版本决策。
## 10.5 Quarch
Quarch 公开强调从简单热插拔循环到复杂数据故障注入的自动化,并被 PyNVMe3 文档用于 PCIe 电源控制和功耗监测。[8][9]
与 Quarch 的差异 Quarch 提供“手和传感器”;FlashOps 提供“任务、状态、证据和决策闭环”。
## 10.6 NI TestStand
NI TestStand 是通用验证与制造测试管理框架,公开支持多语言代码模块、交互式序列、并行执行、资源调度、数据库与多格式报告、部署工具,并在 2026 年产品中强调 Nigel AI。[10]
与 TestStand 的差异 不比通用编排能力,而比存储开箱能力:DUT/固件/主机环境模型、掉盘与数据完整性语义、带外自救、Evidence Bundle、固件 A/B 和最短复现。
## 10.7 PyNVMe3 与自研脚本
PyNVMe3 公开强调 Python、专用 NVMe 驱动、普通 x86/Ubuntu 部署和自动化流水线复用,代表低成本、高可编程的技术路线。[9] 这也是 FlashOps 最现实的价格竞争对手:客户可能认为“Jenkins + Python + 继电器”已经足够。
与自研方案的差异 卖点必须是总拥有成本而非代码行数:稳定状态机、双通道自救、防错盘、证据模型、审计、失败去重、升级与长期维护。
## 10.8 差异化能力清单
差异化能力
为什么有价值
形成壁垒的方式
工具中立的统一控制层
客户无需推翻原有设备和脚本
适配器 SDK、认证连接器、长期兼容性。
测试环境自救
解决真正阻碍夜间无人值守的问题
Host Agent + OOB 双平面、恢复策略数据。
Evidence Bundle
减少“无法复现”和跨团队扯皮
统一事件模型、时间线、证据完整性评分。
基础设施/DUT 故障分离
降低自动化误报和工程师反感
探针交叉验证、故障分类数据集。
自动缩小复现范围
直接节省固件工程师时间
失败签名、实验设计、历史触发条件。
版本门禁与环境指纹
把测试结果转成发布决策
Golden Suite、A/B 基线、审计链。
安全受控 AI
既提高阅读效率,又不引入危险动作
证据引用、工具权限、确定性执行。
# 11 商业化与首批客户策略
首批销售的不是“平台愿景”,而是一条可以立刻省人的真实流程。
## 11.1 PoC 产品包
项目
PoC 内容
范围
1 个测试节点、1 种 DUT、1 份真实 SOP、2 个固件版本、100 次循环。
交付
控制台、Agent、带外恢复、脚本适配、Evidence Bundle、A/B 报告。
客户投入
测试主机、DUT、固件工具、脱敏 SOP、工程师每周固定评审时间。
验收
人工干预下降、安全零事故、证据完整、能够识别至少一种真实或注入故障。
周期
以 12 周 MVP 为上限,前 2 周完成流程发现和风险清单。
## 11.2 商业模式建议
收入项
计费逻辑
早期策略
PoC 项目费
按 SOP 适配和现场集成工作量
保证客户投入资源,避免免费试用失焦。
软件站点许可
按测试节点或并发节点
私有化年费,含基础升级。
带外控制套件
硬件销售或租赁
首版使用成熟器件组合,后续标准化。
高级连接器
ULINK/OakGate/SANBlaze/Quarch/TestStand/私有工具
按连接器或企业包计费。
故障智能模块
失败聚类、最短复现、质量图谱
数据积累后作为高毛利模块。
服务与维护
现场部署、流程迁移、培训、SLA
早期不可避免,但产品化比例要持续提升。
## 11.3 ROI 计算
年度价值 ≈ 节省的值守工时+ 夜间/周末新增的测试机利用时间+ 减少的重复复现工时+ 提前发现回归所避免的返工成本+ 固件发布周期缩短带来的机会价值- 软件、硬件与集成成本
## 11.4 首批客户获取方式
从同学的真实行业关系中筛选 3 家中型团队,不先找平台最成熟的头部厂商。
每家只问一个问题:“哪一种测试每周反复做,最需要人守着?”
要求客户提供一份脱敏 SOP、一份最终报告、一次失败日志和允许测试的设备。
先交付自动执行与证据,不先承诺 AI 根因准确率。
用人工触碰次数、机器利用率、证据产出时间做业务复盘。
## 11.5 不建议的销售表达
不要说
应该说
“我们是 AI SSD 测试平台。”
“我们让你现有的测试平台真正无人值守。”
“AI 可以自动诊断所有故障。”
“系统先保存完整证据,再给出可验证的候选原因。”
“我们可以替代 OakGate/SANBlaze/ULINK。”
“我们统一调度并放大你已经购买的工具价值。”
“支持所有存储接口。”
“首版专注一个真实接口和一条高频流程,随后复制。”
“很快能做芯片级智能。”
“先替代机械执行,数据积累后再深入固件和控制器协同。”
# 12 数据壁垒与长期护城河
代码可以被复制,长期运行产生的故障与环境关系数据更难复制。
## 12.1 四类核心数据资产
数据资产
内容
价值
流程资产
真实 SOP、参数边界、恢复策略、通过门槛
缩短新客户上线时间。
连接器资产
私有工具、商业平台、固件刷写工具的适配
形成行业进入门槛。
故障资产
失败签名、触发条件、最短复现、恢复结果
提升去重、归因和重测能力。
环境图谱
固件、主机、BIOS、OS、驱动、DUT 与结果关系
为未来兼容性测试柜和版本风险模型提供基础。
## 12.2 护城河形成顺序
先用可靠执行和安全能力获得进入实验室的资格。
通过连接器和 SOP 积累形成交付速度优势。
通过统一证据模型积累跨运行的失败签名。
通过最短复现与自适应重测形成直接工程价值。
最终建立固件—环境—故障—修复之间的质量知识图谱。
## 12.3 数据治理原则
默认每个客户数据完全隔离,模型训练需单独授权。
固件二进制、私有日志解码器和内部缺陷不得进入公共模型。
允许客户选择只保存特征、哈希和归一化失败签名。
所有 AI 输出保留使用了哪些证据和模型版本。
支持按项目、设备和时间执行数据保留与删除策略。
# 13 未来产品规划
路线应从“替代一个岗位动作”自然扩展到“存储实验室操作系统”。
图 4 产品演进路线
## 13.1 阶段规划
阶段
核心产品
关键能力
商业目标
P00—6 周
真实 SOP PoC
单机、单 DUT、100 次循环、基础留证
证明人工触碰显著下降。
P16—12 周
FlashOps MVP
Agent、带外自救、A/B 报告、缺陷草稿
完成首个付费或联合设计 PoC。
P23—9 个月
企业版
多节点、权限审计、适配器 SDK、私有化运维
复制到 3—5 家客户。
P39—18 个月
故障智能版
失败图谱、聚类、最短复现、自适应重测
从效率工具升级为研发决策工具。
P418—30 个月
Storage LabOS
多实验室、多工具、资源池与质量图谱
形成平台收入与生态。
P5|并行扩展
兼容性循环测试柜
多主板/BIOS/OS/驱动矩阵与组合缩减
进入 OEM、整机与工业兼容性市场。
## 13.2 未来功能树
方向
未来能力
调度规模化
多节点、多 DUT、多实验室、许可证和仪器资源池、优先级与配额。
自动复现
步骤裁剪、变量控制、版本二分、局部矩阵展开、触发概率估计。
质量图谱
固件、主控、NAND/BOM、主机、BIOS、OS、驱动、故障与修复关系。
发布门禁
与 Git、CI/CD、固件仓库连接,Golden Suite 通过后才允许进入下一阶段。
兼容性测试柜
多真实主机的启动、休眠、驱动、系统更新与数据完整性矩阵。
现场复现
将实验室工作流下发到远程客户机器或边缘设备,回收统一证据。
高级硬件
在需求被验证后开发工业控制器、标准线束、多节点机柜和专用电源模块。
## 13.3 长期不应偏离的方向
长期原则 FlashOps 的核心始终是“把专业测试能力转化为无需人工值守的可靠产出”。即使未来进入兼容性、生产测试或芯片协同,也不应变成一家单纯卖测试脚本或硬件机箱的公司。
# 14 团队配置、投入顺序与治理
资金可以提高速度,但首要资源仍然是真实 SOP、样品和懂业务的工程师。
## 14.1 最小核心团队
角色
建议人数
职责
产品/全栈负责人
1
产品定义、Web、后端编排、AI 工作流、客户沟通。
存储领域负责人
1
测试方法、固件工具、日志解释、客户资源、验收标准。
系统/Agent 工程师
1
Windows/Linux 服务、进程与系统控制、日志、安装升级。
硬件/嵌入式工程师
0.5—1
带外控制、电气安全、线束、看门狗与工业化。
测试/DevOps
0.5—1
故障注入、自身可靠性测试、部署、监控和回归。
AI/数据工程
早期兼职
日志模板、聚类、RAG、模型评估;P3 后转核心。
## 14.2 你与同学的建议分工
你负责
同学负责
共同决策
产品形态、交互、平台架构、AI、开发推进、Demo 与材料
真实 SOP、设备、固件工具、日志含义、行业客户、硬件资金
目标场景、通过标准、首个客户、是否采购专业模块、定价与公司结构
## 14.3 资金投入顺序
先投入真实测试条件:测试主机、DUT、固件版本、工程师时间。
再投入可靠带外控制与商业电源/故障模块,避免自研硬件拖慢软件闭环。
第三投入产品化:Agent 稳定性、安全、审计、部署与连接器。
客户需求稳定后,再投入自研 PCB、工业机箱和多节点硬件。
有持续运行数据后,再扩大 AI 和数据团队。
## 14.4 项目治理
每个功能必须对应减少的人工动作或提高的证据质量。
每两周用真实测试机进行一次端到端故障演练。
危险命令、安全事故和数据丢失拥有最高优先级,不能用“先跑起来”豁免。
任何 AI 功能上线前必须有离线评估集与错误示例。
首个 PoC 期间禁止大规模扩展需求,优先完成一个闭环。
# 15 风险清单与应对策略
这个项目最大的风险不是模型效果,而是测试系统自身不可靠或不安全。
风险
影响
应对
缺少真实 SOP
做出看似完整但没人使用的通用平台
立项前必须拿到流程、日志、报告和测试设备。
误操作错盘
数据损失、项目直接失去信任
多重 DUT 绑定、系统盘保护、命令模板、审批和物理标签。
自救系统不可靠
无人值守名不副实
双通道心跳、分级恢复、恢复上限、定期故障演练。
商用工具无法集成
价值被局限在自研脚本
先验证 CLI/REST/Python 能力;必要时采用 UI 自动化作为过渡。
客户私有日志封闭
AI 诊断能力受限
先做证据与流程;由客户提供解码器插件,不依赖原始语义。
基础设施误报过高
工程师不再相信自动化
建立 INFRA_FAILURE 分类、探针交叉验证与平台自监控。
AI 过度承诺
错误结论影响发布与质量
证据引用、置信等级、人工门禁、禁止危险工具权限。
销售周期长
资金消耗、难以快速验证
先做设计合作伙伴和单 SOP 付费 PoC。
过早自研高速硬件
投入大、调试周期长、无法证明需求
成熟商业模块优先,需求和规模明确后再开发。
被客户自研替代
一次性项目、续费弱
持续强化连接器、运维、安全、数据图谱和跨站点能力。
# 16 立即行动清单
下一步不是继续讨论概念,而是拿到一条真实流程。
## 16.1 向同学索取的材料
一份最耗人、步骤固定、每周重复的固件回归 SOP。
一份脱敏后的人工测试报告和对应原始日志目录。
两个可刷写的固件版本,最好一个为基线、一个存在已知差异。
一块或多块允许反复写入、重启和破坏性测试的 SSD。
一台可长期占用的 Windows 或 Linux 测试主机。
厂商刷固件工具及最小使用说明。
一名愿意每周固定评审的存储验证工程师。
## 16.2 第一次工作坊议程
时段
议题
产出
0—30 分钟
逐步走一遍当前人工流程
现状流程图与角色分工。
30—60 分钟
标记等待、重复、人工判断、危险动作
可替代动作清单与不可替代判断。
60—90 分钟
复盘最近一次真实失败
证据缺口与恢复路径。
90—120 分钟
定义 PoC 场景和验收指标
首个工作流、测试设备、目标数据。
## 16.3 可直接发送给同学的话
沟通模板 你们公司里有没有一种固件测试,每周反复做,步骤基本固定,需要人刷版本、跑脚本、重启、守机器、收日志和填报告?你给我一份脱敏 SOP、一份真实报告、两个固件版本和一台测试机。我先做一个可以无人值守执行、机器卡死自动恢复、失败自动留证、最后输出 A/B 报告的 PoC,不碰你们核心算法,也不要求替换现有测试工具。
## 16.4 立项决策门槛
满足条件
不满足时的处理
能够获得真实 SOP 和固定评审工程师
暂停产品开发,只做流程访谈。
有允许破坏性测试的 DUT 和测试主机
先准备实验环境,不做软件假 Demo。
人工流程确实每周高频重复
否则寻找更高频场景。
PoC 指标可以量化人工触碰与完成率
不能只以“页面好看”验收。
客户接受本地部署和必要的系统权限
否则明确安全边界与接入方式。
# 附录 A 示例工作流
结构化流程只描述意图和受控参数,实际命令来自签名模板。
workflow: firmware_ab_power_state_regressionversion: 1.0safety_level: destructive_test_dut_onlyresources: host: lab-win-01 dut_serial: SSD-TEST-00017 oob_controller: oob-01matrix: firmware: [FW_A, FW_B]steps: - precheck_environment - verify_dut_identity - flash_firmware: artifact: ${firmware} - verify_firmware_version - write_known_pattern: size_gb: 20 - repeat: times: 100 steps: - run_io_profile: profile: mixed_4k_qd32 duration_sec: 300 - collect_nvme_logs - reboot_or_sleep_cycle: mode: hibernate - wait_for_host: timeout_sec: 240 - verify_dut_enumeration - verify_known_pattern on_failure: - freeze_evidence - oob_recovery: ladder: [soft_reboot, reset, ac_cycle] - retry_from_checkpoint: max_attempts: 2 - compare_with_baseline - generate_reportrelease_gate: data_integrity_errors: 0 dut_missing_events: 0 infrastructure_false_positive_rate: < 0.05
# 附录 B Evidence Bundle 字段
每个失败必须能够被另一个工程师独立理解和重放。
分组
字段
身份
Run ID、项目、DUT 型号/序列号、固件、主机、操作者、工作流版本。
环境
主板、BIOS、CPU、内存、OS、内核、驱动、工具版本、测试参数。
时间线
步骤开始/结束、命令、状态变化、心跳、设备枚举、恢复动作。
设备证据
Identify、SMART、Error Log、Telemetry/Persistent Event Log(若支持)、链路状态。
主机证据
Windows Event Log、BSOD/转储、dmesg、journal、服务状态、系统资源。
测试证据
stdout/stderr、结果文件、fio/PyNVMe/商用平台报告、哈希与数据校验。
带外证据
Power/Reset 动作、HDMI 截图/视频、温度、电压/电流(若支持)。
分析
故障分类、失败签名、相似运行、重现概率、候选原因、建议下一步。
完整性
文件哈希、生成时间、缺失字段、证据包版本、数字签名。
# 附录 C 失败分类体系
先准确判断“哪里坏了”,再判断“为什么坏”。
代码
含义
示例
INFRA_NETWORK_FAILURE
实验室网络或控制网络故障
交换机断连、DNS/SSH 不可用。
INFRA_STORAGE_FAILURE
证据存储或测试主机系统盘问题
磁盘满、对象存储不可写。
OOB_CONTROLLER_FAILURE
带外控制器自身故障
控制器失联、继电器无响应。
TEST_TOOL_FAILURE
测试工具或许可证故障
DriveMaster/TestStand/PyNVMe 进程异常。
TEST_SCRIPT_FAILURE
客户脚本逻辑或依赖问题
参数错误、断言错误、文件缺失。
HOST_OS_FAILURE
操作系统级故障
蓝屏、内核崩溃、启动循环。
DUT_ENUMERATION_FAILURE
DUT 未识别或掉盘
PCIe/NVMe 枚举消失。
DUT_COMMAND_FAILURE
NVMe 命令返回异常
超时、状态码错误、重置失败。
DUT_DATA_FAILURE
数据完整性异常
读回不一致、哈希错误。
DUT_PERFORMANCE_REGRESSION
性能相对基线显著下降
P99 延迟或带宽超出阈值。
DUT_THERMAL_FAILURE
温度或热降速异常
达到阈值后掉速或超时。
RECOVERY_FAILURE
规定恢复阶梯未恢复
Reset 和 AC Cycle 后仍不可用。
UNKNOWN
证据不足或多因素耦合
需要工程师进一步分析。
# 附录 D 公开资料来源
竞品与技术生态信息优先采用厂商和标准组织的官方资料。
[1] ULINK — DriveMaster 10 NVMe https://ulinktech.com/products/drivemaster-10-nvme/
[2] ULINK — NVMe Protocol & Regression Test Suites https://ulinktech.com/products/ulink-nvme-test-suites/
[3] ULINK — PCIe-SSD Power Adapter Plus Gen5 https://ulinktech.com/products/pcie-ssd-power-adapter-plus/
[4] Teledyne LeCroy — OakGate SSD Test and Validation Solutions https://www.teledynelecroy.com/ssdtesting/oakgate-ssd-test-solutions.aspx
[5] Teledyne LeCroy — SVF/Enduro SSD Testing Software https://www.teledynelecroy.com/ssdtesting/svf-enduro-software.aspx
[6] SANBlaze — SBExpress-RM5 NVMe Gen5 Test System https://www.sanblaze.com/sbexpress-rm5
[7] SANBlaze — REST Interface https://www.sanblaze.com/rest-interface
[8] Quarch — Data Center & AI Solutions / Fault Injection https://quarch.com/solutions/data-center-ai-solutions/
[9] PyNVMe3 — Developers Guide and User Guide https://pynv.me/ssd/pynvme3-developers-guide/
[10] NI — TestStand https://www.ni.com/ja/shop/electronic-test-instrumentation/application-software-for-electronic-test-and-instrumentation-category/what-is-teststand.html
[11] NVM Express — Error Reporting, SMART, Telemetry and Persistent Event Log https://nvmexpress.org/resource/features-for-error-reporting-smart-log-pages-failures-and-management-capabilities-in-nvme-architectures/
[12] NVM Express — Open Source Software / nvme-cli https://nvmexpress.org/drivers/open-source-software/
[13] Linux Kernel Documentation — NVMe Fault Injection https://docs.kernel.org/fault-injection/nvme-fault-injection.html
注:竞品功能、版本和产品组合会持续变化,正式商务判断前应向厂商索取最新报价、数据表与演示。
# 结语 最终建议
先把一个真实流程跑通,再决定公司要做多大。
最终立项结论 值得做,但必须以真实 SOP 为起点。第一件产品不是“通用存储 AI 平台”,而是“固件回归值守机器人”:工程师配置一次,系统无人值守完成 100 次循环,卡死能自救,失败自动留证,最后输出固件 A/B 结论。这个闭环成立后,再扩展到多节点、故障智能和兼容性循环测试柜。
这条路线与现有专业测试厂商并不矛盾。相反,专业平台越多、客户工具越碎片化,统一控制、无人值守和证据闭环的价值越高。项目真正的护城河将来自长期积累的连接器、真实 SOP、故障签名、恢复策略和环境图谱,而不是一个 AI 对话框。
Binary file not shown.
@@ -0,0 +1,802 @@
STORAGE LABOS
固件回归测试值守机器人
产品定义、开发方案、竞争策略与未来规划
核心命题
不重新发明测试工具,先把工程师从刷版本、守机器、重启、收日志、填报告等机械劳动中解放出来。
产品代号
Storage LabOS · Regression Worker
版本
V1.0
日期
2026年7月27日
适用对象
联合创始人、产品负责人、研发团队、首批试点客户
首个落地场景
固件 A/B 版本的无人值守循环回归
北极星指标
无人值守完成率 + 每工位释放的工程师工时
一句话定位:固件提交后,系统自动刷版本、运行回归、处理卡死、收集证据、复现失败,并生成版本结论和缺陷单。
# 文档摘要
最终建议
先做“单工位固件回归值守机器人”,证明它能在一块 SSD、两个固件版本和一套真实 SOP 下,无人值守完成循环测试;测试主机卡死后自动恢复;失败时保存完整证据;结束后自动形成 A/B 对比报告。不要从协议合规、EDA、PCIe 高速背板或自研主控起步。
本项目的本质不是“AI 会不会判断 SSD”,而是建立一个存储实验室数字执行员:专家负责定义测试和决策,系统负责反复执行、值守、恢复、留证和整理。
公开市场中的 ULINK、OakGate、SANBlaze、Quarch 与 PyNVMe3 已经证明存储验证、供电控制、故障注入和自动化测试存在稳定需求;同时也说明新团队不应在底层测试覆盖和最新 PCIe 代际上正面竞争。你们的切口是这些能力之上的统一控制平面。
问题
方案
可量化结果
工程师夜间值守、失败后人工重启
带外控制器监测主机,分级执行软恢复、Reset、整机断电恢复
人工介入次数、自动恢复成功率
工具和脚本分散,失败现场不完整
统一连接器和 Evidence Bundle,将日志、设备状态、画面、温度和恢复过程对齐
证据完整率、从失败到可提单时间
回归结果靠 Excel 和人工对比
固件 A/B 自动对比、失败指纹聚类、自动生成报告与缺陷单
报告生成时间、重复问题合并率
企业已有工具难以推倒重来
以插件方式接入 DriveMaster、SANBlaze、Quarch、PyNVMe、fio 及私有工具
接入周期、适配器数量
## 目录
上半部分
下半部分
1. 项目判断与战略边界
7. 硬件方案与演进路径
2. 行业痛点、用户与价值模型
8. 竞品分析与竞争策略
3. 产品定位与主打卖点
9. 差异化、护城河与品牌表达
4. MVP 产品定义
10. 产品路线与未来规划
5-6. 产品功能、技术架构与开发方向
11-12. 商业化、团队分工与附录 A-D
# 1. 项目判断与战略边界
## 1.1 核心判断
战略公式
已有测试能力 × 无人值守执行 × 带外自救 × 证据闭环 × 失败复现 = 第一款可卖的产品。
你们最适合的起点不是存储芯片设计、EDA、主控固件本身,也不是再造一套协议测试仪,而是替代存储公司中高频、固定、耗时但决策含量低的执行劳动。
测试与验证并非“无脑工作”本身。真正应该被替代的是其中的机械执行层:准备环境、刷固件、反复启动、等待、检测卡死、收集日志、截图、对比版本、整理报告。测试设计、根因确认和发布决策继续由工程师负责。
## 1.2 产品要解决的任务
把一份固定 SOP 转换成可审计、可恢复、可重复执行的结构化工作流。
在无人值守状态下完成固件刷写、I/O、重启、休眠、恢复和数据校验循环。
测试主机失联或崩溃后,由独立带外控制器恢复环境并从安全检查点继续。
失败发生时自动保存完整现场,而不是只留下“第 67 轮失败”。
将固件 A/B 的结果自动对比,并输出研发可直接使用的报告和缺陷草稿。
兼容客户已有测试工具和私有脚本,降低替换阻力。
## 1.3 明确不做什么
非目标
原因
何时再考虑
重新开发完整 NVMe 协议合规套件
成熟厂商与开源框架已经积累大量用例;首版投入大、差异化弱
客户明确需要且已有稳定收入后,以连接器或合作方式补齐
PCIe Gen5/Gen6 高速背板与协议分析仪
涉及高速信号完整性、时序、电源和认证,容易拖慢产品验证
在单工位软件闭环和客户付费成立后,由专门硬件团队推进
让大模型直接控制破坏性命令
误操作代价高,不能把安全交给概率模型
大模型只生成草稿;确定性规则和人工审批执行
自动批准固件发布
发布是工程责任与组织流程,不应由系统越权
系统提供门禁证据和风险提示
EDA 或芯片设计自动化
进入核心设计签核,专业、信任、数据和销售门槛均过高
长期若有明确行业伙伴再单独评估
## 1.4 产品切入点
图 1|本产品不与底层测试平台争夺协议覆盖,而是作为统一执行与闭环层。
# 2. 行业痛点、用户与价值模型
## 2.1 目标用户
用户角色
日常痛点
本产品交付
固件工程师
新固件出来后要等回归结果;失败信息不完整,复现成本高
版本差异、失败证据、最短复现建议
验证 / QA 工程师
大量时间花在刷版本、守机器、重跑和填表
无人值守执行、自动恢复、自动报告
实验室负责人
机器利用率低,夜间失败无人处理,资源状态不透明
工位在线率、任务队列、人工介入统计
FAE / 客户支持
现场问题难复现,日志来源分散
统一证据包、复现工作流、环境指纹
研发经理
无法准确知道版本风险和回归进度
版本门禁看板、阻断问题、历史趋势
## 2.2 当前人工流程
领取测试盘 → 核对序列号 → 刷入固件 → 重启并确认版本→ 配置测试环境 → 跑现有脚本 → 等待 / 观察→ 卡死后人工重启 → 重跑失败段 → 收集多处日志→ 截图 / 复制数据 → 与上一版对比 → 填 Excel→ 写测试报告 → 建缺陷单 → 工程师再次复现
这里面真正需要专业判断的环节有限,但每一次执行都需要工程师保持在线。夜间失败、主机蓝屏、网络断开和脚本挂起会让“已经自动化的测试”重新变成人工值守。
## 2.3 目标流程
工程师选择 DUT + 固件 A/B + 已审核工作流→ 系统完成安全预检和环境指纹记录→ 自动刷写、运行、重启、休眠、恢复、校验→ 失联时带外控制器分级自救并留痕→ 失败时自动收集 Evidence Bundle 并重现→ 自动聚类同类失败、对比 A/B、生成报告和缺陷草稿→ 工程师只处理需要判断的异常与发布决策
## 2.4 价值模型
北极星指标
每个测试工位每周释放的工程师工时。辅助指标包括无人值守完成率、人工介入次数、自动恢复成功率、证据完整率和从失败到可提单的时间。
指标
定义
产品阶段目标
无人值守完成率 UCR
无人工触碰完成的计划任务数 ÷ 总计划任务数
POC 先达到可用;产品化后持续逼近 98%+
人工介入密度
每 100 小时测试需要人工处理的次数
持续下降,且分类记录介入原因
自动恢复成功率
主机/Agent 失联后由带外控制恢复的成功比例
区分软恢复、Reset、整机断电三层
证据完整率
失败任务是否包含规定的环境、日志、状态和恢复记录
关键字段不得因主机崩溃而丢失
报告交付时间
任务结束到可审阅报告生成的时间
分钟级,而不是人工半天整理
基础设施误报率
被错误归因到 DUT 的脚本、主机或网络故障比例
必须单独监控,否则自动化会制造噪声
# 3. 产品定位与主打卖点
## 3.1 一句话定位
产品定义
固件提交后,系统自动刷版本、运行回归、处理卡死、收集证据、复现失败,并生成版本结论和缺陷单。
产品名可以暂定为 Storage LabOS,第一款模块叫 Regression Worker。品牌不必强调“AI SSD”,应强调“固件回归不再靠人熬夜”。
## 3.2 三个主打点
主打点
用户能感知到的结果
为什么重要
一、自动完成,而不只是自动启动
工程师配置一次,系统整夜运行;主机卡死后能够继续,而不是停在那里等人
多数“自动化脚本”在异常后仍需要值守,这是实际人工成本来源
二、失败自动留证
每个失败形成统一时间线和 Evidence Bundle,包含环境、命令、日志、设备状态、画面、温度和恢复过程
减少“无法复现”和跨团队来回索取材料
三、不推翻现有工具
DriveMaster、SANBlaze、Quarch、PyNVMe、fio、私有刷写工具和脚本均以连接器方式接入
降低客户迁移成本,也避免与成熟平台在底层能力上正面竞争
## 3.3 支撑卖点
基础设施故障与 DUT 故障分开:网络、脚本、系统、Agent、供电和设备异常使用不同状态码。
失败自动复现与缩小范围:固定环境重跑、局部步骤重跑、单变量验证和版本二分。
A/B 版本结论直接可用:不仅给 Pass/Fail,还给新增、消失、加重和偶发问题。
安全优先:绑定 DUT 序列号、破坏性动作白名单、工作流签名、权限和全量审计。
私有化部署:客户日志、固件和工具默认不离开内网;AI 可选本地模型。
报告在测试结束时已完成:工程师从执行者变成审阅者。
## 3.4 对外表达
场景
推荐表述
官网主标题
固件回归不再需要工程师通宵值守。
副标题
机器卡死能自救,失败自动留证,第二天直接看版本结论。
对研发负责人
把初级工程师从刷版本、重启、收日志和填报告中释放出来。
对实验室负责人
提升工位利用率,量化每周节省的人工介入和停机时间。
不建议使用
“AI 精准判断所有 SSD 根因”“替代验证工程师”“一套工具取代所有测试平台”。
# 4. MVP 产品定义
## 4.1 首个场景:固件 A/B 的 100 次循环回归
MVP 只支持一个 SSD 型号、两个固件版本、一台 Windows 或 Linux 测试主机和一套真实 SOP。测试组合可包含 I/O、重启、冷启动、休眠恢复和数据校验。目标不是覆盖所有测试,而是证明全流程能无人值守闭环。
## 4.2 用户操作流程
1. 选择已登记的测试主机与 DUT,系统校验序列号、容量、当前固件和允许动作。
2. 选择固件 A、固件 B 以及一套已审核工作流模板。
3. 设置循环次数、重试策略、报告接收人和是否允许破坏性写入。
4. 系统执行环境预检:磁盘空间、网络、带外控制器、工具版本、日志目录和时间同步。
5. 任务开始后实时显示当前步骤、轮次、主机状态、设备枚举和关键指标。
6. 异常时自动收证、恢复、重试或中止,并明确区分基础设施故障与 DUT 故障。
7. 任务结束后输出固件 A/B 对比、失败簇、证据包和缺陷草稿。
## 4.3 MVP 范围
包含
暂不包含
单工位、单 DUTWindows 或 Linux 二选一先做
多租户云平台与复杂计费
Python / Shell / PowerShell / fio / nvme-cli / 私有 CLI 连接器
完整 NVMe 合规套件
重启、休眠、冷启动、主机掉电恢复
精准 M.2 3.3V 微秒级掉电
树莓派带外 Power / Reset / 心跳 / 温度
PCIe Gen5/Gen6 自研高速背板
设备、主机、固件和环境指纹
自动 BIOS 版本刷写与视觉操作
统一日志、截图、状态和 Evidence Bundle
大规模预测性故障模型
规则判定 + AI 摘要 / 报告 / 复现建议
由 AI 直接执行危险命令或批准发布
## 4.4 MVP 功能模块
模块
MVP 功能
验收重点
资产中心
测试主机、DUT、固件、工具版本、序列号与标签
任何破坏性任务必须绑定允许的 DUT
工作流引擎
步骤、循环、条件、超时、重试、检查点、失败处理
任务重启后能从安全状态继续
Host Agent
执行命令、采集日志、心跳、设备枚举、上传证据
Agent 崩溃不应导致全部证据丢失
带外控制
软重启、Reset、整机电源循环、在线状态监测
测试主机完全失联时仍可操作
Evidence Bus
统一事件时间戳、原始日志、截图、摘要、哈希
失败材料可追溯且不被覆盖
比较与报告
A/B 通过率、失败类型、恢复次数、性能与温度差异
报告结论能回链到证据
AI 助手
SOP 草拟、日志摘要、失败归类建议、缺陷草稿
所有结论必须引用证据,不直接控制危险动作
## 4.5 POC 验收门槛
门槛
最低验收标准
完整执行
对固件 A、B 分别完成规定循环,支持中断恢复和任务审计
零误盘
所有破坏性动作均经过序列号和系统盘保护校验,误操作次数必须为 0
自救能力
模拟 Agent 停止、系统卡死或网络失联时,能够触发至少两级恢复策略
证据闭环
每个失败均可打开一条时间线,并下载统一证据包
版本比较
能够明确显示新增失败、已修复失败、重复失败与基础设施失败
人工减少
与原人工 SOP 对比,显著减少夜间值守、重启、收日志和整理报告时间
# 5. 产品功能与交互方案
## 5.1 页面结构
页面
核心内容
用户动作
总览 Dashboard
运行中任务、工位在线率、异常、人工介入、版本风险
快速判断实验室是否需要人处理
资产中心
主机、DUT、固件、工具、带外控制器、标签与状态
登记、绑定、禁用和审计
工作流模板
SOP 转换后的步骤、参数、权限、版本和审批状态
复制、编辑、审核、发布
任务创建
选择资产、固件、模板、循环、报告和安全策略
启动或预约任务
实时运行
当前轮次、步骤、心跳、设备状态、日志和恢复动作
只在必须时人工接管
证据浏览器
失败时间线、原始日志、截图、环境和哈希
定位证据、下载包、生成缺陷
版本比较
A/B 成功率、失败簇、性能、温度和恢复差异
作出版本判断
失败中心
跨任务聚类、指纹、首次/最近出现、关联版本
去重、标记、复现和分派
## 5.2 失败状态模型
INFRA_NETWORK_FAILURE 网络 / 交换机 / SSH / RPCINFRA_AGENT_FAILURE Agent 或插件运行器异常INFRA_HOST_OS_FAILURE 蓝屏、Kernel Panic、系统无法启动TEST_SCRIPT_FAILURE 脚本异常、参数错误、依赖缺失DUT_ENUMERATION_FAILURE SSD 未识别、掉盘、链路异常DUT_DATA_FAILURE 校验失败、介质错误、不可恢复数据错误DUT_PERFORMANCE_REGRESSION 超出已审核阈值的性能 / 长尾退化DUT_FIRMWARE_FAILURE 刷写、提交、激活或回退异常UNKNOWN 证据不足,等待工程师分类
状态模型必须在大模型之前建立。AI 可以解释和推荐,但真正的状态码应由规则、工具返回值和硬件信号生成。
## 5.3 Evidence Bundle
类别
内容
身份与环境
Run ID、DUT 序列号、型号、固件、主板、BIOS、操作系统、内核/驱动、工具版本
流程上下文
工作流版本、步骤、循环轮次、输入参数、超时、重试和检查点
设备证据
Identify、SMART/Health、Error Log、Self-test、Telemetry/厂商私有日志(可用时)
主机证据
系统日志、事件查看器、dmesg/journal、进程、资源、设备枚举和链路状态
现场证据
HDMI 截图/录像、串口、温度、供电控制动作、主机心跳和网络状态
数据证据
测试文件哈希、校验结果、失败 LBA/范围、fio/PyNVMe/私有工具输出
恢复证据
软恢复、Reset、断电、重新上线、重新枚举和恢复耗时
完整性
所有文件清单、时间戳、大小、哈希和生成组件版本
NVMe 标准已经定义 SMART/Health、Error Information、Device Self-test、Persistent Event 和 Telemetry 等可供诊断的日志或能力;厂商私有日志可通过专用工具或 nvme-cli 插件扩展读取。[11][12]
## 5.4 A/B 报告结构
执行摘要:版本是否建议进入下一阶段、阻断问题数量、关键风险。
环境锁定:两次测试的主机、BIOS、OS、驱动、工具和配置是否一致。
通过率与稳定性:总循环、完整完成、失败、自动恢复、人工介入。
问题变化:新增、已修复、仍存在、出现频率变化和严重性变化。
证据链接:每一个结论回链到原始任务、事件时间线和 Evidence Bundle。
下一步建议:复现工作流、需要固定/变化的变量、建议责任团队。
# 6. 技术架构与开发方向
图 2|MVP 总体架构。带外恢复面独立于测试主机,证据面独立保存失败现场。
## 6.1 核心组件
组件
职责
MVP 建议
Web 控制台
资产、模板、任务、运行状态、证据、比较和权限
Next.js + TypeScript + ECharts
API / Orchestrator
领域模型、权限、安全门禁、任务编排和报告
FastAPI + PostgreSQL
Durable Workflow
长时间任务、重试、等待主机上线、检查点与补偿动作
POC 用数据库状态机;产品化再评估 Temporal 等持久工作流
Host Agent
命令执行、心跳、设备/系统采集、插件隔离与上传
MVP 用 Python 服务;稳定后将核心守护进程迁移到 Go
Plugin Runner
接入 CLI、脚本、DriveMaster、SANBlaze、Quarch 等
清单化插件 + JSON 输入输出 + 超时/权限声明
OOB Controller
Power、Reset、AC、电源状态、HDMI/串口/温度
树莓派 / CM4 / 工业控制器 + 独立 Agent
Evidence Store
原始日志、截图、证据包和不可变清单
MinIO / S3 兼容对象存储;元数据进 PostgreSQL
AI Service
SOP 草拟、摘要、聚类标签、复现建议和报告
可配置本地或客户批准的模型;所有输出引用证据
## 6.2 工作流状态机
DRAFT → APPROVED → QUEUED → PRECHECK → RUNNING ↘ WAITING_FOR_HOST ↘ COLLECTING_EVIDENCE ↘ RECOVERING → RESUME / RETRY ↘ PASSED ↘ FAILED_DUT ↘ FAILED_INFRA ↘ ABORTED / EMERGENCY_STOP
持久状态机比普通脚本循环重要。测试可能持续数小时或数天,控制服务、Agent、主机或网络都可能重启;任务状态必须可恢复,且每一步需要幂等、超时和补偿逻辑。
## 6.3 工作流定义示例
workflow_version: 1name: fw_ab_powerstate_regressionsafety: allowed_dut_serials: ["SAMPLE-001"] destructive_write: true require_approval: truevariables: cycles: 100 firmware: "FW_B.bin"steps: - id: precheck action: lab.precheck - id: flash action: vendor.flash_firmware args: {image: "${firmware}"} - id: reboot action: host.reboot wait_for: {agent_online: true, timeout_sec: 600} - id: loop repeat: "${cycles}" steps: - action: nvme.collect_baseline - action: io.run_profile args: {profile: "4k_mixed", duration_sec: 300} - action: host.hibernate - action: host.wait_resume - action: dut.verify_enumeration - action: data.verify_hashon_failure: - action: evidence.capture_all - action: recovery.escalate - action: failure.classify
## 6.4 插件规范
字段
作用
name / version / platform
标识连接器、版本兼容和运行环境
capabilities
声明刷固件、采日志、I/O、供电、Reset 等能力
risk_level
只读、写入、格式化、Sanitize、固件更新等风险级别
inputs / outputs
使用 JSON Schema 约束参数和结构化结果
timeout / retry / idempotent
定义超时、重试和能否安全重复执行
evidence
声明必须产生的日志、快照和状态字段
healthcheck
任务前验证工具、许可证、设备和依赖是否可用
cleanup / compensation
失败或取消后恢复环境的动作
## 6.5 数据模型
实体
关键字段
Host
主机 ID、硬件、BIOS、OS、驱动、Agent、带外控制器、状态
DUT
序列号、型号、容量、形态、固件、标签、允许动作、生命周期
Firmware
文件哈希、版本、来源、兼容型号、审批、刷写工具
Workflow
版本、步骤、参数、风险、审批、签名、关联 SOP
Run / Step Run
状态、时间、主机、DUT、固件、输入、输出、重试、检查点
Evidence
类型、URI、哈希、时间范围、来源组件、关联步骤
Failure Signature
状态码、日志特征、步骤、环境、DUT 状态、聚类 ID
Intervention
人工介入原因、动作、耗时、结果,用于量化真实节省
## 6.6 AI 与确定性系统的边界
由确定性系统负责
由 AI 辅助
设备序列号与系统盘保护
把自然语言 SOP 草拟成工作流
命令白名单、权限、审批、超时和紧急停止
解释日志和生成工程摘要
真正执行刷写、格式化、Sanitize、供电和 Reset
给失败簇命名、提取相似特征
Pass/Fail 阈值、发布门禁和状态码
提出下一轮单变量验证或复现建议
原始证据采集、哈希和审计
生成 A/B 报告与缺陷草稿
AI 原则
任何 AI 结论都必须包含证据引用、置信度和“尚不能排除”的替代解释。AI 不直接执行危险动作,也不替代工程师作最终根因和发布判断。
## 6.7 安全与私有化
默认部署在客户内网;固件、私有工具、日志和客户数据不上传公共云。
工作流模板需要版本化、审批和签名;运行时禁止临时拼接未审核的危险命令。
系统盘、非白名单序列号和生产盘默认禁止写入;破坏性动作需要双重确认或审批。
Secrets 与日志分离;刷写工具凭据、许可证和 API Token 使用专门密钥存储。
所有自动恢复和人工接管动作写入不可变审计日志。
模型调用可配置脱敏;客户要求时使用本地推理。
# 7. 硬件方案与演进路径
## 7.1 MVP:树莓派作为带外控制器,不作为高性能测试主机
树莓派的价值不是跑满高端 SSD,而是在测试主机完全失联时仍能独立判断在线状态、操作 Power/Reset、记录温度和恢复过程。I/O 与固件测试应优先在真实 x86 Windows/Linux 主机的原生 M.2/U.2 接口上运行。
组件
首版选择
作用
测试主机
普通 x86 台式机或服务器,系统盘与 DUT 分离
运行客户原有工具、驱动和真实负载
带外控制器
Raspberry Pi 5、CM4 或工业 Linux 控制器
心跳、Power、Reset、AC、传感和画面
ATX 控制
光耦隔离的 Power/Reset 控制板
模拟按键,避免直接操作高风险供电轨
供电控制
首版先控制整机 AC;需要 DUT 精确掉电时接入成熟工具
避免首版陷入 M.2 供电时序和信号完整性
画面采集
USB HDMI 采集,可选串口
OS 未启动或蓝屏时仍能留证
传感器
温度为主,后续增加电流/电压
辅助判断热相关和供电相关异常
网络
控制面与带外控制器独立心跳
区分主机崩溃与网络故障
## 7.2 分级恢复策略
1. Agent 仍在线:终止异常进程、清理资源、重新执行安全步骤。
2. Agent 失联但操作系统可达:远程软重启或服务恢复。
3. 主机无响应:带外控制器短按 Reset,并监测重新上线。
4. Reset 无效:执行整机 AC 电源循环,保存断电和恢复时间。
5. 多次恢复失败:进入安全停止,保留现场,禁止无限重启。
## 7.3 硬件演进
阶段
硬件投入
进入条件
H0 现成硬件 POC
测试主机 + 树莓派 + ATX 控制 + 温度 + 可选 HDMI
先证明完整闭环和人工减少
H1 工程样机
工业控制器、隔离 I/O、机箱、状态灯、看门狗、可靠供电
已有试点客户,需要长期无人值守
H2 第三方仪器集成
PSPA、Quarch、智能 PDU、温箱等适配器
客户确实需要 DUT 独立掉电和故障注入
H3 自研带外控制板
多路 Power/Reset、传感、串口、硬件看门狗、远程升级
多个客户有一致需求,外购成本或集成复杂度成为瓶颈
H4 专业 DUT 电源 / 夹层板
精确掉电、测量、旁带信号和保护
由硬件团队完成 SI/PI、电气安全和长期可靠性验证
硬件边界
第一版不要直接切断 M.2 3.3V,也不要自研 Gen5/Gen6 高速转接背板。专业厂商已经提供可软件控制的 DUT 供电、功耗测量、Reset、HotPlug 和故障注入能力;先适配,后自研。[3][5][6][8]
# 8. 竞品分析与竞争策略
## 8.1 市场现状
截至 2026 年 7 月,主流存储验证厂商继续向更高 PCIe 代际、更多 DUT、协议合规、功耗测量、供电控制和故障注入扩展。OakGate 与 SANBlaze 已公开面向 PCIe 6.0 的产品路线,说明新团队若从高速验证硬件正面进入,将面对成熟技术积累与持续代际升级。[13][14]
另一方面,ULINK、SANBlaze、Quarch 和 PyNVMe3 都提供不同程度的自动化或 API 能力,不能把差异化建立在“竞品不会自动化”上。真正可建立的新定位是:跨工具编排、测试主机自救、证据闭环、失败去重与复现,以及对企业真实 SOP 的快速适配。
## 8.2 主要竞品画像
竞品
公开定位与能力
强项
本项目的策略
ULINK DriveMaster + NVMe Suites + PSPA
Windows 上的 NVMe 命令、协议、回归与数据完整性测试;回归套件覆盖多种电源循环、数据比较和 JEDEC 工作负载;PSPA 支持软件控制 DUT 供电与测量。[1][2][3]
测试内容成熟,协议/命令控制和电源循环结合紧密
不重写套件;做上层版本流转、任务调度、主机恢复、跨工具证据和缺陷闭环
Teledyne LeCroy OakGate
专有 SVF 软件与桌面/机架设备组合,支持主流存储协议,可扩展多个 DUT,面向中大型测试团队。[4]
成熟、稳定、规模化、底层验证能力强
把 OakGate 视为高价值执行器;产品在其上方统一编排实验室流程
SANBlaze SBExpress
Turnkey NVMe 验证系统;RM5 为 16 盘位 Gen5 机架设备,支持供电、功耗和错误注入;桌面系统提供 Python、REST、CLI/XML,公开资料称有 900+ 内置测试。[5][6]
协议、合规、硬件控制、API 和多盘位一体化
不比测试数量;比人工介入、异常自救、证据完整性和跨平台复现
Quarch
聚焦功耗、供电循环、HotPlug、旁带和物理层故障注入,提供 Python/REST 等自动化接口,并可与 PyNVMe 等集成。[7][8]
电源与物理故障注入专业、模块化
作为硬件连接器;不先自研精密供电与信号故障注入
PyNVMe3
Python + 专用用户态 NVMe 驱动,可在通用 x86/Ubuntu 平台运行,适合脚本化与 CI,降低专用硬件门槛。[9][10]
灵活、低成本、开发者友好、易于定制
作为首批默认执行器之一;补齐带外自救、资产、安全、证据和报告
企业自研脚本 / Jenkins 类流水线
自由组合 Python、Shell、PowerShell、fio 和私有工具
最贴合内部需求,已有沉没成本
真正的隐形竞品;必须做到接入而非替换,并明显降低维护和值守成本
## 8.3 能力重心对比
能力维度
专业测试平台
自研脚本 / CI
Regression Worker
协议与底层测试覆盖
强,通常是核心卖点
取决于团队积累
不作为首版核心;调用外部能力
供电 / Reset / 故障注入
成熟平台可很强
需要自接硬件
统一适配,并提供带外恢复策略
企业私有 SOP 适配
可定制,但取决于平台
强,但维护分散
以工作流与插件为核心
主机完全卡死后的自救
部分平台具备设备级控制;跨主机流程各异
常需人工处理
产品级主打能力
跨工具证据时间线
通常围绕自身工具
多目录、多格式
统一 Evidence Bundle
基础设施与 DUT 故障区分
依赖具体平台和脚本
容易混杂
统一状态模型和分类
失败去重与最短复现
通常需工程师完成
依赖内部开发
核心增值能力
报告 / 缺陷闭环
有报告,但跨工具整合有限
人工拼接常见
测试结束即生成可提单结果
采购与替换阻力
可能较高
最低
叠加在现有资产上,降低迁移阻力
## 8.4 如何真正脱颖而出
1. 指标不同:竞品常用协议覆盖、测试数量、带宽、盘位和测量精度证明能力;你们用“每周节省多少人工介入、多少失败能够自动形成证据”证明价值。
2. 中立连接层:支持竞品和客户自研工具,把已有投入变成插件。客户无需在迁移前先否定过去的系统。
3. 主机级自救:很多存储工具能控制 DUT,但客户真正痛苦的是测试主机、操作系统、Agent 和网络异常后没人处理。
4. Evidence First:把“失败结果”变成可转交、可复现、可审计的工程资产。
5. Failure Intelligence:基于结构化状态和真实证据做去重、版本关联、单变量复现和历史问题检索。
6. SOP 产品化:每接入一套真实 SOP,就形成可复用模板、连接器和行业知识,而不是一次性外包脚本。
## 8.5 竞争风险
风险
判断
应对
成熟厂商增加 AI、报告或调度
很可能发生,不能依赖单一功能领先
保持跨厂商中立、做连接器广度和真实 SOP 数据
客户选择自研
这是最常见替代方案
以更快上线、带外硬件、证据标准和长期维护降低自研吸引力
被认为只是“套壳 Jenkins”
如果只做任务调度,确实会发生
必须把 OOB 自救、DUT 安全、证据包、失败指纹和复现做深
客户不愿开放私有工具和日志
存储行业数据敏感
内网部署、最小权限、适配器边界、脱敏和可审计
不同客户 SOP 差异过大
可能沦为项目制外包
核心模型标准化,连接器和模板参数化,严格控制定制边界
# 9. 差异化、护城河与品牌表达
## 9.1 可持续护城河
护城河
形成方式
为什么不容易复制
连接器网络
对专业平台、私有 CLI、操作系统和硬件控制器持续适配
单个连接器不难,但长期兼容和组合测试成本高
标准证据模型
把主机、DUT、工具、画面、供电和恢复放入统一时间线
需要深刻理解真实故障流程和客户材料要求
失败指纹库
跨固件、环境、步骤和日志生成稳定签名与聚类
数据来自长期真实回归,不能靠一次模型训练获得
SOP 模板资产
将客户流程抽象为可复用工作流、参数和门禁
越贴近行业,交付越快,客户切换成本越高
带外控制经验
主机状态识别、分级恢复、避免死循环和安全停止
软硬件结合,需要大量异常场景验证
可信度与审计
零误盘、证据完整、可回溯、私有化
存储验证的信任来自长期稳定运行,不只是界面和模型
## 9.2 数据飞轮
更多真实工作流→ 更多结构化运行与失败证据→ 更稳定的失败指纹和恢复策略→ 更快的 SOP 接入与更少误报→ 更高无人值守完成率→ 客户愿意接入更多工位和工具
数据飞轮不能依赖上传客户原始敏感数据。产品应支持在客户本地训练/统计,中心仅同步脱敏的规则、连接器版本和通用失败模式;是否共享由客户明确授权。
## 9.3 品牌与销售语言
建议强调
避免强调
存储实验室数字执行员
万能 AI 存储专家
无人值守完成率和释放工时
测试脚本数量
异常自救和证据闭环
简单任务调度
不替换现有工具,快速叠加
颠覆所有测试平台
工程师保留最终判断
替代验证工程师
私有化、安全、可审计
把所有日志上传云端训练
# 10. 产品路线与未来规划
图 3|路线以里程碑和验收门槛推进,而不是先搭建“大而全”平台。
## 10.1 阶段 A:单工位 POC
交付
进入下一阶段的门槛
一台测试主机、一块允许破坏性测试的 SSD、两个固件版本、一套 SOP
完整跑通 A/B 工作流,所有动作可追溯
Agent、基础工作流、树莓派 Power/Reset、日志和报告
模拟主机失联后能自动恢复
设备身份、安全白名单、Evidence Bundle
误盘为 0;失败证据不因主机崩溃丢失
人工流程对照测量
明确减少了哪些步骤和工时
## 10.2 阶段 B:试点版 Regression Worker
接入客户真实刷写工具、私有日志工具和一到两套回归 SOP。
增加角色权限、模板审批、审计、报告版本和缺陷系统接口。
完善 Windows / Linux 主机故障识别与分级恢复。
建立基础失败指纹、任务对比和工位利用率指标。
形成可重复安装、升级和远程诊断的私有化部署包。
## 10.3 阶段 C:多工位 Storage LabOS
统一资源预约、任务队列、优先级、并发和维护窗口。
主机、DUT、固件、工具和许可证资产管理。
跨工位失败聚类、历史版本关联和实验室健康看板。
连接器 SDK 与客户自助适配框架。
支持接入 OakGate、SANBlaze、Quarch、DriveMaster 等现有资产。
## 10.4 阶段 D:兼容性循环测试柜(方向 4)
在单工位工作流、自救、证据和调度成熟后,增加多主板、多 BIOS、OS、驱动和电源状态矩阵。此时方向 4 不再是一个新项目,而是同一 LabOS 的新资源类型与测试模板。
独立测试节点与带外控制,避免首版设计高速 PCIe 共享背板。
系统镜像恢复、驱动安装、启动前画面采集和兼容性环境指纹。
Pairwise / 风险加权等组合缩减,避免矩阵全排列。
输出固件与主板/BIOS/驱动的兼容性图谱、白名单和最小复现组合。
## 10.5 阶段 E:相邻产品
产品模块
复用能力
新增能力
RMA 返修盘初检
资产、工作流、日志、证据、报告
返修登记、标准复现、初步分流
来料质检工位
多工位调度、设备身份、报告
批量扫码、批次基线、标签/系统接口
工业存储黑匣子
Agent、事件时间线、异常模型
现场长期采集、边缘缓存、远程回传
精确掉电与故障注入
带外控制、工作流、证据
自研电源/信号板或深度仪器集成
实验室运营分析
任务、工位、介入、失败数据
容量规划、利用率和质量趋势
## 10.6 关键决策门
决策门
继续投入的必要证据
从 POC 到试点
真实 SOP 下显著减少人工介入;至少一次异常被自动恢复并形成可用证据
从试点到产品化
客户愿意持续使用并提供第二套 SOP;部署、升级和安全问题可控
从单工位到多工位
客户有明确工位规模和调度痛点,而非为了平台化而平台化
从外购硬件到自研控制板
三个以上客户出现重复硬件需求,且外购成本/集成限制影响销售
进入兼容性测试柜
单工位底座稳定,且有主板/BIOS/OS 矩阵的真实付费需求
# 11. 商业化与首批客户策略
## 11.1 最适合的首批客户
客户特征
为什么适合
中型 SSD、主控、模组或工业存储团队
有真实测试压力,但未必已有完整内部 LabOS
拥有 5-50 个测试工位或大量零散脚本
人工值守和维护成本明显,能量化 ROI
至少有一套固定且高频的固件回归 SOP
容易形成第一条闭环,而不是开放式咨询
愿意提供脱敏报告、正常/异常样品和工程师接口
能验证产品是否真正解决问题
需要私有化部署和现有工具接入
恰好匹配中立控制层定位
首批客户选择
暂不优先挑战已经拥有成熟自研 SLT / LabOS 的头部厂商。先找“已有测试能力,但流程碎片化、夜间仍要人守”的团队。
## 11.2 POC 交付包
一份真实 SOP 的结构化工作流和风险清单。
一台测试主机 Agent 与一套树莓派带外控制。
客户私有刷写工具、I/O 工具和日志工具连接器。
固件 A/B 循环任务、异常自救和 Evidence Bundle。
版本对比报告、人工步骤对照和 ROI 结果。
下一阶段产品化边界与报价依据。
## 11.3 商业模式
收入项
说明
POC / 集成服务
围绕一套 SOP 和一个工位完成连接器、流程和现场验证
软件许可 / 订阅
按工位、并发任务或实验室规模授权,私有化部署
带外控制硬件
标准控制盒、机箱、传感和可选画面采集
高级连接器
专业测试平台、仪器、缺陷系统和私有工具适配
维护与升级
连接器兼容、系统升级、安全补丁和远程支持
后续模块
兼容性柜、RMA、来料质检、运营分析
## 11.4 ROI 计算方式
每月可节省成本 ≈(原每次人工操作分钟 × 每月任务次数 × 工程师综合小时成本)+ 夜间 / 周末值守成本+ 因失败证据不足导致的重复复现时间+ 测试机空转和等待人工恢复的机会成本- 软件与硬件的月度摊销成本
销售时不要只展示“技术很强”,而要对照客户原 SOP 逐步标注哪些动作被删除、哪些异常不再需要人到场、报告提前了多久。
## 11.5 POC 访谈问题
哪一套测试每周重复最多,步骤最固定,最需要人守?
测试主机最常见的中断原因是什么:蓝屏、掉盘、脚本、网络、供电还是环境?
失败后工程师必须收集哪些证据才能建缺陷单?
现有工具有哪些 CLI、API 或可调用入口?哪些只能操作 GUI?
哪些动作可能破坏数据或刷错盘?现有审批与白名单是什么?
一名工程师每周花多少时间在执行、值守、重跑和整理报告上?
哪些问题由于证据不足经常被标记为“未复现”?
# 12. 团队分工、风险与立项动作
## 12.1 初始团队
角色
主要职责
你:产品 / 全栈 / AI
产品定义、控制台、工作流、数据模型、AI 报告、交付体验和项目推进
同学:存储领域 / 客户 / 资金
真实 SOP、测试样品、固件/日志解释、行业关系、首批客户和资源投入
验证工程师(必须)
把真实流程拆成可执行步骤、定义门禁、验收状态和异常分类
嵌入式 / 硬件工程师
带外控制、隔离、可靠供电、看门狗、机箱与后续自研板
后续平台工程师
Agent 稳定性、部署、升级、对象存储、权限和多工位调度
## 12.2 主要风险清单
风险
影响
缓解措施
没有真实 SOP,只按想象开发
做出漂亮但没人使用的通用平台
第一条工作流必须来自同学公司或真实客户
试图一次支持所有工具和接口
项目失控、无法验收
先锁定一个 OS、一个 SSD 类型、一套工具链
只做网页调度,没有异常自救
容易被视为脚本平台或 Jenkins 套壳
带外控制、状态机和证据闭环必须进入 MVP
AI 结论过度承诺
降低工程师信任并带来错误决策
证据引用、置信度、规则优先、人工确认
硬件故障导致误判或损坏 DUT
安全和品牌风险
隔离、保险、白名单、紧急停止和硬件评审
客户定制过多
变成低毛利项目制
插件 SDK、标准模型、可配置模板、定制边界
无法证明节省人工
购买理由不充分
从第一天记录人工介入和原流程基线
## 12.3 立项后的第一批动作
1. 向同学获取一份最耗人的固件回归 SOP、一份脱敏测试报告和两个固件版本。
2. 选定一台独立测试主机和一块允许反复写入/刷写的测试 SSD。
3. 用人工完整执行一次 SOP,逐步记录动作、等待、判断、异常和最终报告。
4. 将流程拆成“专家判断”和“机械执行”,只自动化后者。
5. 先实现设备身份、安全预检、任务状态机和原始证据采集,再做 AI。
6. 接入第一个刷固件工具与第一个 I/O/日志工具。
7. 接入树莓派 Power/Reset,模拟 Agent 失联和主机卡死。
8. 完成固件 A/B 的 100 次循环 Demo,统计人工介入和失败证据。
9. 让真实验证工程师审阅报告,修改 Evidence Bundle 和状态码。
10. 用 Demo 向第二个潜在客户验证是否存在同类 SOP,而不是立即扩功能。
## 12.4 最终立项结论
建议立项
以“固件回归测试值守机器人”为第一产品,底层定位为 Storage LabOS 的单工位执行节点。第一阶段只证明三件事:测试能自动完成、主机异常能自救、失败能自动形成可用证据。完成后再扩展多工位和兼容性测试柜。
# 附录 A|工作流与证据目录示例
## A.1 Evidence Bundle 目录
run-20260727-0001/├── manifest.json├── environment/│ ├── host.json│ ├── dut.json│ ├── firmware.json│ └── tool_versions.json├── timeline/events.ndjson├── device/│ ├── identify.json│ ├── smart-health.json│ ├── error-log.json│ └── telemetry.bin├── host/│ ├── system.log│ ├── kernel.log│ └── device-enumeration.json├── test/│ ├── stdout.log│ ├── stderr.log│ ├── metrics.json│ └── hashes.json├── oob/│ ├── heartbeat.ndjson│ ├── power-actions.ndjson│ ├── temperature.csv│ └── screenshots/├── recovery/recovery.json└── summary/ ├── failure-signature.json ├── ai-summary.md └── issue-draft.md
# 附录 B|失败指纹建议
失败指纹应由稳定、可解释的字段组成,而不是直接使用大模型生成的文本。建议包含:
失败状态码与工作流步骤。
DUT 是否枚举、控制器/命名空间状态和链路信息。
关键错误码、内核事件、工具返回值和日志模板。
固件、主板、BIOS、OS、驱动和电源状态。
失败前后时间窗口内的温度、供电动作和恢复结果。
数据校验是否失败以及失败范围。
将结构化字段归一化后计算签名,再使用聚类或语义模型辅助合并相似问题。工程师可以手动拆分、合并和给簇命名,结果反哺后续规则。
# 附录 C|参考资料与竞品官方来源
以下资料用于核对竞品公开定位与 NVMe 日志能力,访问日期均为 2026 年 7 月 27 日。竞品“缺口”属于基于公开产品定位的战略判断,不代表其不存在非公开或定制功能。
[1] ULINK DriveMaster 10 NVMe
[2] ULINK NVMe Test Suites
[3] ULINK PCIe-SSD Power Adapter Plus Gen4
[4] Teledyne LeCroy OakGate SSD Test and Validation Solutions
[5] SANBlaze SBExpress-RM5
[6] SANBlaze SBExpress-DT5CD
[7] Quarch Data Center & AI Solutions
[8] Quarch PyNVMe / PyNVMe3 Integration
[9] PyNVMe3 User Guide
[10] PyNVMe3 Developers Guide
[11] NVM Express: Error Reporting, SMART, Log Pages and Management
[12] NVM Express: Open Source NVMe CLI
[13] Teledyne LeCroy: OakGate PCIe 6.0 / NVMe Validation Announcement
[14] SANBlaze: Gen6 SSD Testing Announcement
# 附录 D|术语
术语
说明
DUT
Device Under Test,被测设备,本项目主要指 SSD。
OOB / 带外控制
独立于测试主机操作系统的控制路径,用于 Power、Reset、画面、串口和传感。
Evidence Bundle
一次失败或任务的标准化证据包。
UCR
Unattended Completion Rate,无人值守完成率。
Failure Signature
由状态、步骤、日志、环境和设备特征组成的失败指纹。
Regression Worker
Storage LabOS 的第一款单工位固件回归执行模块。
LabOS
面向存储实验室的统一控制平面,管理任务、工位、证据、恢复和智能分析。
SOP
Standard Operating Procedure,企业既有标准操作流程。
— 文档结束 —
Binary file not shown.