From a4d1f678cf1c2ffdf2fbe6ebf01a4d28abfefd2c Mon Sep 17 00:00:00 2001 From: agessaman Date: Fri, 6 Mar 2026 10:02:45 -0800 Subject: [PATCH] Enhance path handling in BotDataViewer and ModernContactsManager - Added support for bytes per hop in the BotDataViewer, allowing for better path data representation. - Updated the contacts template to display bytes per hop and adjusted path formatting based on this value. - Improved the decode path functionality to utilize the correct bytes per hop for decoding paths, enhancing overall path handling and user experience. --- modules/web_viewer/app.py | 11 +++++ modules/web_viewer/templates/contacts.html | 50 ++++++++++++++-------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/modules/web_viewer/app.py b/modules/web_viewer/app.py index 63a9378..b3f6d9f 100644 --- a/modules/web_viewer/app.py +++ b/modules/web_viewer/app.py @@ -3569,6 +3569,7 @@ class BotDataViewer: MAX(c.last_advert_timestamp) as last_message, GROUP_CONCAT(op.path_hex, '|||') as all_paths_hex, GROUP_CONCAT(op.path_length, '|||') as all_paths_length, + GROUP_CONCAT(COALESCE(op.bytes_per_hop, 1), '|||') as all_paths_bytes_per_hop, GROUP_CONCAT(op.observation_count, '|||') as all_paths_observations, GROUP_CONCAT(op.last_seen, '|||') as all_paths_last_seen FROM complete_contact_tracking c @@ -3606,14 +3607,24 @@ class BotDataViewer: if row['all_paths_hex']: paths_hex = row['all_paths_hex'].split('|||') paths_length = row['all_paths_length'].split('|||') if row['all_paths_length'] else [] + paths_bph = row['all_paths_bytes_per_hop'].split('|||') if row.get('all_paths_bytes_per_hop') else [] paths_observations = row['all_paths_observations'].split('|||') if row['all_paths_observations'] else [] paths_last_seen = row['all_paths_last_seen'].split('|||') if row['all_paths_last_seen'] else [] for i, path_hex in enumerate(paths_hex): if path_hex: # Skip empty strings + bph = None + if i < len(paths_bph) and paths_bph[i]: + try: + bph = int(paths_bph[i]) + if bph not in (1, 2, 3): + bph = 1 + except (TypeError, ValueError): + bph = 1 all_paths.append({ 'path_hex': path_hex, 'path_length': int(paths_length[i]) if i < len(paths_length) and paths_length[i] else 0, + 'bytes_per_hop': bph, 'observation_count': int(paths_observations[i]) if i < len(paths_observations) and paths_observations[i] else 1, 'last_seen': paths_last_seen[i] if i < len(paths_last_seen) and paths_last_seen[i] else None }) diff --git a/modules/web_viewer/templates/contacts.html b/modules/web_viewer/templates/contacts.html index 0d4d518..dd168d3 100644 --- a/modules/web_viewer/templates/contacts.html +++ b/modules/web_viewer/templates/contacts.html @@ -982,10 +982,12 @@ class ModernContactsManager { // Create a unique ID for this tooltip const tooltipId = `hops-tooltip-${contact.user_id.substring(0, 16).replace(/[^a-zA-Z0-9]/g, '_')}`; const pathCountText = hasMultiplePaths ? ` (${allPaths.length} paths)` : ''; + const bytesPerHop = contact.out_bytes_per_hop != null && [1, 2, 3].includes(Number(contact.out_bytes_per_hop)) ? Number(contact.out_bytes_per_hop) : ''; return `Primary' : ''; - const formattedPathHex = this.formatPathHex(path.path_hex); + const bph = path.bytes_per_hop != null && [1, 2, 3].includes(Number(path.bytes_per_hop)) ? Number(path.bytes_per_hop) : null; + const formattedPathHex = this.formatPathHex(path.path_hex, bph); + const hopLabel = bph && path.path_length > 0 ? `${Math.floor(path.path_length / bph)} hops, ` : ''; + const bytesPerHopAttr = bph != null ? ` data-path-bph="${bph}"` : ''; pathsHtml += ` -
+
Path ${i + 1} ${primaryBadge} - (${path.path_length} bytes, ${path.observation_count} observations) + (${hopLabel}${path.path_length} bytes, ${path.observation_count} observations)

${this.escapeHtml(formattedPathHex)}

Last seen: ${this.formatTimeAgo(path.last_seen)}
-
@@ -1321,9 +1332,10 @@ class ModernContactsManager { }); } - async decodePathInModal(pathHex, pathIndex) { + async decodePathInModal(pathHex, pathIndex, pathBytesPerHop) { const decodedDiv = document.getElementById(`decoded-path-${pathIndex}`); - const button = document.querySelector(`#path-item-${pathIndex} button`); + const pathItem = document.getElementById(`path-item-${pathIndex}`); + const button = pathItem ? pathItem.querySelector('button') : null; if (!decodedDiv || !button) return; @@ -1335,11 +1347,13 @@ class ModernContactsManager { decodedDiv.innerHTML = '
Decoding path...
'; try { - // Use contact's out_bytes_per_hop when path came from a packet with multi-byte hops - const bytesPerHop = this.currentModalContact?.out_bytes_per_hop || null; + // Prefer this path's bytes_per_hop; fall back to contact's out_bytes_per_hop + const bytesPerHop = (pathBytesPerHop != null && [1, 2, 3].includes(Number(pathBytesPerHop))) + ? Number(pathBytesPerHop) + : (this.currentModalContact?.out_bytes_per_hop != null && [1, 2, 3].includes(Number(this.currentModalContact.out_bytes_per_hop)) ? Number(this.currentModalContact.out_bytes_per_hop) : null); const body = { path_hex: pathHex }; - if (bytesPerHop != null && [1, 2, 3].includes(Number(bytesPerHop))) { - body.bytes_per_hop = Number(bytesPerHop); + if (bytesPerHop != null) { + body.bytes_per_hop = bytesPerHop; } const response = await fetch('/api/decode-path', { method: 'POST',