/* 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 += '';
// 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 += '';
// 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 += '';
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');
}
}