dashboard changes for Session tab

This commit is contained in:
bot
2026-04-05 20:59:22 +03:00
parent e01bbd9e56
commit fee23f0f66
4 changed files with 340 additions and 0 deletions
+150
View File
@@ -11,6 +11,8 @@ let clientsData = [];
let clientsGroupBy = null; // null | 'vendor' | 'ssid'
let presenceData = {};
let alertsData = {};
let sessionsData = [];
let sessionSort = { col: 'frame_count', dir: 'desc' };
let chartBssid = null;
let chartHours = 2;
@@ -570,6 +572,150 @@ function renderDeauthHeatmap(rows) {
});
}
// ── Sessions view ─────────────────────────────────────────────────────────────
function initSessionSort() {
document.querySelectorAll('#sessions-table thead th.sortable').forEach(th => {
th.style.cursor = 'pointer';
th.addEventListener('click', () => {
const col = th.dataset.col;
if (sessionSort.col === col) {
sessionSort.dir = sessionSort.dir === 'desc' ? 'asc' : 'desc';
} else {
sessionSort.col = col;
sessionSort.dir = 'desc';
}
document.querySelectorAll('#sessions-table thead th.sortable').forEach(h => {
h.classList.remove('sort-active');
h.dataset.arrow = '';
});
th.classList.add('sort-active');
th.dataset.arrow = sessionSort.dir === 'desc' ? '↓' : '↑';
renderSessionsView();
});
});
}
async function fetchSessions() {
sessionsData = await fetch('/api/sessions').then(r => r.json());
renderSessionsView();
}
function renderSessionsView() {
const tbody = document.getElementById('sessions-tbody');
const empty = document.getElementById('sessions-empty');
const count = document.getElementById('sessions-count');
// Sort
const { col, dir } = sessionSort;
const sevOrder = { attack: 0, suspicious: 1, normal: 2 };
const data = [...sessionsData].sort((a, b) => {
let av, bv;
if (col === 'bssids') { av = a.unique_bssids.length; bv = b.unique_bssids.length; }
else if (col === 'targets') { av = a.unique_targets.length; bv = b.unique_targets.length; }
else if (col === 'severity') { av = sevOrder[a.severity] ?? 9; bv = sevOrder[b.severity] ?? 9; }
else if (col === 'peak_rssi') { av = a.peak_rssi ?? -200; bv = b.peak_rssi ?? -200; }
else { av = a[col]; bv = b[col]; }
if (av < bv) return dir === 'desc' ? 1 : -1;
if (av > bv) return dir === 'desc' ? -1 : 1;
return 0;
});
count.textContent = `${data.length} session${data.length !== 1 ? 's' : ''}`;
if (!data.length) {
tbody.innerHTML = '';
empty.style.display = 'block';
return;
}
empty.style.display = 'none';
tbody.innerHTML = '';
data.forEach((s, idx) => {
const sevClass = `sev-${s.severity}`;
const duration = s.duration_secs < 60
? `${s.duration_secs}s`
: `${Math.floor(s.duration_secs / 60)}m ${s.duration_secs % 60}s`;
const vendor = s.src_randomized ? '<span class="muted">Randomized</span>' : (s.src_vendor || '<span class="muted">Unknown</span>');
const mainRow = document.createElement('tr');
mainRow.className = `session-row ${sevClass}`;
mainRow.dataset.idx = idx;
mainRow.innerHTML = `
<td class="expand-col"><span class="expand-btn">▶</span></td>
<td class="mono">${s.src}</td>
<td>${vendor}</td>
<td>${s.start.replace('T', ' ')}</td>
<td>${duration}</td>
<td>${s.frame_count}</td>
<td>${s.unique_bssids.length}</td>
<td>${s.unique_targets.length}</td>
<td>${s.node_count}</td>
<td>${s.peak_rssi != null ? s.peak_rssi + ' dBm' : '—'}</td>
<td><span class="sev-label ${sevClass}">${s.severity}</span></td>
`;
tbody.appendChild(mainRow);
// Build detail row
const detailRow = document.createElement('tr');
detailRow.className = 'session-detail';
detailRow.dataset.sessionIdx = idx;
const bssidList = s.unique_bssids.join(', ') || '—';
const targetList = s.unique_targets.join(', ') || '—';
const nodeList = (s.nodes || []).join(', ') || '—';
let evRows = '';
(s.events || []).forEach(ev => {
evRows += `<tr>
<td>${ev.received_at.replace('T', ' ')}</td>
<td>${ev.node_id}</td>
<td>${ev.subtype}</td>
<td class="mono">${ev.src}</td>
<td class="mono">${ev.dst}</td>
<td class="mono">${ev.bssid}</td>
<td>${ev.reason}</td>
<td>${ev.rssi} dBm</td>
</tr>`;
});
const truncNote = s.frame_count > 50
? `<div class="muted" style="font-size:10px;margin-top:4px">Showing first 50 of ${s.frame_count} events</div>`
: '';
detailRow.innerHTML = `
<td colspan="11" class="session-detail-cell">
<div class="session-detail-inner">
<div class="session-meta">
<span><strong>BSSIDs:</strong> ${bssidList}</span>
<span><strong>Targets:</strong> ${targetList}</span>
<span><strong>Nodes:</strong> ${nodeList}</span>
<span><strong>Avg RSSI:</strong> ${s.avg_rssi != null ? s.avg_rssi + ' dBm' : '—'}</span>
<span><strong>Reason:</strong> ${s.dominant_reason}</span>
</div>
<table class="session-events-table">
<thead><tr>
<th>Time</th><th>Node</th><th>Type</th><th>Src</th>
<th>Dst</th><th>BSSID</th><th>Reason</th><th>RSSI</th>
</tr></thead>
<tbody>${evRows}</tbody>
</table>
${truncNote}
</div>
</td>
`;
detailRow.style.display = 'none';
tbody.appendChild(detailRow);
mainRow.addEventListener('click', () => {
const isOpen = detailRow.style.display !== 'none';
detailRow.style.display = isOpen ? 'none' : '';
mainRow.querySelector('.expand-btn').textContent = isOpen ? '▶' : '▼';
});
});
}
// ── Alerts view ───────────────────────────────────────────────────────────────
async function fetchAlerts() {
alertsData = await fetch('/api/alerts').then(r => r.json());
@@ -1022,6 +1168,7 @@ function setView(view) {
document.getElementById('view-detail').classList.toggle('active', view === 'detail');
document.getElementById('view-presence').classList.toggle('active', view === 'presence');
document.getElementById('view-alerts').classList.toggle('active', view === 'alerts');
document.getElementById('view-sessions').classList.toggle('active', view === 'sessions');
document.getElementById('view-analysis').classList.toggle('active', view === 'analysis');
document.getElementById('view-search').classList.toggle('active', view === 'search');
document.getElementById('tab-feed').classList.toggle('active', view === 'feed' || view === 'detail');
@@ -1030,6 +1177,7 @@ function setView(view) {
document.getElementById('tab-clients').classList.toggle('active', view === 'clients');
document.getElementById('tab-presence').classList.toggle('active', view === 'presence');
document.getElementById('tab-alerts').classList.toggle('active', view === 'alerts');
document.getElementById('tab-sessions').classList.toggle('active', view === 'sessions');
document.getElementById('tab-analysis').classList.toggle('active', view === 'analysis');
document.getElementById('tab-search').classList.toggle('active', view === 'search');
}
@@ -1052,6 +1200,8 @@ async function showPresence() { setView('presence'); await fetchPresence(); }
async function showAlerts() { setView('alerts'); await fetchAlerts(); }
async function showSessions() { setView('sessions'); initSessionSort(); await fetchSessions(); }
async function showAnalysis() {
setView('analysis');
const data = await fetch('/api/alerts').then(r => r.json());