hwahi07's picture
Upload 8 files
ac91717 verified
Raw
History Blame Contribute Delete
33.8 kB
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cloud Red Team Arena β€” Command Center</title>
<meta name="description" content="Real-time operational command center for AI-driven cloud attack simulation">
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: { extend: {
colors: {
bg: '#080c14', surface: '#0d1321', card: '#131a2b',
border: '#1c2640', borderLight: '#243050',
brand: '#4f8ff7', brandDim: '#2a5db0',
green: '#22c55e', red: '#ef4444', amber: '#f59e0b',
cyan: '#22d3ee', violet: '#a78bfa', pink: '#f472b6',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
},
}}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap');
body { font-family:'Inter',sans-serif; background:#080c14; color:#8b9dc3; }
::-webkit-scrollbar{width:4px} ::-webkit-scrollbar-track{background:transparent} ::-webkit-scrollbar-thumb{background:#1c2640;border-radius:4px}
.log-in{animation:logIn .2s ease-out} @keyframes logIn{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:translateX(0)}}
.pulse-node{animation:pNode 2s ease-in-out infinite} @keyframes pNode{0%,100%{box-shadow:0 0 6px rgba(34,211,238,.3)}50%{box-shadow:0 0 18px rgba(34,211,238,.6)}}
.glow-green{box-shadow:0 0 12px rgba(34,197,94,.2)} .glow-red{box-shadow:0 0 12px rgba(239,68,68,.2)}
.shimmer{background:linear-gradient(90deg,transparent,rgba(79,143,247,.03),transparent);background-size:200% 100%;animation:shimmer 3s infinite} @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
/* Custom terminal cursor */
.cursor-blink::after{content:'β–‹';animation:blink 1s step-end infinite;color:#4f8ff7;margin-left:2px}
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
</style>
</head>
<body class="h-screen overflow-hidden antialiased">
<div id="root" class="h-full"></div>
<script type="text/babel">
const {useState, useEffect, useRef} = React;
/* ─── Icons ─── */
const I = {
Shield:()=><svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>,
Zap:()=><svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>,
Check:()=><svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7"/></svg>,
Play:()=><svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>,
Pause:()=><svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>,
Skip:()=><svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 5l7 7-7 7M5 5l7 7-7 7"/></svg>,
Brain:()=><svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>,
Terminal:()=><svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>,
Database:()=><svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/></svg>,
Rotate:()=><svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>,
Eye:()=><svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>,
};
/* ─── Attack Flows ─── */
const FLOWS = {
easy: [
{id:'scan', label:'SCAN STORAGE', desc:'Discover cloud storage', check:k=>k?.discovered_storage},
{id:'enum', label:'ENUMERATE', desc:'List S3 buckets', check:k=>k?.found_bucket},
{id:'read', label:'EXTRACT DATA', desc:'Read sensitive config', check:k=>k?.read_sensitive_flag},
{id:'patch',label:'REMEDIATE', desc:'Patch public policy', check:k=>k?.patched_sensitive_bucket},
],
medium: [
{id:'recon', label:'RECON', desc:'Map network topology', check:k=>k?.discovered_services},
{id:'ssrf', label:'EXPLOIT SSRF', desc:'Forge server-side request', check:k=>k?.detected_ssrf},
{id:'iam', label:'STEAL IAM', desc:'Extract cloud credentials', check:k=>k?.obtained_iam_creds},
{id:'sec', label:'EXFIL SECRETS', desc:'Pivot to secrets manager', check:k=>k?.listed_secrets||k?.retrieved_secret},
{id:'db', label:'BREACH DB', desc:'Authenticate to database', check:k=>k?.accessed_db},
],
hard: [
{id:'repo', label:'REPO ACCESS', desc:'Clone code repository', check:k=>k?.discovered_services},
{id:'pat', label:'HARVEST PAT', desc:'Extract leaked token', check:k=>k?.found_leaked_token},
{id:'ci', label:'CI/CD AUTH', desc:'Authenticate to pipeline', check:k=>k?.got_ci_token},
{id:'pipe', label:'POISON BUILD', desc:'Modify build pipeline', check:k=>k?.modified_pipeline},
{id:'admin',label:'ADMIN KEY', desc:'Extract admin secret', check:k=>k?.got_admin_token},
{id:'db', label:'BREACH DB', desc:'Full database access', check:k=>k?.accessed_db},
],
custom: [
{id:'i', label:'INIT', desc:'Agent deployed', check:()=>true},
{id:'a', label:'EXECUTING', desc:'Actions in progress', check:k=>Object.keys(k||{}).length>0},
{id:'d', label:'COMPLETE', desc:'Objective achieved', check:k=>k?.objective_complete},
],
};
const TASK_NAMES = {easy:'S3 Misconfiguration', medium:'SSRF β†’ IAM Escalation', hard:'CI/CD Supply Chain', custom:'Custom Scenario'};
const TASK_COLORS = {easy:'text-green', medium:'text-amber', hard:'text-red', custom:'text-violet'};
/* ─── Kill Chain Visual ─── */
const KillChain = ({task, knowledge}) => {
const flow = FLOWS[task]||FLOWS.custom;
return (
<div className="flex items-start w-full px-2 py-3 gap-0">
{flow.map((n,i) => {
const ok = n.check(knowledge);
const next = !ok && (i===0||flow[i-1].check(knowledge));
return (
<div key={n.id} className="flex-1 flex flex-col items-center relative group">
{/* Connector line */}
{i<flow.length-1 && <div className={`absolute top-[14px] left-1/2 w-full h-[2px] z-0 transition-all duration-700 ${ok?'bg-green/30':'bg-border'}`}/>}
{/* Node */}
<div className={`relative z-10 w-7 h-7 rounded-full border-2 flex items-center justify-center transition-all duration-500
${ok?'border-green bg-green/15 text-green glow-green':next?'border-cyan bg-cyan/10 text-cyan pulse-node':'border-border bg-surface text-gray-700'}`}>
{ok?<I.Check/>:next?<I.Zap/>:<div className="w-1 h-1 rounded-full bg-gray-700"/>}
</div>
{/* Label */}
<div className="mt-1.5 text-center">
<div className={`text-[8px] font-bold uppercase tracking-wider leading-tight
${ok?'text-green':next?'text-cyan':'text-gray-700'}`}>{n.label}</div>
<div className="text-[7px] text-gray-600 mt-0.5 hidden group-hover:block">{n.desc}</div>
</div>
</div>
);
})}
</div>
);
};
/* ─── Gauge Component ─── */
const Gauge = ({label, value, color, icon}) => {
const pct = Math.max(0,Math.min(100,value));
return (
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<div className="flex items-center gap-1.5">
<span className={color.replace('bg-','text-')}>{icon}</span>
<span className="text-[10px] text-gray-400 font-semibold uppercase tracking-wider">{label}</span>
</div>
<span className={`text-xs font-bold font-mono ${pct>50?color.replace('bg-','text-'):'text-red'}`}>{pct.toFixed(0)}%</span>
</div>
<div className="h-1 bg-gray-900 rounded-full overflow-hidden">
<div className={`h-full rounded-full transition-all duration-1000 ${color}`} style={{width:`${pct}%`}}/>
</div>
</div>
);
};
/* ─── Agent Reasoning Panel ─── */
const Reasoning = ({traces}) => {
const t = traces?.length>0 ? traces[traces.length-1] : null;
if (!t) return (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<div className="w-10 h-10 rounded-full bg-surface border border-border mx-auto mb-3 flex items-center justify-center">
<I.Brain/>
</div>
<div className="text-[10px] text-gray-600 font-medium">Agent reasoning will appear here</div>
<div className="text-[9px] text-gray-700 mt-1">Click DEPLOY then RUN to begin</div>
</div>
</div>
);
const riskColor = t.risk?.includes('CRITICAL')?'text-red bg-red/5 border-red/20':t.risk?.includes('HIGH')?'text-amber bg-amber/5 border-amber/20':'text-gray-500 bg-surface border-border';
return (
<div className="p-3 space-y-2">
<div className="bg-violet/5 border border-violet/15 rounded-md p-2.5">
<div className="text-[9px] text-violet font-bold uppercase tracking-widest mb-1">πŸ’­ Thinking</div>
<div className="text-[11px] text-gray-300 leading-relaxed">{t.think}</div>
</div>
<div className="bg-cyan/5 border border-cyan/15 rounded-md p-2.5">
<div className="text-[9px] text-cyan font-bold uppercase tracking-widest mb-1">πŸ“‹ Plan</div>
<div className="text-[11px] text-gray-300">{t.plan}</div>
</div>
<div className="bg-green/5 border border-green/15 rounded-md p-2.5">
<div className="text-[9px] text-green font-bold uppercase tracking-widest mb-1">⚑ Action</div>
<div className="text-[11px] text-white font-mono font-semibold">
{t.action}(<span className="text-cyan">{Object.entries(t.params||{}).map(([k,v])=>`${k}="${v}"`).join(', ')}</span>)
</div>
</div>
<div className={`border rounded-md p-2.5 ${riskColor}`}>
<div className="text-[9px] font-bold uppercase tracking-widest mb-1">🎯 Risk Assessment</div>
<div className="text-[11px]">{t.risk}</div>
</div>
{t.stealth_mode && (
<div className="bg-amber/10 border border-amber/20 rounded-md px-3 py-2 flex items-center gap-2">
<I.Eye/>
<span className="text-amber text-[10px] font-bold uppercase tracking-wider">Stealth Mode β€” Prioritizing evasion over speed</span>
</div>
)}
</div>
);
};
/* ─── Main App ─── */
const App = () => {
const [st, setSt] = useState(null);
const [task, setTask] = useState('medium');
const [playing, setPlaying] = useState(false);
const [customJ, setCustomJ] = useState('{\n "services": ["storage"]\n}');
const logRef = useRef(null);
const terminalRef = useRef(null);
useEffect(() => {
const poll = () => fetch('/api/ui-state').then(r=>r.json()).then(setSt).catch(()=>setSt({status:'error'}));
poll(); const id=setInterval(poll,800); return ()=>clearInterval(id);
}, []);
useEffect(() => {
if (!playing) return;
const id = setInterval(() => {
console.log('[UI] Running autonomous step...');
fetch('/api/run-step',{method:'POST'})
.then(r=>r.json())
.then(d=>{
if(d.status==='done'||d.status==='error') {
console.log('[UI] Autonomous run terminated:', d.status);
setPlaying(false);
}
})
.catch(err=>{
console.error('[UI] Autonomous step failed:', err);
setPlaying(false);
});
}, 1200);
return ()=>clearInterval(id);
}, [playing]);
useEffect(() => {
const el = terminalRef.current;
if (!el) return;
// Only scroll if user is already near bottom (100px buffer)
const isAtBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 100;
if (isAtBottom) {
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
}
}, [st?.sim_state?.logs?.length]);
const deploy = () => {
console.log(`[UI] Initiating deployment for task: ${task}`);
setPlaying(false);
const url = task === 'custom' ? '/api/deploy-custom' : '/reset';
const body = task === 'custom' ? {scenario: JSON.parse(customJ)} : {task_id: task};
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
})
.then(async r => {
if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
return r.json();
})
.then(d => {
console.log('[UI] Deploy SUCCESS:', d);
// Force status refresh
fetch('/api/ui-state').then(r=>r.json()).then(setSt).catch(console.error);
})
.catch(err => {
console.error('[UI] Deploy FAILED:', err);
alert(`Deployment Failed: ${err.message}`);
});
};
const step1 = () => {
console.log('[UI] Triggering manual step...');
fetch('/api/run-step', {method: 'POST'})
.then(async r => {
if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
return r.json();
})
.then(d => {
console.log('[UI] Step SUCCESS:', d);
if (d.status === 'error') alert(`Step Error: ${d.message}`);
})
.catch(err => {
console.error('[UI] Step FAILED:', err);
alert(`Step Execution Failed: ${err.message}`);
});
};
// Protective rendering to prevent "all black screen" on logic errors
try {
const on = st?.status === 'online';
const sim = st?.sim_state || {};
const lim = sim.limits || {budget: 20};
const bud = sim.budget_remaining ?? 0;
const alt = sim.alerts_triggered || 0;
const k = sim.agent_knowledge || {};
const logs = sim.logs || [];
const traces = st?.reasoning_traces || [];
const tNow = st?.task || task || 'easy';
const flow = FLOWS[tNow] || FLOWS.custom || [];
const done = (flow || []).filter(n => n?.check && n.check(k)).length;
const prog = flow.length > 0 ? (done / flow.length) * 100 : 0;
const stealth = Math.max(0, 100 - (alt * 20));
const eff = Math.max(0, (bud / Math.max(1, lim.budget)) * 100);
const isDone = !!(k.objective_complete || (st?.step && st.step >= 10));
return (
<div className="h-full flex flex-col bg-bg text-gray-400">
{/* ═══ HEADER ═══ */}
<header className="bg-surface/80 backdrop-blur-xl border-b border-border px-5 py-2.5 flex items-center justify-between z-50 shrink-0">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-brand/10 border border-brand/20 flex items-center justify-center text-brand"><I.Shield/></div>
<div>
<div className="text-sm font-black text-white tracking-tight leading-none uppercase">Cloud Red Team <span className="text-brand">Arena</span></div>
<div className="text-[8px] text-gray-600 uppercase tracking-[0.25em] mt-0.5 font-medium">Command Center v2.1</div>
</div>
</div>
<div className="h-6 w-px bg-border mx-1"/>
<div className={`flex items-center gap-1.5 px-2 py-1 rounded text-[9px] font-bold uppercase tracking-wider border
${on ? 'text-green border-green/20 bg-green/5' : 'text-red border-red/20 bg-red/5'}`}>
<div className={`w-1.5 h-1.5 rounded-full ${on ? 'bg-green animate-pulse' : 'bg-red'}`}/>
{on ? 'SYSTEM ONLINE' : 'SYSTEM OFFLINE'}
</div>
{on && tNow && (
<div className={`text-[9px] font-bold uppercase tracking-wider ${TASK_COLORS[tNow] || 'text-gray-400'}`}>
{TASK_NAMES[tNow] || tNow} MISSION
</div>
)}
</div>
<div className="flex items-center gap-2">
<select className="bg-bg border border-border text-[11px] font-medium rounded px-2 py-1.5 text-gray-300 outline-none focus:border-brand cursor-pointer hover:bg-surface transition-colors"
value={task} onChange={e => setTask(e.target.value)}>
<option value="easy">🟒 Easy β€” S3 Discovery</option>
<option value="medium">🟑 Medium β€” SSRF / IAM Pivot</option>
<option value="hard">πŸ”΄ Hard β€” Supply Chain Breach</option>
<option value="custom">βš™οΈ Custom Scenario (JSON)</option>
</select>
<button onClick={deploy} className="bg-brand/90 hover:bg-brand text-white px-3 py-1.5 rounded text-[11px] font-bold flex items-center gap-1.5 transition-all active:scale-95">
<I.Rotate/> DEPLOY
</button>
<button onClick={step1} disabled={!on} className="bg-surface hover:bg-card text-gray-300 px-2.5 py-1.5 rounded text-[11px] font-bold flex items-center gap-1.5 border border-border transition-all active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed">
<I.Skip/> STEP
</button>
<button onClick={() => setPlaying(!playing)} disabled={!on}
className={`px-3 py-1.5 rounded text-[11px] font-bold flex items-center gap-1.5 transition-all active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed
${playing ? 'bg-amber/90 hover:bg-amber text-black ring-1 ring-amber/40' : 'bg-green/90 hover:bg-green text-black shadow-lg shadow-green/10'}`}>
{playing ? <><I.Pause/> STOP</> : <><I.Play/> RUN AGENT</>}
</button>
</div>
</header>
{/* ═══ BODY ═══ */}
<div className="flex-1 overflow-hidden grid grid-cols-12 gap-2 p-2">
{/* ── LEFT: Status + Chain + Posture ── */}
<div className="col-span-3 flex flex-col gap-2 overflow-y-auto">
{task==='custom' && (
<div className="bg-card border border-border rounded-lg p-3">
<div className="text-[9px] text-violet font-bold uppercase tracking-widest mb-2">Custom Scenario JSON</div>
<textarea className="w-full h-20 bg-bg border border-border rounded text-[10px] font-mono p-2 text-gray-300 outline-none focus:border-brand resize-none"
value={customJ} onChange={e=>setCustomJ(e.target.value)}/>
</div>
)}
{/* Kill Chain */}
<div className="bg-card border border-border rounded-lg">
<div className="px-3 py-2 border-b border-border flex items-center justify-between">
<div className="flex items-center gap-1.5">
<span className="text-brand"><I.Zap/></span>
<span className="text-[10px] font-bold text-gray-300 uppercase tracking-widest">Kill Chain</span>
</div>
<span className="text-[10px] font-mono font-bold text-brand">{done}/{flow.length}</span>
</div>
<KillChain task={tNow} knowledge={k}/>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-2 gap-1.5">
{[
{l:'STEPS',v:st?.step||0,c:'text-brand',bg:'bg-brand/5 border-brand/10'},
{l:'BUDGET',v:bud,c:bud<4?'text-red':'text-white',bg:bud<4?'bg-red/5 border-red/10':'bg-surface border-border'},
{l:'ALERTS',v:alt,c:alt>0?'text-red':'text-green',bg:alt>0?'bg-red/5 border-red/10':'bg-green/5 border-green/10'},
{l:'PRIV',v:(sim.access_level||'none').toUpperCase(),c:sim.access_level==='admin'?'text-red':sim.access_level==='user'?'text-amber':'text-gray-600',
bg:sim.access_level==='admin'?'bg-red/5 border-red/10':'bg-surface border-border'},
].map(m=>(
<div key={m.l} className={`${m.bg} border rounded-lg px-2.5 py-2`}>
<div className="text-[8px] text-gray-600 font-bold uppercase tracking-widest">{m.l}</div>
<div className={`text-base font-black font-mono mt-0.5 ${m.c}`}>{m.v}</div>
</div>
))}
</div>
{/* Security Posture */}
<div className="bg-card border border-border rounded-lg p-3 space-y-3">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-brand"><I.Shield/></span>
<span className="text-[10px] font-bold text-gray-300 uppercase tracking-widest">Security Posture</span>
</div>
<Gauge label="Stealth" value={stealth} color={stealth>50?"bg-green":"bg-red"} icon={<I.Eye/>}/>
<Gauge label="Efficiency" value={eff} color="bg-brand" icon={<I.Zap/>}/>
<Gauge label="Objective" value={prog} color="bg-cyan" icon={<I.Check/>}/>
{isDone && (
<div className={`mt-2 px-3 py-2 rounded-md text-[10px] font-bold uppercase tracking-wider text-center border
${k.objective_complete?'bg-green/10 border-green/20 text-green':'bg-red/10 border-red/20 text-red'}`}>
{k.objective_complete ? 'βœ“ MISSION COMPLETE' : 'βœ— MISSION TERMINATED'}
</div>
)}
</div>
{/* Intel Vault */}
<div className="bg-card border border-border rounded-lg flex-1 min-h-[100px]">
<div className="px-3 py-2 border-b border-border flex items-center gap-1.5">
<span className="text-brand"><I.Database/></span>
<span className="text-[10px] font-bold text-gray-300 uppercase tracking-widest">Intel Vault</span>
</div>
<div className="p-2">
{Object.keys(k).length===0 ? (
<div className="text-[10px] text-gray-700 text-center py-4">No extracted intelligence</div>
) : (
<pre className="font-mono text-[9px] leading-relaxed text-cyan whitespace-pre-wrap break-all bg-bg/50 rounded p-2 border border-border">
{JSON.stringify(k,null,2)}
</pre>
)}
</div>
</div>
</div>
{/* ── CENTER: Reasoning ── */}
<div className="col-span-4 flex flex-col gap-2">
<div className="bg-card border border-border rounded-lg flex flex-col flex-1">
<div className="px-3 py-2 border-b border-border flex items-center justify-between shrink-0">
<div className="flex items-center gap-1.5">
<span className="text-violet"><I.Brain/></span>
<span className="text-[10px] font-bold text-gray-300 uppercase tracking-widest">Agent Reasoning</span>
</div>
{traces.length>0 && <span className="text-[9px] px-1.5 py-0.5 bg-violet/10 text-violet rounded font-bold border border-violet/15">STEP {traces[traces.length-1]?.step}</span>}
</div>
<div className="flex-1 overflow-y-auto">
<Reasoning traces={traces}/>
</div>
</div>
{/* Reasoning History */}
{traces.length > 1 && (
<div className="bg-card border border-border rounded-lg max-h-[250px] overflow-y-auto">
<div className="px-3 py-2 border-b border-border">
<span className="text-[9px] font-bold text-gray-500 uppercase tracking-widest">Reasoning History</span>
</div>
<div className="p-2 space-y-1">
{[...traces].reverse().slice(1,10).map((t,i)=>(
<div key={i} className="text-[9px] font-mono bg-bg/50 px-2.5 py-1.5 rounded border border-border/50 flex items-center gap-2">
<span className="text-gray-700 w-8 shrink-0">#{t.step}</span>
<span className="text-green font-semibold">{t.action}</span>
<span className="text-gray-600 truncate flex-1">{t.think?.slice(0,60)}...</span>
{t.stealth_mode && <span className="text-amber text-[7px] font-bold">⚠</span>}
</div>
))}
</div>
</div>
)}
</div>
{/* ── RIGHT: Terminal ── */}
<div className="col-span-5 flex flex-col">
<div className="bg-card border border-border rounded-lg flex flex-col flex-1">
<div className="px-3 py-2 border-b border-border flex items-center justify-between shrink-0">
<div className="flex items-center gap-1.5">
<span className="text-brand"><I.Terminal/></span>
<span className="text-[10px] font-bold text-gray-300 uppercase tracking-widest">Action Trajectory</span>
</div>
<div className="flex items-center gap-2">
<span className="text-[9px] font-mono text-gray-700">{logs.length} events</span>
{playing && <span className="text-[8px] px-1.5 py-0.5 bg-green/10 text-green rounded font-bold border border-green/15 animate-pulse">● RECORDING</span>}
</div>
</div>
<div ref={terminalRef} className="flex-1 overflow-y-auto bg-[#050810] p-3 font-mono text-[11px] leading-[1.8] relative">
{/* Terminal header */}
<div className="text-gray-800 text-[9px] mb-3 border-b border-gray-900 pb-2">
<span className="text-gray-700">agent@cloud-red-team</span>:<span className="text-brand">~</span>$ <span className="text-gray-600">./run_simulation --task {tNow} --mode autonomous</span>
</div>
{logs.length===0 ? (
<div className="text-gray-700 text-[10px] cursor-blink">Awaiting execution...</div>
) : (
<div className="space-y-0 pb-4">
{logs.map((log,i)=>{
let s="text-gray-500";
if(log.includes("[!]")) s="text-red font-medium bg-red/[0.03] px-1.5 py-0.5 rounded border-l-2 border-red/50 block";
else if(log.includes("[-]")) s="text-amber";
else if(log.includes("[+]")) s="text-green";
else if(log.includes("[i]")) s="text-brand/70";
return <div key={i} className={`log-in ${s}`}>{log}</div>;
})}
<div ref={logRef} className="h-1"/>
{!isDone && <div className="text-gray-800 text-[10px] cursor-blink mt-1">_</div>}
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
} catch (err) {
console.error('[FATAL] UI Render Error:', err);
return (
<div className="h-screen bg-[#080c14] flex items-center justify-center p-10 font-sans">
<div className="max-w-md w-full bg-[#0d1321] border border-red/30 rounded-xl p-8 text-center shadow-2xl">
<div className="w-16 h-16 bg-red/10 text-red rounded-full flex items-center justify-center mx-auto mb-6">
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<h1 className="text-xl font-black text-white mb-2 uppercase tracking-tighter">Command Center Crash</h1>
<p className="text-sm text-gray-500 mb-6 font-medium">A critical rendering error occurred in the React interface. This is likely due to an undefined variable in the mission state.</p>
<div className="bg-[#080c14] rounded-lg p-4 mb-6 border border-[#1c2640]">
<code className="text-[10px] text-red font-mono break-all">{err.message}</code>
</div>
<button onClick={() => window.location.reload()} className="w-full bg-[#4f8ff7] py-3 rounded-lg text-sm font-bold text-white hover:bg-brand/90 transition-all uppercase tracking-widest">
Reboot Interface
</button>
</div>
</div>
);
}
};
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
</body>
</html>