Files
Charon/frontend/src/components/crowdsec/DashboardTimeRangeSelector.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

84 lines
2.3 KiB
TypeScript

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>
)
}