← back to Cncp Failures Mockups

public/v5-timeline.html

121 lines

<!doctype html><html><head>
<meta charset="utf-8"><title>v5 — Timeline / APM trace</title>
<style>
  :root { --bg:#06070a; --ink:#e4e7ec; --muted:#7d8696; --grid:#16191f; --lane-bg:#0c0e13; --hi:#ff4757; --med:#ffa726; --lo:#42a5f5; --ok:#26d07c; --accent:#7c3aed; }
  body { margin:0; background:var(--bg); color:var(--ink); font-family: -apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif; padding:18px; min-height:100vh; }
  h1 { font-size:16px; font-weight:600; margin:0 0 2px; letter-spacing:-0.01em; }
  .sub { color:var(--muted); font-size:11px; margin-bottom:18px; }
  .legend { display:flex; gap:18px; font-size:11px; margin-bottom:14px; }
  .legend span { display:inline-block; width:10px; height:10px; border-radius:2px; margin-right:5px; vertical-align:middle; }
  .timeline { background:var(--lane-bg); border:1px solid var(--grid); border-radius:8px; overflow:hidden; }
  .ruler { display:grid; grid-template-columns: 200px 1fr; font-size:10px; color:var(--muted); }
  .ruler .label { padding:8px 12px; text-transform:uppercase; letter-spacing:1px; border-right:1px solid var(--grid); }
  .ruler .ticks { display:grid; grid-template-columns: repeat(24, 1fr); padding:8px 0; border-left:1px solid var(--grid); }
  .ruler .ticks div { text-align:left; padding-left:4px; border-left:1px solid var(--grid); font-variant-numeric:tabular-nums; }
  .lane { display:grid; grid-template-columns: 200px 1fr; border-top:1px solid var(--grid); min-height:48px; align-items:center; }
  .lane .info { padding:10px 12px; border-right:1px solid var(--grid); font-size:12px; }
  .lane .info .nm { font-family:ui-monospace,Menlo,monospace; font-weight:500; word-break:break-all; font-size:11.5px; }
  .lane .info .meta { color:var(--muted); font-size:10px; margin-top:3px; display:flex; gap:6px; align-items:center; }
  .lane .track { position:relative; padding:6px 0; }
  .span { position:absolute; height:22px; top:50%; margin-top:-11px; border-radius:3px; padding:0 8px; display:flex; align-items:center; font-size:10px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; cursor:pointer; transition:transform 0.15s; }
  .span:hover { transform:scaleY(1.15); z-index:5; }
  .span.h{ background:var(--hi); box-shadow: 0 0 12px rgba(255,71,87,0.5); color:#fff; }
  .span.m{ background:var(--med); color:#1c1c1c; }
  .span.l{ background:var(--lo); color:#fff; }
  .span.u{ background:#4a5163; color:#fff; }
  .src-pill { display:inline-block; font-size:9px; padding:1px 4px; border-radius:2px; text-transform:uppercase; letter-spacing:0.5px; font-weight:600; }
  .src-pill.pm2{ background:#1e3a5f; color:#90caf9; }
  .src-pill.launchd{ background:#3a2c5e; color:#bb86fc; }
  .src-pill.agent{ background:#3a4a1e; color:#aed581; }
  .src-pill.build{ background:#5e3a1e; color:#ffcc80; }
  .hermes-pane { margin-top:18px; background:var(--lane-bg); border:1px solid var(--grid); border-radius:8px; padding:14px 16px; }
  .hermes-pane h2 { font-size:13px; font-weight:600; margin:0 0 10px; display:flex; align-items:center; gap:8px; }
  .hermes-pane .row { display:grid; grid-template-columns: 100px 1fr; gap:10px; font-size:12px; padding:4px 0; align-items:center; }
  .hermes-pane .bar { height:6px; border-radius:2px; background:var(--accent); }
  .pulse { width:8px; height:8px; border-radius:50%; background:var(--ok); display:inline-block; box-shadow:0 0 8px var(--ok); animation: pulse 2s infinite; }
  @keyframes pulse { 50% { opacity:0.3; } }
  .trace-row { display:grid; grid-template-columns: 80px 50px 1fr 80px; gap:8px; font-size:11px; padding:3px 0; align-items:center; }
  .trace-bar { background:var(--accent); height:6px; border-radius:2px; opacity:0.6; }
</style>
</head><body>
<h1>Failure Timeline · 24h trace</h1>
<div class="sub">Each lane = one process · spans show event windows · severity colors</div>
<div class="legend">
  <div><span style="background:var(--hi)"></span>high</div>
  <div><span style="background:var(--med)"></span>medium</div>
  <div><span style="background:var(--lo)"></span>low</div>
  <div><span style="background:#4a5163"></span>unclassified</div>
</div>

<div class="timeline">
  <div class="ruler">
    <div class="label">Process</div>
    <div class="ticks" id="ticks"></div>
  </div>
  <div id="lanes"></div>
</div>

<div class="hermes-pane">
  <h2><span class="pulse"></span> Hermes 3 · 8B · in mem (4.3GB)</h2>
  <div id="h-rows"></div>
  <h2 style="margin-top:16px;">Recent Call Trace</h2>
  <div id="h-trace"></div>
</div>

<script>
fetch('/data' + location.search).then(r=>r.json()).then(d => {
  // 24-hour window ending now
  const now = Date.now();
  const span24h = 24 * 3600 * 1000;
  const start = now - span24h;
  // Ticks: every 3 hours
  document.getElementById('ticks').style.gridTemplateColumns = 'repeat(8, 1fr)';
  document.getElementById('ticks').innerHTML = Array.from({length:8}, (_, i) => {
    const t = new Date(start + i*3*3600*1000);
    return `<div>${t.getHours().toString().padStart(2,'0')}:00</div>`;
  }).join('');
  const sevClass = s => s==='high'?'h':s==='medium'?'m':s==='low'?'l':'u';
  document.getElementById('lanes').innerHTML = d.failures.map(f => {
    const created = Math.max(start, f.created);
    const last = Math.min(now, f.lastSeen || f.created);
    const leftPct = 100 * (created - start) / span24h;
    const widthPct = Math.max(2, 100 * (last - created) / span24h);
    return `
      <div class="lane">
        <div class="info">
          <div class="nm">${f.processName}</div>
          <div class="meta"><span class="src-pill ${f.source}">${f.source}</span> ×${f.eventCount} ${f.exitCode!=null?'· exit '+f.exitCode:''}</div>
        </div>
        <div class="track">
          <div class="span ${sevClass(f.severity)}" style="left:${leftPct}%; width:${widthPct}%;" title="${f.suggestedAction || ''}">
            ${f.severity || '—'} · ${(f.lastLines||'').split('\\n')[0].slice(0,40)}
          </div>
        </div>
      </div>`;
  }).join('');

  const h = d.hermes.last24h;
  const sev = h.bySev;
  const max = Math.max(1, sev.high, sev.medium, sev.low);
  document.getElementById('h-rows').innerHTML = `
    <div class="row"><span style="color:var(--muted)">total 24h</span><span><b>${h.total}</b> calls · ${h.byType.classify} cls · ${h.byType.chat} chat · ${h.byType['idea-chat']} idea</span></div>
    <div class="row"><span style="color:var(--muted)">latency</span><span><b>${(h.avgLatencyMs/1000).toFixed(1)}s</b> avg · ${h.fail} failed</span></div>
    <div class="row"><span style="color:var(--muted)">severity</span><span>
      <span style="display:inline-block; vertical-align:middle; margin-right:8px;"><span style="color:var(--hi)">${sev.high} H</span> <div class="bar" style="width:${100*sev.high/max}px; background:var(--hi); display:inline-block; height:8px; vertical-align:middle;"></div></span>
      <span style="display:inline-block; vertical-align:middle; margin-right:8px;"><span style="color:var(--med)">${sev.medium} M</span> <div class="bar" style="width:${100*sev.medium/max}px; background:var(--med); display:inline-block; height:8px; vertical-align:middle;"></div></span>
      <span style="display:inline-block; vertical-align:middle;"><span style="color:var(--lo)">${sev.low} L</span> <div class="bar" style="width:${100*sev.low/max}px; background:var(--lo); display:inline-block; height:8px; vertical-align:middle;"></div></span>
    </span></div>`;
  document.getElementById('h-trace').innerHTML = d.hermes.recent.slice(0,8).map(c => {
    const w = Math.min(100, c.latencyMs/150);
    return `
      <div class="trace-row">
        <span style="color:var(--muted);">${new Date(c.ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}</span>
        <span style="color:${c.ok?'var(--ok)':'var(--hi)'}">${c.ok?'✓':'✗'} ${c.type.slice(0,5)}</span>
        <span style="font-family:ui-monospace,Menlo,monospace; font-size:10.5px;">${c.target.slice(0,46)}</span>
        <span><div class="trace-bar" style="width:${w}px;"></div> <span style="color:var(--muted); font-size:10px;">${(c.latencyMs/1000).toFixed(1)}s</span></span>
      </div>`;
  }).join('');
});
</script>
</body></html>