From c2eceb147d99051335fe65df0366e4350dd0222c Mon Sep 17 00:00:00 2001 From: anti Date: Tue, 14 Apr 2026 13:05:07 -0400 Subject: [PATCH] refactor: group fingerprints by type in attacker detail view Replace flat fingerprint card list with a structured section that groups fingerprints by type under two categories: Active Probes (JARM, HASSH, TCP/IP) and Passive Fingerprints (TLS, certificates, latency, etc.). Each group shows its icon, label, and count. --- decnet_web/src/components/AttackerDetail.tsx | 117 ++++++++++++------- 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/decnet_web/src/components/AttackerDetail.tsx b/decnet_web/src/components/AttackerDetail.tsx index d964d5f..0007fdd 100644 --- a/decnet_web/src/components/AttackerDetail.tsx +++ b/decnet_web/src/components/AttackerDetail.tsx @@ -277,46 +277,37 @@ const FpGeneric: React.FC<{ p: any }> = ({ p }) => ( ); -const FingerprintCard: React.FC<{ bounty: any }> = ({ bounty }) => { - const p = getPayload(bounty); - const fpType: string = p.fingerprint_type || 'unknown'; +const FingerprintGroup: React.FC<{ fpType: string; items: any[] }> = ({ fpType, items }) => { const label = fpTypeLabel[fpType] || fpType.toUpperCase().replace(/_/g, ' '); const icon = fpTypeIcon[fpType] || ; - let content: React.ReactNode; - switch (fpType) { - case 'ja3': - content = ; - break; - case 'ja4l': - content = ; - break; - case 'tls_resumption': - content = ; - break; - case 'tls_certificate': - content = ; - break; - case 'jarm': - content = ; - break; - case 'hassh_server': - content = ; - break; - case 'tcpfp': - content = ; - break; - default: - content = ; - } - return ( -
-
- {icon} - {label} +
+
+ {icon} + {label} + {items.length > 1 && ( + ({items.length}) + )} +
+
+ {items.map((fp, i) => { + const p = getPayload(fp); + switch (fpType) { + case 'ja3': return ; + case 'ja4l': return ; + case 'tls_resumption': return ; + case 'tls_certificate': return ; + case 'jarm': return ; + case 'hassh_server': return ; + case 'tcpfp': return ; + default: return ; + } + })}
-
{content}
); }; @@ -591,7 +582,7 @@ const AttackerDetail: React.FC = () => { ); })()} - {/* Fingerprints */} + {/* Fingerprints — grouped by type */} {(() => { const filteredFps = serviceFilter ? attacker.fingerprints.filter((fp) => { @@ -599,16 +590,62 @@ const AttackerDetail: React.FC = () => { return p.service === serviceFilter; }) : attacker.fingerprints; + + // Group fingerprints by type + const groups: Record = {}; + filteredFps.forEach((fp) => { + const p = getPayload(fp); + const fpType: string = p.fingerprint_type || 'unknown'; + if (!groups[fpType]) groups[fpType] = []; + groups[fpType].push(fp); + }); + + // Active probes first, then passive, then unknown + const activeTypes = ['jarm', 'hassh_server', 'tcpfp']; + const passiveTypes = ['ja3', 'ja4l', 'tls_resumption', 'tls_certificate', 'http_useragent', 'vnc_client_version']; + const knownTypes = [...activeTypes, ...passiveTypes]; + const unknownTypes = Object.keys(groups).filter((t) => !knownTypes.includes(t)); + const orderedTypes = [...activeTypes, ...passiveTypes, ...unknownTypes].filter((t) => groups[t]); + + const hasActive = activeTypes.some((t) => groups[t]); + const hasPassive = [...passiveTypes, ...unknownTypes].some((t) => groups[t]); + return (

FINGERPRINTS ({filteredFps.length}{serviceFilter ? ` / ${attacker.fingerprints.length}` : ''})

{filteredFps.length > 0 ? ( -
- {filteredFps.map((fp, i) => ( - - ))} +
+ {/* Active probes section */} + {hasActive && ( +
+
+ + ACTIVE PROBES +
+
+ {activeTypes.filter((t) => groups[t]).map((fpType) => ( + + ))} +
+
+ )} + + {/* Passive fingerprints section */} + {hasPassive && ( +
+
+ + PASSIVE FINGERPRINTS +
+
+ {[...passiveTypes, ...unknownTypes].filter((t) => groups[t]).map((fpType) => ( + + ))} +
+
+ )}
) : (