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.
78 lines
3.3 KiB
JavaScript
78 lines
3.3 KiB
JavaScript
/* ESPILON Honeypot Dashboard — Credentials Tab */
|
|
|
|
import { S } from './state.js';
|
|
import { $id, escHtml, maskPassword, svcIcon, emptyState, skeletonRows } from './utils.js';
|
|
import { api } from './api.js';
|
|
|
|
export async function renderCredentials() {
|
|
const ml = $id('main-list');
|
|
if (!ml) return;
|
|
ml.innerHTML = skeletonRows(5);
|
|
|
|
if (!S.credentials) {
|
|
S.credentials = await api('/api/honeypot/credentials');
|
|
}
|
|
const c = S.credentials;
|
|
if (!c) {
|
|
ml.innerHTML = emptyState('\uD83D\uDD12', 'No credential data', 'Captured credentials will be shown here');
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="ov-2col">';
|
|
|
|
// Top Usernames
|
|
html += '<div class="sb-section"><div class="sb-header">Top Usernames</div><div class="sb-body">';
|
|
html += '<table class="ov-table"><tr><th>#</th><th>Username</th><th>Count</th><th>Services</th></tr>';
|
|
const users = c.top_usernames || [];
|
|
for (let i = 0; i < users.length; i++) {
|
|
const u = users[i];
|
|
html += '<tr><td>' + (i + 1) + '</td>'
|
|
+ '<td class="font-mono">' + escHtml(u.username) + '</td>'
|
|
+ '<td>' + u.cnt + '</td>'
|
|
+ '<td>' + escHtml(Array.isArray(u.services) ? u.services.join(', ') : (u.services || '')) + '</td></tr>';
|
|
}
|
|
html += '</table></div></div>';
|
|
|
|
// Top Passwords
|
|
html += '<div class="sb-section"><div class="sb-header">Top Passwords</div><div class="sb-body">';
|
|
html += '<table class="ov-table"><tr><th>#</th><th>Password</th><th>Count</th><th>Services</th></tr>';
|
|
const passwords = c.top_passwords || [];
|
|
for (let i = 0; i < passwords.length; i++) {
|
|
const p = passwords[i];
|
|
const masked = maskPassword(p.password);
|
|
html += '<tr><td>' + (i + 1) + '</td>'
|
|
+ '<td class="font-mono">' + escHtml(masked) + '</td>'
|
|
+ '<td>' + p.cnt + '</td>'
|
|
+ '<td>' + escHtml(Array.isArray(p.services) ? p.services.join(', ') : (p.services || '')) + '</td></tr>';
|
|
}
|
|
html += '</table></div></div>';
|
|
|
|
// Top Combos
|
|
html += '<div class="sb-section"><div class="sb-header">Top Combos</div><div class="sb-body">';
|
|
html += '<table class="ov-table"><tr><th>#</th><th>Username:Password</th><th>Count</th><th>Service</th></tr>';
|
|
const combos = c.top_combos || [];
|
|
for (let i = 0; i < combos.length; i++) {
|
|
const cb = combos[i];
|
|
html += '<tr><td>' + (i + 1) + '</td>'
|
|
+ '<td class="font-mono">' + escHtml(cb.username) + ':' + escHtml(maskPassword(cb.password)) + '</td>'
|
|
+ '<td>' + cb.cnt + '</td>'
|
|
+ '<td>' + escHtml(cb.service || '') + '</td></tr>';
|
|
}
|
|
html += '</table></div></div>';
|
|
|
|
// By Service
|
|
html += '<div class="sb-section"><div class="sb-header">By Service</div><div class="sb-body">';
|
|
html += '<table class="ov-table"><tr><th>Service</th><th>Unique Users</th><th>Total Attempts</th></tr>';
|
|
const bySvc = c.by_service || [];
|
|
for (let i = 0; i < bySvc.length; i++) {
|
|
const sv = bySvc[i];
|
|
html += '<tr><td>' + svcIcon(sv.service) + ' ' + escHtml(sv.service) + '</td>'
|
|
+ '<td>' + sv.unique_users + '</td>'
|
|
+ '<td>' + sv.total_attempts + '</td></tr>';
|
|
}
|
|
html += '</table></div></div>';
|
|
|
|
html += '</div>';
|
|
ml.innerHTML = html;
|
|
}
|