/* ESPILON Honeypot Dashboard — Utilities */ export const $id = id => document.getElementById(id); export function debounce(fn, ms) { let tid; return function(...args) { clearTimeout(tid); tid = setTimeout(() => fn.apply(this, args), ms); }; } export function escHtml(s) { if (!s) return ''; return String(s).replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } export function formatTime(ts) { if (!ts) return '--:--:--'; const d = new Date(typeof ts === 'number' ? (ts < 1e12 ? ts * 1000 : ts) : ts); return d.toLocaleTimeString('en-GB'); } export function formatDuration(s) { if (!s || s <= 0) return '0s'; const d = Math.floor(s / 86400), h = Math.floor(s % 86400 / 3600), m = Math.floor(s % 3600 / 60), sec = Math.floor(s % 60); if (d > 0) return d + 'd ' + h + 'h'; if (h > 0) return h + 'h ' + m + 'm'; if (m > 0) return m + 'm ' + sec + 's'; return sec + 's'; } export function countryFlag(code) { if (!code || code.length !== 2) return ''; return String.fromCodePoint(...[...code.toUpperCase()].map(c => 0x1F1E6 + c.charCodeAt(0) - 65)); } export function animateCounter(el, target) { if (!el) return; target = parseInt(target) || 0; const start = parseInt(el.textContent) || 0; if (start === target) return; const duration = 400, startTime = performance.now(); function step(now) { const p = Math.min((now - startTime) / duration, 1); const ease = 1 - Math.pow(1 - p, 3); el.textContent = Math.round(start + (target - start) * ease); if (p < 1) requestAnimationFrame(step); } requestAnimationFrame(step); } export function sevColor(s) { return {CRITICAL:'var(--sev-crit)', HIGH:'var(--sev-high)', MEDIUM:'var(--sev-med)', LOW:'var(--sev-low)'}[s] || 'var(--text-secondary)'; } export function sevClass(s) { return {CRITICAL:'sev-critical', HIGH:'sev-high', MEDIUM:'sev-medium', LOW:'sev-low'}[s] || ''; } export function layerForType(t) { if (!t) return 'L3'; if (t.startsWith('WIFI_') || t.startsWith('ARP_')) return 'L2'; if (t === 'SVC_CONNECT') return 'L4'; if (t.startsWith('SVC_')) return 'L7'; return 'L3'; } export function layerColor(l) { return {L2:'var(--layer-l2)', L3:'var(--layer-l3)', L4:'var(--layer-l4)', L7:'var(--layer-l7)'}[l] || 'var(--text-secondary)'; } export function svcIcon(name) { const icons = { ssh:'🔒', telnet:'💻', http:'🌐', mqtt:'📡', ftp:'📁', dns:'🏷️', snmp:'📊', tftp:'📂', coap:'⚙️', redis:'💾', rtsp:'🎥', mysql:'🗃️', modbus:'⚙️', upnp:'🔌', sip:'📞', telnet_alt:'💻', wifi:'📶', net:'🌐' }; return icons[(name || '').toLowerCase()] || '⚙️'; } export function maskPassword(p) { if (!p) return ''; if (p.length <= 3) return p; return p.charAt(0) + '*'.repeat(Math.min(p.length - 2, 8)) + p.charAt(p.length - 1); } export function emptyState(icon, title, subtitle) { return '
' + '
' + icon + '
' + '
' + escHtml(title) + '
' + (subtitle ? '
' + escHtml(subtitle) + '
' : '') + '
'; } export function skeletonRows(n) { let h = ''; for (let i = 0; i < n; i++) { h += '
'; } return h; }