Files
Charon/frontend/src/components/crowdsec/TopAttackingIPsChart.tsx
GitHub Actions 1fe69c2a15 feat: add Top Attacking IPs chart component and integrate into CrowdSec configuration page
- Implemented TopAttackingIPsChart component for visualizing top attacking IPs.
- Created hooks for fetching CrowdSec dashboard data including summary, timeline, top IPs, scenarios, and alerts.
- Added tests for the new hooks to ensure data fetching works as expected.
- Updated translation files for new dashboard terms in multiple languages.
- Refactored CrowdSecConfig page to include a tabbed interface for configuration and dashboard views.
- Added end-to-end tests for CrowdSec dashboard functionality including tab navigation, data display, and interaction with time range and refresh features.
2026-03-25 17:19:15 +00:00

89 lines
2.9 KiB
TypeScript

import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'
import { Skeleton } from '../ui'
import type { TopIP } from '../../api/crowdsecDashboard'
interface TopAttackingIPsChartProps {
data: TopIP[] | undefined
isLoading: boolean
isError: boolean
}
const BAR_COLOR = '#6366f1'
export function TopAttackingIPsChart({ data, isLoading, isError }: TopAttackingIPsChartProps) {
const { t } = useTranslation()
const chartData = useMemo(() => {
if (!data) return []
return data.slice(0, 10).map((ip) => ({
ip: ip.ip,
decisions: ip.count,
}))
}, [data])
if (isLoading) {
return (
<div className="rounded-lg border border-gray-700 bg-gray-900 p-4">
<Skeleton className="h-4 w-40 mb-4" />
<Skeleton className="h-64 w-full" />
</div>
)
}
if (isError) {
return (
<div className="rounded-lg border border-red-700/50 bg-red-900/20 p-4">
<p className="text-sm text-red-300">{t('security.crowdsec.dashboard.topIPsError', 'Failed to load top IPs data.')}</p>
</div>
)
}
if (!chartData.length) {
return (
<div className="rounded-lg border border-gray-700 bg-gray-900 p-4 text-center text-gray-400 py-12">
<p>{t('security.crowdsec.dashboard.noTopIPs', 'No attacking IPs in the selected period.')}</p>
</div>
)
}
return (
<div className="rounded-lg border border-gray-700 bg-gray-900 p-4">
<h3 className="text-sm font-medium text-gray-300 mb-4">
{t('security.crowdsec.dashboard.topAttackingIPs', 'Top Attacking IPs')}
</h3>
<div
role="img"
aria-label={t('security.crowdsec.dashboard.topIPsChartLabel', 'Horizontal bar chart showing top attacking IPs by decision count')}
>
<ResponsiveContainer width="100%" height={280}>
<BarChart data={chartData} layout="vertical" margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" horizontal={false} />
<XAxis type="number" tick={{ fill: '#9ca3af', fontSize: 12 }} stroke="#4b5563" allowDecimals={false} />
<YAxis
dataKey="ip"
type="category"
tick={{ fill: '#9ca3af', fontSize: 11 }}
stroke="#4b5563"
width={120}
/>
<Tooltip
contentStyle={{ backgroundColor: '#1f2937', border: '1px solid #374151', borderRadius: 8 }}
labelStyle={{ color: '#d1d5db' }}
/>
<Bar
dataKey="decisions"
name={t('security.crowdsec.dashboard.decisions', 'Decisions')}
fill={BAR_COLOR}
radius={[0, 4, 4, 0]}
/>
</BarChart>
</ResponsiveContainer>
</div>
</div>
)
}