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 += ` -
${this.escapeHtml(formattedPathHex)}