/* 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 += '
Load More
';
}
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 += 'Copy JSON
';
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 += '';
[{v: data.total_events || 0, l: 'Events'}, {v: credCount, l: 'Credentials'}, {v: cmdCount, l: 'Commands'}, {v: sessCount, l: 'Sessions'}].forEach(s => {
html += '
';
});
html += '
';
// Geo
if (data.is_private) {
html += 'Network
Private Network (LAN)
';
if (data.vendor) html += '
Vendor: ' + escHtml(data.vendor) + '
';
html += '
';
} else if (data.country || data.city || data.isp) {
html += 'Geo / Threat Intel
';
if (data.country) html += '
' + countryFlag(data.country_code) + ' ' + escHtml(data.country) + '
';
if (data.city) html += '
City: ' + escHtml(data.city) + '
';
if (data.isp) html += '
ISP: ' + escHtml(data.isp) + '
';
if (data.vendor) html += '
Vendor: ' + escHtml(data.vendor) + '
';
html += '
';
}
// Tabs
const defaultTab = credCount > 0 ? 'creds' : 'events';
html += '';
['creds', 'cmds', 'sess', 'events'].forEach(t => {
const labels = {creds: 'Credentials (' + credCount + ')', cmds: 'Commands (' + cmdCount + ')', sess: 'Sessions (' + sessCount + ')', events: 'Events'};
html += '' + labels[t] + ' ';
});
html += '
';
// Creds panel
html += '';
if (credCount) {
html += '
User Pass Svc Time ';
data.credentials.forEach(c => {
html += '' + escHtml(c.username || '') + ' ' + escHtml(c.password || '') + ' ' + escHtml(c.service || '') + ' ' + formatTime(c.timestamp) + ' ';
});
html += '
';
} else html += emptyState('\uD83D\uDD12', 'No credentials', '');
html += '
';
// Cmds panel
html += '';
if (cmdCount) {
html += '
Command Svc Time ';
data.commands.forEach(c => {
html += '' + escHtml(c.command || '') + ' ' + escHtml(c.service || '') + ' ' + formatTime(c.timestamp) + ' ';
});
html += '
';
} else html += emptyState('\uD83D\uDCBB', 'No commands', '');
html += '
';
// Sessions panel
html += '';
if (sessCount) {
data.sessions.forEach(s => {
html += '
'
+ '
' + escHtml(s.service || '') + ' session ' + (s.event_count || 0) + ' events
'
+ '
' + formatTime(s.start) + ' \u2014 ' + formatTime(s.end) + '
';
});
} else html += emptyState('\uD83D\uDD17', 'No sessions', '');
html += '
';
// Events panel
html += '';
if (data.events?.length) {
data.events.slice(0, 20).forEach(ev => {
html += '
'
+ '' + escHtml(ev.severity || '') + ' '
+ '' + escHtml(ev.event_type || '') + ' ' + escHtml((ev.detail || '').substring(0, 50)) + ' '
+ '' + formatTime(ev.timestamp) + '
';
});
} else html += emptyState('\uD83D\uDCCB', 'No events', '');
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';
});
}