Your Protection Level
SAFE
All systems operating normally
Events Monitored
0
0 processed and analysed
Active Shields
5
5 defence layers connected
Learning Score
--
Detection maturity rating

🛡 Activity Feed

0 events
All 🧪 Test ⚠ Sim 🚨 Live Critical
🔍
Waiting for events…
The dashboard will populate as AETHER monitors your system.

📊 Learning Progress

Loading…
--/ 100
Detection pass rate: --%  |  Tactic coverage: --%
💡 Recommendations
Run more training cycles to build detection data.

🛡 Protection Shields

Checking…
❤️ Self-Healing
Recovery actions: 0
Threat detectors: 0
Patterns learned: 0
🐝 Swarm Intelligence
Defence agents: 12
Threats blocked: 0
Optimisation particles: 30
📶 WiFi Protection
Protected zones: 0
Scans completed: 0
Threats caught: 0
🧠 Threat Fusion
Connected sensors: 0
Assessments run: 0
Active alerts: 0
🌐 Sentient Grid
Protected regions: 0
Vaccines deployed: 0
Honeypots active: 0
🛡 Sentinel Swarm
Training cycles: --
Detection rate: --
Internal detection engine

🤖 My Bots

Loading…
Choose which defence bots to deploy. Toggle them on or off, review their specialities, and see how they’re performing.
Loading bot roster…

🎯 Training Arena

Loading…
See where your system is weakest, get personalised recommendations, and launch targeted training to strengthen your defences.
System Risk Score
--
Analysing your defences…
🔥 Weakness Heatmap
Loading weakness data…
⚠️ Failing Techniques
No data yet
📋 Training Recommendations
Waiting for assessment…
💡 Bot Recommendations
No recommendations yet

💥 Attack Simulator

Ready
Run simulated attack chains against your defences. Watch your bots respond in real-time and get a grade at the end.
Loading…
Adaptive Difficulty
BasicModerateAdvancedExpertMasterNightmare
Loading attack scenarios…

🏆 Bot Leaderboard & XP

Loading…
Your bots earn XP for every detection, training cycle, and simulation. Level them up to unlock powerful new abilities!
Loading XP data…

🛡️ Sentinel Swarm — Command & Admission Control

Loading…
Live Sentinel Swarm status — DEFCON level, APEX bot admission roster, armed response capabilities, and escalation log. APEX-Only Policy active: only Level 20 bots are cleared for live threat response.
GREEN
All Clear
No active threats. Normal monitoring in progress.
Swarm Process
Checking…
Armed Capabilities
--/--
APEX Bots Cleared
--
Total Bots
--
High-Priority Events
--
Escalation Cases
--
👑 APEX Admission Roster — Cleared for Live Deployment
Loading APEX roster…
APEX-Only Admission Policy (Hard Enforcement)
Sentinel Swarm executes live threat responses — network isolation, IAM quarantine, DEFCON escalation. Only bots that have completed all 20 training levels are authorised to act autonomously. Sub-APEX bots continue training under human supervision and cannot be enrolled or submit escalations.
Response Capabilities — hover for details
Loading capabilities…
Recent Escalation Cases
No escalations logged yet.

💓 Training Heartbeat

Loading…
Live training pulse every 15 minutes. Sparkline shows detection rate over the last 12 training ticks. Green = passed, red = missed detections.
Detection Rate Sparkline
Next tick--
Health
--%
Streak
--
Last Rate
--
24h Runs
--
Recent Training Ticks
Waiting for training data…

🎓 Bot Graduation & Skill Progression

Loading…
Each bot progresses through Bronze → Silver → Gold → Platinum → Diamond → APEX as they level up. Graduating unlocks more powerful defence capabilities.
Loading graduation data…

🏆 Competition & Global Standing

Loading…
See how your defences stack up against the global AETHER community. Your score is calculated from detection rate, MITRE coverage, and training consistency.
🥉
Loading…
--
Calculating percentile…
Detection Rate
--%
MITRE Coverage
--%
Training Reports
--
Pts to Next League
--
Current LeagueNext League
All Leagues
Loading league tiers…

🛡️ Hardening Profiles

12 profiles
One-click presets tailored to your use case. Applies optimal bot configuration, priorities, and training focus instantly.
Loading profiles…

📜 Incident Timeline

Loading…
Visual timeline of attack chains — see which bots caught what, where things were missed, and what was auto-healed.
Waiting for incident data…

📅 Scheduled Training

Off
Schedule automatic training campaigns. Your bots train on your weakest tactics while you sleep.
Enable Scheduled Training
Frequency
Day of Week
Time (UTC)
Tactic Focus (click to toggle, or leave empty for auto-weakest)
Max Duration
Auto-train weakest?

🌍 Community Threat Intel

Loading…
Anonymised threat intelligence from the AETHER community. Install signatures, rulesets, and playbooks shared by other defenders.
Loading community feed…

🛒 Bot Marketplace

Loading…
Community-built configs, rulesets, and plugins to supercharge your bots. All free, reviewed, and one-click install.
Loading marketplace…
Simulation Results
--

🗺️ Detection Strength & Weakness Map

Loading…
Loading tactic coverage…

🤖 Agent Learning Tiers

Loading…
overall_score = (tactic_coverage × 0.4) + (detection_pass_rate × 0.6)
Tactic Coverage
--%
Weight: ×0.4 → -- pts
-- of 14 MITRE tactics covered
Detection Pass Rate
--%
Weight: ×0.6 → -- pts
-- of -- validation steps passed
Awaiting data…
Loading agent learning data…

🧪 Validation Console

0 results
Select an agent and click "Load Results" to see detection validation data.

💻 System Health

Loading…
CPU Usage
--%
Memory
--%
-- / -- GB
Disk
--%
-- / -- GB
AETHER Process
-- MB
Uptime: --
API Status
AETHER Online
Sentinel
Checking…

📈 Event Rate

0 evt/s

📊 Threat Categories

🔗 Defence Layer Connections

0 forwarded

🎯 Autonomous Training

Pending
Training Runs (24h)
0
0 since midnight
0 total all-time
Validation Runs (24h)
0
No result yet
Unique Material
0
0% repeat rate
Sources: --
CALDERA Status
Pending
Waiting for first bootstrap
📋 Last Training Summary
Waiting for training data…
📖 Schedule
Loading schedule…
🧪 Techniques Being Learned
Loading technique data…
'; if (method !== 'GET') return {ok:true, status:'queued', mode:'instant-telemetry', message:'Action accepted by instant telemetry layer. Live backend will supersede this when reachable.'}; return {ok:true, status:'snapshot', mode:'instant-telemetry', items:[], results:[], records:[], generated_at:now}; } function aetherInstantApiResponse(input, init) { if (typeof Response === 'undefined') return null; var method = String((init && init.method) || (input && input.method) || 'GET').toUpperCase(); var rawUrl = ''; try { rawUrl = (typeof input === 'string') ? input : (input && input.url) || ''; } catch (e) {} var path = ''; try { path = new URL(rawUrl || '/', location.origin).pathname.replace(/\/+$/, '') || '/'; } catch (e) { path = rawUrl; } var payload = aetherInstantPayload(path, method); var headers = {'X-AETHER-Telemetry-Mode':'instant'}; if (typeof payload === 'string') { headers['Content-Type'] = payload.indexOf('') === 0 ? 'text/html; charset=utf-8' : 'text/plain; charset=utf-8'; return new Response(payload, {status:200, headers:headers}); } headers['Content-Type'] = 'application/json; charset=utf-8'; return new Response(JSON.stringify(payload), {status:200, headers:headers}); } async function aetherFetchJsonWithInstant(path, timeoutMs, url, preferInstant) { if (preferInstant) { var instantPayload = aetherInstantPayload(path, 'GET'); if (instantPayload) return { data: instantPayload, instant: true }; } var controller = typeof AbortController !== 'undefined' ? new AbortController() : null; var timer = controller ? setTimeout(function() { try { controller.abort(); } catch (e) {} }, timeoutMs || 3000) : null; try { var fetchImpl = window.__AETHER_NATIVE_FETCH__ || window.fetch.bind(window); var options = controller ? { signal: controller.signal } : undefined; var res = await fetchImpl(url || (API_BASE + path), options); if (!res.ok) throw new Error('HTTP ' + res.status); return { data: await res.json(), instant: false }; } catch (e) { var payload = aetherInstantPayload(path, 'GET'); if (payload) return { data: payload, instant: true, error: e }; throw e; } finally { if (timer) clearTimeout(timer); } } function classifyEvent(ev) { const cat = (ev.category || '').toLowerCase(); const text = JSON.stringify(ev.payload || {}).toLowerCase(); if (cat.includes('training') || cat.includes('validation') || cat.includes('caldera') || cat.includes('curriculum') || text.includes('training') || text.includes('bootstrap')) return { type: 'test', label: 'TEST', cls: 'test' }; if (cat.includes('simulation') || cat.includes('red_team') || cat.includes('atomic') || text.includes('simulation') || text.includes('exercise') || text.includes('playbook')) return { type: 'simulation', label: 'SIM', cls: 'simulation' }; return { type: 'real', label: 'LIVE', cls: 'real' }; } function describeEvent(ev) { const cat = (ev.category || '').toLowerCase(); const p = ev.payload || {}; const pri = (ev.priority || 'NORMAL').toUpperCase(); if (cat.includes('recovery') || cat.includes('heal')) return { icon: '\u{1F49A}', iconCls: 'heal', title: 'Self-Healing Action', desc: p.action ? 'Ran recovery action: '+esc(p.action)+'' + (p.component ? ' on '+esc(p.component)+'' : '') : 'System performed a self-healing recovery.' }; if (cat.includes('threat') || cat.includes('detection') || cat.includes('alert') || pri === 'CRITICAL' || pri === 'HIGH') return { icon: '\u{1F6A8}', iconCls: 'alert', title: p.rule_name || p.technique || p.threat_type || 'Threat Detected', desc: buildThreatDesc(p) }; if (cat.includes('wifi') || cat.includes('network') || cat.includes('scan')) return { icon: '\u{1F4F6}', iconCls: 'scan', title: p.event_type || 'Network Scan', desc: p.threat_detected ? 'Threat found: '+esc(p.threat_type||'suspicious activity')+'' + (p.source_ip ? ' from '+esc(p.source_ip)+'' : '') : 'Network scan completed \u2014 no threats found.' }; if (cat.includes('training') || cat.includes('validation') || cat.includes('caldera')) return { icon: '\u{1F3AF}', iconCls: 'training', title: p.step_name || p.summary || 'Training Activity', desc: p.technique ? 'Tested technique '+esc(p.technique)+'' + (p.status ? ' \u2014 result: '+esc(p.status)+'' : '') : (p.summary || 'Autonomous training cycle ran.') }; if (cat.includes('swarm') || cat.includes('mtd') || cat.includes('pso')) return { icon: '\u{1F41D}', iconCls: 'scan', title: p.event_type || 'Swarm Activity', desc: p.mutation ? 'Applied defence mutation: '+esc(p.mutation)+'' : 'Swarm defence activity recorded.' }; if (cat.includes('fusion') || cat.includes('assessment') || cat.includes('cpsa')) return { icon: '\u{1F9E0}', iconCls: 'shield', title: 'Threat Assessment', desc: p.assessment ? 'Assessment: '+esc(p.assessment)+'' : 'Multi-sensor fusion event.' }; return { icon: '\u{1F50D}', iconCls: 'scan', title: ev.category || 'System Event', desc: JSON.stringify(p).slice(0, 160) }; } function buildThreatDesc(p) { var parts = []; if (p.technique || p.technique_id) parts.push('Technique: '+esc(p.technique||p.technique_id)+''); if (p.rule_name || p.rule_id) parts.push('Rule: '+esc(p.rule_name||p.rule_id)+''); if (p.file_path || p.target_file || p.file) parts.push('File: '+esc(p.file_path||p.target_file||p.file)+''); if (p.process || p.process_name || p.pid) parts.push('Process: '+esc(p.process||p.process_name||('PID '+p.pid))+''); if (p.source_ip || p.remote_ip) { var _ip = esc(p.source_ip||p.remote_ip); parts.push('Source: '+_ip+' '); } if (p.action || p.response) parts.push('Action taken: '+esc(p.action||p.response)+''); if (p.severity || p.confidence) parts.push('Severity: '+esc(p.severity||'')+'' + (p.confidence ? ' ('+(p.confidence*100).toFixed(0)+'% confidence)' : '')); return parts.length ? parts.join(' • ') : 'Suspicious activity detected on your system.'; } function addActivityItem(ev) { var eventKey = ev && (ev.event_id || [ev.timestamp || '', ev.category || '', JSON.stringify(ev.payload || {})].join('|')); if (eventKey && seenEventIds.has(eventKey)) return; if (eventKey) seenEventIds.add(eventKey); var feed = document.getElementById('activity-feed'); if (!feedCleared) { feed.innerHTML = ''; feedCleared = true; } var classification = classifyEvent(ev); var desc = describeEvent(ev); var ts = ev.timestamp ? ev.timestamp.slice(11, 19) : new Date().toTimeString().slice(0, 8); var pri = (ev.priority || 'NORMAL').toUpperCase(); var item = document.createElement('div'); item.className = 'activity-item'; item.setAttribute('data-type', classification.type); item.setAttribute('data-priority', pri); item.innerHTML = '
'+desc.icon+'
'+esc(desc.title)+' '+classification.label+''+(pri==='CRITICAL'?' CRITICAL':pri==='HIGH'?' HIGH':'')+'
'+desc.desc+'
'+ts+' • '+(ev.category||'system')+'
'; feed.prepend(item); while (feed.children.length > MAX_FEED_ITEMS) feed.removeChild(feed.lastChild); document.getElementById('feed-count').textContent = feed.children.length + ' events'; } function setStreamStatus(mode) { var pill = document.getElementById('status-pill'); var dot = document.getElementById('ws-dot'); var label = document.getElementById('ws-status'); if (mode === 'socket') { pill.className = 'status-pill online'; dot.className = 'status-dot online'; label.textContent = 'Protected'; return; } if (mode === 'polling') { pill.className = 'status-pill online'; dot.className = 'status-dot online'; label.textContent = window.AETHER_OFFLINE ? 'Instant telemetry' : 'Live via polling'; return; } pill.className = 'status-pill offline'; dot.className = 'status-dot offline'; label.textContent = 'Reconnecting…'; } function connectWS(forceReconnect) { // Skip the WebSocket entirely when no backend is reachable — saves the // ws://localhost:8900 reconnect storm (mixed-content blocked over https). if (window.AETHER_OFFLINE) { setStreamStatus('polling'); return; } // Don't attempt ws:// from an https page — browsers block it as mixed content. try { if (location.protocol === 'https:' && WS_URL && WS_URL.indexOf('ws://') === 0) { setStreamStatus('polling'); return; } } catch (e) {} if (forceReconnect && ws) { try { ws.close(); } catch (e) {} ws = null; } if (wsBackoffTimer) { clearTimeout(wsBackoffTimer); wsBackoffTimer = null; } if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return; ws = new WebSocket(WS_URL); ws.onopen = function() { connected = true; wsFailures = 0; wsBackoffTimer = null; setStreamStatus('socket'); }; ws.onclose = function() { connected = false; wsFailures += 1; ws = null; setStreamStatus('polling'); if (window.__AETHER_SHUTTING_DOWN__) return; // Exponential backoff — retries indefinitely (never gives up) var stepIdx = Math.min(wsFailures - 1, WS_BACKOFF_STEPS.length - 1); var delay = WS_BACKOFF_STEPS[stepIdx] * 1000; wsBackoffTimer = setTimeout(function() { connectWS(false); }, delay); }; ws.onerror = function() { ws.close(); }; ws.onmessage = function(msg) { try { var ev = JSON.parse(msg.data); eventCount++; eventsThisSecond++; addActivityItem(ev); } catch(e) {} }; } async function fetchDashboard() { var apiBadge = document.getElementById('api-status-badge'); try { var res = await fetch(API_BASE + '/api/telemetry/dashboard'); if (!res.ok) return; var d = await res.json(); updateDashboard(d); if (!connected) setStreamStatus('polling'); if (d.recent_events && d.recent_events.length) { d.recent_events.slice().reverse().forEach(function(ev) { addActivityItem(ev); }); } apiBadge.textContent = window.AETHER_OFFLINE ? 'Instant Telemetry' : 'All Systems Online'; apiBadge.className = 'badge badge-green'; } catch(e) { apiBadge.textContent = 'API Offline'; apiBadge.className = 'badge badge-red'; setStreamStatus('offline'); } } function updateDashboard(d) { var tl = d.threat_level || 0; var fill = document.getElementById('threat-fill'); var heroCard = document.getElementById('threat-hero'); var protectionPct = Math.max(0, 100 - tl * 100); fill.style.width = protectionPct + '%'; if (tl > 0.7) { document.getElementById('threat-level').textContent = 'AT RISK'; document.getElementById('threat-level').style.color = 'var(--accent-red)'; fill.style.background = 'var(--accent-red)'; heroCard.style.borderLeftColor = 'var(--accent-red)'; document.getElementById('threat-posture').textContent = 'Threats actively detected \u2014 action required'; } else if (tl > 0.4) { document.getElementById('threat-level').textContent = 'CAUTION'; document.getElementById('threat-level').style.color = 'var(--accent-orange)'; fill.style.background = 'var(--accent-orange)'; heroCard.style.borderLeftColor = 'var(--accent-orange)'; document.getElementById('threat-posture').textContent = 'Elevated activity detected \u2014 monitoring closely'; } else { document.getElementById('threat-level').textContent = 'SAFE'; document.getElementById('threat-level').style.color = 'var(--accent-green)'; fill.style.background = 'var(--accent-green)'; heroCard.style.borderLeftColor = 'var(--accent-green)'; document.getElementById('threat-posture').textContent = 'All systems operating normally'; } var bus = d.event_bus || {}; document.getElementById('events-published').textContent = bus.published || 0; document.getElementById('events-delivered').textContent = (bus.delivered || 0) + ' processed and analysed'; document.getElementById('uptime').textContent = fmtUptime(d.uptime_s || 0); document.getElementById('phases-active').textContent = Object.keys(d.phases || {}).length; var bridges = d.bridges || []; document.getElementById('bridges-count').textContent = bridges.length + ' defence layers connected'; var totalFwd = 0; var bar = document.getElementById('bridge-bar'); bar.innerHTML = ''; bridges.forEach(function(b) { totalFwd += b.events_forwarded || 0; var chip = document.createElement('div'); chip.className = 'bridge-chip'; chip.innerHTML = ''+esc(b.bridge)+' \u2192 '+(b.events_forwarded||0)+''; bar.appendChild(chip); }); document.getElementById('bridge-total').textContent = totalFwd + ' forwarded'; var phases = d.phases || {}; updatePhase(phases.phase2_recovery, 'p2-reports', 'p2-detectors', 'p2-patterns', function(p){return [p.total_reports||p.reports||0, p.detectors||p.detector_count||0, p.patterns_learned||0];}); updatePhase(phases.phase3_swarm, 'p3-agents', 'p3-mtd', 'p3-pso', function(p){return [p.perimeter_agents_active||p.perimeter_agents||12, p.mtd_mutations||0, p.pso_particles||30];}); updatePhase(phases.phase4_wifi_dome, 'p4-zones', 'p4-scans', 'p4-threats', function(p){return [p.zones_active||p.zones||0, p.scans_completed||p.total_scans||0, p.threats_detected||0];}); updatePhase(phases.phase5_fusion, 'p5-sensors', 'p5-assessments', 'p5-alerts', function(p){return [p.n_sensors||p.sensors||0, p.total_assessments||0, p.n_active_alerts||0];}); updatePhase(phases.phase6_grid, 'p6-regions', 'p6-vaccines', 'p6-honeypots', function(p){return [p.n_regions||p.regions||0, p.n_vaccines||p.vaccines||0, p.n_honeypots||p.honeypots||0];}); if (bus.categories_seen && Object.keys(bus.categories_seen).length > 0) { renderCatChart(bus.categories_seen); } } function updatePhase(p, id1, id2, id3, extract) { if (!p) return; var vals = extract(p); document.getElementById(id1).textContent = vals[0]; document.getElementById(id2).textContent = vals[1]; document.getElementById(id3).textContent = vals[2]; } async function fetchTrainingMetrics() { try { var res = await fetch(API_BASE + '/api/telemetry/training'); if (!res.ok) return; var data = await res.json(); var t = data.autonomous_training || {}; var activity = t.activity || {}; var schedule = t.schedule || {}; var material = t.material_diversity || {}; document.getElementById('at-wrapper-runs').textContent = activity.wrapper_runs_24h || 0; document.getElementById('at-overnight-runs').textContent = (activity.wrapper_runs_since_midnight || 0) + ' since midnight'; document.getElementById('at-total-runs').textContent = (activity.wrapper_runs_total || 0) + ' total all-time'; document.getElementById('at-validation-runs').textContent = activity.validation_runs_24h || 0; document.getElementById('at-last-status').textContent = schedule.last_status ? 'Last: ' + schedule.last_status : (t.message || 'No result yet'); document.getElementById('at-unique-material').textContent = material.unique_summaries_24h || 0; document.getElementById('at-repeat-rate').textContent = pct(material.repeat_rate_24h) + ' repeat rate'; if (material.curriculum_sources && material.curriculum_sources.length) { document.getElementById('at-sources').textContent = 'Sources: ' + material.curriculum_sources.join(', '); } document.getElementById('at-caldera').textContent = t.caldera_ready ? 'Ready' : 'Pending'; document.getElementById('at-caldera').style.color = t.caldera_ready ? 'var(--accent-green)' : 'var(--accent-orange)'; document.getElementById('at-caldera-sub').textContent = t.caldera_status || 'Waiting for status'; // Last training summary section var summaryEl = document.getElementById('at-last-summary'); if (schedule.last_summary || t.message) { var parts = []; if (schedule.last_summary) parts.push('Result: ' + esc(schedule.last_summary)); if (schedule.last_status) parts.push('Status: ' + esc(schedule.last_status.toUpperCase()) + ''); if (schedule.last_run_at) { var ago = schedule.minutes_since_last_run; parts.push('Last run: ' + (ago > 60 ? Math.floor(ago/60) + 'h ' + Math.round(ago%60) + 'm ago' : Math.round(ago) + ' min ago')); } if (t.message) parts.push('Note: ' + esc(t.message)); summaryEl.innerHTML = parts.join('
'); } // Schedule info var schedEl = document.getElementById('at-schedule-info'); if (schedule.enabled !== undefined) { var sp = []; sp.push('Pack: ' + esc(schedule.pack_label || schedule.pack_id || 'Unknown')); sp.push('Interval: Every ' + (schedule.interval_minutes || '?') + ' minutes'); sp.push('Enabled: ' + (schedule.enabled ? 'YES' : 'NO') + ''); if (schedule.next_run_at) { var next = new Date(schedule.next_run_at); sp.push('Next run: ' + next.toLocaleTimeString()); } if (activity.last_wrapper_seen_at) { sp.push('Last wrapper activity: ' + new Date(activity.last_wrapper_seen_at).toLocaleTimeString()); } schedEl.innerHTML = sp.join('  •  '); } var sc = document.getElementById('st-cycles'); var sr = document.getElementById('st-rate'); if (sc && (sc.textContent === '--' || sc.textContent === '0')) sc.textContent = activity.wrapper_runs_total || activity.validation_runs_24h || '--'; if (sr && (sr.textContent === '--' || sr.textContent === '0')) sr.textContent = pct(1 - (material.repeat_rate_24h || 0)) + ' unique'; var badge = document.getElementById('training-status-badge'); var status = t.status || 'unknown'; badge.textContent = status.toUpperCase(); badge.className = 'badge ' + (status === 'enabled' ? 'badge-green' : status === 'failed' ? 'badge-red' : 'badge-gold'); } catch(e) {} } async function fetchTechniquesForTraining() { try { var data = _cachedLearningData || await fetchLearningData(); if (!data) return; var L = data.learning || {}; if (!L.available) return; var el = document.getElementById('at-techniques-list'); var techs = L.techniques_tested || []; if (!techs.length) { el.textContent = 'No techniques tested yet.'; return; } el.innerHTML = ''; techs.slice(0, 12).forEach(function(t) { var pctVal = Math.round((t.pass_rate || 0) * 100); var color = pctVal >= 70 ? 'var(--accent-green)' : pctVal >= 40 ? 'var(--cyber-gold)' : 'var(--accent-orange)'; var statusColor = t.status === 'needs_training' ? 'var(--accent-red)' : t.status === 'trained' ? 'var(--accent-green)' : 'var(--cyber-gold)'; var row = document.createElement('div'); row.style.cssText = 'display:grid;grid-template-columns:60px 1fr 80px 50px 70px;align-items:center;gap:8px;padding:6px 10px;border-radius:6px;background:rgba(15,23,42,0.4);font-size:11px;'; row.innerHTML = '' + esc(t.id) + '' + '' + esc(t.name) + '' + 'Tested ' + t.times_tested + 'x • ' + (t.pass||0) + 'P/' + (t.fail||0) + 'F/' + (t.warning||0) + 'W' + '
' + '' + esc((t.status||'unknown').replace('_',' ')) + ''; el.appendChild(row); }); if (techs.length > 12) { var more = document.createElement('div'); more.style.cssText = 'font-size:11px;color:var(--text-muted);padding:4px 10px;'; more.textContent = '+ ' + (techs.length - 12) + ' more techniques...'; el.appendChild(more); } } catch(e) {} } var scoreRingChart = null; async function fetchLearning() { try { _cachedLearningData = null; // clear so sentinel stats re-fetches if needed var data = await fetchLearningData(); if (!data) return; var L = data.learning || {}; if (!L.available) { document.getElementById('learning-badge').textContent = 'Sentinel Not Running'; document.getElementById('learning-badge').className = 'badge badge-gold'; // Show helpful empty-state instead of silent blank panel document.getElementById('score-value').textContent = '--'; document.getElementById('learning-score-hero').textContent = '--'; document.getElementById('score-breakdown').innerHTML = 'No Sentinel Swarm data found — start the training engine to populate this panel.'; document.getElementById('tactic-list').innerHTML = '
' + '⚠ Sentinel Swarm database not found
' + 'To populate learning data, Sentinel Swarm must be running and have completed at least one training session.

' + 'How to fix:
' + ' 1. Run LAUNCH CYBER PLATFORM.bat to start all engines
' + ' 2. Open Sentinel Status ↗ to check connection
' + ' 3. Wait for the first training cycle to complete (usually <5 min)
' + '
'; document.getElementById('rec-list').innerHTML = '
🛡 Start Sentinel Swarm to generate personalised recommendations based on your system’s real weaknesses.
'; return; } var score = L.overall_score || 0; document.getElementById('score-value').textContent = score; document.getElementById('learning-score-hero').textContent = score + '%'; document.getElementById('score-breakdown').innerHTML = 'True detection rate: ' + (L.detection_pass_rate_pct || 0) + '%  |  Tactic coverage: ' + (L.tactic_coverage_pct || 0) + '%'; renderScoreRing(score); // ── Detection Correlation Alert ──────────────────────── var alertEl = document.getElementById('detection-alert'); var detCount = L.detection_count || 0; var agentsCount = L.agents_registered || 0; var totalMissed = L.total_missed_detections || 0; var corrStatus = L.correlation_status || ''; var corrNote = L.correlation_note || ''; var missedArr = L.missed_detections || []; var successArr = L.successful_detections || []; var totalTested = (L.techniques_tested || []).length; if (corrNote || totalMissed > 0 || detCount === 0) { alertEl.style.display = 'flex'; var isOk = detCount > 0 && totalMissed === 0; alertEl.className = 'detection-alert' + (isOk ? ' ok' : ''); document.getElementById('da-icon').textContent = isOk ? '\u2705' : '\u26A0\uFE0F'; document.getElementById('da-title').textContent = isOk ? 'Detection Pipeline Healthy' : 'Detection Correlation Warning'; document.getElementById('da-detail').textContent = corrNote || (totalMissed + ' technique(s) executed but NOT detected'); var metricsHtml = ''; metricsHtml += '' + detCount + ' detections in DB'; metricsHtml += '' + agentsCount + ' agents enrolled'; metricsHtml += '' + totalMissed + ' missed detections'; metricsHtml += '' + successArr.length + ' confirmed detections'; document.getElementById('da-metrics').innerHTML = metricsHtml; } else { alertEl.style.display = 'none'; } // ── Tactic bars ──────────────────────────────────────── var tacticList = document.getElementById('tactic-list'); tacticList.innerHTML = ''; (L.tactic_coverage || []).forEach(function(t) { var pctVal = Math.round((t.avg_pass_rate || 0) * 100); var strength = t.strength || 'untested'; var color = strength === 'strong' ? 'var(--accent-green)' : strength === 'moderate' ? 'var(--cyber-gold)' : strength === 'weak' ? 'var(--accent-red)' : 'var(--text-muted)'; var row = document.createElement('div'); row.className = 'tactic-row'; row.innerHTML = '' + esc(t.tactic) + (t.status === 'gap' ? ' (GAP)' : '') + ' [' + strength + ']' + '
' + pctVal + '%'; tacticList.appendChild(row); }); // ── Strength / Weakness Map ──────────────────────────── var swMap = L.strength_weakness_map || {}; var swGrid = document.getElementById('sw-grid'); swGrid.innerHTML = ''; var swBadge = document.getElementById('sw-badge'); var strongArr = swMap.strengths || []; var weakArr = swMap.weaknesses || []; var moderateArr = swMap.moderate || []; var untestedArr = swMap.untested || []; swBadge.textContent = strongArr.length + ' strong, ' + weakArr.length + ' weak, ' + untestedArr.length + ' untested'; swBadge.className = 'badge ' + (weakArr.length > strongArr.length ? 'badge-red' : strongArr.length > 0 ? 'badge-green' : 'badge-gold'); var allTactics = []; function addSwTiles(arr, cls, label) { arr.forEach(function(item) { var tactic = typeof item === 'string' ? item : item.tactic; var rate = typeof item === 'object' ? item.rate : undefined; allTactics.push({ tactic: tactic, cls: cls, label: label, rate: rate }); }); } addSwTiles(weakArr, 'weak', 'WEAK'); addSwTiles(moderateArr, 'moderate', 'MODERATE'); addSwTiles(strongArr, 'strong', 'STRONG'); addSwTiles(untestedArr, 'untested', 'UNTESTED'); allTactics.forEach(function(t) { var tile = document.createElement('div'); tile.className = 'sw-tile ' + t.cls; tile.innerHTML = '
' + esc(t.tactic) + '
' + '
' + (t.rate !== undefined ? Math.round(t.rate * 100) + '%' : '--') + '
' + '
' + t.label + '
'; swGrid.appendChild(tile); }); if (allTactics.length === 0) { swGrid.innerHTML = '
No tactic data yet. Run training to populate.
'; } // ── Recommendations ──────────────────────────────────── var recList = document.getElementById('rec-list'); recList.innerHTML = ''; (L.recommendations || []).forEach(function(r) { var isCritical = r.toUpperCase().indexOf('CRITICAL') === 0; var item = document.createElement('div'); item.className = 'rec-item'; if (isCritical) item.style.borderLeftColor = 'var(--accent-red)'; item.innerHTML = '' + (isCritical ? '\u{1F6A8}' : '\u{1F4A1}') + ' ' + esc(r); recList.appendChild(item); }); // ── Badge + hero sub ─────────────────────────────────── document.getElementById('learning-badge').textContent = 'Score: ' + score + '/100'; document.getElementById('learning-badge').className = 'badge ' + (score >= 70 ? 'badge-green' : score >= 40 ? 'badge-gold' : 'badge-orange'); var weakCount = (L.weak_techniques || []).length; var gapCount = (L.training_gaps || []).length; var subMsg = totalMissed > 0 ? totalMissed + ' techniques MISSED detection' : weakCount > 0 ? weakCount + ' techniques need more training' : gapCount > 0 ? gapCount + ' tactic gaps remaining' : 'Detection maturity is healthy'; document.getElementById('learning-score-sub').textContent = subMsg; } catch(e) { document.getElementById('learning-badge').textContent = 'Offline'; document.getElementById('learning-badge').className = 'badge badge-red'; } } function renderScoreRing(score) { if (scoreRingChart) scoreRingChart.destroy(); var ctx = document.getElementById('scoreRing').getContext('2d'); var color = score >= 70 ? '#34d399' : score >= 40 ? '#eab308' : '#fb923c'; scoreRingChart = new Chart(ctx, { type: 'doughnut', data: { datasets: [{ data: [score, 100 - score], backgroundColor: [color, 'rgba(30,41,59,0.5)'], borderWidth: 0 }] }, options: { cutout: '78%', responsive: false, plugins: { legend: { display: false }, tooltip: { enabled: false } }, animation: { animateRotate: true, duration: 800 } } }); } var rateCtx = document.getElementById('rateChart').getContext('2d'); var rateChart = new Chart(rateCtx, { type: 'line', data: { labels: Array.from({length: RATE_WINDOW}, function(_, i) { return i + 's'; }), datasets: [{ label: 'Events/sec', data: rateSamples.slice(), borderColor: '#22d3ee', backgroundColor: 'rgba(34,211,238,0.08)', fill: true, tension: 0.3, pointRadius: 0, borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { beginAtZero: true, ticks: { color: '#94a3b8', font: { size: 10 } }, grid: { color: 'rgba(34,211,238,0.06)' } } }, animation: { duration: 200 } } }); var catChart = null; function renderCatChart(categories) { var labels = Object.keys(categories).slice(0, 10); var values = labels.map(function(l) { return categories[l]; }); var colors = ['#22d3ee','#34d399','#eab308','#f87171','#c084fc','#ec4899','#a5d6ff','#7ee787','#ffc680','#d2a8ff']; if (catChart) catChart.destroy(); var ctx = document.getElementById('catChart').getContext('2d'); catChart = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ data: values, backgroundColor: colors.slice(0, labels.length), borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#94a3b8', font: { size: 10 } } } } } }); } safeInterval(function() { rateSamples[rateIdx % RATE_WINDOW] = eventsThisSecond; rateIdx++; eventsThisSecond = 0; rateChart.data.datasets[0].data = rateSamples.slice(rateIdx % RATE_WINDOW).concat(rateSamples.slice(0, rateIdx % RATE_WINDOW)); rateChart.update('none'); var avg = rateSamples.reduce(function(a,b){return a+b;},0) / RATE_WINDOW; document.getElementById('rate-badge').textContent = avg.toFixed(1) + ' evt/s'; }, 1000); // ── Shared learning data cache (avoids duplicate API calls) ───────────── var _cachedLearningData = null; async function fetchLearningData() { // Single fetch used by both fetchLearning() and fetchSentinelStats() try { var res = await fetch(API_BASE + '/api/telemetry/learning'); if (!res.ok) return null; var data = await res.json(); _cachedLearningData = data; return data; } catch(e) { return null; } } async function fetchSentinelStats() { // Reuse learning data fetched by fetchLearning() in the same poll cycle var data = _cachedLearningData; if (!data) data = await fetchLearningData(); try { var L = (data && data.learning) || {}; if (L.available) { document.getElementById('sentinel').style.borderColor = 'var(--border-glow)'; document.getElementById('st-cycles').textContent = L.total_reports || '--'; var trueRate = L.detection_pass_rate_pct || 0; document.getElementById('st-rate').textContent = trueRate + '%'; document.getElementById('st-rate').style.color = trueRate >= 70 ? 'var(--accent-green)' : trueRate >= 40 ? 'var(--cyber-gold)' : 'var(--accent-red)'; } } catch(e) { document.getElementById('sentinel').style.borderColor = ''; } } // ── Agent List & Validation Console ────────────────────────────────────── async function fetchAgentList() { try { var res = await fetch(API_BASE + '/api/agents'); if (!res.ok) return; var data = await res.json(); var sel = document.getElementById('vc-agent-select'); var current = sel.value; sel.innerHTML = ''; (data.agents || []).forEach(function(a) { var opt = document.createElement('option'); opt.value = a.id; opt.textContent = a.name + ' (' + a.type + ') — ' + a.status; sel.appendChild(opt); }); if (current) sel.value = current; } catch(e) {} } async function fetchValidationResults() { var container = document.getElementById('vc-results'); var badge = document.getElementById('vc-badge'); try { var agentId = document.getElementById('vc-agent-select').value; var url = API_BASE + '/api/validation/results'; if (agentId) url += '?agent_id=' + encodeURIComponent(agentId); container.innerHTML = '
Loading validation results…
'; badge.textContent = 'Loading…'; var result = await aetherFetchJsonWithInstant('/api/validation/results', 2500, url, true); var data = result.data; var summary = data.summary || {}; var results = data.results || []; // Summary bar var sumEl = document.getElementById('vc-summary'); sumEl.style.display = 'flex'; document.getElementById('vc-total').textContent = summary.total || 0; document.getElementById('vc-detected').textContent = summary.pass || 0; document.getElementById('vc-missed').textContent = summary.warning || 0; document.getElementById('vc-failed').textContent = summary.fail || 0; var pendEl = document.getElementById('vc-pending'); if (pendEl) pendEl.textContent = summary.pending || 0; document.getElementById('vc-true-rate').textContent = (summary.true_detection_rate || 0) + '%'; document.getElementById('vc-missed-rate').textContent = (summary.missed_rate || 0) + '%'; document.getElementById('vc-db-detections').textContent = summary.detection_count_in_db || 0; document.getElementById('vc-badge').textContent = result.instant ? 'Instant snapshot' : results.length + ' results'; // Result rows container.innerHTML = ''; if (results.length === 0) { container.innerHTML = '
No validation results found.
'; return; } results.forEach(function(r) { var ds = r.detection_status || 'failed'; var icon = ds === 'detected' ? '\u2705' : ds === 'missed' ? '\u274C' : '\u26A0\uFE0F'; var row = document.createElement('div'); row.className = 'vc-row ' + ds; row.innerHTML = '' + icon + '' + '
' + esc(r.name || r.technique_name || '?') + '' + '
' + esc(r.technique_id || '') + (r.tactics && r.tactics.length ? ' — ' + r.tactics.join(', ') : '') + '
' + '' + ds + '' + '' + (r.detection_correlated ? '\u{1F517} correlated' : 'no correlation') + '' + '' + (r.timestamp ? new Date(r.timestamp).toLocaleTimeString() : '') + ''; container.appendChild(row); }); } catch(e) { badge.textContent = 'Idle'; var msg = (e && (e.name === 'AbortError' || /aborted/i.test(String(e.message)))) ? 'Backend took too long to respond. The validation service may still be warming up.' : esc(e.message || 'unknown error'); container.innerHTML = '
Validation results unavailable: ' + msg + '
'; } } // Sentinel link fetcher removed — Sentinel is internal-only, not exposed to the site. // ── My Bots — Fetch, render & toggle ───────────────────────────────────── var _botIconMap = { heart: '❤️', bee: '🐝', shuffle: '🔀', honey: '🍯', route: '🐜', wifi: '📶', brain: '🧠', globe: '🌐', shield: '🛡️', // new bots already store emoji directly — keep key aliases for any legacy refs phishing: '🎣', ransomware: '🔐', dns: '🌀', iot: '👁', usb: '💾', memory: '🧬', supply: '📦', darkweb: '🕸', lateral: '🚧', exfil: '🛂', fraud: '💳', vuln: '🔬' }; function _botIcon(iconField) { // Named key → lookup; already-emoji pass-through; fallback 🤖 return _botIconMap[iconField] || iconField || '🤖'; } async function fetchBotRoster() { try { var res = await fetch(API_BASE + '/api/bots/roster'); if (!res.ok) return; var data = await res.json(); renderBotRoster(data); document.getElementById('bots-badge').textContent = data.total_enabled + ' / ' + (data.total_enabled + data.total_disabled) + ' active'; document.getElementById('bots-badge').className = 'badge ' + (data.total_enabled > 0 ? 'badge-green' : 'badge-red'); } catch (e) { document.getElementById('bots-badge').textContent = 'Offline'; document.getElementById('bots-badge').className = 'badge badge-red'; } } function renderBotRoster(data) { var grid = document.getElementById('bot-roster-grid'); grid.innerHTML = ''; (data.bots || []).forEach(function(bot) { var icon = _botIcon(bot.icon); var strengthPct = bot.avg_tactic_strength == null ? null : Math.round(bot.avg_tactic_strength * 100); var strengthColor = strengthPct >= 60 ? 'var(--accent-green)' : strengthPct >= 30 ? 'var(--cyber-gold)' : 'var(--accent-red)'; var detectRateText = bot.detection_rate == null ? '--' : Math.round(bot.detection_rate * 100) + '%'; var strengthText = strengthPct == null ? '--' : strengthPct + '%'; var strengthWidth = strengthPct == null ? 0 : strengthPct; var evidenceLabel = bot.evidence_state === 'tactic-mapped' ? 'Tactic-mapped learning evidence' : bot.evidence_state === 'phase-only' ? 'Phase trained, no tactic-mapped samples yet' : 'No evidence yet'; var card = document.createElement('div'); card.className = 'bot-card' + (bot.enabled ? '' : ' disabled'); card.innerHTML = '
' + '
' + icon + '
' + '
' + esc(bot.display_name) + '
' + '
' + esc(bot.phase) + ' ' + bot.priority + '
' + '
' + '
' + esc(bot.description) + '
' + '
' + (bot.tactics || []).map(function(t) { return '' + esc(t) + ''; }).join('') + '
' + '
' + '
Detect rate: ' + detectRateText + '
' + '
Cycles: ' + (bot.training_cycles || 0) + '
' + '
Techniques: ' + (bot.techniques_detecting || 0) + '/' + (bot.techniques_seen || 0) + '
' + '
Strength: ' + strengthText + '
' + '
' + '
' + '
' + esc(evidenceLabel) + (bot.samples_seen ? ' • ' + bot.samples_detected + '/' + bot.samples_seen + ' samples detected' : '') + '
' + '
' + '' + (bot.enabled ? 'Active' : 'Disabled') + '' + '' + '
'; grid.appendChild(card); }); } async function toggleBot(el) { var botId = el.getAttribute('data-bot-id'); var enabled = el.checked; var payload = { bots: {} }; payload.bots[botId] = { enabled: enabled }; try { await fetch(API_BASE + '/api/bots/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); showToast((enabled ? 'Enabled' : 'Disabled') + ' bot', 'info'); fetchBotRoster(); fetchWeaknessAssessment(); } catch (e) { showToast('Failed to toggle bot: ' + e.message, 'error'); el.checked = !enabled; } } // ── Training Arena — Weakness heatmap & personalised training ───────── var _riskChart = null; var _currentWeakTactics = []; async function fetchWeaknessAssessment() { try { var res = await fetch(API_BASE + '/api/weakness/assessment'); if (!res.ok) return; var data = await res.json(); if (!data.available) { document.getElementById('arena-badge').textContent = 'No data'; document.getElementById('arena-badge').className = 'badge badge-orange'; return; } renderRiskScore(data); renderWeaknessHeatmap(data); renderFailingTechniques(data); renderTrainingRecommendations(data); renderBotRecommendations(data); document.getElementById('arena-badge').textContent = data.risk_level.toUpperCase(); document.getElementById('arena-badge').className = 'badge badge-' + (data.risk_level === 'critical' ? 'red' : data.risk_level === 'elevated' ? 'orange' : 'green'); } catch (e) { document.getElementById('arena-badge').textContent = 'Error'; document.getElementById('arena-badge').className = 'badge badge-red'; } } function renderRiskScore(data) { var score = data.risk_score || 0; var level = data.risk_level || 'low'; var el = document.getElementById('risk-score-value'); el.textContent = score; el.className = 'risk-value ' + level; var detail = document.getElementById('risk-score-detail'); detail.textContent = data.critical_weaknesses.length + ' critical, ' + data.moderate_weaknesses.length + ' moderate, ' + data.strengths.length + ' strong tactics'; // Draw risk ring var canvas = document.getElementById('riskScoreRing'); var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, 72, 72); var colour = level === 'critical' ? '#f87171' : level === 'elevated' ? '#eab308' : '#34d399'; var bgColour = 'rgba(30,41,59,0.5)'; var angle = (score / 100) * Math.PI * 2; ctx.beginPath(); ctx.arc(36, 36, 30, 0, Math.PI * 2); ctx.strokeStyle = bgColour; ctx.lineWidth = 6; ctx.stroke(); ctx.beginPath(); ctx.arc(36, 36, 30, -Math.PI / 2, -Math.PI / 2 + angle); ctx.strokeStyle = colour; ctx.lineWidth = 6; ctx.lineCap = 'round'; ctx.stroke(); } function renderWeaknessHeatmap(data) { var grid = document.getElementById('weakness-heatmap'); grid.innerHTML = ''; _currentWeakTactics = []; var allTactics = (data.critical_weaknesses || []).concat(data.moderate_weaknesses || []).concat(data.strengths || []); allTactics.forEach(function(item) { var cls = item.strength === 'weak' || item.strength === 'untested' ? 'critical' : item.strength === 'moderate' ? 'elevated' : 'strong'; if (cls === 'critical') _currentWeakTactics.push(item.tactic); var tile = document.createElement('div'); tile.className = 'heat-tile ' + cls; var botHtml = (item.covering_bots || []).map(function(b) { return '' + esc(b.name) + ''; }).join(', '); tile.innerHTML = '
' + esc(item.tactic) + '
' + '
' + Math.round(item.pass_rate * 100) + '%
' + '
' + item.strength.toUpperCase() + ' · ' + item.techniques_count + ' techniques
' + (botHtml ? '
' + botHtml + '
' : ''); tile.onclick = function() { trainSingleTactic(item.tactic); }; grid.appendChild(tile); }); } function renderFailingTechniques(data) { var list = document.getElementById('failing-tech-list'); list.innerHTML = ''; var techs = data.failing_techniques || []; if (techs.length === 0) { list.innerHTML = '
✅ No failing techniques detected — nice!
'; return; } techs.forEach(function(t) { var el = document.createElement('div'); el.className = 'fail-tech'; el.innerHTML = '' + esc(t.id) + '' + '' + esc(t.name) + '' + 'tested ' + t.times_tested + 'x'; list.appendChild(el); }); } function renderTrainingRecommendations(data) { var container = document.getElementById('training-recs'); container.innerHTML = ''; var recs = data.training_recommendations || []; if (recs.length === 0) { container.innerHTML = '
✅ No urgent training needed.
'; return; } recs.slice(0, 8).forEach(function(rec) { var item = document.createElement('div'); item.className = 'train-rec-item'; item.innerHTML = '
' + '
' + esc(rec.tactic) + '
' + esc(rec.reason) + '
' + ''; container.appendChild(item); }); } function renderBotRecommendations(data) { var container = document.getElementById('bot-rec-list'); container.innerHTML = ''; var recs = data.bot_recommendations || []; if (recs.length === 0) { container.innerHTML = '
✅ All relevant bots are active.
'; return; } recs.forEach(function(rec) { var el = document.createElement('div'); el.className = 'bot-rec-item'; el.innerHTML = '' + esc(rec.action) + '' + '' + esc(rec.bot_name) + '' + '' + esc(rec.reason) + '' + ''; container.appendChild(el); }); } async function trainSingleTactic(tactic) { showToast('Starting training for ' + tactic + '…', 'info'); try { var res = await fetch(API_BASE + '/api/training/custom', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tactics: [tactic] }), }); var data = await res.json(); if (data.status === 'completed') { showToast('Training completed for ' + tactic, 'success'); } else { showToast('Training queued for ' + tactic, 'info'); } setTimeout(fetchWeaknessAssessment, 2000); } catch (e) { showToast('Training failed: ' + e.message, 'error'); } } async function trainAllWeak() { if (_currentWeakTactics.length === 0) { showToast('No weak tactics to train!', 'info'); return; } var btn = document.getElementById('train-all-weak-btn'); btn.disabled = true; btn.textContent = '⏳ Training…'; showToast('Starting training for ' + _currentWeakTactics.length + ' weak tactics…', 'info'); try { var res = await fetch(API_BASE + '/api/training/custom', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tactics: _currentWeakTactics }), }); var data = await res.json(); showToast('Training ' + data.status + ' for ' + _currentWeakTactics.length + ' tactics', data.status === 'completed' ? 'success' : 'info'); setTimeout(fetchWeaknessAssessment, 2000); } catch (e) { showToast('Training failed: ' + e.message, 'error'); } finally { btn.disabled = false; btn.textContent = '🎯 Train Weakest Tactics'; } } async function enableBotFromRec(botId) { var payload = { bots: {} }; payload.bots[botId] = { enabled: true }; try { await fetch(API_BASE + '/api/bots/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); showToast('Bot enabled!', 'success'); fetchBotRoster(); fetchWeaknessAssessment(); } catch (e) { showToast('Failed: ' + e.message, 'error'); } } // ── Enhancement 1: Bot XP & Leaderboard ────────────────────────────────── async function fetchBotXP() { try { var res = await fetch(API_BASE + '/api/bots/xp'); if (!res.ok) return; var data = await res.json(); renderXPLeaderboard(data); document.getElementById('xp-badge').textContent = 'Total XP: ' + data.total_xp; } catch(e) { document.getElementById('xp-badge').textContent = 'Offline'; document.getElementById('xp-badge').className = 'badge badge-red'; } } function renderXPLeaderboard(data) { var board = document.getElementById('xp-leaderboard'); board.innerHTML = ''; var lb = data.leaderboard || []; lb.forEach(function(bot, idx) { var rank = idx + 1; var rankCls = rank === 1 ? 'gold' : rank === 2 ? 'silver' : rank === 3 ? 'bronze' : ''; var icon = _botIcon(bot.icon); // Prestige-aware badge: APEX Omega gets rainbow shimmer, others get prestige colour var prestige = bot.prestige || 0; var prestigeStars = prestige > 0 ? ' ' + '★'.repeat(prestige) : ''; var badgeCls = prestige >= 5 ? 'max omega' : prestige > 0 ? 'max' : bot.level >= 16 ? 'max' : bot.level >= 10 ? 'high' : bot.level >= 5 ? 'mid' : 'low'; var prestigeColour = bot.prestige_colour || ''; var levelBadgeStyle = prestigeColour ? 'style="background:' + prestigeColour + ';color:#000;"' : ''; var abilitiesHtml = (bot.unlocked_abilities || []).slice(-5).map(function(a) { return '' + esc(a) + ''; }).join(''); // Progress bar: if APEX (prestige ≥ 1) show prestige progress, else normal XP progress var barPct = prestige > 0 ? (bot.prestige_progress_pct || 0) : (bot.progress_pct || 0); var barColour = prestigeColour || (bot.level >= 20 ? '#fbbf24' : ''); var barStyle = barColour ? 'background:' + barColour + ';' : ''; var xpLabel = prestige > 0 ? (bot.xp + ' XP — Prestige ' + prestige + prestigeStars + ' → ' + (bot.next_prestige_xp || 'MAX') + ' XP') : (bot.xp + ' / ' + bot.next_level_xp + ' XP'); var row = document.createElement('div'); row.className = 'xp-bot-row'; row.innerHTML = '
#' + rank + '
' + '
' + icon + '
' + '
' + '
' + esc(bot.display_name) + (!bot.enabled ? ' (disabled)' : '') + '
' + '
' + esc(bot.title) + esc(prestigeStars) + '
' + '
' + esc(bot.xp_source_label || 'XP source unknown') + (bot.samples_seen ? ' • ' + bot.samples_detected + '/' + bot.samples_seen + ' mapped samples' : '') + '
' + (abilitiesHtml ? '
' + abilitiesHtml + '
' : '') + '
' + '
' + '
' + '
' + xpLabel + '
' + '
' + '
' + (prestige > 0 ? '👑' : bot.level) + '
' + '
' + bot.xp + ' XP
'; board.appendChild(row); }); } async function apexSeedAll() { var btn = document.getElementById('apex-seed-btn'); if (btn) { btn.disabled = true; btn.textContent = '⚡ Seeding…'; } showToast('Initiating APEX Seed Protocol — elevating all bots to maximum…', 'info'); try { var res = await fetch(API_BASE + '/api/bots/apex-seed', { method: 'POST' }); var data = await res.json(); if (res.ok) { showToast('✅ ' + data.message + ' — ' + data.bots_elevated + ' bots at ' + Math.round(data.learned_confidence_floor * 100) + '% confidence floor', 'success'); setTimeout(function() { fetchBotXP(); fetchWeaknessAssessment(); fetchTrainingMetrics(); }, 1500); } else { showToast('APEX Seed failed: ' + (data.error || 'Unknown error'), 'error'); } } catch(e) { showToast('APEX Seed failed: ' + e.message, 'error'); } finally { if (btn) { btn.disabled = false; btn.textContent = '⚡ APEX Seed All Bots'; } } } function toggleEyesOnlyPanel() { var panel = document.getElementById('eyes-only-panel'); var button = document.getElementById('eyes-only-toggle'); if (!panel || !button) return; var open = panel.style.display !== 'none'; panel.style.display = open ? 'none' : 'block'; button.textContent = open ? '👁 Cyber Citizen Eyes Only' : '🙈 Hide Eyes Only'; } function renderEyesOnlyStats(bridge) { var recent = bridge.recent_5m || {}; var cadence = bridge.training_interval_seconds || 0; var cadenceText = cadence ? Math.round(cadence) + 's' : '--'; var detectRate = recent.detection_rate == null ? '--' : Math.round(recent.detection_rate * 100) + '%'; document.getElementById('eo-cadence').textContent = cadenceText; document.getElementById('eo-window-note').textContent = 'Showing the last ' + (recent.window_minutes || 5) + ' minutes'; document.getElementById('eo-absorbed').textContent = recent.absorbed_samples || 0; document.getElementById('eo-active-agents').textContent = (recent.active_agents || 0) + ' active engines'; document.getElementById('eo-detections').textContent = recent.detections || 0; document.getElementById('eo-detect-rate').textContent = detectRate + ' absorption confirmed'; document.getElementById('eo-techniques').textContent = (recent.unique_techniques || []).length; document.getElementById('eo-last-proof').textContent = recent.absorption_confirmed ? 'Recent absorption verified' : 'No absorbed samples in window'; var list = document.getElementById('eyes-only-agents'); var agents = bridge.agents || {}; var rows = Object.keys(agents).map(function(agentId) { var agent = agents[agentId] || {}; var slice = agent.recent_5m || {}; return { id: agentId, name: agent.agent_name || agentId, absorbed: slice.absorbed_samples || 0, detections: slice.detections || 0, rate: slice.detection_rate, lastEventAt: slice.last_event_at, techniques: slice.unique_techniques || [] }; }).filter(function(row) { return row.absorbed > 0 || row.detections > 0 || row.techniques.length > 0; }).sort(function(a, b) { return b.absorbed - a.absorbed; }); if (!rows.length) { list.innerHTML = '
No engines absorbed new samples in the current 5-minute window.
'; return; } list.innerHTML = rows.map(function(row) { var rateText = row.rate == null ? '--' : Math.round(row.rate * 100) + '%'; var proof = row.lastEventAt ? new Date(row.lastEventAt).toLocaleTimeString() : 'no recent timestamp'; return '
' + '
' + esc(row.name) + '
' + esc(row.id) + '
' + '
Absorbed
' + row.absorbed + '
' + '
Detected
' + row.detections + '
' + '
Rate
' + rateText + '
' + '
Techniques: ' + esc(row.techniques.join(', ') || 'none') + '
Last proof: ' + esc(proof) + '
' + '
'; }).join(''); } // ── Enhancement 2: Attack Simulator ────────────────────────────────────── var scenariosLoaded = false; var scenariosRetryTimer = null; var scenariosRequestInFlight = false; function setScenarioState(message, badgeText, badgeClass, showRetry) { var grid = document.getElementById('sim-scenario-grid'); var badge = document.getElementById('sim-badge'); if (!grid || !badge) return; badge.textContent = badgeText; badge.className = 'badge ' + badgeClass; grid.innerHTML = '
' + esc(message) + (showRetry ? '
' : '') + '
'; } function scheduleScenarioRetry(delayMs) { if (scenariosRetryTimer) clearTimeout(scenariosRetryTimer); scenariosRetryTimer = setTimeout(function() { fetchScenarios(true); }, delayMs); } function retryScenarios() { if (scenariosRetryTimer) clearTimeout(scenariosRetryTimer); fetchScenarios(true); } async function fetchScenarios(force) { if (scenariosRequestInFlight) return; if (scenariosLoaded && !force) return; scenariosRequestInFlight = true; setScenarioState('Loading attack scenarios…', 'Loading', 'badge-red', false); try { var result = await aetherFetchJsonWithInstant('/api/simulator/scenarios', 2500, null, !force); var data = result.data; var scenarios = Array.isArray(data.scenarios) ? data.scenarios : []; if (!scenarios.length) throw new Error('No scenarios available'); renderScenarios(scenarios); scenariosLoaded = true; var badge = document.getElementById('sim-badge'); if (badge) { badge.textContent = result.instant ? 'Instant Scenarios' : scenarios.length + ' Scenarios'; badge.className = 'badge badge-red'; } } catch(e) { scenariosLoaded = false; setScenarioState('Attack scenarios are temporarily unavailable.', 'Unavailable', 'badge-red', true); } finally { scenariosRequestInFlight = false; } } function renderScenarios(scenarios) { var grid = document.getElementById('sim-scenario-grid'); if (!grid) return; grid.innerHTML = ''; if (!scenarios.length) { setScenarioState('No attack scenarios are available.', 'Empty', 'badge-red', true); return; } scenarios.forEach(function(s) { var tactics = Array.isArray(s.tactics) ? s.tactics : []; var steps = Array.isArray(s.steps) ? s.steps : []; var card = document.createElement('div'); card.className = 'sim-card'; card.innerHTML = '
' + '
' + (s.icon || '💥') + '
' + '
' + esc(s.name || 'Unnamed Scenario') + '
' + '' + esc(s.difficulty || 'unknown') + '
' + '
' + '
' + esc(s.description || 'No description available.') + '
' + '
' + tactics.map(function(t) { return '' + esc(t) + ''; }).join('') + '
' + ''; grid.appendChild(card); }); } async function runSimulation(scenarioId, btn) { btn.disabled = true; btn.textContent = '⏳ Running…'; showToast('Starting attack simulation…', 'info'); try { var res = await fetch(API_BASE + '/api/simulator/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scenario_id: scenarioId }), }); var data = await res.json(); if (res.ok) { showSimResults(data); fetchBotXP(); // refresh XP after sim } else { showToast('Simulation failed: ' + (data.error || 'Unknown'), 'error'); } } catch(e) { showToast('Simulation error: ' + e.message, 'error'); } finally { btn.disabled = false; btn.textContent = '⚡ Run'; } } function showSimResults(data) { document.getElementById('sim-modal-title').textContent = data.scenario + ' — Results'; var gradeEl = document.getElementById('sim-grade'); gradeEl.textContent = data.grade; var gradeCls = data.grade.startsWith('A') ? 'grade-a' : data.grade === 'B' ? 'grade-b' : data.grade === 'C' ? 'grade-c' : data.grade === 'D' ? 'grade-d' : 'grade-f'; gradeEl.className = 'sim-grade ' + gradeCls; document.getElementById('sim-score-text').textContent = data.percentage + '% — ' + data.total_score + ' / ' + data.max_score + ' points (' + data.difficulty + ' difficulty)'; document.getElementById('sim-xp-awarded').textContent = '🏆 +' + data.xp_awarded + ' XP awarded to all bots!'; var list = document.getElementById('sim-step-list'); list.innerHTML = ''; (data.steps || []).forEach(function(step) { var el = document.createElement('div'); el.className = 'sim-step ' + (step.detected ? 'detected' : 'missed'); el.innerHTML = '
' + (step.detected ? '✅' : '❌') + '
' + '
' + '
' + esc(step.phase) + '
' + '
' + esc(step.name) + '
' + '
' + esc(step.technique) + ' · confidence: ' + Math.round(step.confidence * 100) + '%
' + '
' + '
' + (step.detected ? 'DETECTED' : 'MISSED') + '
'; list.appendChild(el); }); document.getElementById('sim-modal-overlay').classList.add('active'); } function closeSimModal() { document.getElementById('sim-modal-overlay').classList.remove('active'); } function setPanelMessage(containerId, message, actionHtml) { var container = document.getElementById(containerId); if (!container) return; container.innerHTML = '
' + esc(message) + (actionHtml ? '
' + actionHtml + '
' : '') + '
'; } // ── Enhancement 3: Adaptive Difficulty ─────────────────────────────────── async function fetchDifficulty() { try { var res = await fetch(API_BASE + '/api/training/difficulty'); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var current = data.current || {}; document.getElementById('diff-level-name').textContent = current.name || 'Unknown'; document.getElementById('diff-level-num').textContent = 'Level ' + (current.level || 1) + ' / 6 — Score: ' + data.overall_score; var fill = document.getElementById('diff-meter-fill'); var pctW = ((current.level || 1) / 6) * 100; fill.style.width = pctW + '%'; fill.className = 'diff-meter-fill level-' + (current.level || 1); if (data.next_level) { document.getElementById('diff-level-num').textContent += ' — ' + data.points_to_next + ' pts to ' + data.next_level.name; } } catch(e) { document.getElementById('diff-level-name').textContent = 'Unavailable'; document.getElementById('diff-level-num').textContent = 'Adaptive difficulty data will retry automatically.'; } } // ── Enhancement 5: Hardening Profiles ──────────────────────────────────── var _botNameMap = { self_healing: 'Self-Healing', swarm_ids: 'Swarm IDS', mtd_defence: 'MTD', bee_optimizer: 'Bee Optimizer', ant_router: 'Ant Router', wifi_dome: 'WiFi Dome', threat_fusion: 'Threat Fusion', sentient_grid: 'Sentient Grid' }; async function fetchProfiles() { try { var res = await fetch(API_BASE + '/api/profiles'); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var profiles = Array.isArray(data.profiles) ? data.profiles : []; renderProfiles(profiles); document.getElementById('profiles-badge').textContent = profiles.length + ' profiles'; } catch(e) { document.getElementById('profiles-badge').textContent = 'Retrying'; setPanelMessage('profile-grid', 'Profiles are temporarily unavailable. Auto-retrying…', ''); } } function renderProfiles(profiles) { var grid = document.getElementById('profile-grid'); grid.innerHTML = ''; if (!profiles.length) { setPanelMessage('profile-grid', 'No hardening profiles are available right now.', ''); return; } profiles.forEach(function(p) { var botsHtml = Object.keys(p.bots || {}).map(function(bid) { var on = p.bots[bid]; var name = _botNameMap[bid] || bid; return '' + esc(name) + ''; }).join(''); var card = document.createElement('div'); card.className = 'profile-card'; card.innerHTML = '
' + '
' + p.icon + '
' + '
' + esc(p.name) + '
' + esc(p.recommended_for) + '
' + '
' + '
' + esc(p.description) + '
' + '
' + botsHtml + '
' + '
Focus: ' + (p.training_focus || []).join(', ') + '
' + ''; grid.appendChild(card); }); } async function applyProfile(profileId, btn) { btn.disabled = true; btn.textContent = '⏳ Applying…'; try { var res = await fetch(API_BASE + '/api/profiles/apply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profile_id: profileId }), }); var data = await res.json(); showToast(data.message || 'Profile applied!', 'success'); fetchBotRoster(); fetchWeaknessAssessment(); } catch(e) { showToast('Failed: ' + e.message, 'error'); } finally { btn.disabled = false; btn.textContent = '⚡ Apply'; } } // ── Enhancement 6: Incident Timeline ───────────────────────────────────── async function fetchIncidentTimeline() { try { var res = await fetch(API_BASE + '/api/incidents/timeline'); if (!res.ok) return; var data = await res.json(); renderIncidents(data.incidents || []); document.getElementById('incident-badge').textContent = data.total + ' chains'; } catch(e) { document.getElementById('incident-badge').textContent = 'Error'; } } function renderIncidents(incidents) { var container = document.getElementById('incident-timeline'); container.innerHTML = ''; if (incidents.length === 0) { container.innerHTML = '
✅ No incident chains detected. All clear!
'; return; } incidents.slice(0, 8).forEach(function(chain) { var el = document.createElement('div'); el.className = 'incident-chain'; var dotsHtml = chain.steps.map(function(step, i) { var cls = step.detected ? 'detected' : 'missed'; var icon = step.detected ? '✓' : '✗'; var arrow = i < chain.steps.length - 1 ? '' : ''; return '
' + icon + '
' + arrow; }).join(''); el.innerHTML = '
' + '' + chain.severity + '' + '' + chain.detected_steps + ' / ' + chain.total_steps + ' detected (' + Math.round(chain.detection_rate * 100) + '%)' + '
' + '
' + dotsHtml + '
' + '
' + (chain.start_time || '') + '
'; container.appendChild(el); }); } // ── Enhancement 7: Scheduled Training ──────────────────────────────────── var _allMitreTactics = [ 'Reconnaissance', 'Resource Development', 'Initial Access', 'Execution', 'Persistence', 'Privilege Escalation', 'Defense Evasion', 'Credential Access', 'Discovery', 'Lateral Movement', 'Collection', 'Command and Control', 'Exfiltration', 'Impact' ]; var _selectedSchedTactics = []; function initScheduleChips() { var container = document.getElementById('sched-tactic-chips'); container.innerHTML = ''; _allMitreTactics.forEach(function(t) { var chip = document.createElement('span'); chip.className = 'sched-tactic-chip' + (_selectedSchedTactics.indexOf(t) >= 0 ? ' selected' : ''); chip.textContent = t; chip.onclick = function() { var idx = _selectedSchedTactics.indexOf(t); if (idx >= 0) { _selectedSchedTactics.splice(idx, 1); chip.classList.remove('selected'); } else { _selectedSchedTactics.push(t); chip.classList.add('selected'); } }; container.appendChild(chip); }); } async function fetchSchedule() { try { var res = await fetch(API_BASE + '/api/training/schedule'); if (!res.ok) throw new Error('HTTP ' + res.status); var s = await res.json(); document.getElementById('sched-enabled').checked = s.enabled; document.getElementById('sched-frequency').value = s.frequency || 'weekly'; document.getElementById('sched-day').value = s.day_of_week || 'saturday'; document.getElementById('sched-time').value = s.time_utc || '02:00'; document.getElementById('sched-duration').value = String(s.max_duration_minutes || 30); document.getElementById('sched-auto-weak').checked = s.auto_weakest !== false; _selectedSchedTactics = s.tactics || []; initScheduleChips(); document.getElementById('sched-badge').textContent = s.enabled ? 'Active' : 'Off'; document.getElementById('sched-badge').className = 'badge ' + (s.enabled ? 'badge-green' : 'badge-orange'); } catch(e) { document.getElementById('sched-badge').textContent = 'Retrying'; document.getElementById('sched-badge').className = 'badge badge-orange'; } } async function saveSchedule() { var payload = { enabled: document.getElementById('sched-enabled').checked, frequency: document.getElementById('sched-frequency').value, day_of_week: document.getElementById('sched-day').value, time_utc: document.getElementById('sched-time').value, max_duration_minutes: parseInt(document.getElementById('sched-duration').value), auto_weakest: document.getElementById('sched-auto-weak').checked, tactics: _selectedSchedTactics, }; try { await fetch(API_BASE + '/api/training/schedule', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); document.getElementById('sched-badge').textContent = payload.enabled ? 'Active' : 'Off'; document.getElementById('sched-badge').className = 'badge ' + (payload.enabled ? 'badge-green' : 'badge-orange'); showToast('Schedule saved!', 'success'); } catch(e) { showToast('Failed to save: ' + e.message, 'error'); } } // ── Enhancement 4: Community Threat Intel Feed ─────────────────────────── async function fetchCommunityFeed() { try { var res = await fetch(API_BASE + '/api/community/feed'); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var entries = Array.isArray(data.entries) ? data.entries : []; renderCommunityFeed(entries); document.getElementById('cti-badge').textContent = entries.length + ' entries'; } catch(e) { document.getElementById('cti-badge').textContent = 'Retrying'; setPanelMessage('community-feed', 'Community feed is temporarily unavailable. Auto-retrying…', ''); } } function renderCommunityFeed(entries) { var container = document.getElementById('community-feed'); container.innerHTML = ''; if (!entries.length) { setPanelMessage('community-feed', 'No community intelligence entries are available right now.', ''); return; } entries.forEach(function(e) { var el = document.createElement('div'); el.className = 'cti-entry'; el.innerHTML = '
' + esc(e.type) + '
' + '
' + '
' + esc(e.title) + '
' + '
' + esc(e.description) + '
' + '
' + 'by ' + esc(e.author) + '' + '❤️ ' + e.likes + '' + '' + e.severity + '' + (e.verified ? '✓ Verified' : '') + '' + (e.timestamp || '').slice(0, 10) + '' + '
' + '
' + (e.tactics || []).map(function(t) { return '' + esc(t) + ''; }).join(' ') + '
' + '
'; container.appendChild(el); }); } // ── Enhancement 8: Bot Marketplace ─────────────────────────────────────── async function fetchMarketplace() { try { var res = await fetch(API_BASE + '/api/marketplace/listings'); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var listings = Array.isArray(data.listings) ? data.listings : []; renderMarketplace(listings); document.getElementById('mp-badge').textContent = listings.length + ' items'; } catch(e) { document.getElementById('mp-badge').textContent = 'Retrying'; setPanelMessage('mp-grid', 'Marketplace is temporarily unavailable. Auto-retrying…', ''); } } function renderMarketplace(listings) { var grid = document.getElementById('mp-grid'); grid.innerHTML = ''; if (!listings.length) { setPanelMessage('mp-grid', 'No marketplace listings are available right now.', ''); return; } listings.forEach(function(item) { var card = document.createElement('div'); card.className = 'mp-card'; var stars = ''; for (var i = 0; i < 5; i++) stars += i < Math.round(item.rating) ? '★' : '☆'; card.innerHTML = '
' + '
' + item.icon + '
' + '
' + esc(item.name) + '
by ' + esc(item.author) + '
' + '
' + '
' + esc(item.description) + '
' + '
' + '' + stars + ' ' + item.rating + '' + '📥 ' + item.downloads + '' + '' + item.category + '' + (item.verified ? '✓ Verified' : '') + '
' + '
' + (item.tactics || []).map(function(t) { return '' + esc(t) + ''; }).join(' ') + '
' + ''; grid.appendChild(card); }); } async function installMarketplaceItem(listingId, btn) { btn.disabled = true; btn.textContent = '⏳ Installing…'; try { var res = await fetch(API_BASE + '/api/marketplace/install', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ listing_id: listingId }), }); var data = await res.json(); showToast(data.message || 'Installed!', 'success'); btn.textContent = '✅ Installed'; btn.style.borderColor = 'var(--accent-green)'; btn.style.color = 'var(--accent-green)'; } catch(e) { showToast('Install failed: ' + e.message, 'error'); btn.disabled = false; btn.textContent = '📦 Install'; } } // ── Boot all new panels ────────────────────────────────────────────────── (async function initDashboard() { await initializeApiBase(); var _dashboardFlowsStarted = false; function afterSystemHealthReady(onReady) { if (window.AETHER_OFFLINE || window.AETHER_SYSTEM_HEALTH_READY) { onReady(); return; } try { window.addEventListener('aether:system-health-ready', onReady, { once: true }); } catch (e) {} } function retryBootstrapHealth() { if (window.AETHER_OFFLINE || window.AETHER_SYSTEM_HEALTH_READY) return; setTimeout(function() { if (window.AETHER_OFFLINE || window.AETHER_SYSTEM_HEALTH_READY) return; try { fetchSystemHealth(); } catch (e) {} retryBootstrapHealth(); }, 4000); } function startDashboardFlows() { if (_dashboardFlowsStarted) return; _dashboardFlowsStarted = true; connectWS(true); loadSettings(); if (typeof document !== 'undefined') { document.addEventListener('visibilitychange', function () { if (!document.hidden) { try { fetchDashboard(); fetchSystemHealth(); fetchHeartbeat(); } catch (e) {} } }); } safeInterval(fetchDashboard, POLL_INTERVAL); safeInterval(fetchTrainingMetrics, 8000); safeInterval(fetchLearning, 10000); safeInterval(fetchSentinelStats, 10000); safeInterval(fetchTechniquesForTraining, 10000); safeInterval(fetchSystemHealth, 8000); safeInterval(fetchAgentList, 30000); safeInterval(fetchAgentLearning, 15000); safeInterval(fetchBotRoster, 15000); safeInterval(fetchWeaknessAssessment, 20000); safeInterval(fetchBotXP, 15000); safeInterval(function() { fetchScenarios(false); }, 30000); safeInterval(fetchDifficulty, 20000); safeInterval(fetchIncidentTimeline, 12000); safeInterval(fetchProfiles, 60000); safeInterval(fetchSchedule, 60000); safeInterval(fetchCommunityFeed, 45000); safeInterval(fetchMarketplace, 45000); safeInterval(fetchHeartbeat, 30000); safeInterval(fetchGraduation, 30000); safeInterval(fetchCompetition, 30000); safeInterval(fetchSentinelCommand, 20000); // ── Staggered boot waves ─────────────────────────────────────────── // Browsers cap concurrent connections per origin (~6). Firing 22 fetches at // once causes later panels to time out. We split into staged waves, but only // after the first health probe has succeeded or the page is explicitly offline. function _wave(fns) { if (window.AETHER_OFFLINE && !window.AETHER_INSTANT_TELEMETRY) return; fns.forEach(function(fn) { try { fn(); } catch(e) { console.warn('boot fetch failed', e); } }); } setTimeout(function() { _wave([fetchDashboard, fetchHeartbeat, fetchSentinelStats, fetchTrainingMetrics]); }, 250); setTimeout(function() { _wave([ fetchLearning, fetchTechniquesForTraining, fetchAgentList, fetchAgentLearning, fetchBotRoster, fetchValidationResults ]); }, 700); setTimeout(function() { _wave([ fetchWeaknessAssessment, fetchBotXP, fetchScenarios, fetchDifficulty, fetchProfiles, fetchIncidentTimeline, fetchSchedule, fetchCommunityFeed, fetchMarketplace, fetchGraduation, fetchCompetition, fetchSentinelCommand ]); }, 2200); } // ── Bootstrap around health first ──────────────────────────────────── // Browsers cap concurrent connections per origin (~6). Firing 22 fetches at // once causes later panels to time out. We split into 3 waves: critical // (immediate), secondary (+800ms), tertiary (+2200ms). When the live // backend is unreachable, keep the waves running against the instant // telemetry layer so the dashboard never opens with empty panels. fetchSystemHealth(); afterSystemHealthReady(startDashboardFlows); if (window.AETHER_OFFLINE) startDashboardFlows(); else retryBootstrapHealth(); })(); // ── Sentinel Swarm Command ─────────────────────────────────────────── async function fetchSentinelCommand() { try { var [statusRes, rosterRes] = await Promise.all([ fetch(API_BASE + '/api/sentinel/status'), fetch(API_BASE + '/api/sentinel/apex-roster'), ]); var status = statusRes.ok ? await statusRes.json() : null; var roster = rosterRes.ok ? await rosterRes.json() : null; if (status) renderSentinelStatus(status); if (roster) renderSentinelRoster(roster); } catch(e) { var b = document.getElementById('sentinel-cmd-badge'); if (b) { b.textContent = 'Offline'; b.className = 'badge badge-red'; } } } function renderSentinelStatus(d) { // DEFCON indicator var light = document.getElementById('sent-defcon-light'); var name = document.getElementById('sent-defcon-name'); var label = document.getElementById('sent-defcon-label'); var desc = document.getElementById('sent-defcon-desc'); var colour = d.defcon_colour || '#22c55e'; if (light) { light.style.color = colour; light.style.background = colour; light.style.boxShadow = '0 0 14px ' + colour; } if (name) { name.textContent = d.defcon_name || 'GREEN'; name.style.color = colour; } if (label) label.textContent = d.defcon_label || 'All Clear'; if (desc) desc.textContent = d.defcon_desc || ''; // Live indicator var liveEl = document.getElementById('sent-live-indicator'); if (liveEl) { liveEl.textContent = d.sentinel_live ? '🟢 Online' : '🔴 Offline'; liveEl.style.color = d.sentinel_live ? '#22c55e' : '#ef4444'; } // Armed count var armedEl = document.getElementById('sent-armed-count'); if (armedEl) { armedEl.textContent = (d.armed_capabilities || 0) + '/' + (d.total_capabilities || 0); armedEl.className = 'sentinel-stat-val ' + (d.armed_capabilities >= 6 ? 'green' : 'gold'); } // Stat cards var hiEl = document.getElementById('sent-high-events'); if (hiEl) { hiEl.textContent = d.event_bus_high || 0; hiEl.className = 'sentinel-stat-val ' + (d.event_bus_high >= 5 ? 'red' : d.event_bus_high >= 2 ? 'orange' : 'green'); } var escEl = document.getElementById('sent-esc-count'); if (escEl) escEl.textContent = (d.recent_escalations || []).length; // Badge var badge = document.getElementById('sentinel-cmd-badge'); if (badge) { badge.textContent = 'DEFCON ' + d.defcon_name + (d.sentinel_live ? ' • Live' : ' • Swarm Offline'); badge.className = 'badge ' + (d.defcon >= 3 ? 'badge-red' : d.defcon >= 2 ? 'badge-gold' : d.defcon >= 1 ? 'badge-gold' : 'badge-green'); } // Capabilities list var capList = document.getElementById('sent-cap-list'); if (capList && d.capabilities) { capList.innerHTML = ''; d.capabilities.forEach(function(cap) { var row = document.createElement('div'); row.className = 'sentinel-cap-row'; row.innerHTML = '' + esc(cap.icon) + '' + '' + esc(cap.name) + '' + '' + esc(cap.layer) + '' + '' + (cap.has_teeth ? '✓ ARMED' : '○ PLANNED') + '' + ' — ' + esc(cap.notes) + ''; capList.appendChild(row); }); } // Escalation log var escList = document.getElementById('sent-esc-list'); if (escList) { var escs = d.recent_escalations || []; if (!escs.length) { escList.innerHTML = '
No escalations logged yet. System is operating within normal parameters.
'; } else { escList.innerHTML = ''; escs.forEach(function(ev) { var row = document.createElement('div'); row.className = 'sentinel-esc-row ' + (ev.defcon_name || 'GREEN'); var ts = ev.timestamp ? ago(ev.timestamp) : '--'; row.innerHTML = '' + esc(ts) + '' + '' + esc(ev.case_id || '--') + ' via ' + esc(ev.source_bot_id || '--') + '' + '' + esc(ev.defcon_name || '--') + '' + '' + esc(ev.action || '--') + ''; escList.appendChild(row); }); } } } function renderSentinelRoster(d) { // Apex count stats var apexEl = document.getElementById('sent-apex-count'); if (apexEl) { apexEl.textContent = d.apex_count || 0; apexEl.className = 'sentinel-stat-val ' + (d.apex_count > 0 ? 'gold' : 'red'); } var totEl = document.getElementById('sent-total-bots'); if (totEl) totEl.textContent = d.total_bots || 0; // APEX grid var grid = document.getElementById('sent-apex-grid'); if (!grid) return; grid.innerHTML = ''; var apexBots = d.apex_roster || []; if (!apexBots.length) { grid.innerHTML = '
' + '👑 No APEX bots yet — bots must reach Level 20 to be cleared for Sentinel Swarm deployment.
'; } else { apexBots.forEach(function(bot) { var card = document.createElement('div'); card.className = 'sentinel-apex-card'; card.innerHTML = '
' + esc(_botIcon(bot.icon)) + '
' + '
' + esc(bot.display_name) + '
' + '
Lvl ' + bot.level + ' — ' + esc(bot.title) + '
' + '
' + '
CLEARED
'; grid.appendChild(card); }); } // Ineligible summary var inelEl = document.getElementById('sent-ineligible-summary'); if (inelEl && d.ineligible && d.ineligible.length) { inelEl.innerHTML = '
⏳ ' + d.ineligible_count + ' bot(s) still in training — not yet APEX. ' + 'Nearest: ' + (d.ineligible[0] ? esc(d.ineligible[0].display_name) + ' (' + d.ineligible[0].levels_remaining + ' levels away)' : '--') + '
'; } else if (inelEl) { inelEl.innerHTML = ''; } } async function sendManualEscalation() { var level = prompt('DEFCON level (0=Green, 1=Yellow, 2=Orange, 3=Red):', '2'); if (level === null) return; level = parseInt(level) || 1; var evidence = prompt('Brief description / evidence:', 'Manual operator escalation') || 'Manual operator escalation'; try { var res = await fetch(API_BASE + '/api/sentinel/escalate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ threat_level: level, source_bot_id: 'manual', action: 'notify', evidence: evidence, }) }); var d = await res.json(); if (d.accepted) { alert('✅ Escalation accepted — ' + d.defcon_name + '\n' + d.message); fetchSentinelCommand(); } else { alert('❌ Error: ' + (d.message || JSON.stringify(d))); } } catch(e) { alert('Connection error: ' + e.message); } } // ── Training Heartbeat ──────────────────────────────────────────────── var _hbSparklineChart = null; async function fetchHeartbeat() { try { var res = await fetch(API_BASE + '/api/telemetry/heartbeat'); if (!res.ok) return; var d = await res.json(); renderHeartbeat(d); } catch(e) { document.getElementById('hb-badge').textContent = 'Offline'; document.getElementById('hb-badge').className = 'badge badge-red'; } } function renderHeartbeat(d) { var ticks = d.ticks || []; var enabled = d.enabled; var health = d.health_pct || 0; var streak = d.streak_pass || 0; var lastRate = d.last_detection_rate; var runs24h = d.wrapper_runs_24h || 0; var next = d.next_tick_in_mins; var interval = d.interval_minutes || 15; // badge var badge = document.getElementById('hb-badge'); badge.textContent = enabled ? 'Active — every ' + interval + 'min' : 'Scheduler Off'; badge.className = 'badge ' + (enabled ? 'badge-green' : 'badge-gold'); // stats document.getElementById('hb-health').textContent = health + '%'; document.getElementById('hb-health').className = 'hb-stat-value ' + (health >= 80 ? 'green' : health >= 50 ? 'cyan' : 'red'); document.getElementById('hb-streak').textContent = streak + ' in a row'; document.getElementById('hb-streak').className = 'hb-stat-value ' + (streak >= 3 ? 'green' : streak > 0 ? 'cyan' : 'red'); document.getElementById('hb-last-rate').textContent = lastRate != null ? lastRate + '%' : '--'; document.getElementById('hb-last-rate').className = 'hb-stat-value ' + (lastRate >= 80 ? 'green' : lastRate >= 50 ? 'gold' : lastRate == null ? '' : 'red'); document.getElementById('hb-runs-24h').textContent = runs24h; // next-tick bar if (next != null && interval > 0) { var pct = Math.round(((interval - next) / interval) * 100); document.getElementById('hb-next-fill').style.width = Math.min(100, pct) + '%'; document.getElementById('hb-next-label').textContent = next > 0 ? 'in ' + next + ' min' : 'now'; } // sparkline (Chart.js — already loaded in page) var rates = ticks.map(function(t) { return t.detection_rate != null ? t.detection_rate : 0; }); var labels = ticks.map(function(t, i) { return t.mins_ago != null ? t.mins_ago + 'm ago' : 'T-' + (ticks.length - i); }); var colours = ticks.map(function(t) { var s = (t.status || '').toLowerCase(); return (s === 'passed' || s === 'pass' || s === 'ok') ? 'rgba(52,211,153,0.85)' : 'rgba(248,81,73,0.85)'; }); var ctx = document.getElementById('hb-sparkline'); if (ctx && typeof Chart !== 'undefined') { if (_hbSparklineChart) { _hbSparklineChart.destroy(); _hbSparklineChart = null; } _hbSparklineChart = new Chart(ctx.getContext('2d'), { type: 'bar', data: { labels: labels, datasets: [{ data: rates, backgroundColor: colours, borderRadius: 3 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(c) { return c.parsed.y + '% detection'; } } }}, scales: { x: { display: false }, y: { min: 0, max: 100, display: true, grid: { color: 'rgba(48,54,61,0.4)' }, ticks: { color: 'rgba(139,148,158,0.8)', font: { size: 9 }, stepSize: 25 } } }, animation: { duration: 400 }, } }); } else if (ctx && !ctx._noChart) { ctx._noChart = true; // Minimal canvas fallback var c2d = ctx.getContext('2d'); var W = ctx.width = ctx.parentElement.offsetWidth || 300; var H = ctx.height = 80; c2d.clearRect(0, 0, W, H); if (rates.length) { var step = W / rates.length; rates.forEach(function(r, i) { var barH = (r / 100) * (H - 8); c2d.fillStyle = colours[i]; c2d.fillRect(i * step + 1, H - barH - 4, step - 2, barH); }); } } // tick list var list = document.getElementById('hb-tick-list'); list.innerHTML = ''; if (!ticks.length) { list.innerHTML = '
No training ticks yet. Start a training cycle to begin.
'; return; } ticks.slice().reverse().forEach(function(t) { var s = (t.status || '').toLowerCase(); var cls = (s === 'passed' || s === 'pass' || s === 'ok') ? 'pass' : s === 'warning' || s === 'warn' ? 'warn' : 'fail'; var agoTxt = t.mins_ago != null ? t.mins_ago + 'm ago' : '--'; var rateTxt = t.detection_rate != null ? t.detection_rate + '%' : '--'; var statusIcon = cls === 'pass' ? '✅' : cls === 'warn' ? '⚠️' : '❌'; var row = document.createElement('div'); row.className = 'hb-tick-row ' + cls; row.innerHTML = '' + esc(agoTxt) + '' + '' + rateTxt + '' + '' + statusIcon + ' ' + esc(t.status || '--') + '' + '' + esc(t.pack || '--') + ''; list.appendChild(row); }); } // ── Bot Graduation ──────────────────────────────────────────────────── async function fetchGraduation() { try { var res = await fetch(API_BASE + '/api/bots/graduation'); if (!res.ok) return; var d = await res.json(); renderGraduation(d); } catch(e) { document.getElementById('grad-badge').textContent = 'Offline'; document.getElementById('grad-badge').className = 'badge badge-red'; } } function renderGraduation(d) { var bots = d.bots || []; var events = d.recent_events || []; var badge = document.getElementById('grad-badge'); badge.textContent = d.total_milestones_achieved + ' milestones' + (d.apex_count ? ' • ' + d.apex_count + ' APEX' : ''); badge.className = 'badge ' + (d.apex_count > 0 ? 'badge-gold' : 'badge-cyan'); // Recent graduation events var evWrap = document.getElementById('grad-recent-wrap'); var evList = document.getElementById('grad-events'); if (events.length) { evWrap.style.display = 'block'; evList.innerHTML = ''; events.slice(-5).reverse().forEach(function(ev) { var row = document.createElement('div'); row.className = 'grad-event-row'; row.innerHTML = '' + esc(_botIcon(ev.icon)) + '' + '
' + esc(ev.display_name) + '
' + '
' + esc(ev.event) + (ev.timestamp ? ' — ' + ago(ev.timestamp) : '') + '
' + '' + esc(ev.badge) + ' ' + esc(ev.tier) + ''; evList.appendChild(row); }); } else { evWrap.style.display = 'none'; } // Bot cards var milestoneEmoji = ['🥉','🥈','🥇','💎','🌟','👑']; var grid = document.getElementById('grad-grid'); grid.innerHTML = ''; bots.forEach(function(bot) { var tierClass = 'tier-' + bot.current_tier; var card = document.createElement('div'); card.className = 'grad-card ' + tierClass; // milestone dots var totalMs = bot.milestones_total || 6; var achieved= bot.milestones_achieved || 0; var dotHtml = ''; for (var i = 0; i < totalMs; i++) { var emoji = milestoneEmoji[i] || '●'; dotHtml += '' + (i < achieved ? emoji : '·') + ''; } var nextHtml = bot.next_milestone ? '
Next: ' + esc(bot.next_milestone.badge + ' ' + bot.next_milestone.tier) + ' — Lvl ' + bot.next_milestone.at_level + ' (' + bot.next_milestone.levels_away + ' levels away)
' + '' + esc(bot.next_milestone.reward) + '
' : '
👑 MAX TIER ACHIEVED
'; card.innerHTML = '
' + '
' + esc(_botIcon(bot.icon)) + '
' + '
' + esc(bot.display_name) + '
' + '
' + esc(bot.phase) + '
' + '
' + esc(bot.current_badge) + '
' + '
' + '
' + dotHtml + '
' + '
Lvl ' + bot.level + ' — ' + esc(bot.title) + ' — ' + bot.xp + ' XP
' + nextHtml; if (!bot.enabled) { card.style.opacity = '0.45'; } grid.appendChild(card); }); } function ago(isoStr) { if (!isoStr) return '--'; try { var diff = Math.round((new Date() - new Date(isoStr)) / 60000); if (diff < 1) return 'just now'; if (diff < 60) return diff + 'm ago'; if (diff < 1440) return Math.floor(diff/60) + 'h ago'; return Math.floor(diff/1440) + 'd ago'; } catch(_) { return isoStr; } } // ── Competition Standing ────────────────────────────────────────────── async function fetchCompetition() { try { var res = await fetch(API_BASE + '/api/competition/standing'); if (!res.ok) return; var d = await res.json(); renderCompetition(d); } catch(e) { document.getElementById('comp-badge').textContent = 'Offline'; document.getElementById('comp-badge').className = 'badge badge-red'; } } function renderCompetition(d) { // Badge card document.getElementById('comp-league-icon').textContent = d.league_icon || '🥉'; document.getElementById('comp-league-name').textContent = d.league || '--'; document.getElementById('comp-league-name').style.color = d.league_colour || '#fff'; document.getElementById('comp-score').textContent = d.score || '--'; document.getElementById('comp-score').style.color = d.league_colour || 'var(--cyber-cyan)'; document.getElementById('comp-badge-card').style.borderColor = (d.league_colour || 'var(--border-glow)') + '55'; var pctile = d.global_percentile; document.getElementById('comp-percentile').textContent = pctile != null ? 'Top ' + (100 - pctile) + '% globally (est.)' : 'Score your system to get a rank'; var deltaEl = document.getElementById('comp-delta'); if (d.weekly_delta != null) { deltaEl.textContent = (d.weekly_delta >= 0 ? '▲ +' : '▼ ') + d.weekly_delta + ' pts this week'; deltaEl.className = 'comp-delta ' + (d.weekly_delta >= 0 ? 'up' : 'down'); } else { deltaEl.textContent = d.sentinel_live ? '' : 'Start Sentinel to track progress'; deltaEl.className = 'comp-delta'; } // Stats document.getElementById('comp-detect-rate').textContent = (d.detection_rate || 0) + '%'; document.getElementById('comp-tactic-cov').textContent = (d.tactic_coverage || 0) + '%'; document.getElementById('comp-reports').textContent = d.total_reports || 0; document.getElementById('comp-pts-next').textContent = d.pts_to_next_league > 0 ? d.pts_to_next_league + ' pts' : '—'; // Progress bar to next league var tiers = d.tiers || []; var curTier = tiers.find(function(t) { return t.name === d.league; }); if (curTier && d.next_league) { var range = curTier.max_score - curTier.min_score; var pos = d.score - curTier.min_score; var pct = range > 0 ? Math.min(100, Math.round((pos / range) * 100)) : 100; document.getElementById('comp-next-fill').style.width = pct + '%'; document.getElementById('comp-next-fill').style.background = d.league_colour || 'var(--cyber-cyan)'; document.getElementById('comp-league-from').textContent = d.league; document.getElementById('comp-league-to').textContent = d.next_league; } // Tier list var list = document.getElementById('comp-tier-list'); list.innerHTML = ''; tiers.forEach(function(t) { var isActive = t.name === d.league; var row = document.createElement('div'); row.className = 'comp-tier-row' + (isActive ? ' active' : ''); row.innerHTML = '' + esc(t.icon) + '' + '' + esc(t.name) + '' + (isActive ? 'YOU ARE HERE' : '') + '' + t.min_score + '–' + t.max_score + ''; list.appendChild(row); }); // page badge var badge = document.getElementById('comp-badge'); badge.textContent = d.league + ' • Score ' + d.score; badge.className = 'badge badge-gold'; } // ── Settings Management ────────────────────────────────────────────────── var currentSettings = {}; function applyLocalSettingsToUI() { var apiInput = document.querySelector('[data-local-setting="api_base"]'); if (apiInput) apiInput.value = getApiOverride() || API_BASE; // Load alerting channel config from localStorage var alertingRaw = localStorage.getItem('aether_alerting'); var alerting = alertingRaw ? JSON.parse(alertingRaw) : {}; document.querySelectorAll('[data-local-setting]').forEach(function(el) { var key = el.getAttribute('data-local-setting'); if (!key.startsWith('alerting.')) return; var field = key.split('.')[1]; if (alerting[field] !== undefined) el.value = alerting[field]; }); } function collectLocalSettingsFromUI() { var apiInput = document.querySelector('[data-local-setting="api_base"]'); var alerting = {}; document.querySelectorAll('[data-local-setting]').forEach(function(el) { var key = el.getAttribute('data-local-setting'); if (!key.startsWith('alerting.')) return; var field = key.split('.')[1]; alerting[field] = el.value; }); return { api_base: apiInput ? normalizeApiBase(apiInput.value) : '', alerting: alerting }; } function toggleSettings() { var panel = document.getElementById('settings-panel'); var overlay = document.getElementById('settings-overlay'); var isOpen = panel.classList.contains('open'); panel.classList.toggle('open'); overlay.classList.toggle('open'); if (!isOpen) loadSettings(); } async function loadSettings() { applyLocalSettingsToUI(); try { var res = await fetch(API_BASE + '/api/settings'); if (!res.ok) return; currentSettings = await res.json(); applySettingsToUI(currentSettings); applyTheme(currentSettings.appearance ? currentSettings.appearance.theme : 'dark'); } catch(e) {} } function applySettingsToUI(settings) { document.querySelectorAll('[data-setting]').forEach(function(el) { var path = el.getAttribute('data-setting').split('.'); var val = settings; for (var i = 0; i < path.length; i++) { val = val ? val[path[i]] : undefined; } if (val === undefined) return; if (el.type === 'checkbox') el.checked = !!val; else el.value = String(val); }); } function collectSettingsFromUI() { var settings = JSON.parse(JSON.stringify(currentSettings)); document.querySelectorAll('[data-setting]').forEach(function(el) { var path = el.getAttribute('data-setting').split('.'); var obj = settings; for (var i = 0; i < path.length - 1; i++) { if (!obj[path[i]]) obj[path[i]] = {}; obj = obj[path[i]]; } var key = path[path.length - 1]; if (el.type === 'checkbox') obj[key] = el.checked; else if (el.tagName === 'SELECT' && !isNaN(el.value)) obj[key] = Number(el.value); else obj[key] = el.value; }); return settings; } async function saveSettings() { var localSettings = collectLocalSettingsFromUI(); if (localSettings.api_base) { setApiBase(localSettings.api_base, { persist: true }); connectWS(true); } // Persist alerting channel config locally (never sent to API — contains credentials) if (localSettings.alerting) { localStorage.setItem('aether_alerting', JSON.stringify(localSettings.alerting)); } var settings = collectSettingsFromUI(); try { var res = await fetch(API_BASE + '/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); if (res.ok) { currentSettings = await res.json(); currentSettings = currentSettings.settings || currentSettings; applyTheme(currentSettings.appearance ? currentSettings.appearance.theme : 'dark'); fetchDashboard(); showToast('Settings saved successfully', 'success'); } else { showToast('Failed to save settings', 'error'); } } catch(e) { showToast('Could not reach API', 'error'); } } async function resetSettings() { try { var res = await fetch(API_BASE + '/api/settings/reset', { method: 'POST' }); if (res.ok) { var data = await res.json(); currentSettings = data.settings || {}; applySettingsToUI(currentSettings); applyTheme('dark'); showToast('Settings reset to defaults', 'info'); } } catch(e) { showToast('Could not reach API', 'error'); } } async function testAlert() { var statusEl = document.getElementById('test-alert-status'); if (statusEl) { statusEl.textContent = 'Sending…'; statusEl.style.color = 'var(--text-muted)'; } var alertingRaw = localStorage.getItem('aether_alerting'); var alerting = alertingRaw ? JSON.parse(alertingRaw) : {}; // Build payload — only include non-empty channels var channels = {}; if (alerting.slack_webhook) channels.slack_webhook = alerting.slack_webhook; if (alerting.teams_webhook) channels.teams_webhook = alerting.teams_webhook; if (alerting.pagerduty_key) channels.pagerduty_key = alerting.pagerduty_key; if (alerting.email_recipients) channels.email_recipients = alerting.email_recipients; if (!Object.keys(channels).length) { showToast('Configure at least one alerting channel first', 'warning'); if (statusEl) { statusEl.textContent = 'No channels configured.'; statusEl.style.color = 'var(--cyber-gold)'; } return; } try { var res = await fetch(API_BASE + '/api/settings/test-alert', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channels: channels, min_severity: alerting.min_severity || 'HIGH' }) }); var data = await res.json(); if (res.ok) { showToast('Test alert dispatched — check your channels', 'success'); if (statusEl) { statusEl.textContent = 'Dispatched \u2713'; statusEl.style.color = 'var(--accent-green)'; } } else { showToast('Test alert failed: ' + (data.detail || res.status), 'error'); if (statusEl) { statusEl.textContent = 'Failed: ' + (data.detail || res.status); statusEl.style.color = 'var(--accent-red)'; } } } catch(e) { showToast('Could not reach API — check AETHER is running', 'error'); if (statusEl) { statusEl.textContent = 'API unreachable'; statusEl.style.color = 'var(--accent-red)'; } } } // ── Theme Toggle ───────────────────────────────────────────────────────── function applyTheme(theme) { if (theme === 'light') { document.body.classList.add('theme-light'); document.getElementById('theme-icon').textContent = '\u2600\uFE0F'; document.getElementById('theme-label').textContent = 'Dark Mode'; } else { document.body.classList.remove('theme-light'); document.getElementById('theme-icon').textContent = '\uD83C\uDF19'; document.getElementById('theme-label').textContent = 'Light Mode'; } } function toggleTheme() { var isLight = document.body.classList.contains('theme-light'); var newTheme = isLight ? 'dark' : 'light'; applyTheme(newTheme); try { localStorage.setItem('aether_theme', newTheme); } catch(e) {} currentSettings.appearance = currentSettings.appearance || {}; currentSettings.appearance.theme = newTheme; fetch(API_BASE + '/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ appearance: { theme: newTheme } }) }).catch(function(){}); } // Restore theme from localStorage on load (function restoreTheme() { try { var saved = localStorage.getItem('aether_theme'); if (saved === 'light' || saved === 'dark') applyTheme(saved); } catch(e) {} })(); // ── Getting Started onboarding hint ───────────────────────────────────── (function initOnboardingHint() { // Always keep the Getting Started button visible — it links to home.html // so users can re-open the marketing/onboarding page at any time. var btn = document.getElementById('qa-onboarding'); if (btn) btn.style.display = ''; })(); // ── Toast Notifications ────────────────────────────────────────────────── function showToast(msg, type) { var container = document.getElementById('toast-container'); var toast = document.createElement('div'); toast.className = 'toast ' + (type || 'info'); toast.innerHTML = '' + (type === 'success' ? '\u2705' : type === 'error' ? '\u274C' : '\u2139\uFE0F') + ' ' + esc(msg); container.appendChild(toast); setTimeout(function() { toast.remove(); }, 3200); } // ── Quick Actions ──────────────────────────────────────────────────────── async function triggerScan() { var btn = document.getElementById('qa-scan'); btn.classList.add('spinning'); btn.disabled = true; showToast('System scan initiated...', 'info'); try { var res = await fetch(API_BASE + '/api/actions/scan', { method: 'POST' }); if (res.ok) { showToast('Scan triggered successfully', 'success'); } else { showToast('Scan trigger failed', 'error'); } } catch(e) { showToast('Could not reach API', 'error'); } setTimeout(function() { btn.classList.remove('spinning'); btn.disabled = false; }, 3000); } async function triggerTraining() { var btn = document.getElementById('qa-train'); btn.classList.add('spinning'); btn.disabled = true; showToast('Training cycle started...', 'info'); try { var res = await fetch(API_BASE + '/api/actions/train', { method: 'POST' }); if (res.ok) { showToast('Training triggered successfully', 'success'); fetchTrainingMetrics(); } else { showToast('Training trigger failed', 'error'); } } catch(e) { showToast('Could not reach API', 'error'); } setTimeout(function() { btn.classList.remove('spinning'); btn.disabled = false; }, 3000); } async function exportReport() { var btn = document.getElementById('qa-export'); btn.classList.add('spinning'); showToast('Generating security report...', 'info'); try { var res = await fetch(API_BASE + '/api/export/report'); if (!res.ok) { showToast('Report generation failed', 'error'); return; } var report = await res.json(); var blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'AETHER_Security_Report_' + new Date().toISOString().slice(0, 10) + '.json'; a.click(); URL.revokeObjectURL(url); showToast('Report downloaded', 'success'); } catch(e) { showToast('Export failed', 'error'); } btn.classList.remove('spinning'); } async function exportReportHTML() { var btn = document.getElementById('qa-export-html'); if (btn) btn.classList.add('spinning'); showToast('Generating HTML executive report…', 'info'); try { var res = await fetch(API_BASE + '/api/export/html'); if (!res.ok) { showToast('HTML report generation failed', 'error'); return; } var html = await res.text(); var blob = new Blob([html], { type: 'text/html' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'AETHER_Executive_Report_' + new Date().toISOString().slice(0, 10) + '.html'; a.click(); URL.revokeObjectURL(url); showToast('HTML report downloaded', 'success'); } catch(e) { showToast('HTML export failed: ' + e.message, 'error'); } if (btn) btn.classList.remove('spinning'); } // ── Feed Filters ───────────────────────────────────────────────────────── var activeFeedFilter = 'all'; var feedSearchQuery = ''; function setFeedFilter(filter, el) { activeFeedFilter = filter; document.querySelectorAll('.filter-chip').forEach(function(c) { c.classList.remove('active'); }); el.classList.add('active'); applyFeedFilters(); } function filterFeedBySearch(query) { feedSearchQuery = query.toLowerCase(); applyFeedFilters(); } function applyFeedFilters() { var items = document.querySelectorAll('#activity-feed .activity-item'); items.forEach(function(item) { var type = item.getAttribute('data-type') || ''; var priority = item.getAttribute('data-priority') || ''; var text = item.textContent.toLowerCase(); var showType = activeFeedFilter === 'all' || type === activeFeedFilter || (activeFeedFilter === 'critical' && (priority === 'CRITICAL' || priority === 'HIGH')); var showSearch = !feedSearchQuery || text.indexOf(feedSearchQuery) >= 0; item.style.display = (showType && showSearch) ? '' : 'none'; }); } // ── System Health ──────────────────────────────────────────────────────── var _healthRetryCount = 0; var _healthLastOk = 0; var _systemHealthRequest = null; window.AETHER_SYSTEM_HEALTH_READY = false; function renderSystemHealth(data, isFallback) { var s = data.system || {}; var p = data.aether_process || {}; var svc = data.services || {}; function healthColor(pct) { return pct > 85 ? 'var(--accent-red)' : pct > 65 ? 'var(--accent-orange)' : 'var(--accent-green)'; } document.getElementById('h-cpu').textContent = s.cpu_percent + '%'; document.getElementById('h-cpu-bar').style.width = s.cpu_percent + '%'; document.getElementById('h-cpu-bar').style.background = healthColor(s.cpu_percent); document.getElementById('h-mem').textContent = s.memory_percent + '%'; document.getElementById('h-mem-bar').style.width = s.memory_percent + '%'; document.getElementById('h-mem-bar').style.background = healthColor(s.memory_percent); document.getElementById('h-mem-detail').textContent = s.memory_used_gb + ' / ' + s.memory_total_gb + ' GB'; document.getElementById('h-disk').textContent = s.disk_percent + '%'; document.getElementById('h-disk-bar').style.width = s.disk_percent + '%'; document.getElementById('h-disk-bar').style.background = healthColor(s.disk_percent); document.getElementById('h-disk-detail').textContent = s.disk_used_gb + ' / ' + s.disk_total_gb + ' GB'; document.getElementById('h-proc-mem').textContent = (p.memory_mb || 0) + ' MB'; document.getElementById('h-proc-up').textContent = 'Uptime: ' + fmtUptime(p.uptime_s || 0); document.getElementById('h-sentinel').textContent = svc.sentinel_swarm ? '\u2705' : '\u274C'; document.getElementById('h-sentinel-sub').textContent = isFallback ? 'Instant snapshot' : (svc.sentinel_swarm ? 'Online' : 'Offline'); document.getElementById('health-badge').textContent = isFallback ? 'Instant Health' : (s.hostname || 'System OK'); document.getElementById('health-badge').className = isFallback ? 'badge badge-gold' : 'badge badge-green'; } function markSystemHealthReady() { if (window.AETHER_SYSTEM_HEALTH_READY) return; window.AETHER_SYSTEM_HEALTH_READY = true; try { window.dispatchEvent(new Event('aether:system-health-ready')); } catch (e) {} } function onSystemHealthReady(onReady) { if (window.AETHER_OFFLINE || window.AETHER_SYSTEM_HEALTH_READY) { onReady(); return; } try { window.addEventListener('aether:system-health-ready', onReady, { once: true }); } catch (e) {} } async function fetchSystemHealth() { if (_systemHealthRequest) return _systemHealthRequest; _systemHealthRequest = (async function() { try { var fetchImpl = window.__AETHER_NATIVE_FETCH__ || window.fetch.bind(window); var controller = new AbortController(); var timeoutMs = _healthLastOk === 0 ? 3500 : 15000; var timeout = setTimeout(function() { controller.abort(); }, timeoutMs); var res = await fetchImpl(API_BASE + '/api/system-health', { signal: controller.signal }); clearTimeout(timeout); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); _healthRetryCount = 0; _healthLastOk = Date.now(); markSystemHealthReady(); renderSystemHealth(data, false); } catch(e) { _healthRetryCount++; var sub = document.getElementById('h-sentinel-sub'); var badge = document.getElementById('health-badge'); if (_healthLastOk === 0 && window.AETHER_INSTANT_TELEMETRY) { var fallback = aetherInstantPayload('/api/system-health'); if (fallback) { renderSystemHealth(fallback, true); markSystemHealthReady(); return; } } if (_healthRetryCount <= 2 && _healthLastOk > 0) { // brief blip — keep showing last-known status sub.textContent = 'Reconnecting\u2026'; } else if (_healthLastOk === 0) { sub.textContent = 'Connecting\u2026'; badge.textContent = 'Connecting'; badge.className = 'badge badge-gold'; } else { sub.textContent = 'Health probe retrying (' + _healthRetryCount + ')'; badge.textContent = 'Unavailable'; badge.className = 'badge badge-red'; } } finally { _systemHealthRequest = null; } })(); return _systemHealthRequest; } // ── Agent Learning Tiers & Score Breakdown ────────────────────────────── var _agentIconMap = { heart: '\u2764\uFE0F', bee: '\uD83D\uDC1D', wifi: '\uD83D\uDCF6', brain: '\uD83E\uDDE0', globe: '\uD83C\uDF10', shield: '\uD83D\uDEE1', atom: '\u269B\uFE0F', honey: '\uD83C\uDF6F', radar: '\uD83D\uDCE1', server: '\uD83D\uDDA5\uFE0F', route: '\uD83D\uDDFA\uFE0F', shuffle: '\uD83D\uDD00', eye: '\uD83D\uDC41\uFE0F', fingerprint: '\uD83E\uDDBB', heartbeat: '\uD83D\uDC93', signal: '\uD83D\uDCF6', link: '\uD83D\uDD17', lock: '\uD83D\uDD12', trap: '\uD83E\uDEA4', cut: '\u2702\uFE0F', }; var _tierColorMap = { 'Phase 2': 'var(--cyber-cyan)', 'Phase 3': 'var(--cyber-pink)', 'Phase 4': 'var(--cyber-gold)', 'Phase 5': 'var(--accent-green)', 'Phase 6': 'var(--accent-purple)', 'Sentinel': 'var(--cyber-cyan)', }; var _tierIconClass = { 'Phase 2': 'recovery', 'Phase 3': 'swarm', 'Phase 4': 'wifi', 'Phase 5': 'fusion', 'Phase 6': 'grid', }; async function fetchAgentLearning() { try { // Fetch both agents and agent-learning in parallel var [agRes, alRes] = await Promise.all([ fetch(API_BASE + '/api/agents'), fetch(API_BASE + '/api/agent-learning'), ]); var agData = agRes.ok ? await agRes.json() : { agents: [], total: 0 }; var alData = alRes.ok ? await alRes.json() : {}; var agents = agData.agents || []; var tierCounts = agData.tier_counts || {}; var bridge = alData.bridge || {}; var scoreExp = alData.score_explanation || {}; var badge = document.getElementById('at-badge'); renderEyesOnlyStats(bridge); // ── Score Breakdown ────────────────────────────────── var tc = scoreExp.tactic_coverage || {}; var dp = scoreExp.detection_pass_rate || {}; document.getElementById('sb-tactic-val').textContent = (tc.value || 0) + '%'; document.getElementById('sb-tactic-contrib').textContent = (tc.contribution || 0); document.getElementById('sb-tactic-detail').textContent = tc.detail || ''; document.getElementById('sb-detect-val').textContent = (dp.value || 0) + '%'; document.getElementById('sb-detect-contrib').textContent = (dp.contribution || 0); document.getElementById('sb-detect-detail').textContent = dp.detail || ''; var interp = scoreExp.interpretation || ''; var interpEl = document.getElementById('sb-interpretation'); interpEl.textContent = interp; var overall = scoreExp.overall_score || 0; if (overall >= 70) { interpEl.style.background = 'rgba(52,211,153,0.12)'; interpEl.style.color = 'var(--accent-green)'; } else if (overall >= 40) { interpEl.style.background = 'rgba(234,179,8,0.12)'; interpEl.style.color = 'var(--cyber-gold)'; } else { interpEl.style.background = 'rgba(248,113,113,0.12)'; interpEl.style.color = 'var(--accent-red)'; } // ── Agent Tier Grid ────────────────────────────────── var grid = document.getElementById('at-grid'); grid.innerHTML = ''; // Group agents by tier var tiers = {}; agents.forEach(function(a) { var t = a.tier || 'Unknown'; if (!tiers[t]) tiers[t] = []; tiers[t].push(a); }); var totalAgents = agents.length; badge.textContent = totalAgents + ' agents across ' + Object.keys(tiers).length + ' tiers'; badge.className = 'badge ' + (totalAgents > 50 ? 'badge-green' : totalAgents > 20 ? 'badge-cyan' : 'badge-gold'); // Render tier groups var tierOrder = ['Phase 2', 'Phase 3', 'Phase 4', 'Phase 5', 'Phase 6', 'Sentinel']; tierOrder.forEach(function(tierName) { var tierAgents = tiers[tierName]; if (!tierAgents || !tierAgents.length) return; // Find the coordinator (phase_engine type) var coordinator = tierAgents.find(function(a) { return a.type === 'phase_engine'; }); var subAgents = tierAgents.filter(function(a) { return a.type !== 'phase_engine'; }); // Group sub-agents by type for compact display var typeGroups = {}; subAgents.forEach(function(a) { var t = a.type; if (!typeGroups[t]) typeGroups[t] = { agents: [], name: a.name, type: t, icon: a.icon, role: a.role }; typeGroups[t].agents.push(a); }); var card = document.createElement('div'); card.className = 'at-card'; card.style.borderColor = _tierColorMap[tierName] || 'var(--border)'; var iconCls = _tierIconClass[tierName] || 'recovery'; var coordName = coordinator ? coordinator.name : tierName; var tierColor = _tierColorMap[tierName] || 'var(--cyber-cyan)'; // Bridge learning data for this tier var bridgeAgents = bridge.agents || {}; var phaseKey = coordinator ? coordinator.id : ''; var blearn = bridgeAgents[phaseKey] || {}; var html = '
' + '
' + (_agentIconMap[coordinator ? coordinator.icon : 'shield'] || '\uD83D\uDEE1') + '
' + '
' + esc(coordName) + '
' + '
' + esc(tierName) + ' \u2022 ' + tierAgents.length + ' entities
'; // Stats row var detRate = blearn.detection_rate !== undefined ? Math.round(blearn.detection_rate * 100) : null; var techSeen = blearn.techniques_seen || 0; var techDetected = blearn.techniques_detected || 0; var cycles = (bridge.total_cycles || 0); html += '
'; html += '
' + subAgents.length + '
Sub-Agents
'; if (detRate !== null) { var rateColor = detRate >= 70 ? 'var(--accent-green)' : detRate >= 30 ? 'var(--cyber-gold)' : 'var(--accent-red)'; html += '
' + detRate + '%
Detection Rate
'; } else { html += '
--
Detection Rate
'; } html += '
'; // Confidence bar if (detRate !== null) { var barColor = detRate >= 70 ? 'var(--accent-green)' : detRate >= 30 ? 'var(--cyber-gold)' : 'var(--accent-red)'; html += '
Learning Confidence
'; html += '
'; } // Sub-agent type breakdown (compact) html += '
'; var groupKeys = Object.keys(typeGroups); groupKeys.forEach(function(gk) { var g = typeGroups[gk]; var count = g.agents.length; var icon = _agentIconMap[g.icon] || '\u2022'; var statusActive = g.agents.filter(function(a) { var s = String(a.status || '').toLowerCase(); return s === 'active' || s === 'idle' || s === 'patrolling' || s === 'online'; }).length; var statusColor = statusActive === count ? 'var(--accent-green)' : statusActive > 0 ? 'var(--cyber-gold)' : 'var(--accent-red)'; html += '
' + '' + icon + '' + '' + count + '\u00D7 ' + esc(g.name.replace(/\s*\d+$/, '').replace(/^(Swarm Agent|PSO Particle|Bee Scout|Grid Swarm)\s.*/, '$1')) + '' + '' + statusActive + '/' + count + '' + '
'; }); html += '
'; // Tactics this engine covers (from bridge data) if (blearn.tactics_covered && blearn.tactics_covered.length) { html += '
'; blearn.tactics_covered.forEach(function(tac) { html += '' + esc(tac) + ''; }); html += '
'; } card.innerHTML = html; grid.appendChild(card); }); if (totalAgents === 0) { grid.innerHTML = '
No agents found. Check platform status.
'; } } catch(e) { document.getElementById('at-badge').textContent = 'Error'; document.getElementById('at-badge').className = 'badge badge-red'; } } // ── Network Devices ────────────────────────────────────────────────────── async function fetchNetworkDevices() { var panel = document.getElementById('network-panel'); panel.style.display = ''; var grid = document.getElementById('device-grid'); grid.innerHTML = '
Scanning network\u2026
'; var btn = document.getElementById('qa-network'); btn.classList.add('spinning'); try { var res = await fetch(API_BASE + '/api/network/devices'); if (!res.ok) { grid.innerHTML = '
Scan failed
'; return; } var data = await res.json(); var devices = data.devices || []; document.getElementById('device-count').textContent = devices.length + ' devices'; if (!devices.length) { grid.innerHTML = '
No devices found on network.
'; return; } grid.innerHTML = ''; var _roleIcons = { gateway: '\uD83C\uDF10', printer: '\uD83D\uDDA8\uFE0F', camera: '\uD83D\uDCF7', phone: '\uD83D\uDCF1', media_device: '\uD83D\uDCFA', smart_speaker: '\uD83D\uDD0A', iot_device: '\uD83E\uDD16', virtual_machine: '\u2601\uFE0F', apple_device: '\uD83C\uDF4E', web_server: '\uD83D\uDDA5\uFE0F', remote_access: '\uD83D\uDD12', file_share: '\uD83D\uDCC1', endpoint: '\uD83D\uDCBB' }; var _riskColors = { low: 'var(--accent-green)', medium: 'var(--accent-yellow, #eab308)', high: 'var(--accent-red)' }; devices.forEach(function(d) { var card = document.createElement('div'); card.className = 'device-card'; // Apply VT border highlight if (d.vt && d.vt.verdict === 'malicious') card.classList.add('vt-malicious'); else if (d.vt && d.vt.verdict === 'suspicious') card.classList.add('vt-suspicious'); var icon = _roleIcons[d.role] || '\uD83D\uDCBB'; var displayName = d.hostname ? esc(d.hostname) : esc(d.ip); var subLine = d.hostname ? '' + esc(d.ip) + '' : ''; var gwBadge = d.is_gateway ? ' GATEWAY' : ''; var macLine = d.mac ? '
' + esc(d.mac) + (d.vendor ? ' \u00b7 ' + esc(d.vendor) : '') + '
' : ''; var roleBadge = '' + esc((d.role || 'device').replace('_', ' ')) + ''; var portLine = d.ports && d.ports.length ? '
Ports: ' + d.ports.slice(0,8).join(', ') + (d.ports.length > 8 ? ' +' + (d.ports.length - 8) : '') + '
' : ''; var riskDot = '
'; // VirusTotal badge var vtBadge = ''; if (d.vt) { var vtClass = 'vt-' + (d.vt.verdict || 'clean'); var vtLabel = d.vt.verdict === 'malicious' ? '\u26A0 VT ' + d.vt.malicious + '/' + d.vt.total + ' malicious' : d.vt.verdict === 'suspicious' ? '\u26A0 VT suspicious' : '\u2713 VT clean'; var vtTitle = 'VirusTotal: ' + (d.vt.malicious || 0) + ' malicious, ' + (d.vt.suspicious || 0) + ' suspicious of ' + (d.vt.total || 0) + ' engines' + (d.vt.country ? ' \u00b7 ' + d.vt.country : '') + (d.vt.asn ? ' \u00b7 ' + d.vt.asn : ''); vtBadge = '' + vtLabel + ''; } else { var vtIp = esc(d.ip); vtBadge = 'VT Check'; } card.innerHTML = '
' + icon + '
' + '
' + '
' + displayName + gwBadge + '
' + subLine + macLine + '
' + roleBadge + '
' + portLine + '
' + vtBadge + '
' + (d.cve_score > 0 ? '
🔒 CVE Score: ' + d.cve_score + ' • ' + (d.cve_matches && d.cve_matches.length ? esc(d.cve_matches[0].cve) + ': ' + esc(d.cve_matches[0].desc.slice(0,60)) : '') + '
' : '') + '
' + '
' + riskDot + '' + esc(d.risk || 'low') + '' + '
'; grid.appendChild(card); }); showToast('Found ' + devices.length + ' network devices', 'success'); } catch(e) { grid.innerHTML = '
Network scan unavailable
'; } btn.classList.remove('spinning'); } async function vtCheckDevice(ip) { var btn = document.getElementById('vtbtn-' + ip); if (!btn) return; btn.className = 'vt-badge vt-pending'; btn.textContent = 'Checking\u2026'; try { var res = await fetch(API_BASE + '/api/network/enrich', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip: ip }), }); var data = await res.json(); if (!res.ok) { btn.className = 'vt-badge vt-private'; btn.textContent = data.detail || 'VT unavailable'; return; } if (data.private) { btn.className = 'vt-badge vt-private'; btn.textContent = 'Private IP'; return; } var verdict = data.verdict || 'clean'; var vtClass = 'vt-' + verdict; var vtLabel = verdict === 'malicious' ? '\u26A0 VT ' + data.malicious + '/' + data.total + ' malicious' : verdict === 'suspicious' ? '\u26A0 VT suspicious' : '\u2713 VT clean'; btn.className = 'vt-badge ' + vtClass; btn.textContent = vtLabel; btn.title = 'Malicious: ' + (data.malicious || 0) + ' Suspicious: ' + (data.suspicious || 0) + ' Total engines: ' + (data.total || 0) + (data.country ? ' Country: ' + data.country : '') + (data.asn ? ' ASN: ' + data.asn : ''); // Remove onclick so it doesn't re-check btn.removeAttribute('onclick'); btn.style.cursor = 'default'; // Highlight card border var card = btn.closest('.device-card'); if (card) { if (verdict === 'malicious') card.classList.add('vt-malicious'); else if (verdict === 'suspicious') card.classList.add('vt-suspicious'); } showToast('VT: ' + ip + ' \u2014 ' + vtLabel, verdict === 'malicious' ? 'error' : verdict === 'suspicious' ? 'info' : 'success'); } catch(e) { btn.className = 'vt-badge vt-private'; btn.textContent = 'VT error'; } }