espilon-source/tools/C3PO/hp_dashboard/static/hp/js/sessions.js
Eun0us 79c2a4d4bf c3po: full server rewrite with modular routes and honeypot dashboard
Replace monolithic CLI and web server with route-based Flask API.
New routes: api_commands, api_build, api_can, api_monitor, api_ota,
api_tunnel. Add honeypot security dashboard with real-time SSE,
MITRE ATT&CK mapping, kill chain analysis.

New TUI with commander/help modules. Add session management,
tunnel proxy core, CAN bus data store. Docker support.
2026-02-28 20:12:27 +01:00

153 lines
7.0 KiB
JavaScript

/* 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 = '<div class="session-list">';
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 += '<div class="ev-row session-row" data-action="replay" data-session="' + escHtml(s.session_id) + '" data-ip="' + escHtml(s.src_ip) + '" data-service="' + escHtml(s.service || '') + '">'
+ '<div class="ev-row-line1">'
+ '<span class="ev-service">' + svcIcon(s.service || '') + ' ' + escHtml(s.service || '?') + escHtml(port) + '</span>'
+ '<span class="ev-ip" data-action="attacker" data-ip="' + escHtml(s.src_ip) + '">' + escHtml(s.src_ip) + '</span>'
+ '<span class="ev-time">' + startT + ' \u2192 ' + endT + '</span>'
+ '<span class="ev-severity ' + sevCls + '">' + escHtml(s.max_severity || 'LOW') + '</span>'
+ '</div>'
+ '<div class="ev-row-line2">'
+ '<span>\uD83D\uDCC4 ' + (s.event_count || 0) + ' events</span>'
+ '<span>' + dur + '</span>'
+ (s.auth_count ? '<span>\uD83D\uDD11 ' + s.auth_count + '</span>' : '')
+ (s.cmd_count ? '<span>\u2328 ' + s.cmd_count + '</span>' : '')
+ (s.malware_count ? '<span class="text-crit">malware: ' + s.malware_count + '</span>' : '')
+ '</div>'
+ '</div>';
}
html += '</div>';
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 '<div class="replay-line replay-line-connect">Connected from ' + escHtml(evt.src_ip || '') + '</div>';
if (type === 'SVC_AUTH_ATTEMPT') {
const ok = detail.toLowerCase().includes('success');
return '<div class="replay-line ' + (ok ? 'replay-line-auth-ok' : 'replay-line-auth-fail') + '">AUTH user=\'' + escHtml(evt.username || '') + '\' pass=\'' + escHtml(evt.password || '') + '\' [' + (ok ? 'OK' : 'FAIL') + ']</div>';
}
if (type === 'SVC_COMMAND')
return '<div class="replay-line replay-line-cmd">$ ' + escHtml(evt.command || detail) + '</div>';
if (type === 'SVC_HTTP_REQUEST')
return '<div class="replay-line replay-line-http">' + escHtml(detail) + '</div>';
return '<div class="replay-line">' + escHtml(detail) + '</div>';
}
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');
}