<!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 66–67</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>
