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.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { TimeRange } from '../../api/crowdsecDashboard'
|
||||
|
||||
interface DashboardTimeRangeSelectorProps {
|
||||
value: TimeRange
|
||||
onChange: (range: TimeRange) => void
|
||||
}
|
||||
|
||||
const RANGES: TimeRange[] = ['1h', '6h', '24h', '7d', '30d']
|
||||
|
||||
const RANGE_LABELS: Record<TimeRange, string> = {
|
||||
'1h': '1H',
|
||||
'6h': '6H',
|
||||
'24h': '24H',
|
||||
'7d': '7D',
|
||||
'30d': '30D',
|
||||
}
|
||||
|
||||
export function DashboardTimeRangeSelector({ value, onChange }: DashboardTimeRangeSelectorProps) {
|
||||
const { t } = useTranslation()
|
||||
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([])
|
||||
|
||||
const selectedIndex = RANGES.indexOf(value)
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
let nextIndex: number
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
case 'ArrowDown':
|
||||
nextIndex = (selectedIndex + 1) % RANGES.length
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowUp':
|
||||
nextIndex = (selectedIndex - 1 + RANGES.length) % RANGES.length
|
||||
break
|
||||
case 'Home':
|
||||
nextIndex = 0
|
||||
break
|
||||
case 'End':
|
||||
nextIndex = RANGES.length - 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
onChange(RANGES[nextIndex])
|
||||
buttonRefs.current[nextIndex]?.focus()
|
||||
},
|
||||
[selectedIndex, onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
role="radiogroup"
|
||||
aria-label={t('security.crowdsec.dashboard.timeRange', 'Time range')}
|
||||
className="inline-flex rounded-lg border border-gray-700 bg-gray-900 p-1"
|
||||
>
|
||||
{RANGES.map((range, i) => (
|
||||
<button
|
||||
key={range}
|
||||
ref={(el) => { buttonRefs.current[i] = el }}
|
||||
role="radio"
|
||||
aria-checked={value === range}
|
||||
tabIndex={value === range ? 0 : -1}
|
||||
onClick={() => onChange(range)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${
|
||||
value === range
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-400 hover:text-white hover:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
{RANGE_LABELS[range]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user