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.
104 lines
3.5 KiB
JavaScript
104 lines
3.5 KiB
JavaScript
/* 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, '"').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 '<div class="empty-state">'
|
|
+ '<div class="empty-state-icon">' + icon + '</div>'
|
|
+ '<div class="empty-state-title">' + escHtml(title) + '</div>'
|
|
+ (subtitle ? '<div class="empty-state-sub">' + escHtml(subtitle) + '</div>' : '')
|
|
+ '</div>';
|
|
}
|
|
|
|
export function skeletonRows(n) {
|
|
let h = '';
|
|
for (let i = 0; i < n; i++) {
|
|
h += '<div class="skeleton skeleton-row"></div>';
|
|
}
|
|
return h;
|
|
}
|