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