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.
81 lines
4.0 KiB
JavaScript
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;
|
|
}
|