espilon-source/tools/C3PO/hp_dashboard/static/hp/js/ui.js
Eun0us 79c2a4d4bf c3po: full server rewrite with modular routes and honeypot dashboard
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.
2026-02-28 20:12:27 +01:00

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();
}