/* ESPILON Honeypot Dashboard — Sessions Tab + Replay */ import { S } from './state.js'; import { $id, escHtml, formatTime, formatDuration, sevClass, svcIcon, emptyState, skeletonRows } from './utils.js'; import { api } from './api.js'; // ── Sessions Tab ──────────────────────────────────────────── export async function renderSessions() { const ml = $id('main-list'); if (!ml) return; ml.innerHTML = skeletonRows(5); const url = '/api/honeypot/sessions?limit=50'; const data = await api(url); S.sessions = (data && data.sessions) ? data.sessions : []; if (!S.sessions.length) { ml.innerHTML = emptyState('\uD83D\uDC64', 'No sessions recorded', 'Session data appears when attackers interact with services'); return; } let html = '
'; for (let i = 0; i < S.sessions.length; i++) { const s = S.sessions[i]; const sevCls = sevClass(s.max_severity || 'LOW'); const startT = formatTime(s.start_time); const endT = s.end_time ? formatTime(s.end_time) : 'active'; const dur = s.end_time && s.start_time ? formatDuration(Math.round((new Date(s.end_time) - new Date(s.start_time)) / 1000)) : 'ongoing'; const port = s.dst_port ? ':' + s.dst_port : ''; html += '
' + '
' + '' + svcIcon(s.service || '') + ' ' + escHtml(s.service || '?') + escHtml(port) + '' + '' + escHtml(s.src_ip) + '' + '' + startT + ' \u2192 ' + endT + '' + '' + escHtml(s.max_severity || 'LOW') + '' + '
' + '
' + '\uD83D\uDCC4 ' + (s.event_count || 0) + ' events' + '' + dur + '' + (s.auth_count ? '\uD83D\uDD11 ' + s.auth_count + '' : '') + (s.cmd_count ? '\u2328 ' + s.cmd_count + '' : '') + (s.malware_count ? 'malware: ' + s.malware_count + '' : '') + '
' + '
'; } html += '
'; ml.innerHTML = html; } // ── Session Replay ────────────────────────────────────────── export async function openReplay(sessionId, ip, service) { const modal = $id('replay-modal'); modal.classList.add('open'); $id('replay-title').textContent = 'Session ' + sessionId; $id('replay-meta').textContent = ip + ' — ' + service; $id('replay-terminal').innerHTML = ''; $id('replay-progress').style.width = '0%'; $id('replay-counter').textContent = '0/0'; $id('replay-play-btn').textContent = 'Play'; S._replayEvents = []; S._replayIdx = 0; S._replayPlaying = false; if (S._replayInterval) { clearInterval(S._replayInterval); S._replayInterval = null; } const data = await api('/api/honeypot/sessions/' + sessionId); if (data && data.events) S._replayEvents = data.events; $id('replay-counter').textContent = '0/' + S._replayEvents.length; } export function toggleReplayPlayback() { S._replayPlaying = !S._replayPlaying; $id('replay-play-btn').textContent = S._replayPlaying ? 'Pause' : 'Play'; if (S._replayPlaying) { const speed = parseInt($id('replay-speed').value) || 500; S._replayInterval = setInterval(replayStep, speed); } else if (S._replayInterval) { clearInterval(S._replayInterval); S._replayInterval = null; } } export function replayStep() { if (S._replayIdx >= S._replayEvents.length) { S._replayPlaying = false; $id('replay-play-btn').textContent = 'Play'; if (S._replayInterval) { clearInterval(S._replayInterval); S._replayInterval = null; } return; } const evt = S._replayEvents[S._replayIdx]; const term = $id('replay-terminal'); term.innerHTML += renderReplayLine(evt); term.scrollTop = term.scrollHeight; S._replayIdx++; const total = S._replayEvents.length; $id('replay-progress').style.width = (S._replayIdx / total * 100) + '%'; $id('replay-counter').textContent = S._replayIdx + '/' + total; } function renderReplayLine(evt) { const type = evt.event_type || '', detail = evt.detail || ''; if (type === 'SVC_CONNECT') return '
Connected from ' + escHtml(evt.src_ip || '') + '
'; if (type === 'SVC_AUTH_ATTEMPT') { const ok = detail.toLowerCase().includes('success'); return '
AUTH user=\'' + escHtml(evt.username || '') + '\' pass=\'' + escHtml(evt.password || '') + '\' [' + (ok ? 'OK' : 'FAIL') + ']
'; } if (type === 'SVC_COMMAND') return '
$ ' + escHtml(evt.command || detail) + '
'; if (type === 'SVC_HTTP_REQUEST') return '
' + escHtml(detail) + '
'; return '
' + escHtml(detail) + '
'; } export function replayReset() { S._replayIdx = 0; S._replayPlaying = false; if (S._replayInterval) { clearInterval(S._replayInterval); S._replayInterval = null; } $id('replay-play-btn').textContent = 'Play'; $id('replay-terminal').innerHTML = ''; $id('replay-progress').style.width = '0%'; $id('replay-counter').textContent = '0/' + S._replayEvents.length; } export function seekReplay(e) { const bar = e.currentTarget, rect = bar.getBoundingClientRect(); const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); const total = S._replayEvents.length, target = Math.round(pct * total); const term = $id('replay-terminal'); term.innerHTML = ''; for (let i = 0; i < target && i < total; i++) term.innerHTML += renderReplayLine(S._replayEvents[i]); term.scrollTop = term.scrollHeight; S._replayIdx = target; $id('replay-progress').style.width = (target / total * 100) + '%'; $id('replay-counter').textContent = target + '/' + total; } export function updateReplaySpeed() { if (S._replayPlaying && S._replayInterval) { clearInterval(S._replayInterval); S._replayInterval = setInterval(replayStep, parseInt($id('replay-speed').value) || 500); } } export function closeReplay() { S._replayPlaying = false; if (S._replayInterval) { clearInterval(S._replayInterval); S._replayInterval = null; } $id('replay-modal')?.classList.remove('open'); }