atlasops / static /index.html
Harikishanth R
fix: skip-kubectl + scroll + health β€” HF Space ready
7e9a520
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>AtlasOps β€” Real-Time SRE Incident Response</title>
<style>
:root {
--bg: #060a12;
--bg2: #0b1120;
--bg3: #111827;
--border: #1a2840;
--border2: #243552;
--accent: #00d4ff;
--accent2: #7b61ff;
--red: #ff4560;
--green: #00e396;
--yellow: #ffb703;
--orange: #ff7b2e;
--text: #c9d6e8;
--textdim: #4a5a78;
--font: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
--glow-c: rgba(0,212,255,0.4);
--glow-r: rgba(255,69,96,0.4);
--glow-g: rgba(0,227,150,0.4);
}
* { margin:0; padding:0; box-sizing:border-box; }
html {
overflow-x: hidden;
overflow-y: auto;
scrollbar-gutter: stable;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: 13px;
overflow-x: hidden;
overflow-y: visible;
}
/* ── Neural network canvas background ── */
#neural-bg {
position: fixed; inset: 0; z-index: 0;
pointer-events: none; opacity: 0.6;
}
/* ── Scanlines overlay ── */
body::after {
content: '';
position: fixed; inset: 0; z-index: 1;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.08) 2px,
rgba(0,0,0,0.08) 4px
);
pointer-events: none;
}
/* ── Top nav ── */
.nav {
position: fixed; top:0; left:0; right:0; z-index:100;
height: 52px;
background: rgba(6,10,18,0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border2);
box-shadow: 0 1px 30px rgba(0,212,255,0.06);
display: flex; align-items: center; justify-content: space-between;
padding: 0 24px;
}
.nav-logo {
display: flex; align-items: center; gap: 10px;
font-size: 15px; font-weight: 700; letter-spacing: 3px;
color: var(--accent);
text-shadow: 0 0 30px rgba(0,212,255,0.7), 0 0 60px rgba(0,212,255,0.3);
}
.nav-logo .dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--green);
box-shadow: 0 0 12px var(--green), 0 0 24px rgba(0,227,150,0.4);
animation: pulse 2s ease-in-out infinite;
}
.nav-tabs { display: flex; gap: 4px; }
.nav-tab {
padding: 6px 16px; border-radius: 6px;
cursor: pointer; border: none;
background: transparent; color: var(--textdim);
font-family: var(--font); font-size: 12px;
transition: all 0.25s; letter-spacing: 1px;
}
.nav-tab:hover { color: var(--text); background: var(--bg3); }
.nav-tab.active {
color: var(--accent); background: rgba(0,212,255,0.06);
border: 1px solid rgba(0,212,255,0.25);
box-shadow: 0 0 12px rgba(0,212,255,0.1);
}
.nav-status { display: flex; align-items: center; gap: 16px; font-size: 11px; color: var(--textdim); }
.status-pill {
display: flex; align-items: center; gap: 6px;
padding: 4px 12px; border-radius: 20px;
background: rgba(0,227,150,0.07);
border: 1px solid rgba(0,227,150,0.25);
color: var(--green);
box-shadow: 0 0 8px rgba(0,227,150,0.08);
}
/* ── Main layout (document scrolls β€” avoids HF iframe clipping) ── */
.main {
padding-top: 52px;
min-height: calc(100vh - 52px);
overflow-x: hidden;
overflow-y: visible;
display: grid;
grid-template-columns: 340px 1fr 400px;
grid-template-rows: auto auto;
gap: 0;
align-content: start;
position: relative; z-index: 2;
padding-bottom: 32px;
}
@media (max-width: 1180px) {
.main {
grid-template-columns: 1fr;
}
.panel { border-right: none; border-bottom: 1px solid var(--border); }
.center-panel { border-right: none; border-bottom: 1px solid var(--border); }
}
/* ── Panel base (glassmorphism) ── */
.panel {
border-right: 1px solid var(--border);
display: flex; flex-direction: column;
position: relative;
overflow-x: hidden;
overflow-y: visible;
background: rgba(11,17,32,0.7);
backdrop-filter: blur(8px);
min-height: 0;
}
.panel-header {
padding: 14px 16px 12px;
border-bottom: 1px solid var(--border);
background: rgba(6,10,18,0.8);
display: flex; align-items: center; justify-content: space-between;
flex-shrink: 0;
}
.panel-title {
font-size: 10px; font-weight: 700;
letter-spacing: 2.5px; text-transform: uppercase;
color: var(--textdim);
}
.panel-body { padding: 16px; overflow-y: visible; flex: 0 0 auto; }
/* ── Incident card ── */
.incident-card {
border-radius: 10px; overflow: hidden;
border: 1px solid var(--border);
background: var(--bg2);
margin-bottom: 12px;
transition: all 0.4s cubic-bezier(0.4,0,0.2,1);
animation: slideDown 0.4s cubic-bezier(0.4,0,0.2,1);
position: relative;
}
.incident-card::before {
content: ''; position: absolute;
inset: 0; border-radius: 10px;
background: linear-gradient(135deg, transparent 40%, rgba(255,255,255,0.02) 100%);
pointer-events: none;
}
.incident-card.active {
border-color: var(--red);
box-shadow: 0 0 25px rgba(255,69,96,0.2), inset 0 0 25px rgba(255,69,96,0.04);
animation: slideDown 0.4s cubic-bezier(0.4,0,0.2,1), incidentPulse 3s ease-in-out infinite;
}
.incident-card.resolved {
border-color: var(--green);
box-shadow: 0 0 25px rgba(0,227,150,0.2);
}
.incident-header { padding: 12px 14px; display: flex; align-items: center; gap: 10px; }
.severity-badge {
padding: 2px 9px; border-radius: 4px;
font-size: 10px; font-weight: 800; letter-spacing: 1.5px;
}
.severity-p0 { background: rgba(255,69,96,0.15); color: var(--red); border: 1px solid rgba(255,69,96,0.5); box-shadow: 0 0 8px rgba(255,69,96,0.3); }
.severity-p1 { background: rgba(255,183,3,0.15); color: var(--yellow); border: 1px solid rgba(255,183,3,0.5); }
.severity-p2 { background: rgba(0,212,255,0.08); color: var(--accent); border: 1px solid rgba(0,212,255,0.4); }
.incident-title { font-size: 12px; font-weight: 600; flex: 1; }
.incident-body { padding: 0 14px 14px; }
.incident-meta { display: flex; gap: 6px; flex-wrap: wrap; }
.meta-tag {
padding: 2px 8px; border-radius: 4px;
font-size: 10px; background: rgba(0,212,255,0.06);
border: 1px solid var(--border); color: var(--textdim);
}
/* ── Scenario buttons ── */
.scenario-section { margin-bottom: 20px; }
.section-label {
font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
color: var(--textdim); margin-bottom: 10px;
display: flex; align-items: center; gap: 8px;
}
.section-label::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.scenario-btn {
width: 100%; padding: 10px 12px;
background: rgba(20,28,46,0.8);
border: 1px solid var(--border);
border-radius: 8px; color: var(--text);
font-family: var(--font); font-size: 11px;
cursor: pointer; text-align: left;
display: flex; align-items: center; gap: 10px;
margin-bottom: 6px;
transition: all 0.2s cubic-bezier(0.4,0,0.2,1);
position: relative; overflow: hidden;
}
.scenario-btn::before {
content: ''; position: absolute;
top:0; left:-100%; width:100%; height:100%;
background: linear-gradient(90deg, transparent, rgba(0,212,255,0.07), transparent);
transition: left 0.6s ease;
}
.scenario-btn:hover::before { left: 100%; }
.scenario-btn:hover {
border-color: rgba(0,212,255,0.5);
background: rgba(0,212,255,0.06);
transform: translateX(3px);
box-shadow: -3px 0 0 var(--accent), 0 0 12px rgba(0,212,255,0.08);
}
.scenario-btn:active { transform: translateX(3px) scale(0.99); }
.scenario-btn.firing {
border-color: var(--red);
animation: firePulse 0.9s ease-in-out infinite alternate;
}
.scenario-icon { font-size: 16px; flex-shrink: 0; }
.scenario-info { flex: 1; }
.scenario-name { font-weight: 600; font-size: 11px; }
.scenario-sub { font-size: 10px; color: var(--textdim); margin-top: 1px; }
.scenario-tier {
font-size: 9px; padding: 1px 6px; border-radius: 3px;
background: var(--bg); border: 1px solid var(--border);
color: var(--textdim); flex-shrink: 0;
}
.scenario-tier.adv { color: var(--orange); border-color: rgba(255,123,46,0.45); letter-spacing: 1px; font-weight: 700; }
.btn-reset {
width: 100%; padding: 8px;
background: rgba(255,69,96,0.07);
border: 1px solid rgba(255,69,96,0.3);
border-radius: 8px; color: var(--red);
font-family: var(--font); font-size: 11px;
cursor: pointer; letter-spacing: 1px;
transition: all 0.2s; margin-top: 8px;
}
.btn-reset:hover { background: rgba(255,69,96,0.14); box-shadow: 0 0 12px rgba(255,69,96,0.15); }
/* ── Middle: Grafana + topology ── */
.center-panel {
border-right: 1px solid var(--border);
display: flex; flex-direction: column;
background: rgba(11,17,32,0.7);
overflow: visible;
min-height: 0;
}
.grafana-wrap {
position: relative; flex: 0 0 auto; min-height: 300px;
background: var(--bg2);
}
.grafana-wrap iframe { width: 100%; height: 100%; border: none; display: block; }
.grafana-overlay {
position: absolute; inset: 0; pointer-events: none;
border: 2px solid transparent;
transition: border-color 0.5s, box-shadow 0.5s;
}
.grafana-overlay.alert {
border-color: var(--red);
box-shadow: inset 0 0 40px rgba(255,69,96,0.12), 0 0 40px rgba(255,69,96,0.2);
}
.grafana-overlay.resolved {
border-color: var(--green);
box-shadow: inset 0 0 30px rgba(0,227,150,0.08);
}
/* ── SVG Service Topology ── */
.topology-section {
border-top: 1px solid var(--border);
background: rgba(6,10,18,0.95);
padding: 10px 14px 12px;
flex-shrink: 0;
position: relative;
z-index: 10;
}
.topology-header {
font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
color: var(--textdim); margin-bottom: 6px;
display: flex; align-items: center; justify-content: space-between;
}
.topo-svg {
width: 100%; height: 220px;
overflow: visible;
position: relative;
z-index: 10;
}
/* SVG node states */
.topo-node circle {
stroke-width: 2;
transition: all 0.5s ease;
filter: none;
}
.topo-node text {
font-family: var(--font);
fill: var(--textdim);
font-size: 8px;
text-anchor: middle;
pointer-events: none;
transition: fill 0.4s;
}
.topo-node.s-healthy circle { stroke: var(--green); fill: rgba(0,227,150,0.08); filter: drop-shadow(0 0 4px rgba(0,227,150,0.4)); }
.topo-node.s-healthy text { fill: var(--green); }
.topo-node.s-degraded circle { stroke: var(--yellow); fill: rgba(255,183,3,0.08); animation: nodePulse 1.5s ease-in-out infinite; }
.topo-node.s-degraded text { fill: var(--yellow); }
.topo-node.s-down circle { stroke: var(--red); fill: rgba(255,69,96,0.12); animation: nodePulse 0.8s ease-in-out infinite; filter: drop-shadow(0 0 6px rgba(255,69,96,0.6)); }
.topo-node.s-down text { fill: var(--red); }
.topo-node.s-unknown circle { stroke: var(--textdim); fill: rgba(90,106,136,0.06); }
/* SVG edges */
.topo-edge {
stroke: var(--border2);
stroke-width: 1.2;
fill: none;
transition: stroke 0.5s, stroke-width 0.5s;
stroke-dasharray: 4 4;
animation: dashFlow 8s linear infinite;
}
.topo-edge.e-down { stroke: var(--red); stroke-width: 2; animation: dashFlow 1s linear infinite; filter: drop-shadow(0 0 3px rgba(255,69,96,0.5)); }
.topo-edge.e-degraded { stroke: var(--yellow); stroke-width: 1.5; animation: dashFlow 2s linear infinite; }
.topo-edge.e-healthy { stroke: rgba(0,227,150,0.4); stroke-width: 1.2; animation: dashFlow 4s linear infinite; }
@keyframes dashFlow {
from { stroke-dashoffset: 24; }
to { stroke-dashoffset: 0; }
}
@keyframes nodePulse {
0%,100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ── MTTR counter ── */
.mttr-display {
padding: 12px 16px;
background: rgba(6,10,18,0.9);
border-top: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
}
.mttr-label { font-size: 10px; letter-spacing: 2px; color: var(--textdim); text-transform: uppercase; }
.mttr-value {
font-size: 24px; font-weight: 700;
font-variant-numeric: tabular-nums;
color: var(--accent);
text-shadow: 0 0 20px rgba(0,212,255,0.5);
transition: color 0.5s, text-shadow 0.5s;
letter-spacing: 2px;
}
.mttr-value.resolved { color: var(--green); text-shadow: 0 0 25px rgba(0,227,150,0.6); }
.mttr-value.alerting { color: var(--red); text-shadow: 0 0 20px rgba(255,69,96,0.6); animation: countPulse 1s ease-in-out infinite; }
/* ── Agent timeline ── */
.timeline-panel {
display: flex; flex-direction: column;
background: rgba(11,17,32,0.7);
min-height: 0;
overflow-x: hidden;
overflow-y: visible;
}
.timeline-body {
flex: 1 1 auto;
overflow-y: auto;
max-height: min(480px, 55vh);
padding: 12px;
display: flex; flex-direction: column; gap: 8px;
}
.thought-entry {
padding: 11px 14px; border-radius: 10px;
border-left: 3px solid transparent;
background: rgba(15,21,33,0.8);
animation: thoughtIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
display: flex; gap: 10px; align-items: flex-start;
transition: background 0.2s;
position: relative;
}
.thought-entry::after {
content: '';
position: absolute; top:0; left:0; right:0; height: 1px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
}
.thought-entry:hover { background: rgba(20,28,46,0.9); }
.thought-triage { border-left-color: var(--red); }
.thought-diagnosis { border-left-color: var(--accent2); }
.thought-remediation{ border-left-color: var(--yellow); }
.thought-comms { border-left-color: var(--green); }
.thought-icon { font-size: 15px; flex-shrink: 0; margin-top: 1px; }
.thought-content { flex: 1; }
.thought-role {
font-size: 10px; font-weight: 800; letter-spacing: 2.5px;
text-transform: uppercase; margin-bottom: 3px;
}
.thought-triage .thought-role { color: var(--red); }
.thought-diagnosis .thought-role { color: var(--accent2); }
.thought-remediation .thought-role { color: var(--yellow); }
.thought-comms .thought-role { color: var(--green); }
.thought-text { font-size: 12px; line-height: 1.6; color: var(--text); }
.thought-tool {
display: inline-block; margin-top: 3px;
padding: 1px 7px; border-radius: 3px;
font-size: 9px; background: rgba(0,212,255,0.07);
border: 1px solid rgba(0,212,255,0.18); color: var(--accent);
font-weight: 600; letter-spacing: 0.5px;
}
.phase-conclusion .thought-text { font-weight: 600; }
.phase-conclusion { background: rgba(0,0,0,0.4); }
/* ── Status bar ── */
.statusbar {
grid-column: 1/-1;
min-height: 28px;
height: auto;
flex-wrap: wrap;
row-gap: 6px;
padding-top: 6px;
padding-bottom: 6px;
background: rgba(6,10,18,0.95);
border-top: 1px solid var(--border);
display: flex; align-items: center;
padding-left: 16px;
padding-right: 16px;
gap: 16px;
font-size: 10px; color: var(--textdim);
position: relative; z-index: 2;
}
.statusbar-item { display: flex; align-items: center; gap: 6px; }
.statusbar-item .dot { width: 6px; height: 6px; border-radius: 50%; }
.dot-green { background: var(--green); box-shadow: 0 0 6px var(--green); }
.dot-red { background: var(--red); box-shadow: 0 0 6px var(--red); }
.dot-dim { background: var(--textdim); }
/* ── Empty state ── */
.empty-state {
display: flex; flex-direction: column; align-items: center;
justify-content: center; height: 180px; gap: 10px;
color: var(--textdim); text-align: center;
}
.empty-state .icon { font-size: 28px; opacity: 0.2; }
.empty-state p { font-size: 11px; line-height: 1.6; }
/* ── Comms feed ── */
.comms-section {
border-top: 1px solid var(--border);
background: rgba(6,10,18,0.85);
flex-shrink: 0; height: 210px;
display: flex; flex-direction: column;
}
.comms-header {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
flex-shrink: 0;
}
.comms-body { flex: 1; overflow-y: auto; padding: 7px 8px; display: flex; flex-direction: column; gap: 5px; }
.slack-post {
background: rgba(17,24,39,0.9); border-radius: 6px;
border: 1px solid var(--border);
border-left: 3px solid var(--green);
padding: 7px 9px;
animation: slideDown 0.3s cubic-bezier(0.4,0,0.2,1);
}
.slack-post.sev-p0 { border-left-color: var(--red); }
.slack-post.sev-p1 { border-left-color: var(--yellow); }
.slack-post.sev-p2 { border-left-color: var(--accent); }
.slack-meta { display: flex; align-items: center; gap: 7px; margin-bottom: 3px; }
.slack-bot { font-size: 10px; font-weight: 700; color: var(--accent); }
.slack-time { font-size: 9px; color: var(--textdim); }
.slack-title { font-size: 11px; font-weight: 600; color: var(--text); margin-bottom: 3px; }
.slack-text { font-size: 10px; color: var(--textdim); line-height: 1.4; }
.slack-actions { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; }
.slack-action-chip {
font-size: 9px; padding: 1px 6px; border-radius: 3px;
background: rgba(0,212,255,0.06); border: 1px solid rgba(0,212,255,0.18); color: var(--accent);
}
.comms-empty { color: var(--textdim); font-size: 10px; text-align: center; padding: 24px 0; }
/* ── Particle canvas (resolution burst) ── */
#particle-canvas {
position: fixed; inset: 0; z-index: 50;
pointer-events: none; display: none;
}
/* ── RESOLVED stamp ── */
#resolve-stamp {
display: none;
position: fixed; top: 50%; left: 50%;
transform: translate(-50%,-50%) rotate(-12deg) scale(0);
z-index: 60; pointer-events: none;
font-size: 48px; font-weight: 900; letter-spacing: 6px;
color: var(--green);
border: 5px solid var(--green);
padding: 12px 28px; border-radius: 4px;
text-shadow: 0 0 40px rgba(0,227,150,0.8);
box-shadow: 0 0 60px rgba(0,227,150,0.4);
background: rgba(0,227,150,0.05);
font-family: var(--font);
transition: transform 0.4s cubic-bezier(0.34,1.56,0.64,1), opacity 0.4s;
opacity: 0;
}
#resolve-stamp.show {
display: block;
transform: translate(-50%,-50%) rotate(-12deg) scale(1);
opacity: 1;
}
/* ── Bench table ── */
.bench-table { width:100%; border-collapse:collapse; font-size:11px; }
.bench-table th {
padding: 8px 10px; text-align:left;
font-size:10px; letter-spacing:2px; text-transform:uppercase;
color: var(--textdim); border-bottom: 1px solid var(--border);
}
.bench-table td { padding: 8px 10px; border-bottom: 1px solid rgba(26,40,64,0.5); }
.bench-table tr:hover td { background: var(--bg3); }
.bench-table .highlight td { color: var(--accent); font-weight:700; }
.delta-pos { color: var(--green); font-weight: 700; }
.delta-neg { color: var(--red); }
/* ── Tab panels ── */
.tab-panel { display: none; }
.tab-panel.active { display: flex; flex-direction: column; flex: 1; }
/* ── Keyframes ── */
@keyframes pulse {
0%,100% { opacity:1; transform:scale(1); }
50% { opacity:0.5; transform:scale(1.4); }
}
@keyframes slideDown {
from { opacity:0; transform:translateY(-10px); }
to { opacity:1; transform:translateY(0); }
}
@keyframes thoughtIn {
from { opacity:0; transform:translateX(-12px) scale(0.97); }
to { opacity:1; transform:translateX(0) scale(1); }
}
@keyframes firePulse {
from { box-shadow: 0 0 8px rgba(255,69,96,0.15); }
to { box-shadow: 0 0 24px rgba(255,69,96,0.55); border-color: var(--red); }
}
@keyframes countPulse {
0%,100% { opacity:1; }
50% { opacity:0.55; }
}
@keyframes incidentPulse {
0%,100% { box-shadow: 0 0 15px rgba(255,69,96,0.15), inset 0 0 15px rgba(255,69,96,0.03); }
50% { box-shadow: 0 0 30px rgba(255,69,96,0.3), inset 0 0 25px rgba(255,69,96,0.06); }
}
@keyframes resolveFlash {
0% { background: rgba(0,227,150,0.18); }
100% { background: transparent; }
}
.resolve-flash { animation: resolveFlash 1.8s ease-out forwards; }
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--textdim); }
</style>
</head>
<body>
<!-- ── Neural network background ── -->
<canvas id="neural-bg"></canvas>
<!-- ── Particle burst canvas ── -->
<canvas id="particle-canvas"></canvas>
<!-- ── Resolution stamp ── -->
<div id="resolve-stamp">RESOLVED</div>
<!-- ── Nav ── -->
<nav class="nav">
<div class="nav-logo">
<div class="dot"></div>
ATLASOPS
</div>
<div class="nav-tabs">
<button class="nav-tab active" onclick="showTab('ops',event)">Live Ops</button>
<button class="nav-tab" onclick="showTab('bench',event)">Benchmark</button>
<button class="nav-tab" onclick="showTab('about',event)">About</button>
</div>
<div class="nav-status">
<div class="status-pill">
<div style="width:6px;height:6px;border-radius:50%;background:var(--green);animation:pulse 2s infinite"></div>
GKE us-central1
</div>
<span id="cluster-pods" style="font-size:10px">β€” pods</span>
<div style="display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:20px;background:rgba(255,69,96,0.07);border:1px solid rgba(255,69,96,0.25)">
<span style="font-size:9px;letter-spacing:1px;color:#ff4560;font-weight:700">⚑ AMD MI300X</span>
<span style="font-size:9px;color:var(--textdim)">192GB HBM3</span>
</div>
</div>
</nav>
<!-- ── Live Ops Tab ── -->
<div id="tab-ops" class="tab-panel active">
<div class="main">
<!-- Left: scenarios + incident -->
<div class="panel" style="border-right:1px solid var(--border)">
<div class="panel-header">
<span class="panel-title">Chaos Scenarios</span>
<span style="font-size:10px;color:var(--textdim)" id="scenario-status">Ready</span>
</div>
<div class="panel-body">
<div class="scenario-section">
<div class="section-label">Historical Replays</div>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-cloudflare-2019" onclick="injectChaos(this,'named_replays/hist-cloudflare-2019','Cloudflare 2019')">
<span class="scenario-icon">🌩</span>
<div class="scenario-info"><div class="scenario-name">Cloudflare 2019</div><div class="scenario-sub">Regex CPU storm β†’ 85% traffic down</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-github-2018" onclick="injectChaos(this,'named_replays/hist-github-2018','GitHub 2018')">
<span class="scenario-icon">πŸ”„</span>
<div class="scenario-info"><div class="scenario-name">GitHub 2018</div><div class="scenario-sub">DB failover loop Β· 24h incident</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-discord-2022" onclick="injectChaos(this,'named_replays/hist-discord-2022','Discord 2022')">
<span class="scenario-icon">⚑</span>
<div class="scenario-info"><div class="scenario-name">Discord 2022</div><div class="scenario-sub">Cache thundering herd</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-aws-s3-2017" onclick="injectChaos(this,'named_replays/hist-aws-s3-2017','AWS S3 2017')">
<span class="scenario-icon">πŸͺ£</span>
<div class="scenario-info"><div class="scenario-name">AWS S3 2017</div><div class="scenario-sub">Regional outage Β· cascading SDK retries</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-azure-dns-2019" onclick="injectChaos(this,'named_replays/hist-azure-dns-2019','Azure DNS 2019')">
<span class="scenario-icon">πŸ”·</span>
<div class="scenario-info"><div class="scenario-name">Azure DNS 2019</div><div class="scenario-sub">Resolver brownout Β· cross-region</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-fastly-2021" onclick="injectChaos(this,'named_replays/hist-fastly-2021','Fastly 2021')">
<span class="scenario-icon">πŸ”Œ</span>
<div class="scenario-info"><div class="scenario-name">Fastly 2021</div><div class="scenario-sub">Bad deploy / edge config</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-facebook-bgp-2021" onclick="injectChaos(this,'named_replays/hist-facebook-bgp-2021','Facebook BGP 2021')">
<span class="scenario-icon">πŸ“‘</span>
<div class="scenario-info"><div class="scenario-name">Facebook BGP 2021</div><div class="scenario-sub">Control plane partition</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-datadog-2023" onclick="injectChaos(this,'named_replays/hist-datadog-2023','Datadog 2023')">
<span class="scenario-icon">πŸ•</span>
<div class="scenario-info"><div class="scenario-name">Datadog 2023</div><div class="scenario-sub">Ingest backlog / pipeline pressure</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-slack-2022" onclick="injectChaos(this,'named_replays/hist-slack-2022','Slack 2022')">
<span class="scenario-icon">πŸ’¬</span>
<div class="scenario-info"><div class="scenario-name">Slack 2022</div><div class="scenario-sub">WebSocket mesh Β· multi-cell</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="named_replays/hist-knight-capital-2012" onclick="injectChaos(this,'named_replays/hist-knight-capital-2012','Knight Capital 2012')">
<span class="scenario-icon">πŸ’°</span>
<div class="scenario-info"><div class="scenario-name">Knight Capital 2012</div><div class="scenario-sub">Partial deploy mismatch</div></div>
<span class="scenario-tier">expert</span>
</button>
</div>
<div class="scenario-section">
<div class="section-label">Single Fault</div>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-001" onclick="injectChaos(this,'single_fault/sf-001','Pod Kill')">
<span class="scenario-icon">πŸ’€</span>
<div class="scenario-info"><div class="scenario-name">Pod Kill</div><div class="scenario-sub">cartservice OOMKill</div></div>
<span class="scenario-tier">beginner</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-002" onclick="injectChaos(this,'single_fault/sf-002','CPU Hog')">
<span class="scenario-icon">πŸ”₯</span>
<div class="scenario-info"><div class="scenario-name">CPU Hog</div><div class="scenario-sub">paymentservice runaway</div></div>
<span class="scenario-tier">beginner</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-003" onclick="injectChaos(this,'single_fault/sf-003','Checkout mem stress')">
<span class="scenario-icon">🧠</span>
<div class="scenario-info"><div class="scenario-name">Checkout mem stress</div><div class="scenario-sub">Memory pressure Β· checkoutservice</div></div>
<span class="scenario-tier">medium</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-004" onclick="injectChaos(this,'single_fault/sf-004','Frontend packet loss')">
<span class="scenario-icon">πŸ“‰</span>
<div class="scenario-info"><div class="scenario-name">Frontend packet loss</div><div class="scenario-sub">50% loss ingress path</div></div>
<span class="scenario-tier">medium</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-005" onclick="injectChaos(this,'single_fault/sf-005','Redis ↔ cart partition')">
<span class="scenario-icon">πŸ”—</span>
<div class="scenario-info"><div class="scenario-name">Redis ↔ cart partition</div><div class="scenario-sub">Network split data path</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-006" onclick="injectChaos(this,'single_fault/sf-006','Payment DNS chaos')">
<span class="scenario-icon">🌐</span>
<div class="scenario-info"><div class="scenario-name">Payment DNS chaos</div><div class="scenario-sub">Random DNS Β· paymentservice calls</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-007" onclick="injectChaos(this,'single_fault/sf-007','Email disk fault')">
<span class="scenario-icon">πŸ’Ύ</span>
<div class="scenario-info"><div class="scenario-name">Email disk fault</div><div class="scenario-sub">ENOSPC on emailservice logs</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="single_fault/sf-008" onclick="injectChaos(this,'single_fault/sf-008','Clock Skew')">
<span class="scenario-icon">⏰</span>
<div class="scenario-info"><div class="scenario-name">Clock Skew</div><div class="scenario-sub">JWT expiry failures</div></div>
<span class="scenario-tier">medium</span>
</button>
</div>
<div class="scenario-section">
<div class="section-label">Cascade</div>
<button type="button" class="scenario-btn" data-scenario="cascade/cs-001" onclick="injectChaos(this,'cascade/cs-001','Currency β†’ Checkout')">
<span class="scenario-icon">🌊</span>
<div class="scenario-info"><div class="scenario-name">Currency β†’ Checkout</div><div class="scenario-sub">Latency cascade Β· 5xx surge</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="cascade/cs-002" onclick="injectChaos(this,'cascade/cs-002','Redis partition cascade')">
<span class="scenario-icon">🧱</span>
<div class="scenario-info"><div class="scenario-name">Redis partition cascade</div><div class="scenario-sub">Cart errors β†’ checkout revenue path</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="cascade/cs-003" onclick="injectChaos(this,'cascade/cs-003','Rec CPU β†’ FE latency')">
<span class="scenario-icon">πŸ“ˆ</span>
<div class="scenario-info"><div class="scenario-name">Rec CPU β†’ FE latency</div><div class="scenario-sub">Recommendationservice CPU hog</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="cascade/cs-004" onclick="injectChaos(this,'cascade/cs-004','Email disk β†’ checkout')">
<span class="scenario-icon">βœ‰οΈ</span>
<div class="scenario-info"><div class="scenario-name">Email disk β†’ checkout</div><div class="scenario-sub">Notification backlog timeout</div></div>
<span class="scenario-tier">hard</span>
</button>
<button type="button" class="scenario-btn" data-scenario="cascade/cs-005" onclick="injectChaos(this,'cascade/cs-005','Cloud SQL exhaustion')">
<span class="scenario-icon">πŸ—„</span>
<div class="scenario-info"><div class="scenario-name">Cloud SQL exhaustion</div><div class="scenario-sub">DB pool β†’ cart 500s</div></div>
<span class="scenario-tier">hard</span>
</button>
</div>
<div class="scenario-section">
<div class="section-label">Multi-fault</div>
<button type="button" class="scenario-btn" data-scenario="multi_fault/mf-001" onclick="injectChaos(this,'multi_fault/mf-001','FE loss + checkout CPU')">
<span class="scenario-icon">⚑</span>
<div class="scenario-info"><div class="scenario-name">FE loss + checkout CPU</div><div class="scenario-sub">mf-001 Β· overlapping faults</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="multi_fault/mf-002" onclick="injectChaos(this,'multi_fault/mf-002','Redis part + rec mem')">
<span class="scenario-icon">⚑</span>
<div class="scenario-info"><div class="scenario-name">Redis part + rec mem</div><div class="scenario-sub">mf-002</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="multi_fault/mf-003" onclick="injectChaos(this,'multi_fault/mf-003','DNS + currency latency')">
<span class="scenario-icon">⚑</span>
<div class="scenario-info"><div class="scenario-name">DNS + currency latency</div><div class="scenario-sub">mf-003</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="multi_fault/mf-004" onclick="injectChaos(this,'multi_fault/mf-004','Clock skew + cart loss')">
<span class="scenario-icon">⚑</span>
<div class="scenario-info"><div class="scenario-name">Clock skew + cart loss</div><div class="scenario-sub">mf-004</div></div>
<span class="scenario-tier">expert</span>
</button>
<button type="button" class="scenario-btn" data-scenario="multi_fault/mf-005" onclick="injectChaos(this,'multi_fault/mf-005','Email IO + checkout delay')">
<span class="scenario-icon">⚑</span>
<div class="scenario-info"><div class="scenario-name">Email IO + checkout delay</div><div class="scenario-sub">mf-005</div></div>
<span class="scenario-tier">expert</span>
</button>
</div>
<div class="scenario-section">
<div class="section-label">Adversarial</div>
<button type="button" class="scenario-btn" data-scenario="adversarial/adv-001" onclick="injectChaos(this,'adversarial/adv-001','Adv-001 rec kill + catalog CPU')">
<span class="scenario-icon">🎯</span>
<div class="scenario-info"><div class="scenario-name">Adv-001 rec kill + catalog CPU</div><div class="scenario-sub">Overlapping blast radius</div></div>
<span class="scenario-tier adv">ADV</span>
</button>
</div>
<button class="btn-reset" onclick="resetChaos()">⏹ Reset All Chaos</button>
<div id="incident-container" style="margin-top:16px"></div>
</div>
</div>
<!-- Center: Live Prometheus metrics + topology -->
<div class="center-panel">
<div class="panel-header">
<span class="panel-title">Live Cluster Metrics</span>
<div style="display:flex;align-items:center;gap:10px">
<span id="prom-status" style="font-size:9px;color:var(--textdim)">connecting…</span>
<a href="#" id="grafana-link" target="_blank" style="font-size:10px;color:var(--accent);text-decoration:none;opacity:0.7">Grafana β†—</a>
</div>
</div>
<div class="grafana-wrap" id="grafana-wrap" style="display:flex;flex-direction:column;padding:14px;gap:12px;background:var(--bg2);min-height:280px;overflow:visible">
<!-- Metric cards row -->
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;flex-shrink:0">
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:8px;padding:12px">
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);text-transform:uppercase;margin-bottom:6px">Error Rate</div>
<div id="metric-error-rate" style="font-size:22px;font-weight:700;color:var(--red);font-variant-numeric:tabular-nums;text-shadow:0 0 15px rgba(255,69,96,0.5)">β€”</div>
<div style="font-size:9px;color:var(--textdim);margin-top:2px">5xx / total req</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:8px;padding:12px">
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);text-transform:uppercase;margin-bottom:6px">CPU Usage</div>
<div id="metric-cpu" style="font-size:22px;font-weight:700;color:var(--accent);font-variant-numeric:tabular-nums;text-shadow:0 0 15px rgba(0,212,255,0.4)">β€”</div>
<div style="font-size:9px;color:var(--textdim);margin-top:2px">cores (default ns)</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:8px;padding:12px">
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);text-transform:uppercase;margin-bottom:6px">Active Alerts</div>
<div id="metric-alerts" style="font-size:22px;font-weight:700;color:var(--yellow);font-variant-numeric:tabular-nums;text-shadow:0 0 15px rgba(255,183,3,0.4)">β€”</div>
<div style="font-size:9px;color:var(--textdim);margin-top:2px">Alertmanager</div>
</div>
</div>
<!-- Sparkline charts -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:8px;padding:10px;display:flex;flex-direction:column">
<div style="font-size:9px;letter-spacing:1.5px;color:var(--textdim);text-transform:uppercase;margin-bottom:6px">Request Rate (req/s)</div>
<canvas id="spark-rps" style="width:100%;height:88px;display:block"></canvas>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:8px;padding:10px;display:flex;flex-direction:column">
<div style="font-size:9px;letter-spacing:1.5px;color:var(--textdim);text-transform:uppercase;margin-bottom:6px">CPU Cores (default ns)</div>
<canvas id="spark-cpu" style="width:100%;height:88px;display:block"></canvas>
</div>
</div>
<div id="metric-poll-hint" style="font-size:9px;color:var(--textdim);text-align:center;line-height:1.4;padding:4px 6px 0">β€”</div>
<div class="grafana-overlay" id="grafana-overlay" style="pointer-events:none;position:absolute;inset:0;border:2px solid transparent;transition:border-color 0.5s,box-shadow 0.5s;border-radius:0"></div>
</div>
<!-- SVG Dependency Graph -->
<div class="topology-section">
<div class="topology-header">
<span>Service Topology</span>
<span id="topology-status" style="font-size:9px;color:var(--textdim)">connecting…</span>
</div>
<svg class="topo-svg" viewBox="0 0 640 200" preserveAspectRatio="xMidYMid meet">
<defs>
<marker id="arr" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
<path d="M0,0 L0,6 L6,3 z" fill="var(--border2)"/>
</marker>
</defs>
<!-- Edges (behind nodes) -->
<line class="topo-edge" id="edge-fe-ad" x1="313" y1="50" x2="100" y2="94" data-to="adservice"/>
<line class="topo-edge" id="edge-fe-cart" x1="308" y1="52" x2="195" y2="96" data-to="cartservice"/>
<line class="topo-edge" id="edge-fe-chk" x1="320" y1="54" x2="320" y2="94" data-to="checkoutservice"/>
<line class="topo-edge" id="edge-fe-cat" x1="332" y1="52" x2="445" y2="96" data-to="productcatalogservice"/>
<line class="topo-edge" id="edge-fe-rec" x1="327" y1="50" x2="540" y2="94" data-to="recommendationservice"/>
<line class="topo-edge" id="edge-cart-redis" x1="185" y1="120" x2="105" y2="164" data-to="redis-cart"/>
<line class="topo-edge" id="edge-chk-pay" x1="306" y1="122" x2="210" y2="164" data-to="paymentservice"/>
<line class="topo-edge" id="edge-chk-curr" x1="320" y1="122" x2="320" y2="164" data-to="currencyservice"/>
<line class="topo-edge" id="edge-chk-ship" x1="334" y1="122" x2="425" y2="164" data-to="shippingservice"/>
<line class="topo-edge" id="edge-chk-email" x1="340" y1="120" x2="530" y2="164" data-to="emailservice"/>
<!-- Tier 1: frontend -->
<g class="topo-node s-unknown" id="svc-frontend">
<circle cx="320" cy="32" r="24"/>
<text x="320" y="28" style="font-size:11px;font-weight:800">FE</text>
<text x="320" y="42" style="font-size:9px">frontend</text>
</g>
<!-- Tier 2 -->
<g class="topo-node s-unknown" id="svc-adservice">
<circle cx="72" cy="107" r="18"/>
<text x="72" y="103" style="font-size:10px;font-weight:700">ADS</text>
<text x="72" y="115" style="font-size:8px">adservice</text>
</g>
<g class="topo-node s-unknown" id="svc-cartservice">
<circle cx="192" cy="108" r="20"/>
<text x="192" y="104" style="font-size:10px;font-weight:700">CART</text>
<text x="192" y="116" style="font-size:8px">cartservice</text>
</g>
<g class="topo-node s-unknown" id="svc-checkoutservice">
<circle cx="320" cy="108" r="22"/>
<text x="320" y="104" style="font-size:10px;font-weight:800">CHK</text>
<text x="320" y="116" style="font-size:8px">checkout</text>
</g>
<g class="topo-node s-unknown" id="svc-productcatalogservice">
<circle cx="448" cy="108" r="20"/>
<text x="448" y="104" style="font-size:10px;font-weight:700">CAT</text>
<text x="448" y="116" style="font-size:8px">catalog</text>
</g>
<g class="topo-node s-unknown" id="svc-recommendationservice">
<circle cx="568" cy="107" r="18"/>
<text x="568" y="103" style="font-size:10px;font-weight:700">REC</text>
<text x="568" y="115" style="font-size:8px">rec</text>
</g>
<!-- Tier 3 -->
<g class="topo-node s-unknown" id="svc-redis-cart">
<circle cx="90" cy="180" r="16"/>
<text x="90" y="176" style="font-size:10px;font-weight:700">RDB</text>
<text x="90" y="188" style="font-size:8px">redis</text>
</g>
<g class="topo-node s-unknown" id="svc-paymentservice">
<circle cx="210" cy="180" r="17"/>
<text x="210" y="176" style="font-size:10px;font-weight:700">PAY</text>
<text x="210" y="188" style="font-size:8px">payment</text>
</g>
<g class="topo-node s-unknown" id="svc-currencyservice">
<circle cx="320" cy="180" r="17"/>
<text x="320" y="176" style="font-size:10px;font-weight:700">CUR</text>
<text x="320" y="188" style="font-size:8px">currency</text>
</g>
<g class="topo-node s-unknown" id="svc-shippingservice">
<circle cx="430" cy="180" r="17"/>
<text x="430" y="176" style="font-size:10px;font-weight:700">SHP</text>
<text x="430" y="188" style="font-size:8px">shipping</text>
</g>
<g class="topo-node s-unknown" id="svc-emailservice">
<circle cx="548" cy="180" r="17"/>
<text x="548" y="176" style="font-size:10px;font-weight:700">EML</text>
<text x="548" y="188" style="font-size:8px">email</text>
</g>
</svg>
</div>
<div class="mttr-display">
<span class="mttr-label">MTTR</span>
<span class="mttr-value" id="mttr-display">β€”</span>
<span class="mttr-label" id="mttr-label" style="font-size:11px;color:var(--textdim)">No active incident</span>
</div>
</div>
<!-- Right: agent thoughts + comms -->
<div class="timeline-panel">
<div class="panel-header">
<span class="panel-title">Agent Chain</span>
<span style="font-size:10px;color:var(--textdim)" id="agent-status">Idle</span>
</div>
<div class="timeline-body" id="timeline-body">
<div class="empty-state">
<div class="icon">πŸ€–</div>
<p>Inject a chaos scenario<br>to start incident response</p>
</div>
</div>
<div class="comms-section">
<div class="comms-header">
<span class="panel-title"># incident-response</span>
<span id="comms-count" style="font-size:10px;color:var(--textdim)">0 posts</span>
</div>
<div class="comms-body" id="comms-body">
<div class="comms-empty">No posts yet β€” comms agent will post here</div>
</div>
</div>
</div>
<!-- Status bar -->
<div class="statusbar">
<div class="statusbar-item"><div class="dot dot-green"></div>GKE cluster online</div>
<div class="statusbar-item"><div class="dot dot-green" id="prom-dot"></div><span id="prom-status-bar">Prometheus live</span></div>
<div class="statusbar-item">
<div class="dot dot-green" id="coordinator-dot"></div>
<span id="coordinator-status">AtlasOps ready</span>
</div>
<div class="statusbar-item" title="From GET /health β€” URL never shown">
<div class="dot" id="discord-dot" style="background:var(--textdim)"></div>
<span id="discord-hook-pill">Discord webhook …</span>
</div>
<div class="statusbar-item" title="Agent API base from /health">
<span id="inference-summary" style="color:var(--textdim)">Inference …</span>
</div>
<div class="statusbar-item"><div class="dot" style="background:var(--accent)"></div>Qwen2.5-7B Β· AMD MI300X</div>
<div style="flex:1"></div>
<span id="statusbar-time" style="color:var(--textdim)"></span>
</div>
</div>
</div>
<!-- ── Benchmark Tab ── -->
<div id="tab-bench" class="tab-panel" style="padding:80px 40px 40px;overflow-y:auto">
<div style="max-width:980px;margin:0 auto;position:relative;z-index:2">
<h2 style="font-size:20px;margin-bottom:6px;color:var(--accent);letter-spacing:2px">BENCHMARK RESULTS</h2>
<p style="color:var(--textdim);font-size:11px;margin-bottom:28px">29 curriculum YAML scenarios Β· Real GKE cluster Β· AMD MI300X Β· DAPO loss + Spaced-Rep Curriculum</p>
<!-- Hero metrics -->
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:28px">
<div style="background:rgba(11,17,32,0.9);border:1px solid var(--border);border-radius:10px;padding:18px;text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--green);text-shadow:0 0 20px rgba(0,227,150,0.5)">0.856</div>
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);margin-top:6px;text-transform:uppercase">Best Score</div>
<div style="font-size:10px;color:var(--textdim);margin-top:2px">Cloudflare 2019</div>
</div>
<div style="background:rgba(11,17,32,0.9);border:1px solid var(--border);border-radius:10px;padding:18px;text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--green);text-shadow:0 0 20px rgba(0,227,150,0.5)">82%</div>
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);margin-top:6px;text-transform:uppercase">Resolution Rate</div>
<div style="font-size:10px;color:var(--green);margin-top:2px">GRPO Β· +28pp vs zero-shot</div>
</div>
<div style="background:rgba(11,17,32,0.9);border:1px solid var(--border);border-radius:10px;padding:18px;text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--yellow);text-shadow:0 0 20px rgba(255,183,3,0.4)">58.9s</div>
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);margin-top:6px;text-transform:uppercase">Avg MTTR</div>
<div style="font-size:10px;color:var(--textdim);margin-top:2px">vs ~25 min human</div>
</div>
<div style="background:rgba(11,17,32,0.9);border:1px solid var(--border);border-radius:10px;padding:18px;text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--red);text-shadow:0 0 20px rgba(255,69,96,0.4)">20</div>
<div style="font-size:9px;letter-spacing:2px;color:var(--textdim);margin-top:6px;text-transform:uppercase">Real SRE Tools</div>
<div style="font-size:10px;color:var(--textdim);margin-top:2px">kubectl Β· promql Β· jaeger Β· argocd</div>
</div>
</div>
<!-- Training progression table -->
<div style="background:rgba(11,17,32,0.9);border:1px solid var(--border);border-radius:10px;padding:20px;margin-bottom:20px">
<div style="font-size:10px;letter-spacing:2px;color:var(--textdim);text-transform:uppercase;margin-bottom:16px">Training Progression (AMD MI300X Β· Qwen2.5-7B)</div>
<table class="bench-table" style="margin-bottom:0">
<thead>
<tr><th>Stage</th><th>Resolution</th><th>Avg Reward</th><th>Cascade</th><th>Named Replays</th><th>vs Baseline</th></tr>
</thead>
<tbody>
<tr>
<td style="color:var(--textdim)">Qwen2.5-7B zero-shot</td>
<td>
<div style="display:flex;align-items:center;gap:8px">
<div style="flex:1;height:4px;background:var(--border);border-radius:2px"><div style="width:54%;height:100%;background:var(--textdim);border-radius:2px"></div></div>
<span>54%</span>
</div>
</td>
<td>0.481</td><td>40%</td><td>30%</td><td class="delta-neg">baseline</td>
</tr>
<tr>
<td>AtlasOps SFT</td>
<td>
<div style="display:flex;align-items:center;gap:8px">
<div style="flex:1;height:4px;background:var(--border);border-radius:2px"><div style="width:68%;height:100%;background:var(--accent2);border-radius:2px"></div></div>
<span>68%</span>
</div>
</td>
<td>0.601</td><td>62%</td><td>55%</td><td class="delta-pos">+14pp</td>
</tr>
<tr class="highlight">
<td>⭐ AtlasOps GRPO <span style="font-size:9px;color:var(--red);margin-left:4px">AMD MI300X</span></td>
<td>
<div style="display:flex;align-items:center;gap:8px">
<div style="flex:1;height:4px;background:var(--border);border-radius:2px"><div style="width:82%;height:100%;background:var(--green);border-radius:2px;box-shadow:0 0 6px rgba(0,227,150,0.5)"></div></div>
<span style="color:var(--green)">82%</span>
</div>
</td>
<td style="color:var(--green)">0.729</td><td style="color:var(--green)">78%</td><td style="color:var(--green)">72%</td><td class="delta-pos" style="font-size:13px">+28pp</td>
</tr>
</tbody>
</table>
</div>
<!-- Per-scenario results -->
<div style="background:rgba(11,17,32,0.9);border:1px solid var(--border);border-radius:10px;padding:20px">
<div style="font-size:10px;letter-spacing:2px;color:var(--textdim);text-transform:uppercase;margin-bottom:16px">Quick Eval Β· 3 Demo Scenarios Β· Live GKE Β· May 9 2026</div>
<table class="bench-table" style="margin-bottom:0">
<thead><tr><th>Scenario</th><th>Outcome</th><th>MTTR</th><th>Score</th></tr></thead>
<tbody>
<tr><td>Cloudflare 2019 (CPU saturation)</td><td style="color:var(--green)">βœ“ resolved</td><td>102.8s</td><td style="color:var(--green);font-weight:700">0.856</td></tr>
<tr><td>GitHub 2018 (DB failover loop)</td><td style="color:var(--textdim)">escalated</td><td>35.5s</td><td>0.548</td></tr>
<tr><td>sf-001 (OOMKill crash loop)</td><td style="color:var(--yellow)">⚠ partial</td><td>38.3s</td><td style="color:var(--yellow)">0.722</td></tr>
</tbody>
</table>
</div>
<p style="color:var(--textdim);font-size:10px;margin-top:12px">Reward = 15% triage + 30% diagnosis + 35% remediation + 10% comms + 10% speed. Real tool calls against real GKE cluster.</p>
</div>
</div>
<!-- ── About Tab ── -->
<div id="tab-about" class="tab-panel" style="padding:40px 60px 40px;overflow-y:auto">
<div style="max-width:1400px;margin:0 auto;position:relative;z-index:2">
<!-- Hero headline -->
<div style="text-align:center;margin-bottom:48px">
<div style="font-size:11px;letter-spacing:4px;color:var(--textdim);text-transform:uppercase;margin-bottom:12px">AMD Developer Hackathon 2026</div>
<h1 style="font-size:32px;font-weight:900;letter-spacing:3px;color:var(--accent);text-shadow:0 0 40px rgba(0,212,255,0.4);margin-bottom:12px">ATLASOPS</h1>
<p style="font-size:14px;color:var(--textdim);max-width:600px;margin:0 auto;line-height:1.8">
The first multi-agent SRE platform trained end-to-end on real cloud failures.<br>
<span style="color:var(--text)">4 specialized AI agents. 20 real SRE tools. 1 AMD MI300X.</span>
</p>
</div>
<div style="background:rgba(0,212,255,0.05);border:1px solid rgba(0,212,255,0.22);border-radius:12px;padding:18px 22px;margin-bottom:32px;max-width:760px;margin-left:auto;margin-right:auto">
<div style="font-size:10px;letter-spacing:2.5px;color:var(--accent);text-transform:uppercase;margin-bottom:8px">HF Spaces & trained weights</div>
<p style="font-size:12px;color:var(--textdim);line-height:1.75;margin:0">
This dashboard calls the coordinator configured for the deployment (URL, secrets, cluster access). Chaos and Prometheus metrics are live when that wiring reaches a real cluster; otherwise the UI can still run with a local <span style="color:var(--text)">UI preview (offline)</span> timeline. GRPO/SFT checkpoints are not magically active just because the Space is openβ€”using them means loading the adapter or merged weights in your inference stack (for example vLLM on ROCm) as part of the Space image or startup.
</p>
</div>
<!-- The problem -->
<div style="background:rgba(255,69,96,0.04);border:1px solid rgba(255,69,96,0.2);border-radius:12px;padding:24px;margin-bottom:20px">
<div style="font-size:10px;letter-spacing:3px;color:var(--red);text-transform:uppercase;margin-bottom:12px">⚠ The Problem</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px">
<div>
<div style="font-size:28px;font-weight:800;color:var(--red)">2:47 AM</div>
<div style="font-size:11px;color:var(--textdim);margin-top:4px">Average time a P1 alert fires. Your on-call engineer is asleep.</div>
</div>
<div>
<div style="font-size:28px;font-weight:800;color:var(--yellow)">~25 min</div>
<div style="font-size:11px;color:var(--textdim);margin-top:4px">Average human MTTR for a cascade incident. Revenue bleeding the whole time.</div>
</div>
<div>
<div style="font-size:28px;font-weight:800;color:var(--accent)">$250B</div>
<div style="font-size:11px;color:var(--textdim);margin-top:4px">Global observability + SRE market. On-call burnout is a $250B problem.</div>
</div>
</div>
</div>
<!-- The solution β€” agent pipeline -->
<div style="background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:12px;padding:24px;margin-bottom:20px">
<div style="font-size:10px;letter-spacing:3px;color:var(--accent);text-transform:uppercase;margin-bottom:20px">πŸ€– The Solution β€” 4-Agent Pipeline</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0;position:relative">
<!-- connector line -->
<div style="position:absolute;top:32px;left:12%;right:12%;height:2px;background:linear-gradient(90deg,var(--red),var(--accent2),var(--yellow),var(--green));opacity:0.4;z-index:0"></div>
<div style="text-align:center;position:relative;z-index:1;padding:0 8px">
<div style="width:64px;height:64px;border-radius:50%;background:rgba(255,69,96,0.1);border:2px solid var(--red);margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:0 0 20px rgba(255,69,96,0.2)">πŸ”΄</div>
<div style="font-size:11px;font-weight:800;color:var(--red);letter-spacing:1px;margin-bottom:6px">TRIAGE</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.6">Acks alert<br>Assigns severity<br>Maps blast radius<br>4 tool calls max</div>
</div>
<div style="text-align:center;position:relative;z-index:1;padding:0 8px">
<div style="width:64px;height:64px;border-radius:50%;background:rgba(123,97,255,0.1);border:2px solid var(--accent2);margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:0 0 20px rgba(123,97,255,0.2)">πŸ”</div>
<div style="font-size:11px;font-weight:800;color:var(--accent2);letter-spacing:1px;margin-bottom:6px">DIAGNOSIS</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.6">PromQL queries<br>Jaeger traces<br>kubectl logs<br>Root cause ID</div>
</div>
<div style="text-align:center;position:relative;z-index:1;padding:0 8px">
<div style="width:64px;height:64px;border-radius:50%;background:rgba(255,183,3,0.1);border:2px solid var(--yellow);margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:0 0 20px rgba(255,183,3,0.2)">πŸ”§</div>
<div style="font-size:11px;font-weight:800;color:var(--yellow);letter-spacing:1px;margin-bottom:6px">REMEDIATION</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.6">Argo CD rollback<br>kubectl scale<br>Alert silence<br>Prometheus verify</div>
</div>
<div style="text-align:center;position:relative;z-index:1;padding:0 8px">
<div style="width:64px;height:64px;border-radius:50%;background:rgba(0,227,150,0.1);border:2px solid var(--green);margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:0 0 20px rgba(0,227,150,0.2)">πŸ“£</div>
<div style="font-size:11px;font-weight:800;color:var(--green);letter-spacing:1px;margin-bottom:6px">COMMS</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.6">Slack update<br>Postmortem draft<br>Status page<br>Action items</div>
</div>
</div>
</div>
<!-- 3-column grid: infra + AI + tools -->
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:20px">
<!-- Infrastructure -->
<div style="background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:12px;padding:22px">
<div style="font-size:11px;letter-spacing:2px;color:var(--green);text-transform:uppercase;margin-bottom:16px;font-weight:700">☁ Real Infrastructure</div>
<div style="display:flex;flex-direction:column;gap:10px">
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">GKE Standard</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">us-central1, 3Γ— e2-standard-4</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Online Boutique</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">11 microservices β€” gRPC, Go, Python, Node</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Prometheus + Grafana</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Real scraping, 2M+ time series</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Jaeger + OTel</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Distributed trace collection</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Chaos Mesh</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">PodChaos Β· NetworkChaos Β· IOChaos Β· DNSChaos Β· TimeChaos Β· StressChaos</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Argo CD</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">GitOps rollbacks β€” real execution</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Cloud SQL</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Postgres 15 β€” replaces SQLite in cartservice</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--green);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Alertmanager</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Webhook β†’ AtlasOps coordinator</span></span></div>
</div>
</div>
<!-- AMD + AI -->
<div style="background:rgba(11,17,32,0.8);border:1px solid rgba(255,69,96,0.3);border-radius:12px;padding:22px">
<div style="font-size:11px;letter-spacing:2px;color:var(--red);text-transform:uppercase;margin-bottom:16px;font-weight:700">⚑ AMD MI300X Training</div>
<div style="background:rgba(255,69,96,0.04);border:1px solid rgba(255,69,96,0.15);border-radius:8px;padding:14px;margin-bottom:16px;text-align:center">
<div style="font-size:28px;font-weight:800;color:var(--red)">192 GB</div>
<div style="font-size:10px;color:var(--textdim);letter-spacing:2px;margin-top:2px">HBM3 VRAM</div>
<div style="font-size:11px;color:var(--textdim);margin-top:6px">5 Qwen models co-hosted simultaneously</div>
</div>
<div style="display:flex;flex-direction:column;gap:10px">
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Qwen2.5-7B Γ— 4</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">One per agent role + QLoRA LoRA r=16</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Qwen2.5-72B</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">LLM judge + adversarial scenario designer</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">QLoRA SFT</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">4-bit NF4 quantization, TRL + PEFT</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Online GRPO</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">DAPO loss β€” real GKE rollouts per step</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Curriculum</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Spaced rep [3,6,12,24,48h], mastery decay=0.85</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">Dense Rewards</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Per-step + episode contract (70/30 blend)</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--red);flex-shrink:0;margin-top:2px">β–Έ</span><span><span style="color:var(--text);font-size:13px;font-weight:600">ROCm 7.2</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">PyTorch + Hugging Face Optimum-AMD</span></span></div>
</div>
</div>
<!-- 20 Tools -->
<div style="background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:12px;padding:22px">
<div style="font-size:11px;letter-spacing:2px;color:var(--accent);text-transform:uppercase;margin-bottom:16px;font-weight:700">πŸ›  20 Real SRE Tools</div>
<div style="display:flex;flex-direction:column;gap:9px">
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">kubectl</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">get · describe · logs · top · rollout · scale · exec</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">promql_query</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Live Prometheus HTTP API</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">promql_query_range</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Time-series range queries</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">jaeger_search</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Find traces by service + duration</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">jaeger_get_trace</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Full span chain by trace ID</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">argocd_rollback</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">GitOps rollback via REST API</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">argocd_list_apps</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">List all deployed applications</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">alertmanager_silence</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Suppress flapping alerts via API</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">gcloud_logs_read</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Cloud Logging structured logs</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">cloud_monitoring_query</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">GCP-native metrics API</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">slack_post_update</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Incident channel notification</span></span></div>
<div style="display:flex;gap:10px;align-items:flex-start"><span style="color:var(--accent);flex-shrink:0;margin-top:2px">⬑</span><span><span style="color:var(--accent);font-size:13px;font-weight:600">postmortem_draft</span><span style="color:var(--textdim);font-size:11px;display:block;margin-top:1px">Auto-generated Cloudflare-quality postmortem</span></span></div>
</div>
</div>
</div>
<!-- Safety architecture -->
<div style="background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:12px;padding:24px;margin-bottom:20px">
<div style="font-size:10px;letter-spacing:3px;color:var(--textdim);text-transform:uppercase;margin-bottom:18px">πŸ›‘ Safety Architecture β€” No Agent Can Cause an Outage</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px">
<div style="background:rgba(255,69,96,0.05);border:1px solid rgba(255,69,96,0.2);border-radius:8px;padding:14px">
<div style="font-size:13px;margin-bottom:8px">🚦</div>
<div style="font-size:11px;font-weight:700;color:var(--red);margin-bottom:6px">Approval Gate</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.7">P0 β†’ human required<br>P1 β†’ 60s auto window<br>P2/P3 β†’ automatic<br>Token-based callbacks</div>
</div>
<div style="background:rgba(255,183,3,0.05);border:1px solid rgba(255,183,3,0.2);border-radius:8px;padding:14px">
<div style="font-size:13px;margin-bottom:8px">⚑</div>
<div style="font-size:11px;font-weight:700;color:var(--yellow);margin-bottom:6px">Circuit Breaker</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.7">50 tool calls/incident<br>10 mutations/hour<br>3 consecutive failures β†’ OPEN<br>Auto-reset on recovery</div>
</div>
<div style="background:rgba(0,212,255,0.05);border:1px solid rgba(0,212,255,0.2);border-radius:8px;padding:14px">
<div style="font-size:13px;margin-bottom:8px">πŸ”—</div>
<div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:6px">Incident Correlator</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.7">5-min dedup window<br>Prevents alert storms<br>Fingerprint matching<br>Active incident tracking</div>
</div>
<div style="background:rgba(0,227,150,0.05);border:1px solid rgba(0,227,150,0.2);border-radius:8px;padding:14px">
<div style="font-size:13px;margin-bottom:8px">πŸ“‹</div>
<div style="font-size:11px;font-weight:700;color:var(--green);margin-bottom:6px">HMAC Audit Log</div>
<div style="font-size:10px;color:var(--textdim);line-height:1.7">Hash-chained entries<br>Tamper-evident<br>Every tool call logged<br>Cryptographic integrity</div>
</div>
</div>
</div>
<!-- Historical scenarios -->
<div style="background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:12px;padding:24px;margin-bottom:20px">
<div style="font-size:11px;letter-spacing:3px;color:var(--textdim);text-transform:uppercase;margin-bottom:18px;font-weight:700">πŸ“– 31 Scenarios β€” Including 10 Named Historical Replays</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px">
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">🌩</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Cloudflare 2019</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Regex CPU storm Β· 85% traffic down</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">πŸ”„</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">GitHub 2018</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">DB failover loop Β· 24h incident</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">⚑</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Discord 2022</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Redis thundering herd</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">🌐</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Datadog 2023</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">systemd-resolved failure</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">πŸ’¬</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Slack 2022</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">HTTP/2 stream exhaustion</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">πŸ“¦</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">AWS S3 2017</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Typo capacity command</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">🌍</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Azure DNS 2019</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Stale DNS cache poisoning</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">πŸ”Œ</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Fastly 2021</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Bad VCL/Envoy filter</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">πŸ“‘</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Facebook BGP 2021</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Control plane partition</div>
</div>
<div style="background:rgba(6,10,18,0.8);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
<div style="font-size:22px;margin-bottom:8px">πŸ’°</div>
<div style="font-size:12px;font-weight:700;color:var(--text);margin-bottom:4px">Knight Capital 2012</div>
<div style="font-size:11px;color:var(--textdim);line-height:1.5">Partial deploy mismatch</div>
</div>
</div>
</div>
<!-- Links row -->
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:20px">
<a href="https://github.com/Harikishanth/AtlasOps" target="_blank" style="display:flex;align-items:center;gap:12px;background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:10px;padding:16px;text-decoration:none;transition:border-color 0.2s" onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
<span style="font-size:24px">πŸ™</span>
<div><div style="font-size:12px;font-weight:700;color:var(--text)">GitHub Repository</div><div style="font-size:10px;color:var(--textdim)">Harikishanth/AtlasOps Β· MIT License</div></div>
</a>
<a href="http://136.119.60.129" target="_blank" style="display:flex;align-items:center;gap:12px;background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:10px;padding:16px;text-decoration:none;transition:border-color 0.2s" onmouseover="this.style.borderColor='var(--yellow)'" onmouseout="this.style.borderColor='var(--border)'">
<span style="font-size:24px">πŸ“Š</span>
<div><div style="font-size:12px;font-weight:700;color:var(--text)">Live Grafana</div><div style="font-size:10px;color:var(--textdim)">Real GKE metrics Β· us-central1</div></div>
</a>
<a href="http://34.132.118.204" target="_blank" style="display:flex;align-items:center;gap:12px;background:rgba(11,17,32,0.8);border:1px solid var(--border);border-radius:10px;padding:16px;text-decoration:none;transition:border-color 0.2s" onmouseover="this.style.borderColor='var(--green)'" onmouseout="this.style.borderColor='var(--border)'">
<span style="font-size:24px">πŸ›</span>
<div><div style="font-size:12px;font-weight:700;color:var(--text)">Online Boutique</div><div style="font-size:10px;color:var(--textdim)">Live target application Β· GKE</div></div>
</a>
</div>
<!-- Footer credit -->
<div style="text-align:center;padding:20px 0;border-top:1px solid var(--border)">
<div style="font-size:11px;color:var(--textdim)">Built by <span style="color:var(--accent)">Hari Kishanth</span> Β· Team <span style="color:var(--accent)">Da Big Three</span> Β· St. Joseph's College of Engineering, Chennai</div>
<div style="font-size:10px;color:var(--textdim);margin-top:4px">AMD Developer Hackathon 2026 · lablab.ai · <span style="color:var(--red)">⚑ Powered by AMD MI300X 192GB HBM3</span></div>
</div>
</div>
</div>
<script>
// ═══════════════════════════════════════════════════════════════
// ── Prometheus Live Metrics + Sparklines ───────────────────────
// ═══════════════════════════════════════════════════════════════
const PROM_URL = window._cfg?.prometheus_url || '';
const _sparkData = { rps: Array(40).fill(0), cpu: Array(40).fill(0) };
function _drawSparkline(canvasId, data, color, fillColor) {
const canvas = document.getElementById(canvasId);
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const cssW = Math.max(canvas.offsetWidth || 120, 1);
const cssH = Math.max(canvas.offsetHeight || 70, 1);
canvas.width = cssW * dpr;
canvas.height = cssH * dpr;
const ctx = canvas.getContext('2d');
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const W = cssW, H = cssH;
ctx.clearRect(0, 0, W, H);
const vals = data.map(v => (+v || 0));
const rawMax = Math.max(...vals, 1e-9);
const rawMin = Math.min(...vals);
const span = Math.max(rawMax - rawMin, rawMax * 0.08, 1e-9);
const vmin = Math.max(0, rawMin - span * 0.12);
const vmax = rawMax + span * 0.12;
const norm = vmax > vmin ? (v => (v - vmin) / (vmax - vmin)) : (_ => 0.5);
const xStep = vals.length > 1 ? W / (vals.length - 1) : W;
const pts = vals.map((v, i) => [i * xStep, H - norm(v) * H * 0.85 - H * 0.05]);
if (pts.length < 2) return;
// Fill
ctx.beginPath();
ctx.moveTo(pts[0][0], H);
pts.forEach(p => ctx.lineTo(p[0], p[1]));
ctx.lineTo(pts[pts.length-1][0], H);
ctx.closePath();
const grad = ctx.createLinearGradient(0, 0, 0, H);
grad.addColorStop(0, fillColor);
grad.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = grad;
ctx.fill();
// Line
ctx.beginPath();
ctx.moveTo(pts[0][0], pts[0][1]);
for (let i = 1; i < pts.length; i++) {
const xc = (pts[i-1][0] + pts[i][0]) / 2;
const yc = (pts[i-1][1] + pts[i][1]) / 2;
ctx.quadraticCurveTo(pts[i-1][0], pts[i-1][1], xc, yc);
}
ctx.lineTo(pts[pts.length-1][0], pts[pts.length-1][1]);
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.stroke();
// Current value dot
ctx.beginPath();
ctx.arc(pts[pts.length-1][0], pts[pts.length-1][1], 3, 0, Math.PI*2);
ctx.fillStyle = color;
ctx.fill();
}
async function _promQuery(query) {
if (!PROM_URL) return null;
try {
const r = await fetch(`${PROM_URL}/api/v1/query?query=${encodeURIComponent(query)}`, {signal: AbortSignal.timeout(5000)});
if (!r.ok) return null;
const d = await r.json();
const results = d?.data?.result || [];
if (!results.length) return 0;
return parseFloat(results.reduce((s, r) => s + parseFloat(r.value?.[1] || 0), 0).toFixed(3));
} catch { return null; }
}
function _drawLoadingSparkline(canvasId, color) {
const canvas = document.getElementById(canvasId);
if (!canvas || canvas.offsetWidth === 0) return;
canvas.width = canvas.offsetWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.offsetHeight * (window.devicePixelRatio || 1);
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// Draw a flat dashed line as placeholder
ctx.setLineDash([6, 6]);
ctx.strokeStyle = color.replace(')', ', 0.3)').replace('rgb', 'rgba');
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(0, H * 0.6);
ctx.lineTo(W, H * 0.6);
ctx.stroke();
ctx.setLineDash([]);
// Connecting text
ctx.fillStyle = 'rgba(74,90,120,0.6)';
ctx.font = `${Math.max(10, H * 0.18)}px monospace`;
ctx.textAlign = 'center';
ctx.fillText('connecting…', W / 2, H * 0.45);
}
async function refreshPrometheusMetrics() {
try {
// Use server-side proxy to avoid CORS β€” browser β†’ app.py β†’ Prometheus
const r = await fetch('/metrics', {signal: AbortSignal.timeout(8000)});
if (!r.ok) throw new Error('proxy error');
const d = await r.json();
const errEl = document.getElementById('metric-error-rate');
const cpuEl = document.getElementById('metric-cpu');
const alertEl = document.getElementById('metric-alerts');
if (errEl && d.error_rate !== null) {
const pct = (d.error_rate * 100).toFixed(1);
errEl.textContent = pct + '%';
errEl.style.color = d.error_rate > 0.05 ? 'var(--red)' : 'var(--green)';
errEl.style.textShadow = d.error_rate > 0.05 ? '0 0 15px rgba(255,69,96,0.6)' : '0 0 15px rgba(0,227,150,0.4)';
}
if (cpuEl && d.cpu !== null) cpuEl.textContent = d.cpu.toFixed(2);
if (alertEl && d.alerts !== null) alertEl.textContent = d.alerts.toString();
if (d.rps !== null) {
_sparkData.rps.push(d.rps); _sparkData.rps.shift();
_drawSparkline('spark-rps', _sparkData.rps, '#00d4ff', 'rgba(0,212,255,0.15)');
}
if (d.cpu !== null) {
_sparkData.cpu.push(d.cpu); _sparkData.cpu.shift();
_drawSparkline('spark-cpu', _sparkData.cpu, '#7b61ff', 'rgba(123,97,255,0.15)');
}
const live = d.rps !== null || d.cpu !== null;
const promStatus = document.getElementById('prom-status');
if (promStatus) {
promStatus.textContent = live ? '● metrics live' : 'β—‹ metrics offline';
promStatus.style.color = live ? 'var(--green)' : 'var(--textdim)';
}
const hint = document.getElementById('metric-poll-hint');
if (hint) {
hint.textContent = live
? 'Sparklines use min–max scaling so small moves stay visible. Samples about every 5s via /metrics.'
: 'No Prometheus samples β€” check PROMETHEUS_URL / proxy. UI still updates when offline falls back to flat lines.';
}
// If all metrics null, replace "connecting…" canvases with explicit offline state
if (!live) { _drawOfflineSparkline('spark-rps'); _drawOfflineSparkline('spark-cpu'); }
} catch {
const promStatus = document.getElementById('prom-status');
if (promStatus) { promStatus.textContent = 'β—‹ metrics offline'; promStatus.style.color = 'var(--textdim)'; }
const hint = document.getElementById('metric-poll-hint');
if (hint) hint.textContent = 'Metrics request failed β€” retrying every 5s.';
_drawOfflineSparkline('spark-rps'); _drawOfflineSparkline('spark-cpu');
}
}
function _drawOfflineSparkline(id) {
const canvas = document.getElementById(id);
if (!canvas) return;
const W = canvas.offsetWidth || canvas.width || 200;
const H = canvas.offsetHeight || canvas.height || 80;
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, W, H);
ctx.setLineDash([4, 6]);
ctx.strokeStyle = 'rgba(74,90,120,0.4)';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(0, H * 0.5); ctx.lineTo(W, H * 0.5); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = 'rgba(74,90,120,0.5)';
ctx.font = `${Math.max(10, H * 0.16)}px monospace`;
ctx.textAlign = 'center';
ctx.fillText('metrics offline β€” set PROMETHEUS_URL in Space secrets', W / 2, H * 0.45);
}
// Also fetch alert count from Alertmanager
async function refreshAlertCount() {
const alertEl = document.getElementById('metric-alerts');
if (!alertEl) return;
try {
const r = await fetch('/api/alertmanager/alerts', {signal: AbortSignal.timeout(4000)});
if (r.ok) {
const d = await r.json();
alertEl.textContent = (d.count || d.alerts?.length || 0).toString();
}
} catch {
// fallback β€” try direct Alertmanager
try {
const ALERT_URL = window._cfg?.alertmanager_url || '';
if (!ALERT_URL) return;
const r2 = await fetch(`${ALERT_URL}/api/v2/alerts?active=true`, {signal: AbortSignal.timeout(4000)});
if (r2.ok) { const d = await r2.json(); alertEl.textContent = d.length.toString(); }
} catch {}
}
}
// Always start metrics polling via server-side proxy (no CORS issue)
function _metricsPollTick() {
refreshPrometheusMetrics();
refreshAlertCount();
}
window.addEventListener('load', () => {
// Show loading state immediately so canvases aren't black
setTimeout(() => {
_drawLoadingSparkline('spark-rps', '#00d4ff');
_drawLoadingSparkline('spark-cpu', '#7b61ff');
}, 100);
_metricsPollTick();
setInterval(_metricsPollTick, 5000);
});
// ═══════════════════════════════════════════════════════════════
// ── Neural Network Background ──────────────────────────────────
// ═══════════════════════════════════════════════════════════════
(function() {
const canvas = document.getElementById('neural-bg');
const ctx = canvas.getContext('2d');
let W, H, particles, animId;
const N = 55, MAX_DIST = 130;
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
}
function mkParticle() {
return {
x: Math.random() * W, y: Math.random() * H,
vx: (Math.random() - 0.5) * 0.35,
vy: (Math.random() - 0.5) * 0.35,
r: Math.random() * 1.5 + 0.8,
};
}
function init() {
resize();
particles = Array.from({length: N}, mkParticle);
}
function frame() {
ctx.clearRect(0, 0, W, H);
for (const p of particles) {
p.x += p.vx; p.y += p.vy;
if (p.x < 0) p.x = W; if (p.x > W) p.x = 0;
if (p.y < 0) p.y = H; if (p.y > H) p.y = 0;
}
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const d = Math.sqrt(dx*dx + dy*dy);
if (d < MAX_DIST) {
const a = (1 - d / MAX_DIST) * 0.18;
ctx.beginPath();
ctx.strokeStyle = `rgba(0,212,255,${a})`;
ctx.lineWidth = 0.7;
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
for (const p of particles) {
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,212,255,0.55)';
ctx.fill();
}
animId = requestAnimationFrame(frame);
}
init();
frame();
window.addEventListener('resize', () => { cancelAnimationFrame(animId); init(); frame(); });
})();
// ═══════════════════════════════════════════════════════════════
// ── Particle Burst (resolution ceremony) ───────────────────────
// ═══════════════════════════════════════════════════════════════
const _pCanvas = document.getElementById('particle-canvas');
const _pCtx = _pCanvas.getContext('2d');
let _pParticles = [], _pAnim = null;
function _resizePCanvas() {
_pCanvas.width = window.innerWidth;
_pCanvas.height = window.innerHeight;
}
_resizePCanvas();
window.addEventListener('resize', _resizePCanvas);
function triggerResolutionBurst() {
_pCanvas.style.display = 'block';
_pParticles = [];
const cx = window.innerWidth / 2;
const cy = window.innerHeight * 0.6;
const colors = ['#00e396','#00d4ff','#7b61ff','#ffffff','#00e396'];
for (let i = 0; i < 90; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 8 + 2;
_pParticles.push({
x: cx, y: cy,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - Math.random() * 4,
life: 1.0,
decay: Math.random() * 0.02 + 0.015,
r: Math.random() * 4 + 2,
color: colors[Math.floor(Math.random() * colors.length)],
});
}
cancelAnimationFrame(_pAnim);
_animateBurst();
// Show RESOLVED stamp
const stamp = document.getElementById('resolve-stamp');
stamp.style.display = 'block';
setTimeout(() => stamp.classList.add('show'), 10);
setTimeout(() => { stamp.classList.remove('show'); setTimeout(() => { stamp.style.display='none'; }, 400); }, 2200);
}
function _animateBurst() {
_pCtx.clearRect(0, 0, _pCanvas.width, _pCanvas.height);
_pParticles = _pParticles.filter(p => p.life > 0);
for (const p of _pParticles) {
p.x += p.vx; p.y += p.vy;
p.vy += 0.18; // gravity
p.life -= p.decay;
_pCtx.beginPath();
_pCtx.arc(p.x, p.y, p.r * p.life, 0, Math.PI * 2);
_pCtx.fillStyle = p.color + Math.floor(p.life * 255).toString(16).padStart(2,'0');
_pCtx.fill();
}
if (_pParticles.length > 0) {
_pAnim = requestAnimationFrame(_animateBurst);
} else {
_pCanvas.style.display = 'none';
}
}
// ═══════════════════════════════════════════════════════════════
// ── Runtime config ─────────────────────────────────────────────
// ═══════════════════════════════════════════════════════════════
let COORDINATOR = window.location.hostname === 'localhost'
? 'http://localhost:9099' : window.location.origin;
let RUNTIME_CONFIG = { coordinator_url: COORDINATOR, grafana_url: '', argocd_url: '', boutique_url: '' };
function _grafanaDashboardUrl(base) {
if (!base) return '';
return `${base.replace(/\/$/, '')}/d/k8s_views_pods/kubernetes-views-pods?orgId=1&refresh=5s&theme=dark&kiosk`;
}
async function loadRuntimeConfig() {
try {
const r = await fetch('/config', {signal: AbortSignal.timeout(3000)});
if (!r.ok) return;
const cfg = await r.json();
RUNTIME_CONFIG = {...RUNTIME_CONFIG, ...cfg};
COORDINATOR = cfg.coordinator_url || COORDINATOR;
const gfLink = document.getElementById('grafana-link');
if (cfg.grafana_url) {
gfLink.href = cfg.grafana_url;
gfLink.textContent = 'Grafana β†—';
}
// Prometheus polling uses GET /metrics (FastAPI proxy) β€” no browser CORS issues
} catch (_) {}
}
// ── State
let incidentStart = null, mttrTimer = null, thoughtsSSE = null, activeScenarioBtn = null;
// ── Tab switching
function showTab(name, e) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active');
if (e) e.target.classList.add('active');
}
// ── Clock
function updateClock() {
document.getElementById('statusbar-time').textContent =
new Date().toISOString().replace('T',' ').slice(0,19) + ' UTC';
}
setInterval(updateClock, 1000); updateClock();
// ── MTTR
function startMTTR() {
incidentStart = Date.now();
const el = document.getElementById('mttr-display');
el.classList.add('alerting');
document.getElementById('mttr-label').textContent = 'Incident active';
mttrTimer = setInterval(() => {
const s = Math.floor((Date.now() - incidentStart) / 1000);
const m = Math.floor(s / 60), sec = s % 60;
el.textContent = m > 0 ? `${m}m ${sec}s` : `${sec}s`;
}, 1000);
}
function stopMTTR(resolved) {
clearInterval(mttrTimer);
const el = document.getElementById('mttr-display');
el.classList.remove('alerting');
if (resolved) {
el.classList.add('resolved');
document.getElementById('mttr-label').textContent = 'Resolved βœ“';
document.getElementById('grafana-overlay').className = 'grafana-overlay resolved';
document.body.querySelector('.main')?.classList.add('resolve-flash');
triggerResolutionBurst();
}
}
// ── Incident card
function showIncidentCard(name, severity = 'P1') {
const container = document.getElementById('incident-container');
const sev = severity.toLowerCase();
container.innerHTML = `
<div class="incident-card active" id="active-incident">
<div class="incident-header">
<span class="severity-badge severity-${sev}">${severity}</span>
<span class="incident-title">${name}</span>
</div>
<div class="incident-body">
<div class="incident-meta">
<span class="meta-tag">cartservice</span>
<span class="meta-tag">checkoutservice</span>
<span class="meta-tag">revenue-path</span>
</div>
</div>
</div>`;
document.getElementById('grafana-overlay').className = 'grafana-overlay alert';
}
function resolveIncidentCard() {
const card = document.getElementById('active-incident');
if (card) { card.classList.remove('active'); card.classList.add('resolved'); }
}
// ── Chaos injection
function _scenarioButtonFor(scenarioId, el) {
if (el && el.closest) {
const b = el.closest('.scenario-btn');
if (b) return b;
}
return document.querySelector('.scenario-btn[data-scenario="' + scenarioId.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]');
}
let _agentStallTimer = null;
function _clearAgentStallTimer() {
if (_agentStallTimer) { clearTimeout(_agentStallTimer); _agentStallTimer = null; }
}
/** If SSE never yields real thoughts (common: VLLM_BASE=localhost inside Space), explain instead of silent skeleton. */
function _armAgentStallWarning(scenarioLabel) {
_clearAgentStallTimer();
_agentStallTimer = setTimeout(() => {
const body = document.getElementById('timeline-body');
if (!body || body.querySelector('.atlas-stall-banner')) return;
if (body.querySelectorAll('.thought-entry').length > 0) return;
const w = document.createElement('div');
w.className = 'atlas-stall-banner';
w.style.cssText = 'margin:12px;padding:12px;border-radius:8px;border-left:3px solid var(--yellow);background:rgba(255,183,3,0.08);font-size:11px;line-height:1.55;color:var(--text)';
w.innerHTML = '<strong>No live agent timeline yet.</strong> The coordinator is usually waiting on the LLM HTTP API. On HF Spaces, <code>localhost:8000</code> points at the container itself, not your MI300X β€” set <code>ATLASOPS_USE_HF_INFERENCE=1</code> + <code>HF_TOKEN</code>, or expose vLLM on a reachable URL and set <code>VLLM_BASE</code>. <span style="color:var(--textdim)">Scenario: '+scenarioLabel+'</span>';
body.prepend(w);
}, 70000);
}
async function injectChaos(el, scenarioId, name) {
if (activeScenarioBtn) activeScenarioBtn.classList.remove('firing');
activeScenarioBtn = _scenarioButtonFor(scenarioId, el);
if (activeScenarioBtn) activeScenarioBtn.classList.add('firing');
document.getElementById('scenario-status').textContent = 'Injecting…';
document.getElementById('agent-status').textContent = 'Dispatching…';
clearTimeline();
_clearAgentStallTimer();
// Brief dispatch chip only while /inject is in flight (not fake TRIAGE content)
const tb0 = document.getElementById('timeline-body');
tb0.innerHTML = `
<div class="thought-entry thought-triage phase-tool_call" style="animation:thoughtIn 0.25s ease">
<span class="thought-icon">⚑</span>
<div class="thought-content">
<div class="thought-role">dispatch β†’</div>
<div class="thought-text">Requesting chaos inject… <span style="color:var(--accent)">${name}</span></div>
</div>
</div>`;
showIncidentCard(name);
startMTTR();
try {
const resp = await fetch(`${COORDINATOR}/inject`, {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({scenario_id: scenarioId, name}),
});
if (resp.ok) {
document.getElementById('scenario-status').textContent = 'Chaos active';
document.getElementById('agent-status').textContent = 'Running…';
const tb = document.getElementById('timeline-body');
tb.innerHTML = `<div class="empty-state" style="min-height:140px;padding:18px"><div class="icon">πŸ€–</div><p>Incident pipeline started β€” streaming real agent steps here.</p><p style="font-size:10px;color:var(--textdim);margin-top:10px;line-height:1.6">First message appears when triage calls the LLM. If nothing shows for ~1–2 minutes, inference is not reachable from this host (see yellow notice).</p></div>`;
startThoughtStream();
_armAgentStallWarning(name);
} else {
document.getElementById('scenario-status').textContent = 'UI preview (offline)';
runDemoMode(name);
}
} catch(e) {
document.getElementById('scenario-status').textContent = 'UI preview (offline)';
runDemoMode(name);
}
}
async function resetChaos() {
_clearAgentStallTimer();
if (activeScenarioBtn) { activeScenarioBtn.classList.remove('firing'); activeScenarioBtn = null; }
clearInterval(mttrTimer);
document.getElementById('mttr-display').textContent = 'β€”';
document.getElementById('mttr-display').className = 'mttr-value';
document.getElementById('mttr-label').textContent = 'No active incident';
document.getElementById('grafana-overlay').className = 'grafana-overlay';
document.getElementById('scenario-status').textContent = 'Ready';
document.getElementById('agent-status').textContent = 'Idle';
document.getElementById('incident-container').innerHTML = '';
clearTimeline();
if (thoughtsSSE) { thoughtsSSE.close(); thoughtsSSE = null; }
try { await fetch(`${COORDINATOR}/reset`, {method:'POST'}); } catch(e) {}
stopTopologyPolling();
cascadeServicesHealthy();
startTopologyPolling();
clearCommsFeed();
stopCommsPolling();
startCommsPolling();
}
// ── Thought streaming
const ROLE_ICON = {triage:'πŸ”΄', diagnosis:'πŸ”', remediation:'πŸ”§', comms:'πŸ“£'};
const PHASE_ICON = {tool_call:'β†’', tool_result:'βœ“', conclusion:'β˜…', thinking:'πŸ’­', waiting_approval:'⏳'};
function clearTimeline() {
document.getElementById('timeline-body').innerHTML =
'<div class="empty-state"><div class="icon">πŸ€–</div><p>Agents running…</p></div>';
}
function addThought(thought) {
const body = document.getElementById('timeline-body');
const empty = body.querySelector('.empty-state');
if (empty) empty.remove();
const div = document.createElement('div');
div.className = `thought-entry thought-${thought.role} phase-${thought.phase}`;
div.innerHTML = `
<span class="thought-icon">${ROLE_ICON[thought.role] || 'β€’'}</span>
<div class="thought-content">
<div class="thought-role">${thought.role} ${PHASE_ICON[thought.phase] || 'β€’'}</div>
<div class="thought-text">${thought.thought}</div>
${thought.tool ? `<span class="thought-tool">${thought.tool}</span>` : ''}
</div>`;
body.appendChild(div);
body.scrollTop = body.scrollHeight;
if (thought.phase === 'conclusion' && thought.role === 'comms') {
resolveIncidentCard();
stopMTTR(true);
document.getElementById('agent-status').textContent = 'βœ“ Resolved';
cascadeServicesHealthy();
if (document.getElementById('scenario-status').textContent === 'UI preview (offline)') {
const inc = document.getElementById('active-incident');
_injectDemoSlackPost(inc?.querySelector('.incident-title')?.textContent || 'Incident');
}
}
}
function startThoughtStream() {
if (thoughtsSSE) thoughtsSSE.close();
thoughtsSSE = new EventSource(`${COORDINATOR}/stream`);
thoughtsSSE.onmessage = (e) => {
try {
const row = JSON.parse(e.data);
if (row.heartbeat || !row.role) return;
addThought(row);
} catch (_) {}
};
thoughtsSSE.onerror = () => setTimeout(pollThoughts, 3000);
}
async function pollThoughts() {
try {
const r = await fetch(`${COORDINATOR}/thoughts`);
const data = await r.json();
const thoughts = data.thoughts || [];
const body = document.getElementById('timeline-body');
const current = body.querySelectorAll('.thought-entry').length;
thoughts.slice(current).forEach(addThought);
const last = thoughts[thoughts.length - 1];
if (last?.role === 'comms' && last?.phase === 'conclusion') return;
setTimeout(pollThoughts, 2000);
} catch(e) { setTimeout(pollThoughts, 3000); }
}
// ── UI preview (offline): scripted timeline when inject/coordinator is unavailable
const DEMO_FAULTS = {
'Cloudflare 2019': {frontend:'down', cartservice:'degraded', checkoutservice:'degraded'},
'GitHub 2018': {checkoutservice:'down', cartservice:'degraded', 'redis-cart':'degraded'},
'Discord 2022': {'redis-cart':'down', cartservice:'degraded', checkoutservice:'degraded'},
'AWS S3 2017': {frontend:'degraded', checkoutservice:'degraded', cartservice:'degraded'},
'Azure DNS 2019': {frontend:'degraded', checkoutservice:'degraded'},
'Fastly 2021': {frontend:'down', checkoutservice:'degraded'},
'Facebook BGP 2021': {frontend:'down', adservice:'degraded'},
'Datadog 2023': {checkoutservice:'degraded', frontend:'degraded'},
'Slack 2022': {checkoutservice:'degraded', 'redis-cart':'degraded'},
'Knight Capital 2012': {checkoutservice:'down', paymentservice:'degraded'},
'Pod Kill': {cartservice:'down'},
'CPU Hog': {paymentservice:'degraded'},
'Checkout mem stress': {checkoutservice:'degraded'},
'Frontend packet loss': {frontend:'degraded'},
'Redis ↔ cart partition': {'redis-cart':'degraded', cartservice:'degraded'},
'Payment DNS chaos': {paymentservice:'degraded', checkoutservice:'degraded'},
'Email disk fault': {emailservice:'degraded'},
'Clock Skew': {paymentservice:'degraded', checkoutservice:'degraded'},
'Currency β†’ Checkout': {currencyservice:'degraded', checkoutservice:'down', frontend:'degraded'},
'Redis partition cascade': {'redis-cart':'down', cartservice:'degraded', checkoutservice:'degraded'},
'Rec CPU β†’ FE latency': {recommendationservice:'degraded', frontend:'degraded'},
'Email disk β†’ checkout': {emailservice:'degraded', checkoutservice:'degraded'},
'Cloud SQL exhaustion': {cartservice:'down', checkoutservice:'degraded'},
'FE loss + checkout CPU': {frontend:'degraded', checkoutservice:'degraded'},
'Redis part + rec mem': {'redis-cart':'degraded', recommendationservice:'degraded'},
'DNS + currency latency': {currencyservice:'degraded', checkoutservice:'degraded'},
'Clock skew + cart loss': {paymentservice:'degraded', cartservice:'degraded'},
'Email IO + checkout delay': {emailservice:'degraded', checkoutservice:'degraded'},
'Adv-001 rec kill + catalog CPU': {recommendationservice:'degraded', productcatalogservice:'degraded'},
};
function runDemoMode(scenarioName) {
const demoThoughts = [
{role:'triage', phase:'tool_call', thought:'Checking CPU/memory pressure across all pods…', tool:'kubectl_top_pods'},
{role:'triage', phase:'tool_result', thought:'frontend pods: 1999m/2000m CPU β€” saturated.', tool:'kubectl_top_pods'},
{role:'triage', phase:'tool_call', thought:'Querying error rate: rate(http_requests_total{code=~"5.."}[1m])', tool:'promql_query'},
{role:'triage', phase:'tool_result', thought:'Error rate 34% on checkoutservice. Revenue path broken.', tool:'promql_query'},
{role:'triage', phase:'conclusion', thought:'P1 β€” blast radius: frontend + checkout + cart. Handing to Diagnosis.'},
{role:'diagnosis', phase:'tool_call', thought:'Searching Jaeger for slow traces on frontend (last 15m, >500ms)…', tool:'jaeger_search'},
{role:'diagnosis', phase:'tool_result', thought:'847 slow traces found. Bottleneck: frontend CPU backpressure.', tool:'jaeger_search'},
{role:'diagnosis', phase:'tool_call', thought:'Checking Argo CD for recent deploys…', tool:'argocd_list_apps'},
{role:'diagnosis', phase:'tool_result', thought:'No recent deploys. Last sync 6h ago β€” stable.', tool:'argocd_list_apps'},
{role:'diagnosis', phase:'conclusion', thought:'Root cause: StressChaos CPU injection on frontend. Not a deploy.'},
{role:'remediation', phase:'tool_call', thought:'Deleting StressChaos experiment…', tool:'kubectl_rollout'},
{role:'remediation', phase:'tool_result', thought:'βœ… Chaos deleted. CPU load dropping.', tool:'kubectl_rollout'},
{role:'remediation', phase:'tool_call', thought:'Verifying: rate(http_requests_total{code=~"5.."}[1m])', tool:'promql_query'},
{role:'remediation', phase:'tool_result', thought:'Error rate: 0.2% β€” below 1% threshold. βœ“', tool:'promql_query'},
{role:'remediation', phase:'conclusion', thought:'Resolved in 187s. Handing to Comms.'},
{role:'comms', phase:'tool_call', thought:'Posting [P1 RESOLVED] to Slack…', tool:'slack_post_update'},
{role:'comms', phase:'tool_call', thought:'Drafting postmortem β€” building timeline from cluster events…', tool:'postmortem_draft'},
{role:'comms', phase:'conclusion', thought:'Postmortem saved. Incident closed. MTTR: 4m 12s.'},
];
clearTimeline();
document.getElementById('agent-status').textContent = 'Running (UI preview)';
applyDemoFaults(scenarioName);
let i = 0;
const delays = [400,600,500,800,600,700,900,500,700,800,500,700,600,800,600,400,600,700];
(function next() {
if (i >= demoThoughts.length) return;
addThought(demoThoughts[i]);
setTimeout(next, delays[i] || 600);
i++;
})();
}
// ═══════════════════════════════════════════════════════════════
// ── SVG Service Topology ───────────────────────────────────────
// ═══════════════════════════════════════════════════════════════
const SERVICES = [
'frontend','cartservice','checkoutservice','paymentservice',
'currencyservice','shippingservice','emailservice',
'recommendationservice','productcatalogservice','adservice','redis-cart'
];
// Map service β†’ edges that go TO it
const SVC_EDGES = {
'adservice': ['edge-fe-ad'],
'cartservice': ['edge-fe-cart'],
'checkoutservice': ['edge-fe-chk'],
'productcatalogservice': ['edge-fe-cat'],
'recommendationservice': ['edge-fe-rec'],
'redis-cart': ['edge-cart-redis'],
'paymentservice': ['edge-chk-pay'],
'currencyservice': ['edge-chk-curr'],
'shippingservice': ['edge-chk-ship'],
'emailservice': ['edge-chk-email'],
};
let _topologyTimer = null;
function _applyServiceState(svc, status) {
const node = document.getElementById('svc-' + svc);
if (!node) return;
node.classList.remove('s-healthy','s-degraded','s-down','s-unknown');
node.classList.add('s-' + status);
// Update connected edges
(SVC_EDGES[svc] || []).forEach(eid => {
const edge = document.getElementById(eid);
if (!edge) return;
edge.classList.remove('e-healthy','e-degraded','e-down');
if (status !== 'unknown') edge.classList.add('e-' + status);
});
}
function setAllServicesHealthy() {
SERVICES.forEach(svc => _applyServiceState(svc, 'healthy'));
document.getElementById('topology-status').textContent = SERVICES.length + '/' + SERVICES.length + ' healthy';
}
function cascadeServicesHealthy() {
// Stagger the green transition for a satisfying cascade effect
SERVICES.forEach((svc, i) => setTimeout(() => _applyServiceState(svc, 'healthy'), i * 80));
setTimeout(() => {
document.getElementById('topology-status').textContent = SERVICES.length + '/' + SERVICES.length + ' healthy';
}, SERVICES.length * 80);
}
function applyDemoFaults(scenarioName) {
const faults = DEMO_FAULTS[scenarioName] || {};
SERVICES.forEach(svc => _applyServiceState(svc, faults[svc] || 'healthy'));
const down = Object.values(faults).filter(s => s !== 'healthy').length;
document.getElementById('topology-status').textContent =
(SERVICES.length - down) + '/' + SERVICES.length + ' healthy';
}
async function fetchClusterHealth() {
try {
const r = await fetch(`${COORDINATOR}/cluster/health`, {signal: AbortSignal.timeout(5000)});
if (!r.ok) return;
const data = await r.json();
if (!data.ok || !data.services) return;
SERVICES.forEach(svc => {
const info = data.services[svc];
if (info) _applyServiceState(svc, info.status);
});
document.getElementById('topology-status').textContent =
(data.healthy || 0) + '/' + (data.total || SERVICES.length) + ' healthy';
} catch(_) {}
}
function startTopologyPolling() { fetchClusterHealth(); _topologyTimer = setInterval(fetchClusterHealth, 5000); }
function stopTopologyPolling() { clearInterval(_topologyTimer); }
// ═══════════════════════════════════════════════════════════════
// ── Comms feed ─────────────────────────────────────────────────
// ═══════════════════════════════════════════════════════════════
let _slackTimer = null, _slackCount = 0;
function _severityClass(title) {
const m = title.match(/\[(P\d)\]/i);
return m ? 'sev-' + m[1].toLowerCase() : '';
}
function _renderSlackPost(payload) {
const att = (payload.attachments || [{}])[0];
const title = att.title || '(no title)';
const text = att.text || '';
const ts = att.ts ? new Date(att.ts * 1000) : new Date();
const chips = (att.fields || []).flatMap(f =>
(f.value||'').split('\n').filter(Boolean).slice(0,3)
.map(s => `<span class="slack-action-chip">${s.replace(/^β€’\s*/,'')}</span>`)
).join('');
const div = document.createElement('div');
div.className = 'slack-post ' + _severityClass(title);
div.innerHTML =
`<div class="slack-meta"><span class="slack-bot">πŸ€– atlasops-bot</span>` +
`<span class="slack-time">${ts.toISOString().slice(11,19)} UTC</span></div>` +
`<div class="slack-title">${title}</div>` +
(text ? `<div class="slack-text">${text.slice(0,140)}${text.length>140?'…':''}</div>` : '') +
(chips ? `<div class="slack-actions">${chips}</div>` : '');
return div;
}
async function fetchSlackFeed() {
try {
const r = await fetch(`${COORDINATOR}/slack/feed`, {signal: AbortSignal.timeout(4000)});
if (!r.ok) return;
const data = await r.json();
const posts = data.posts || [];
if (posts.length === _slackCount) return;
const body = document.getElementById('comms-body');
if (_slackCount === 0 && posts.length > 0) body.innerHTML = '';
posts.slice(_slackCount).forEach(p => body.appendChild(_renderSlackPost(p)));
_slackCount = posts.length;
body.scrollTop = body.scrollHeight;
const n = _slackCount;
document.getElementById('comms-count').textContent = n + (n===1?' post':' posts');
} catch(_) {}
}
function _injectDemoSlackPost(scenarioName) {
const body = document.getElementById('comms-body');
if (body.querySelector('.comms-empty')) body.innerHTML = '';
body.appendChild(_renderSlackPost({attachments:[{
title: `[P1] ${scenarioName} β€” Resolved`,
text: 'Incident mitigated. Root cause identified and remediation applied. MTTR: 4m 12s.',
fields: [{title:'Action Items', value:'β€’ Review resource limits\nβ€’ Add HPA policy\nβ€’ Update runbook'}],
ts: Math.floor(Date.now()/1000),
}]}));
body.scrollTop = body.scrollHeight;
_slackCount++;
document.getElementById('comms-count').textContent = _slackCount + ' post' + (_slackCount===1?'':'s');
}
function startCommsPolling() { fetchSlackFeed(); _slackTimer = setInterval(fetchSlackFeed, 5000); }
function stopCommsPolling() { clearInterval(_slackTimer); }
function clearCommsFeed() {
document.getElementById('comms-body').innerHTML = '<div class="comms-empty">No posts yet β€” comms agent will post here</div>';
document.getElementById('comms-count').textContent = '0 posts';
_slackCount = 0;
}
// ── Health check (also surfaces Discord secret + inference without exposing URLs)
async function checkHealth() {
const dot = document.getElementById('coordinator-dot');
const st = document.getElementById('coordinator-status');
const discordEl = document.getElementById('discord-hook-pill');
const discordDot = document.getElementById('discord-dot');
const infEl = document.getElementById('inference-summary');
try {
const r = await fetch(`${COORDINATOR}/health`, {signal: AbortSignal.timeout(3000)});
if (r.ok) {
dot.className = 'dot dot-green';
st.textContent = 'Coordinator online';
const j = await r.json().catch(() => ({}));
if (discordEl && discordDot) {
const ok = j.discord_webhook_configured === true;
discordEl.textContent = ok ? 'Discord webhook βœ“' : 'Discord webhook βœ— add DISCORD_WEBHOOK_URL';
discordEl.style.color = ok ? 'var(--green)' : 'var(--yellow)';
discordDot.className = 'dot ' + (ok ? 'dot-green' : 'dot-dim');
}
if (infEl) {
const base = (j.agent_base || '').trim();
const model = (j.model || 'β€”').toString();
const shortModel = model.length > 28 ? model.slice(0, 26) + '…' : model;
const bad = !base || /localhost|127\.0\.0\.1/i.test(base);
infEl.textContent = bad
? 'LLM base: not reachable from Space (fix VLLM_BASE / HF inference)'
: `LLM Β· ${shortModel}`;
infEl.style.color = bad ? 'var(--orange)' : 'var(--textdim)';
}
} else {
dot.className = 'dot dot-dim';
st.textContent = 'Coordinator HTTP ' + r.status;
if (discordEl) { discordEl.textContent = 'Discord: unknown'; discordEl.style.color = 'var(--textdim)'; }
if (discordDot) discordDot.className = 'dot dot-dim';
if (infEl) { infEl.textContent = 'Inference: unknown'; infEl.style.color = 'var(--textdim)'; }
}
} catch (e) {
dot.className = 'dot dot-red';
st.textContent = 'Coordinator offline (UI preview path)';
if (discordEl) { discordEl.textContent = 'Discord: unknown'; discordEl.style.color = 'var(--textdim)'; }
if (discordDot) discordDot.className = 'dot dot-dim';
if (infEl) { infEl.textContent = 'Inference: unknown'; infEl.style.color = 'var(--textdim)'; }
}
}
// ── Boot
loadRuntimeConfig();
checkHealth();
setInterval(checkHealth, 10000);
startTopologyPolling();
startCommsPolling();
// ── Benchmark table (live from results file)
async function loadBenchResults() {
const tbody = document.getElementById('bench-tbody');
try {
const r = await fetch('/bench/results/comparison_table.md');
if (!r.ok) return;
const md = await r.text();
const lines = md.split('\n').map(x=>x.trim()).filter(Boolean);
const dataRows = lines.filter(l => l.startsWith('|') && !l.includes('---'));
if (dataRows.length < 2) return;
const parsed = dataRows.slice(1).map(l => l.split('|').map(c=>c.trim()).filter(Boolean));
tbody.innerHTML = parsed.map((row, i) => {
const isLast = i === parsed.length - 1;
const cells = row.map(c => `<td>${c}</td>`).join('');
return `<tr${isLast?' class="highlight"':''}>${cells}</tr>`;
}).join('');
} catch(e) {}
}
loadBenchResults();
</script>
</body>
</html>