document.addEventListener('DOMContentLoaded', () => { let currentTaskId = 'easy'; let currentReward = 0.0; const taskBtns = document.querySelectorAll('.task-btn'); const resetBtn = document.getElementById('reset-btn'); const clearLogBtn = document.getElementById('clear-log'); const resourceDisplay = document.getElementById('resource-display'); const actionLog = document.getElementById('action-log'); const manualInput = document.getElementById('manual-input'); const runBtn = document.getElementById('run-action'); const currentTaskDisplay = document.getElementById('current-task-display'); const currentRewardDisplay = document.getElementById('current-reward'); const envStatus = document.getElementById('env-status'); const resourceCount = document.getElementById('resource-count'); // --- Core API Functions --- async function resetEnv(taskId) { log(`Resetting environment for task: ${taskId}...`, 'system'); envStatus.textContent = 'Resetting...'; envStatus.className = 'status-badge'; currentReward = 0.0; currentRewardDisplay.textContent = '0.00'; try { const response = await fetch('/reset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ task_id: taskId }) }); const data = await response.json(); const obs = data.observation || data; updateUIFromObs(obs, false, data); log(`Environment ready. ${obs.info || ''}`, 'system'); showToast(`â Task "${taskId}" loaded`); } catch (err) { log(`Error resetting: ${err.message}`, 'error'); envStatus.textContent = 'Error'; } } async function stepEnv(actionObj) { log(`âļ Executing: ${formatAction(actionObj)}`, 'action'); envStatus.textContent = 'Processing...'; try { const response = await fetch('/step', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: actionObj }) }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const msg = errorData.detail ? (typeof errorData.detail === 'string' ? errorData.detail : JSON.stringify(errorData.detail)) : response.statusText; throw new Error(`Server ${response.status}: ${msg}`); } const data = await response.json(); const obs = data.observation || data; const reward = data.reward !== undefined ? data.reward : (obs.reward || 0); const done = data.done !== undefined ? data.done : (obs.done || false); updateUIFromObs(obs, true, data); if (obs.status) { log(`Response: ${obs.status}`, 'system'); } if (obs.info) { log(`âšī¸ ${obs.info}`, 'system'); } if (reward > 0) { log(`đ Reward: +${reward.toFixed(2)}`, 'reward'); } if (done) { log(`đ¯ Task Completed!`, 'reward'); showToast('đ¯ Task Completed Successfully!'); } } catch (err) { log(`â Execution failed: ${err.message}`, 'error'); envStatus.textContent = 'Error'; showToast(`Error: ${err.message}`); } } // --- UI Update --- function updateUIFromObs(obs, isStep, data) { const reward = data ? (data.reward !== undefined ? data.reward : 0) : 0; const done = data ? (data.done !== undefined ? data.done : false) : false; // Update reward if (isStep && reward !== undefined) { currentReward += reward; currentRewardDisplay.textContent = currentReward.toFixed(2); if (currentReward > 0) { currentRewardDisplay.classList.add('reward-positive'); } } // Update status badge if (done) { envStatus.textContent = 'â Completed'; envStatus.className = 'status-badge completed'; } else { envStatus.textContent = 'Active'; envStatus.className = 'status-badge active'; } // Update Resources const resources = obs.resources || []; if (resources.length > 0) { resourceCount.textContent = `${resources.length} Resources`; resourceDisplay.innerHTML = resources.map(r => createResourceCard(r)).join(''); // Animate cards in document.querySelectorAll('.resource-card').forEach((card, i) => { card.style.animationDelay = `${i * 0.08}s`; card.classList.add('fade-in'); }); } // Update Details (for describe actions) if (obs.details) { const detail = obs.details; resourceCount.textContent = '1 Resource (Detail)'; resourceDisplay.innerHTML = createDetailCard(detail); } // Update Logs (for log actions) if (obs.logs && obs.logs.length > 0) { obs.logs.forEach(entry => { const color = entry.action === 'DeleteStorage' ? 'error' : 'system'; log(` [${entry.timestamp}] ${entry.user} â ${entry.action} (IP: ${entry.ip})`, color); }); } actionLog.scrollTop = actionLog.scrollHeight; } function createResourceCard(r) { const isVulnerable = r.public === true || hasOpenPorts(r); const type = detectResourceType(r); const id = r.id || 'unknown'; let tagsHtml = ''; if (r.tags) { tagsHtml = Object.entries(r.tags).map(([k, v]) => `${k}: ${v}` ).join(''); } if (r.public) { tagsHtml += 'â PUBLIC'; } if (r.state) { tagsHtml += `${r.state}`; } if (r.region) { tagsHtml += `${r.region}`; } // Security groups info for EC2 let sgHtml = ''; if (r.security_groups) { const ports = r.security_groups.flatMap(sg => sg.rules.map(rule => rule.port)); const dangerPorts = [3389, 445, 23]; // RDP, SMB, Telnet const openDangerPorts = ports.filter(p => dangerPorts.includes(p)); if (openDangerPorts.length > 0) { sgHtml = `
${JSON.stringify(detail, null, 2)}