/* ESPILON Honeypot Dashboard — Overview Tab */
import { S, SERVICES, MONITORS } from './state.js';
import { $id, escHtml, formatTime, countryFlag, sevClass, layerForType, layerColor, animateCounter, emptyState } from './utils.js';
import { renderTimeline, renderSevDonut } from './charts.js';
import { postApi, fetchAll } from './api.js';
import { showToast } from './ui.js';
const DEVICE_IMG = '/hp-static/hp/img/floating.png';
// ── Device Cards ────────────────────────────────────────────
function renderDeviceCards() {
if (!S.devices.length) {
return emptyState('\uD83D\uDCE1', 'No honeypot devices connected', 'Connect an ESP32 honeypot to start monitoring');
}
let html = '';
S.devices.forEach(d => {
// BUG FIX: case-insensitive status comparison
const st = (d.status || '').toLowerCase();
const isOnline = st === 'online' || st === 'connected';
const dotColor = isOnline ? 'var(--status-success)' : 'var(--sev-high)';
const statusTxt = isOnline ? 'Online' : 'Offline';
const lastSeen = d.last_seen ? formatTime(d.last_seen) : '--';
let runCount = 0, totalSvc = 0;
const devSvc = S.services[d.id] || {};
for (const sn in devSvc) { totalSvc++; if (devSvc[sn] && devSvc[sn].running) runCount++; }
let evtCount = 0;
S.events.forEach(ev => { if (ev.device_id === d.id) evtCount++; });
const sevCounts = {CRITICAL:0, HIGH:0, MEDIUM:0, LOW:0};
S.events.forEach(ev => { if (ev.device_id === d.id && sevCounts[ev.severity] !== undefined) sevCounts[ev.severity]++; });
html += '
';
html += '
';
html += '
';
html += '';
html += '';
html += '
';
html += '
';
html += '
' + escHtml(d.id) + '
';
html += '
' + escHtml(d.ip || 'unknown') + '
';
html += '
' + statusTxt + '
';
html += '
Last seen: ' + lastSeen + '
';
html += '
';
// Stats bar
html += '
';
html += '
' + runCount + '/' + totalSvc + '
Services
';
html += '
' + evtCount + '
Events
';
html += '
' + sevCounts.CRITICAL + '
Critical
';
html += '
';
// Severity mini-bar
const sevTotal = Object.values(sevCounts).reduce((a, b) => a + b, 0) || 1;
html += '
';
if (sevCounts.CRITICAL) html += '';
if (sevCounts.HIGH) html += '';
if (sevCounts.MEDIUM) html += '';
if (sevCounts.LOW) html += '';
html += '
';
html += '
';
});
return html;
}
// ── Top Attackers Table ─────────────────────────────────────
function renderTopAttackersTable() {
const top = S.attackers.slice(0, 8);
if (!top.length) return emptyState('\uD83D\uDC64', 'No attackers', 'Attacker data will appear here');
let html = '
#
IP
Vendor
Count
';
top.forEach((a, i) => {
const flag = countryFlag(a.country_code);
html += '
'
+ '
' + (i + 1) + '
'
+ '
' + (flag ? flag + ' ' : '') + escHtml(a.ip) + '
'
+ '
' + escHtml(a.vendor || '-') + '
'
+ '
' + (a.count || 0) + '
';
});
return html + '
';
}
// ── Service Grid ────────────────────────────────────────────
function renderServiceGrid() {
let svcStatus = {};
for (const devId in S.services) {
const ds = S.services[devId];
for (const sn in ds) if (!svcStatus[sn] || ds[sn].running) svcStatus[sn] = ds[sn];
}
let html = '
';
}
// ── Recent Events ───────────────────────────────────────────
function renderRecentEvents() {
const recent = S.events.slice(0, 10);
if (!recent.length) return emptyState('\uD83D\uDCCA', 'No events', 'Events will appear here as they are detected');
return '