/* ESPILON Honeypot Dashboard — Events Tab + Attacker Modal */ import { S } from './state.js'; import { $id, escHtml, formatTime, sevClass, layerForType, layerColor, countryFlag, debounce, emptyState, skeletonRows } from './utils.js'; import { api } from './api.js'; import { showToast, closeDetail, closeModal } from './ui.js'; // ── Events Tab ────────────────────────────────────────────── const EVENTS_PAGE_SIZE = 50; let _eventsHasMore = true; export function renderEvents() { const sb = $id('search-bar'); if (sb) sb.style.display = ''; const ml = $id('main-list'); if (!ml) return; if (!S.events || !S.events.length) { ml.innerHTML = emptyState('\uD83D\uDCCA', 'No events recorded', 'Events will appear here as they are detected'); return; } let html = ''; for (let i = 0; i < S.events.length; i++) { html += buildEventRow(S.events[i]); } if (_eventsHasMore) { html += '
'; } ml.innerHTML = html; } export function buildEventRow(evt) { const layer = layerForType(evt.event_type); let detail = evt.detail || ''; if (detail.length > 80) detail = detail.substring(0, 80) + '\u2026'; let mitre = ''; if (evt.mitre_techniques) { mitre = buildMitreBadges(evt.mitre_techniques); } const safeId = parseInt(evt.id) || 0; return '
' + '
' + '' + formatTime(evt.timestamp) + '' + '[' + layer + ']' + '' + escHtml(evt.service || '') + '' + '' + escHtml(evt.event_type) + '' + '' + escHtml(evt.severity) + '' + '
' + '
' + '' + escHtml(evt.src_ip) + '' + '' + escHtml(detail) + '' + '' + mitre + '' + '
' + '
'; } export function buildMitreBadges(mitreJson) { let techniques; if (!mitreJson) return ''; try { if (typeof mitreJson === 'string') { techniques = JSON.parse(mitreJson); } else { techniques = mitreJson; } } catch (e) { return ''; } if (!techniques || !techniques.length) return ''; let html = ''; for (let i = 0; i < techniques.length; i++) { const t = techniques[i]; const tid = typeof t === 'string' ? t : (t.technique_id || t.id || t); const name = typeof t === 'object' ? (t.name || t.technique_name || '') : ''; const tactic = typeof t === 'object' ? (t.tactic || '') : ''; let title = tid; if (name) title += ' - ' + name; if (tactic) title += ' (' + tactic + ')'; html += '' + escHtml(String(tid)) + ''; } return html; } export function prependEventRow(evt) { if (S.tab !== 'timeline') return; const ml = $id('main-list'); if (!ml) return; const empty = ml.querySelector('.empty-state'); if (empty) ml.innerHTML = ''; const wrapper = document.createElement('div'); wrapper.innerHTML = buildEventRow(evt); const el = wrapper.firstChild; if (el) { el.classList.add('flash'); ml.insertBefore(el, ml.firstChild); setTimeout(function() { el.classList.remove('flash'); }, 2000); } } // ── Search & Filters ──────────────────────────────────────── const _debouncedFilter = debounce(() => applyFilters(), 300); export function onSearchKey(e) { if (!e) { applyFilters(); return; } if (e.key === 'Enter') applyFilters(); else if (e.key === 'Escape') { e.target.value = ''; applyFilters(); } else _debouncedFilter(); } function _buildFilterParams() { const params = new URLSearchParams(); const q = ($id('search-input')?.value || '').trim(); const type = $id('f-type')?.value; const sev = $id('f-sev')?.value; const service = $id('f-service')?.value; const ip = ($id('f-ip')?.value || '').trim(); if (q) params.set('q', q); if (type) params.set('type', type); if (sev) params.set('severity', sev); if (service) params.set('service', service); if (ip) params.set('ip', ip); return params; } export async function applyFilters() { const params = _buildFilterParams(); params.set('limit', String(EVENTS_PAGE_SIZE)); const data = await api('/api/honeypot/search?' + params.toString()); if (data) { S.events = data.events || []; _eventsHasMore = S.events.length >= EVENTS_PAGE_SIZE; renderEvents(); } } export async function loadMoreEvents() { const params = _buildFilterParams(); params.set('limit', String(EVENTS_PAGE_SIZE)); params.set('offset', String(S.events.length)); const data = await api('/api/honeypot/search?' + params.toString()); if (data && data.events && data.events.length) { S.events.push(...data.events); _eventsHasMore = data.events.length >= EVENTS_PAGE_SIZE; renderEvents(); } else { _eventsHasMore = false; /* Remove the Load More button */ const btn = document.querySelector('[data-action="load-more-events"]'); if (btn && btn.parentElement) btn.parentElement.remove(); } } export function toggleFilters() { $id('filter-panel')?.classList.toggle('active'); } // ── Event Detail Panel ────────────────────────────────────── export async function showDetail(eventId) { const panel = $id('detail-panel'), body = $id('detail-body'); if (!panel || !body) return; panel.classList.add('open'); body.innerHTML = skeletonRows(6); const evt = await api('/api/honeypot/events/' + eventId); if (!evt) { body.innerHTML = '

Failed to load event

'; return; } let html = ''; // Connection html += '

Connection

'; if (evt.src_ip) html += '
Source IP' + escHtml(evt.src_ip) + '
'; if (evt.src_port) html += '
Src Port' + escHtml(String(evt.src_port)) + '
'; if (evt.dst_port) html += '
Dst Port' + escHtml(String(evt.dst_port)) + '
'; if (evt.service) html += '
Service' + escHtml(evt.service) + '
'; if (evt.session_id) html += '
Session' + escHtml(evt.session_id) + '
'; html += '
'; // Event html += '

Event

'; html += '
Type' + escHtml(evt.event_type) + '
'; html += '
Severity' + escHtml(evt.severity) + '
'; html += '
Time' + formatTime(evt.timestamp) + '
'; if (evt.device_id) html += '
Device' + escHtml(evt.device_id) + '
'; if (evt.detail) html += '
Detail' + escHtml(evt.detail) + '
'; html += '
'; // Auth if (evt.username) { html += '

Authentication

'; html += '
Username' + escHtml(evt.username) + '
'; if (evt.password) html += '
Password' + escHtml(evt.password) + '
'; html += '
'; } // Payload if (evt.command || evt.url || evt.path) { html += '

Payload

'; if (evt.command) html += '
Command' + escHtml(evt.command) + '
'; if (evt.url) html += '
URL' + escHtml(evt.url) + '
'; if (evt.path) html += '
Path' + escHtml(evt.path) + '
'; html += '
'; } // Tags if (evt.malware_tag || evt.os_tag) { html += '

Tags

'; if (evt.malware_tag) html += '' + escHtml(evt.malware_tag) + ''; if (evt.os_tag) html += '' + escHtml(evt.os_tag) + ''; html += '
'; } // MITRE if (evt.mitre_techniques) { html += '

MITRE ATT&CK

'; html += buildMitreBadges(evt.mitre_techniques); html += '
'; } // Related if (evt.related && evt.related.length) { html += '

Related Events

'; evt.related.slice(0, 10).forEach(r => { html += '
#' + (parseInt(r.id) || 0) + ' ' + escHtml(r.event_type) + ' \u2014 ' + escHtml(r.severity) + ' \u2014 ' + formatTime(r.timestamp) + '
'; }); html += '
'; } // Copy JSON window._detailEvtJson = JSON.stringify(evt, null, 2); html += '
'; body.innerHTML = html; } // ── Export ─────────────────────────────────────────────────── export function exportData(format) { const params = new URLSearchParams(); params.set('format', format); const q = ($id('search-input')?.value || '').trim(); if (q) params.set('q', q); if ($id('f-type')?.value) params.set('type', $id('f-type').value); if ($id('f-sev')?.value) params.set('severity', $id('f-sev').value); if ($id('f-service')?.value) params.set('service', $id('f-service').value); const ip = ($id('f-ip')?.value || '').trim(); if (ip) params.set('ip', ip); window.open('/api/honeypot/export?' + params.toString()); } // ── Attacker Modal ────────────────────────────────────────── export async function showAttackerModal(ip) { const modal = $id('attacker-modal'), body = $id('modal-body'); if (!modal || !body) return; modal.classList.add('open'); $id('modal-title').textContent = ip; body.innerHTML = skeletonRows(5); const data = await api('/api/honeypot/attacker/' + ip); if (!data) { body.innerHTML = '

Not found

'; return; } const credCount = data.credentials?.length || 0; const cmdCount = data.commands?.length || 0; const sessCount = data.sessions?.length || 0; let html = ''; // Header badges html += ''; // Stats grid html += ''; // Geo if (data.is_private) { html += ''; } else if (data.country || data.city || data.isp) { html += ''; } // Tabs const defaultTab = credCount > 0 ? 'creds' : 'events'; html += ''; // Creds panel html += ''; // Cmds panel html += ''; // Sessions panel html += ''; // Events panel html += ''; body.innerHTML = html; } export function switchModalTab(tabName) { document.querySelectorAll('.modal-tab-btn').forEach(b => { const active = b.dataset.tab === tabName; b.classList.toggle('active', active); }); document.querySelectorAll('.modal-tab-panel').forEach(p => { p.style.display = p.dataset.tab === tabName ? '' : 'none'; }); }