import { useEffect, useId, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from 'react'; import { createPortal } from 'react-dom'; import type { JsonValue } from '../../domain/canonical-model'; import type { EnvironmentSnapshot, ScannerReportSection, ScannerReportTable } from '../../domain/environment-context'; import { environmentEvidenceForGroup, environmentNativeHeading, environmentSectionTitle, environmentSectionProse, environmentSectionReference, environmentTableTitle, type EnvironmentEvidenceGroup } from './environment-result-evidence'; const PAGE_SIZE = 100; const TABLE_PAGE_SIZES = [10, 25, 100] as const; const TABLE_RESIZE_DIRECTIONS = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'] as const; type TableResizeDirection = typeof TABLE_RESIZE_DIRECTIONS[number]; const TABLE_VIEWPORT_MARGIN = 8; const TABLE_MIN_WIDTH = 360; const TABLE_MIN_HEIGHT = 260; function valueText(value: JsonValue | undefined): string { if (value === undefined) return '—'; if (value === null) return 'null'; if (typeof value === 'string') return value === '' ? '""' : value; return JSON.stringify(value); } function SourceTableDialog({ table, title, context, onClose, onOpenLink }: { table: ScannerReportTable; title: string; context: string; onClose(): void; onOpenLink?: (targetId: string) => void; }) { const titleId = useId(); const dialogRef = useRef(null); const searchRef = useRef(null); const resizeRef = useRef<{ pointerId: number; direction: TableResizeDirection; startX: number; startY: number; left: number; top: number; width: number; height: number } | null>(null); const [geometry, setGeometry] = useState(null); const [query, setQuery] = useState(''); const [pageSize, setPageSize] = useState(25); const [page, setPage] = useState(0); const width = table.rows.reduce((maximum, row) => Math.max(maximum, row.length), table.headers.length); const filteredRows = useMemo(() => { const needle = query.trim().toLocaleLowerCase(); const indexed = table.rows.map((row, rowIndex) => ({ row, rowIndex })); return needle ? indexed.filter(({ row }) => row.some(value => valueText(value).toLocaleLowerCase().includes(needle))) : indexed; }, [query, table.rows]); const pageCount = Math.max(1, Math.ceil(filteredRows.length / pageSize)); const start = page * pageSize; const shownRows = filteredRows.slice(start, start + pageSize); const startResize = (direction: TableResizeDirection) => (event: ReactPointerEvent) => { const bounds = dialogRef.current?.getBoundingClientRect(); if (!bounds) return; resizeRef.current = { pointerId: event.pointerId, direction, startX: event.clientX, startY: event.clientY, left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height }; event.currentTarget.setPointerCapture(event.pointerId); event.preventDefault(); }; const moveResize = (event: ReactPointerEvent) => { const resize = resizeRef.current; if (!resize || resize.pointerId !== event.pointerId) return; const dx = event.clientX - resize.startX; const dy = event.clientY - resize.startY; let { left, top, width, height } = resize; const minWidth = Math.min(TABLE_MIN_WIDTH, window.innerWidth - TABLE_VIEWPORT_MARGIN * 2); const minHeight = Math.min(TABLE_MIN_HEIGHT, window.innerHeight - TABLE_VIEWPORT_MARGIN * 2); if (resize.direction.includes('e')) width = Math.max(minWidth, Math.min(resize.width + dx, window.innerWidth - resize.left - TABLE_VIEWPORT_MARGIN)); if (resize.direction.includes('s')) height = Math.max(minHeight, Math.min(resize.height + dy, window.innerHeight - resize.top - TABLE_VIEWPORT_MARGIN)); if (resize.direction.includes('w')) { width = Math.max(minWidth, Math.min(resize.width - dx, resize.left + resize.width - TABLE_VIEWPORT_MARGIN)); left = resize.left + resize.width - width; } if (resize.direction.includes('n')) { height = Math.max(minHeight, Math.min(resize.height - dy, resize.top + resize.height - TABLE_VIEWPORT_MARGIN)); top = resize.top + resize.height - height; } setGeometry({ left, top, width, height }); }; const stopResize = (event: ReactPointerEvent) => { if (resizeRef.current?.pointerId !== event.pointerId) return; resizeRef.current = null; if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId); }; useEffect(() => { const onViewportResize = () => { setGeometry(current => { if (!current) return current; const width = Math.min(Number(current.width), window.innerWidth - TABLE_VIEWPORT_MARGIN * 2); const height = Math.min(Number(current.height), window.innerHeight - TABLE_VIEWPORT_MARGIN * 2); return { width, height, left: Math.max(TABLE_VIEWPORT_MARGIN, Math.min(Number(current.left), window.innerWidth - width - TABLE_VIEWPORT_MARGIN)), top: Math.max(TABLE_VIEWPORT_MARGIN, Math.min(Number(current.top), window.innerHeight - height - TABLE_VIEWPORT_MARGIN)) }; }); }; window.addEventListener('resize', onViewportResize); return () => window.removeEventListener('resize', onViewportResize); }, []); useEffect(() => { searchRef.current?.focus(); }, []); const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); onClose(); return; } if (event.key === 'Tab' && dialogRef.current) { const focusable = [...dialogRef.current.querySelectorAll('button:not(:disabled), input:not(:disabled), select:not(:disabled), summary')]; const first = focusable[0]; const last = focusable.at(-1); if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); } } event.stopPropagation(); }; return createPortal(
{ if (event.target === event.currentTarget) onClose(); }}>

{context}

{title}

{table.rows.length.toLocaleString('en-US')} rows { setQuery(event.target.value); setPage(0); }}/>
{width ?
{Array.from({ length: width }, (_, index) => )}{shownRows.map(({ row, rowIndex }) => {Array.from({ length: width }, (_, index) => { const targetId = table.cellLinks?.find(link => link.rowIndex === rowIndex && link.columnIndex === index)?.targetId; return ; })} )}
{table.headers[index] || `Column ${index + 1}`}
{targetId && onOpenLink ? : valueText(row[index])}
:

No columns retained in this source table.

} {width > 0 && filteredRows.length === 0 &&

No rows match this search.

}
{filteredRows.length ? `Showing ${start + 1}–${Math.min(start + pageSize, filteredRows.length)} of ${filteredRows.length}` : 'Showing 0 of 0'} rows
{page + 1} / {pageCount}
{TABLE_RESIZE_DIRECTIONS.map(direction =>
, document.body); } function SourceInfoDialog({ title, context, prose, interactiveGraph, children, onClose }: { title: string; context: string; prose?: string; interactiveGraph?: boolean; children?: ReactNode; onClose(): void; }) { const titleId = useId(); const closeRef = useRef(null); const dialogRef = useRef(null); useEffect(() => { closeRef.current?.focus(); }, []); return createPortal(
{ if (event.key === 'Escape') { event.stopPropagation(); onClose(); } if (event.key === 'Tab' && dialogRef.current) { const focusable = [...dialogRef.current.querySelectorAll('button:not(:disabled), input, select, summary')]; const first = focusable[0]; const last = focusable.at(-1); if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); } } event.stopPropagation(); }} onMouseDown={event => { if (event.target === event.currentTarget) onClose(); }}>

{context}

{title}

{prose &&

{prose}

} {interactiveGraph &&

The interactive graph is available in the original PingCastle report.

} {children}
, document.body); } function EvidenceTable({ table, title, onOpenLink }: { table: ScannerReportTable; title: string; onOpenLink(targetId: string): void; }) { const [query, setQuery] = useState(''); const [pageSize, setPageSize] = useState(25); const [page, setPage] = useState(0); const width = table.rows.reduce((maximum, row) => Math.max(maximum, row.length), table.headers.length); const needle = query.trim().toLocaleLowerCase(); const filteredRows = needle ? table.rows.map((row, rowIndex) => ({ row, rowIndex })) .filter(({ row }) => row.some(value => valueText(value).toLocaleLowerCase().includes(needle))) : table.rows.map((row, rowIndex) => ({ row, rowIndex })); const start = page * pageSize; const shownRows = filteredRows.slice(start, start + pageSize); const pageCount = Math.max(1, Math.ceil(filteredRows.length / pageSize)); return
{title &&
{title}
}
{ setQuery(event.target.value); setPage(0); }}/>
{width > 0 &&
{Array.from({ length: width }, (_, index) => )} {shownRows.map(({ row, rowIndex }) => {Array.from({ length: width }, (_, columnIndex) => { const targetId = table.cellLinks?.find(link => link.rowIndex === rowIndex && link.columnIndex === columnIndex)?.targetId; return ; })})}
{table.headers[index] || `Column ${index + 1}`}
{targetId ? : valueText(row[columnIndex])}
} {width > 0 && !filteredRows.length &&

No rows match this search.

} {width > 0 &&
{filteredRows.length ? `Showing ${start + 1}–${Math.min(start + pageSize, filteredRows.length)} of ${filteredRows.length}` : 'Showing 0 of 0'} rows
{page + 1} / {pageCount}
}
; } function EvidenceSection({ section, snapshot, linkedSections }: { section: ScannerReportSection; snapshot: EnvironmentSnapshot; linkedSections?: Map; }) { const title = environmentSectionTitle(snapshot, section); const prose = environmentSectionProse(section, title); const titles = section.tables.map(table => environmentTableTitle(snapshot, section, table)); const hasUnstructuredTables = section.tables.some(table => !table.presentation); const linkedSection = (targetId: string) => linkedSections?.get(environmentSectionReference(section, targetId)); const [modalStack, setModalStack] = useState([]); const [openInlineIds, setOpenInlineIds] = useState([]); const triggerRef = useRef(null); const openModal = (targetId: string) => { if (!modalStack.length) triggerRef.current = document.activeElement as HTMLElement; setModalStack(ids => [...ids, targetId]); }; const openModalId = modalStack.at(-1); const modalIndexes = openModalId ? section.tables.flatMap((table, index) => table.targetId === openModalId ? [index] : []) : []; const modalIndex = modalIndexes.length === 1 ? modalIndexes[0] : -1; const modalDetail = section.modalDetails?.find(detail => detail.targetId === openModalId); const linkedChild = openModalId ? linkedSection(openModalId) : undefined; const linkedChildTitle = environmentNativeHeading(section.detailLinks?.find(link => link.targetId === openModalId)?.label) || (linkedChild && environmentSectionTitle(snapshot, linkedChild)) || title; const closeModal = () => { setModalStack(ids => ids.slice(0, -1)); if (modalStack.length === 1) triggerRef.current?.focus(); }; const renderTable = (table: ScannerReportTable, index: number) => { return ; }; return

{title}

{prose && !hasUnstructuredTables &&

{prose}

} {section.tables.map((table, index) => table.presentation === 'modal' || table.presentation === 'linked-inline' && section.detailLinks?.some(link => link.targetId === table.targetId) ? null : renderTable(table, index))} {section.detailLinks?.map(link => { const open = openInlineIds.includes(link.targetId); const linkedIndexes = section.tables.flatMap((table, index) => table.targetId === link.targetId ? [index] : []); return
{open &&
{link.prose &&

{link.prose}

} {linkedIndexes.map(index => renderTable(section.tables[index], index))} {linkedSection(link.targetId) && }
}
; })} {modalIndex >= 0 && } {modalIndexes.length > 1 && {modalIndexes.map(index => renderTable(section.tables[index], index))} } {modalIndex < 0 && modalDetail && } {modalIndexes.length === 0 && !modalDetail && linkedChild && }
; } export function EnvironmentEvidenceTables({ snapshot, group }: { snapshot?: EnvironmentSnapshot; group: EnvironmentEvidenceGroup }) { const [visibleSections, setVisibleSections] = useState(PAGE_SIZE); if (!snapshot) return

No retained Client Information available for this selection.

; const { sections } = environmentEvidenceForGroup(snapshot, group); const linkedSections = new Map(); for (const section of sections) { const key = environmentSectionReference(section, section.sectionKey); if (sections.filter(candidate => environmentSectionReference(candidate, candidate.sectionKey) === key).length === 1) linkedSections.set(key, section); } const linkedKeys = new Set(sections.flatMap(section => [ ...(section.detailLinks?.map(link => environmentSectionReference(section, link.targetId)) ?? []), ...section.tables.flatMap(table => table.cellLinks?.map(link => environmentSectionReference(section, link.targetId)) ?? []) ])); const topSections = sections.filter(section => { const key = environmentSectionReference(section, section.sectionKey); return !linkedKeys.has(key) || !linkedSections.has(key); }); if (!sections.length) return

No original report sections for this category.

; return
{sections.length > 0 &&
{topSections.slice(0, visibleSections).map(section => )} {visibleSections < topSections.length && }
}
; }