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.
132 lines
4.7 KiB
JavaScript
132 lines
4.7 KiB
JavaScript
/* ESPILON Honeypot Dashboard — UI Components */
|
|
|
|
import { escHtml, $id } from './utils.js';
|
|
|
|
// ── Toasts ──────────────────────────────────────────────────
|
|
|
|
export function showToast(title, msg, detail, type) {
|
|
type = type || 'info';
|
|
const c = $id('toast-container');
|
|
if (!c) return;
|
|
const icons = {
|
|
success: '✓',
|
|
error: '✗',
|
|
warning: '⚠',
|
|
info: 'ℹ'
|
|
};
|
|
const colors = {
|
|
success: 'var(--status-success)',
|
|
error: 'var(--status-error)',
|
|
warning: 'var(--status-warning)',
|
|
info: 'var(--accent-secondary)'
|
|
};
|
|
const d = document.createElement('div');
|
|
d.className = 'toast';
|
|
d.dataset.type = type;
|
|
d.innerHTML = '<div class="toast-icon" style="color:' + colors[type] + '">' + (icons[type] || '') + '</div>'
|
|
+ '<div class="toast-body"><div class="toast-title">' + escHtml(title) + '</div>'
|
|
+ '<div class="toast-text">' + escHtml(msg) + '</div></div>'
|
|
+ '<div class="toast-progress" style="background:' + colors[type] + '"></div>';
|
|
c.appendChild(d);
|
|
requestAnimationFrame(() => d.classList.add('show'));
|
|
setTimeout(() => { d.classList.remove('show'); setTimeout(() => d.remove(), 300); }, 4000);
|
|
}
|
|
|
|
// ── Modals & Panels ─────────────────────────────────────────
|
|
|
|
export function closeDetail() {
|
|
$id('detail-panel')?.classList.remove('open');
|
|
}
|
|
|
|
export function closeModal() {
|
|
$id('attacker-modal')?.classList.remove('open');
|
|
}
|
|
|
|
// ── Sidebar & Nav Toggles ───────────────────────────────────
|
|
|
|
export function toggleSidebar() {
|
|
$id('sidebar')?.classList.toggle('sidebar-open');
|
|
$id('sidebar-overlay')?.classList.toggle('active');
|
|
}
|
|
|
|
export function toggleNavMenu() {
|
|
$id('nav-tabs')?.classList.toggle('nav-open');
|
|
}
|
|
|
|
export function toggleSbSection(headerEl) {
|
|
const section = headerEl.closest('.sb-section');
|
|
if (!section) return;
|
|
section.classList.toggle('collapsed');
|
|
document.querySelectorAll('.sb-section').forEach((sec, i) => {
|
|
if (sec === section) localStorage.setItem('sb_' + i, sec.classList.contains('collapsed'));
|
|
});
|
|
}
|
|
|
|
export function scrollToAlerts() {
|
|
const el = $id('alerts-panel');
|
|
if (el) el.scrollIntoView({behavior: 'smooth'});
|
|
}
|
|
|
|
export function restoreSidebarState() {
|
|
document.querySelectorAll('.sb-section').forEach((sec, i) => {
|
|
if (localStorage.getItem('sb_' + i) === 'true') sec.classList.add('collapsed');
|
|
});
|
|
}
|
|
|
|
// ── Event Delegation ────────────────────────────────────────
|
|
// Central click handler that replaces inline onclick attributes.
|
|
// HTML elements use data-action="..." attributes; the action map
|
|
// is populated by app.js after all modules are loaded.
|
|
|
|
const _actionHandlers = {};
|
|
|
|
export function registerAction(name, handler) {
|
|
_actionHandlers[name] = handler;
|
|
}
|
|
|
|
export function registerActions(map) {
|
|
Object.assign(_actionHandlers, map);
|
|
}
|
|
|
|
export function setupEventDelegation() {
|
|
document.addEventListener('click', (e) => {
|
|
const el = e.target.closest('[data-action]');
|
|
if (!el) return;
|
|
const action = el.dataset.action;
|
|
const handler = _actionHandlers[action];
|
|
if (handler) {
|
|
e.preventDefault();
|
|
handler(el, e);
|
|
}
|
|
});
|
|
|
|
// Modal backdrop clicks
|
|
$id('attacker-modal')?.addEventListener('click', function(e) {
|
|
if (e.target === this) closeModal();
|
|
});
|
|
$id('replay-modal')?.addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
// closeReplay is registered via registerAction from sessions.js;
|
|
// for the backdrop we just hide the modal directly.
|
|
$id('replay-modal')?.classList.remove('open');
|
|
}
|
|
});
|
|
}
|
|
|
|
// ── Responsive Sidebar ──────────────────────────────────────
|
|
|
|
export function setupResponsive() {
|
|
function checkResponsive() {
|
|
const w = window.innerWidth;
|
|
const sidebarBtn = $id('sidebar-toggle-btn');
|
|
if (sidebarBtn) sidebarBtn.style.display = w < 1024 ? '' : 'none';
|
|
// On resize to desktop, remove mobile sidebar state
|
|
if (w >= 1024) {
|
|
$id('sidebar')?.classList.remove('sidebar-open');
|
|
$id('sidebar-overlay')?.classList.remove('active');
|
|
}
|
|
}
|
|
window.addEventListener('resize', checkResponsive);
|
|
checkResponsive();
|
|
}
|