From d0ee2b1ccfd2541acd5a929424fd80f9e52d1aff Mon Sep 17 00:00:00 2001 From: Mariusz Banach Date: Wed, 18 Feb 2026 02:50:48 +0100 Subject: [PATCH] MAESTRO: add hop chain visualisation --- ...er-analyzer-Phase-06-Interactive-Report.md | 2 +- .../report/HopChainVisualisation.tsx | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/report/HopChainVisualisation.tsx diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md index 1beb57a..f638241 100644 --- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md +++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md @@ -40,7 +40,7 @@ ReportContainer - [x] T030 [US4] Write failing tests (TDD Red) in `frontend/src/__tests__/report/TestResultCard.test.tsx` (each severity level, expand/collapse, error indicator), `HopChainVisualisation.test.tsx` (render with sample hops), `ReportSearchBar.test.tsx` (filter simulation), `ReportExport.test.tsx` (export trigger), `SecurityAppliancesSummary.test.tsx` (render with sample appliances, empty state), `ReportContainer.test.tsx` (full report with mixed results) - [x] T031 [P] [US4] Create `frontend/src/components/report/TestResultCard.tsx` — collapsible card per test result. Severity-coloured indicator (red=spam, amber=suspicious, green=clean per FR-09), header name, monospace value, analysis text. Failed tests show error indicator (FR-25). Expand/collapse with animation, keyboard accessible (NFR-02). Verify `TestResultCard.test.tsx` passes (TDD Green) -- [ ] T032 [P] [US4] Create `frontend/src/components/report/HopChainVisualisation.tsx` — vertical flow diagram of mail server hop chain (FR-08): hostname, IP, timestamp, server version, connecting arrows. FontAwesome server/network icons. Responsive. Verify `HopChainVisualisation.test.tsx` passes (TDD Green) +- [x] T032 [P] [US4] Create `frontend/src/components/report/HopChainVisualisation.tsx` — vertical flow diagram of mail server hop chain (FR-08): hostname, IP, timestamp, server version, connecting arrows. FontAwesome server/network icons. Responsive. Verify `HopChainVisualisation.test.tsx` passes (TDD Green) - [ ] T033 [P] [US4] Create `frontend/src/components/report/ReportSearchBar.tsx` — search/filter bar above report (FR-20). Filters by text match against test name, header name, or analysis text. Highlights matches, shows count. FontAwesome search icon, Escape to clear. Verify `ReportSearchBar.test.tsx` passes (TDD Green) - [ ] T034 [P] [US4] Create `frontend/src/components/report/ReportExport.tsx` — export as HTML (styled standalone page) or JSON (raw data) per FR-21. FontAwesome download icons, triggers browser download. Verify `ReportExport.test.tsx` passes (TDD Green) - [ ] T035 [US4] Create `frontend/src/components/report/SecurityAppliancesSummary.tsx` — summary listing detected email security products as badges/tags with FontAwesome shield icons. Handle empty state (no appliances detected). Verify `SecurityAppliancesSummary.test.tsx` passes (TDD Green) diff --git a/frontend/src/components/report/HopChainVisualisation.tsx b/frontend/src/components/report/HopChainVisualisation.tsx new file mode 100644 index 0000000..b718d3f --- /dev/null +++ b/frontend/src/components/report/HopChainVisualisation.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faArrowDown, + faClock, + faNetworkWired, + faServer, +} from "@fortawesome/free-solid-svg-icons"; + +import type { HopChainNode } from "../../types/analysis"; + +type HopChainVisualisationProps = { + hopChain: HopChainNode[]; +}; + +const formatDelay = (delay?: number | null): string | null => { + if (delay === null || delay === undefined || Number.isNaN(delay)) { + return null; + } + return `${delay.toFixed(2)}s`; +}; + +export default function HopChainVisualisation({ + hopChain, +}: HopChainVisualisationProps) { + if (hopChain.length === 0) { + return ( +
+

No hop chain data available.

+
+ ); + } + + return ( +
+
+ {hopChain.map((node, index) => { + const delayLabel = formatDelay(node.delay); + + return ( +
+
+
+ + + +
+
+ + {node.hostname} + + {node.ip ? ( + + {node.ip} + + ) : null} +
+
+ {node.timestamp ? ( + + + {node.timestamp} + + ) : null} + {node.serverInfo ? ( + + + {node.serverInfo} + + ) : null} + {delayLabel ? ( + + +{delayLabel} + + ) : null} +
+
+
+
+ {index < hopChain.length - 1 ? ( +
+ + + +
+ ) : null} +
+ ); + })} +
+
+ ); +}