Cybersecurity-Projects/PROJECTS/advanced/ai-threat-detection/frontend/src/components/alert-feed.tsx

71 lines
2.0 KiB
TypeScript

// ===================
// © AngelaMos | 2026
// alert-feed.tsx
// ===================
import { useEffect, useRef } from 'react'
import type { WebSocketAlert } from '@/api/types'
import styles from './alert-feed.module.scss'
import { SeverityBadge } from './severity-badge'
interface AlertFeedProps {
alerts: WebSocketAlert[]
isConnected: boolean
maxHeight?: string
}
function formatTime(timestamp: string): string {
return new Date(timestamp).toLocaleTimeString()
}
export function AlertFeed({
alerts,
isConnected,
maxHeight,
}: AlertFeedProps): React.ReactElement {
const listRef = useRef<HTMLDivElement>(null)
const alertCount = alerts.length
// biome-ignore lint/correctness/useExhaustiveDependencies: scroll on new alerts
useEffect(() => {
if (listRef.current) {
listRef.current.scrollTop = 0
}
}, [alertCount])
return (
<div className={styles.feed}>
<div className={styles.header}>
<h3 className={styles.title}>Live Alerts</h3>
<span
className={`${styles.status} ${isConnected ? styles.connected : styles.disconnected}`}
/>
</div>
<div
ref={listRef}
className={styles.list}
style={maxHeight ? { maxHeight } : undefined}
>
{alerts.length === 0 ? (
<div className={styles.empty}>Waiting for alerts...</div>
) : (
alerts.map((alert, i) => (
<div key={`${alert.timestamp}-${i}`} className={styles.row}>
<span className={styles.time}>{formatTime(alert.timestamp)}</span>
<span className={styles.ip}>{alert.source_ip}</span>
<span className={styles.path}>{alert.request_path}</span>
<SeverityBadge
severity={alert.severity as 'HIGH' | 'MEDIUM' | 'LOW'}
/>
<span className={styles.score}>
{alert.threat_score.toFixed(2)}
</span>
</div>
))
)}
</div>
</div>
)
}