From 8c249f6987c84e1b151571b7d210f265579eb6a3 Mon Sep 17 00:00:00 2001 From: anti Date: Tue, 14 Apr 2026 01:38:24 -0400 Subject: [PATCH] fix: service badges filter commands/fingerprints locally Clicking a service badge in the attacker detail view now filters the commands and fingerprints sections on that page instead of navigating away. Click again to clear. Header shows filtered/total counts. --- decnet_web/src/components/AttackerDetail.tsx | 148 +++++++++++-------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/decnet_web/src/components/AttackerDetail.tsx b/decnet_web/src/components/AttackerDetail.tsx index 394845e..c4d93cb 100644 --- a/decnet_web/src/components/AttackerDetail.tsx +++ b/decnet_web/src/components/AttackerDetail.tsx @@ -216,6 +216,7 @@ const AttackerDetail: React.FC = () => { const [attacker, setAttacker] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [serviceFilter, setServiceFilter] = useState(null); useEffect(() => { const fetchAttacker = async () => { @@ -330,17 +331,27 @@ const AttackerDetail: React.FC = () => {

SERVICES TARGETED

- {attacker.services.length > 0 ? attacker.services.map((svc) => ( - navigate(`/attackers?service=${encodeURIComponent(svc)}`)} - title={`Filter attackers by ${svc.toUpperCase()}`} - > - {svc.toUpperCase()} - - )) : ( + {attacker.services.length > 0 ? attacker.services.map((svc) => { + const isActive = serviceFilter === svc; + return ( + setServiceFilter(isActive ? null : svc)} + title={isActive ? 'Clear filter' : `Filter by ${svc.toUpperCase()}`} + > + {svc.toUpperCase()} + + ); + }) : ( No services recorded )}
@@ -371,59 +382,76 @@ const AttackerDetail: React.FC = () => { {/* Commands */} -
-
-

COMMANDS ({attacker.commands.length})

-
- {attacker.commands.length > 0 ? ( -
- - - - - - - - - - - {attacker.commands.map((cmd, i) => ( - - - - - - - ))} - -
TIMESTAMPSERVICEDECKYCOMMAND
- {cmd.timestamp ? new Date(cmd.timestamp).toLocaleString() : '-'} - {cmd.service}{cmd.decky}{cmd.command}
+ {(() => { + const filteredCmds = serviceFilter + ? attacker.commands.filter((cmd) => cmd.service === serviceFilter) + : attacker.commands; + return ( +
+
+

COMMANDS ({filteredCmds.length}{serviceFilter ? ` / ${attacker.commands.length}` : ''})

+
+ {filteredCmds.length > 0 ? ( +
+ + + + + + + + + + + {filteredCmds.map((cmd, i) => ( + + + + + + + ))} + +
TIMESTAMPSERVICEDECKYCOMMAND
+ {cmd.timestamp ? new Date(cmd.timestamp).toLocaleString() : '-'} + {cmd.service}{cmd.decky}{cmd.command}
+
+ ) : ( +
+ {serviceFilter ? `NO ${serviceFilter.toUpperCase()} COMMANDS CAPTURED` : 'NO COMMANDS CAPTURED'} +
+ )}
- ) : ( -
- NO COMMANDS CAPTURED -
- )} -
+ ); + })()} {/* Fingerprints */} -
-
-

FINGERPRINTS ({attacker.fingerprints.length})

-
- {attacker.fingerprints.length > 0 ? ( -
- {attacker.fingerprints.map((fp, i) => ( - - ))} + {(() => { + const filteredFps = serviceFilter + ? attacker.fingerprints.filter((fp) => { + const p = getPayload(fp); + return p.service === serviceFilter; + }) + : attacker.fingerprints; + return ( +
+
+

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

+
+ {filteredFps.length > 0 ? ( +
+ {filteredFps.map((fp, i) => ( + + ))} +
+ ) : ( +
+ {serviceFilter ? `NO ${serviceFilter.toUpperCase()} FINGERPRINTS CAPTURED` : 'NO FINGERPRINTS CAPTURED'} +
+ )}
- ) : ( -
- NO FINGERPRINTS CAPTURED -
- )} -
+ ); + })()} {/* UUID footer */}