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

81 lines
4.0 KiB
JavaScript

/* ESPILON Honeypot Dashboard — Charts */
import { escHtml, formatTime } from './utils.js';
// ── Severity Donut ──────────────────────────────────────────
export function renderSevDonut(container, stats) {
if (!container) return;
const sevs = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
const rawColors = {CRITICAL: '#f43f5e', HIGH: '#fb923c', MEDIUM: '#fbbf24', LOW: '#4ade80'};
const total = sevs.reduce((s, k) => s + (stats[k] || 0), 0);
if (!total) {
container.innerHTML = '<div class="chart-container text-muted fs-sm" style="height:100px">No events</div>';
return;
}
let gradParts = [], angle = 0;
sevs.forEach(s => {
const v = stats[s] || 0;
if (!v) return;
const seg = (v / total) * 360;
gradParts.push(rawColors[s] + ' ' + angle + 'deg ' + (angle + seg) + 'deg');
angle += seg;
});
let html = '<div class="chart-donut-wrapper">';
html += '<div class="chart-donut-ring" style="background:conic-gradient(' + gradParts.join(',') + ')">'
+ '<div class="chart-donut-center">'
+ '<div class="chart-donut-total">' + total + '</div>'
+ '<div class="chart-donut-label">events</div></div></div>';
html += '<div class="chart-legend">';
sevs.forEach(s => {
const v = stats[s] || 0, pct = (v / total * 100).toFixed(1);
html += '<div class="chart-legend-item">'
+ '<span class="chart-legend-dot" style="background:' + rawColors[s] + '"></span>'
+ '<span class="chart-legend-label">' + s + '</span>'
+ '<span class="fw-600">' + v + '</span>'
+ '<span class="text-muted fs-xs">(' + pct + '%)</span></div>';
});
html += '</div></div>';
container.innerHTML = html;
}
// ── Timeline Bar Chart ──────────────────────────────────────
export function renderTimeline(container, data, height) {
if (!container) return;
height = height || 100;
if (!data || !data.length) {
container.innerHTML = '<div class="chart-container text-muted fs-sm" style="height:' + height + 'px">No activity</div>';
return;
}
const maxTotal = Math.max(...data.map(d => d.total || 0), 1);
const barW = Math.max(Math.floor((container.offsetWidth || 600) / data.length) - 2, 3);
const labelEvery = Math.max(1, Math.floor(data.length / 8));
const chartH = height - 18;
let html = '<div class="chart-timeline" style="height:' + height + 'px">';
data.forEach((d, i) => {
const t = d.total || 0, barH = t > 0 ? Math.max((t / maxTotal) * chartH, 2) : 0;
const crit = d.CRITICAL || 0, high = d.HIGH || 0, med = d.MEDIUM || 0, low = d.LOW || 0;
let timeLabel = '';
if (d.time && i % labelEvery === 0) {
const dt = new Date(typeof d.time === 'number' ? (d.time < 1e12 ? d.time * 1000 : d.time) : d.time);
timeLabel = String(dt.getHours()).padStart(2, '0') + ':' + String(dt.getMinutes()).padStart(2, '0');
}
const tooltip = (timeLabel || formatTime(d.time)) + ' \u2014 Total:' + t
+ ' (C:' + crit + ' H:' + high + ' M:' + med + ' L:' + low + ')';
html += '<div class="chart-bar-col" style="width:' + barW + 'px;height:' + chartH + 'px" title="' + escHtml(tooltip) + '">';
if (t > 0) {
if (crit) html += '<div class="chart-bar-crit" style="height:' + (crit / t) * barH + 'px"></div>';
if (high) html += '<div class="chart-bar-high" style="height:' + (high / t) * barH + 'px"></div>';
if (med) html += '<div class="chart-bar-med" style="height:' + (med / t) * barH + 'px"></div>';
if (low) html += '<div class="chart-bar-low" style="height:' + (low / t) * barH + 'px"></div>';
}
if (timeLabel) {
html += '<div class="chart-time-label">' + timeLabel + '</div>';
}
html += '</div>';
});
html += '</div>';
container.innerHTML = html;
}