From e292fd7d05aa4a368a613a858ab466247d4822fd Mon Sep 17 00:00:00 2001 From: anti Date: Thu, 21 May 2026 16:17:38 -0400 Subject: [PATCH] feat(web): surface bgp_prefix and rpki_status in AttackerDetail and export AttackerData type gets bgp_prefix / rpki_status / rpki_source. TimelineSection renders prefix inline next to AS number; RPKI status shows as a green RPKI VALID / red RPKI INVALID badge, or dim NO ROA for not-found. rpki-status-badge CSS added to Dashboard.css. Export network block extended with the three new fields. --- .../web/router/attackers/api_export_attackers.py | 4 ++++ .../AttackerDetail/sections/TimelineSection.tsx | 14 ++++++++++++++ .../src/components/AttackerDetail/types.ts | 3 +++ decnet_web/src/components/Dashboard.css | 16 ++++++++++++++++ decnet_web/src/test/fixtures/attacker.ts | 6 ++++++ 5 files changed, 43 insertions(+) diff --git a/decnet/web/router/attackers/api_export_attackers.py b/decnet/web/router/attackers/api_export_attackers.py index 1e2778a4..3bbd0f7c 100644 --- a/decnet/web/router/attackers/api_export_attackers.py +++ b/decnet/web/router/attackers/api_export_attackers.py @@ -40,6 +40,10 @@ def _shape_observation(row: dict) -> dict: "network": { "asn": row.get("asn"), "as_name": row.get("as_name"), + "bgp_prefix": row.get("bgp_prefix"), + "asn_source": row.get("asn_source"), + "rpki_status": row.get("rpki_status"), + "rpki_source": row.get("rpki_source"), "ptr_record": row.get("ptr_record"), }, "threat_intel": { diff --git a/decnet_web/src/components/AttackerDetail/sections/TimelineSection.tsx b/decnet_web/src/components/AttackerDetail/sections/TimelineSection.tsx index f7517c53..d4ac4905 100644 --- a/decnet_web/src/components/AttackerDetail/sections/TimelineSection.tsx +++ b/decnet_web/src/components/AttackerDetail/sections/TimelineSection.tsx @@ -164,6 +164,11 @@ export const TimelineSection: React.FC = ({ attacker, open, onToggle }) = {attacker.asn != null ? ( AS{attacker.asn} + {attacker.bgp_prefix && ( + + {attacker.bgp_prefix} + + )} {attacker.as_name && ( {attacker.as_name} @@ -174,6 +179,15 @@ export const TimelineSection: React.FC = ({ attacker, open, onToggle }) = ({attacker.asn_source}) )} + {attacker.rpki_status === 'valid' && ( + RPKI VALID + )} + {attacker.rpki_status === 'invalid' && ( + RPKI INVALID + )} + {attacker.rpki_status === 'not-found' && ( + RPKI NO ROA + )} ) : ( unknown diff --git a/decnet_web/src/components/AttackerDetail/types.ts b/decnet_web/src/components/AttackerDetail/types.ts index b97f7319..88d7adac 100644 --- a/decnet_web/src/components/AttackerDetail/types.ts +++ b/decnet_web/src/components/AttackerDetail/types.ts @@ -73,7 +73,10 @@ export interface AttackerData { country_source: string | null; asn: number | null; as_name: string | null; + bgp_prefix: string | null; asn_source: string | null; + rpki_status: string | null; + rpki_source: string | null; ptr_record: string | null; updated_at: string; behavior: AttackerBehavior | null; diff --git a/decnet_web/src/components/Dashboard.css b/decnet_web/src/components/Dashboard.css index 1217db59..390b9e4a 100644 --- a/decnet_web/src/components/Dashboard.css +++ b/decnet_web/src/components/Dashboard.css @@ -514,6 +514,22 @@ color: var(--text-color); } +.rpki-status-badge { + font-size: 0.65rem; + padding: 2px 8px; + letter-spacing: 1px; +} +.rpki-status-badge.valid { + border: 1px solid var(--matrix); + background: var(--matrix-tint-5); + color: var(--matrix); +} +.rpki-status-badge.invalid { + border: 1px solid var(--alert); + background: var(--alert-tint-10); + color: var(--alert); +} + .back-button { display: inline-flex; align-items: center; diff --git a/decnet_web/src/test/fixtures/attacker.ts b/decnet_web/src/test/fixtures/attacker.ts index 7f80e01d..8cfae126 100644 --- a/decnet_web/src/test/fixtures/attacker.ts +++ b/decnet_web/src/test/fixtures/attacker.ts @@ -19,7 +19,10 @@ export interface AttackerFixture { country_source: string | null; asn: number | null; as_name: string | null; + bgp_prefix: string | null; asn_source: string | null; + rpki_status: string | null; + rpki_source: string | null; ptr_record: string | null; updated_at: string; behavior: null; @@ -61,7 +64,10 @@ export const makeAttacker = (overrides: Partial = {}): Attacker country_source: 'maxmind', asn: 64500, as_name: 'EXAMPLE-AS', + bgp_prefix: null, asn_source: 'maxmind', + rpki_status: null, + rpki_source: null, ptr_record: null, updated_at: '2026-05-09T11:00:00Z', behavior: null,