/* ESPILON Honeypot Dashboard — Kill Chain Tab */ import { S, KC_PHASES } from './state.js'; import { $id, escHtml, formatTime, formatDuration, emptyState, skeletonRows } from './utils.js'; import { api } from './api.js'; import { showToast } from './ui.js'; export function scoreColor(score) { if (score >= 150) return 'var(--sev-crit)'; if (score >= 80) return 'var(--sev-high)'; if (score >= 40) return 'var(--sev-med)'; return 'var(--sev-low)'; } export function phaseColor(order, total) { const lightness = 65 - (order / total) * 35; const saturation = 50 + (order / total) * 30; return 'hsl(0,' + saturation + '%,' + lightness + '%)'; } export async function renderKillChain() { const ml = $id('main-list'); if (!ml) return; ml.innerHTML = skeletonRows(5); S.killchain = await api('/api/honeypot/killchain?limit=20'); if (!S.killchain || !S.killchain.attackers || !S.killchain.attackers.length) { ml.innerHTML = emptyState('\uD83D\uDEE1\uFE0F', 'No kill chain data', 'Attack progression will be tracked here'); return; } const phases = S.killchain.phases || KC_PHASES; const attackers = S.killchain.attackers; let html = '
'; // Header row with phase names html += '
'; html += 'Attacker'; html += 'Score'; html += '
'; for (let p = 0; p < phases.length; p++) { html += '' + escHtml(phases[p].label) + ''; } html += '
'; html += 'Events'; html += 'Duration'; html += '
'; // Attacker rows for (let i = 0; i < attackers.length; i++) { const a = attackers[i]; html += '
'; // IP + full chain badge html += ''; if (a.is_full_chain) html += '\u26A0'; html += escHtml(a.ip) + ''; // Score html += '' + (a.score || 0) + ''; // Phase progression bar html += '
'; for (let p = 0; p < phases.length; p++) { const phaseId = phases[p].id; const hasPhase = a.phases && a.phases[phaseId] && a.phases[phaseId].count > 0; const segColor = hasPhase ? phaseColor(phases[p].order, phases.length) : 'var(--bg-secondary)'; const segTitle = phases[p].label + (hasPhase ? ' (' + a.phases[phaseId].count + ' events)' : ' (none)'); html += '
'; } html += '
'; // Event count html += '' + (a.total_events || 0) + ''; // Duration const dur = a.duration_seconds ? formatDuration(a.duration_seconds) : '-'; html += '' + dur + ''; html += '
'; } html += '
'; ml.innerHTML = html; } export async function showKillChainDetail(ip) { const data = await api('/api/honeypot/killchain/' + encodeURIComponent(ip)); if (!data) { showToast('Error', 'Failed to load kill chain for ' + ip, '', 'error'); return; } const phases = data.phase_defs || KC_PHASES; let html = '
'; // Header html += '
'; html += '
' + escHtml(data.ip) + '
'; html += '
Score: ' + (data.score || 0) + '
'; html += '
'; // Duration + progression html += '
'; html += 'Max phase: ' + escHtml(data.max_phase || '-') + ''; html += 'Progression: ' + (data.progression_pct || 0) + '%'; if (data.duration_seconds) html += 'Duration: ' + formatDuration(data.duration_seconds) + ''; if (data.is_full_chain) html += '\u26A0 Full Kill Chain'; html += '
'; // Visual progression bar html += '
'; for (let p = 0; p < phases.length; p++) { const phaseId = phases[p].id; const hasPhase = data.phases && data.phases[phaseId] && data.phases[phaseId].count > 0; const segColor = hasPhase ? phaseColor(phases[p].order, phases.length) : 'var(--bg-secondary)'; const segBorder = hasPhase ? 'none' : '1px solid var(--border-color)'; html += '
'; } html += '
'; // Phase details for (let p = 0; p < phases.length; p++) { const phaseId = phases[p].id; const phaseData = (data.phases && data.phases[phaseId]) ? data.phases[phaseId] : null; const active = phaseData && phaseData.count > 0; const color = active ? phaseColor(phases[p].order, phases.length) : 'var(--border-color)'; html += '
'; html += '
'; html += '' + escHtml(phases[p].label) + ''; if (active) { html += '' + phaseData.count + ' events · first seen ' + formatTime(phaseData.first_seen) + ''; } html += '
'; if (active && phaseData.techniques && phaseData.techniques.length) { html += '
'; for (let t = 0; t < phaseData.techniques.length; t++) { const tech = phaseData.techniques[t]; const tid = typeof tech === 'string' ? tech : (tech.technique_id || tech.id || tech); html += '' + escHtml(String(tid)) + ''; } html += '
'; } html += '
'; } html += '
'; // Show in detail panel const panel = $id('detail-panel'); if (panel) { const panelBody = panel.querySelector('.detail-body') || panel; panelBody.innerHTML = html; panel.classList.add('open'); } }