refactor(decnet_web/AttackerDetail): extract AttackerHeader section

Lift the header (IP, country tag, traversal badge, identity badge)
into its own section component. Tag helper moves to a shared
AttackerDetail/ui.tsx so future sections can reuse it without
re-importing through AttackerDetail.tsx.

- New AttackerDetail/sections/AttackerHeader.tsx (~50 LOC)
- New AttackerDetail/ui.tsx for shared presentational helpers
- AttackerDetail.tsx imports both; local Tag definition deleted
- AttackerHeader.test.tsx covers country present/absent,
  TRAVERSAL badge, IDENTITY click-through, identity null path
This commit is contained in:
2026-05-09 04:39:30 -04:00
parent 22cfb10617
commit 653ae04e88
5 changed files with 130 additions and 54 deletions

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Crosshair } from '../../../icons';
import { Tag } from '../ui';
import type { AttackerData } from '../types';
interface Props {
attacker: AttackerData;
}
/** Page header: crosshair + IP + country / traversal / identity badges.
* The identity badge is click-through to the resolved-actor page. */
export const AttackerHeader: React.FC<Props> = ({ attacker }) => {
const navigate = useNavigate();
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<Crosshair size={32} className="violet-accent" />
<h1 className="matrix-text" style={{ fontSize: '1.8rem', letterSpacing: '2px' }}>
{attacker.ip}
</h1>
{attacker.country_code && (
<Tag color="var(--text-color)">
<span
title={attacker.country_source ? `source: ${attacker.country_source}` : undefined}
style={{ letterSpacing: '2px' }}
>
{attacker.country_code}
</span>
</Tag>
)}
{attacker.is_traversal && (
<span className="traversal-badge" style={{ fontSize: '0.8rem' }}>TRAVERSAL</span>
)}
{attacker.identity_id && (
<span
className="traversal-badge"
style={{
fontSize: '0.8rem',
cursor: 'pointer',
letterSpacing: '2px',
}}
title="Resolved identity — click to view all observations linked to this actor"
onClick={() => navigate(`/identities/${attacker.identity_id}`)}
>
IDENTITY · {attacker.identity_id.slice(0, 8)}
</span>
)}
</div>
);
};