Spaces:
Running
Running
| <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> | |