espilon-source/tools/C3PO/hp_dashboard/static/hp/js/sidebar.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

212 lines
7.3 KiB
JavaScript

/* ESPILON Honeypot Dashboard — Sidebar Renders */
import { S } from './state.js';
import { $id, escHtml, formatTime, countryFlag, sevColor, layerForType, layerColor, emptyState } from './utils.js';
import { renderSevDonut } from './charts.js';
// ── Severity Stats ──────────────────────────────────────────
export function renderSevStats() {
const el = $id('sev-stats');
if (!el) return;
const bySev = (S.stats && S.stats.by_severity) ? S.stats.by_severity : {};
const sevs = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
const counts = [];
let total = 0;
for (let i = 0; i < sevs.length; i++) {
const c = bySev[sevs[i]] || 0;
counts.push(c);
total += c;
}
let html = '';
// Donut placeholder
html += '<div id="sev-donut-sb" class="text-center mb-sm"></div>';
// Count list
html += '<div>';
for (let i = 0; i < sevs.length; i++) {
html += '<div class="sev-stat-row">';
html += '<span><span class="sev-stat-dot" style="background:' + sevColor(sevs[i]) + '"></span>' + sevs[i] + '</span>';
html += '<span class="sev-stat-count">' + counts[i] + '</span>';
html += '</div>';
}
html += '</div>';
el.innerHTML = html;
// Render donut into the container
const donutEl = $id('sev-donut-sb');
if (donutEl) {
const statsObj = {};
for (let i = 0; i < sevs.length; i++) statsObj[sevs[i]] = counts[i];
renderSevDonut(donutEl, statsObj);
}
}
// ── Layer Stats ─────────────────────────────────────────────
export function renderLayerStats() {
const el = $id('layer-stats');
if (!el) return;
const layerCounts = { L2: 0, L3: 0, L4: 0, L7: 0 };
if (S.events) {
for (let i = 0; i < S.events.length; i++) {
const l = layerForType(S.events[i].event_type);
if (layerCounts[l] !== undefined) layerCounts[l]++;
}
}
const layers = ['L2', 'L3', 'L4', 'L7'];
let html = '<div class="layer-grid">';
for (let i = 0; i < layers.length; i++) {
const l = layers[i];
html += '<div class="layer-box" style="border-top-color:' + layerColor(l) + '">';
html += '<div class="layer-box-val">' + layerCounts[l] + '</div>';
html += '<div class="layer-box-label">' + l + '</div>';
html += '</div>';
}
html += '</div>';
el.innerHTML = html;
}
// ── Attackers ───────────────────────────────────────────────
export function renderAttackers() {
const el = $id('attackers-list');
if (!el) return;
const list = S.attackers || [];
const top = list.slice(0, 10);
if (!top.length) {
el.innerHTML = emptyState('\u{1F464}', 'No attackers yet', '');
return;
}
const maxCount = top[0].total_events || top[0].count || 1;
let html = '';
for (let i = 0; i < top.length; i++) {
const a = top[i];
const cnt = a.total_events || a.count || 0;
const pct = Math.round((cnt / maxCount) * 100);
const flag = a.country_code ? countryFlag(a.country_code) + ' ' : '';
const vendor = a.vendor || '';
html += '<div class="atk-row" data-action="attacker" data-ip="' + escHtml(a.ip) + '">';
html += '<div class="atk-row-top">';
html += '<span class="atk-row-ip">' + flag + escHtml(a.ip) + '</span>';
html += '<span class="atk-row-count">' + cnt + '</span>';
html += '</div>';
if (vendor) {
html += '<div class="atk-row-vendor">' + escHtml(vendor) + '</div>';
}
html += '<div class="atk-row-bar">';
html += '<div class="atk-row-bar-fill" style="width:' + pct + '%"></div>';
html += '</div>';
html += '</div>';
}
el.innerHTML = html;
}
// ── Alerts ──────────────────────────────────────────────────
export function renderAlerts() {
const el = $id('alerts-list');
const badge = $id('alert-badge');
const banner = $id('alert-banner');
const bannerCount = $id('alert-banner-count');
const alerts = S.alerts || [];
const unacked = alerts.filter(function(a) { return !a.acknowledged; });
// Badge
if (badge) {
badge.textContent = unacked.length;
badge.style.display = unacked.length > 0 ? '' : 'none';
}
// Banner
if (banner) {
banner.style.display = unacked.length > 0 ? '' : 'none';
}
if (bannerCount) {
bannerCount.textContent = unacked.length;
}
// List
if (!el) return;
if (!alerts.length) {
el.innerHTML = emptyState('\u{1F514}', 'No alerts', '');
return;
}
let html = '';
for (let i = 0; i < alerts.length; i++) {
const a = alerts[i];
const borderCol = sevColor(a.severity || 'MEDIUM');
const acked = a.acknowledged;
html += '<div class="alert-item' + (acked ? ' acked' : '') + '" style="border-left-color:' + borderCol + '">';
html += '<div class="alert-item-top">';
html += '<div class="alert-item-msg">' + escHtml(a.message || a.detail || 'Alert') + '</div>';
if (!acked) {
html += '<button class="btn-ack" data-action="ack-alert" data-id="' + a.id + '">ACK</button>';
}
html += '</div>';
if (a.timestamp) {
html += '<div class="alert-item-time">' + formatTime(a.timestamp) + '</div>';
}
html += '</div>';
}
el.innerHTML = html;
}
// ── Command History ─────────────────────────────────────────
export function renderHistory() {
const el = $id('cmd-history');
if (!el) return;
const list = S.history || [];
const items = list.slice(0, 20);
if (!items.length) {
el.innerHTML = emptyState('\u{2328}\u{FE0F}', 'No commands yet', '');
return;
}
let html = '';
for (let i = 0; i < items.length; i++) {
const h = items[i];
let statusDot;
if (h.status === 'completed') {
statusDot = 'var(--sev-low)';
} else if (h.status === 'pending' || h.status === 'sent') {
statusDot = 'var(--sev-med)';
} else {
statusDot = 'var(--sev-high)';
}
let cmdName = h.command_name || h.command || '?';
if (cmdName.length > 24) cmdName = cmdName.substring(0, 24) + '\u2026';
const timeStr = h.sent_at ? formatTime(h.sent_at) : '';
html += '<div class="hist-item">';
html += '<span class="hist-dot" style="background:' + statusDot + '"></span>';
html += '<span class="hist-cmd" title="' + escHtml(h.command_name || h.command || '') + '">' + escHtml(cmdName) + '</span>';
html += '<span class="hist-time">' + timeStr + '</span>';
html += '</div>';
}
el.innerHTML = html;
}
// ── Full Sidebar Render ─────────────────────────────────────
export function renderSidebar() {
renderSevStats();
renderLayerStats();
renderAttackers();
renderAlerts();
renderHistory();
}