import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { request as invoke } from '../../utils/request'; import { Trash2, AlertCircle, Plus, Search, X } from 'lucide-react'; interface IpBlacklistEntry { ip_pattern: string; reason?: string; added_at: number; expires_at?: number; added_by?: string; } interface Props { refreshKey?: number; } export const BlacklistManager: React.FC = ({ refreshKey }) => { const { t } = useTranslation(); const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(''); // Add Modal State const [isAddOpen, setIsAddOpen] = useState(false); const [newIp, setNewIp] = useState(''); const [newReason, setNewReason] = useState(''); const [newExpires, setNewExpires] = useState(''); const loadBlacklist = async () => { setLoading(true); try { const data = await invoke('get_ip_blacklist'); setEntries(data); } catch (e) { console.error('Failed to load blacklist', e); } finally { setLoading(false); } }; useEffect(() => { loadBlacklist(); }, [refreshKey]); const handleAdd = async () => { try { let expiresAt = undefined; if (newExpires) { // Parse expires (e.g. "24h", "7d", or timestamp) // generic simple parser for hours const hours = parseInt(newExpires); if (!isNaN(hours)) { expiresAt = Math.floor(Date.now() / 1000) + hours * 3600; } } await invoke('add_ip_to_blacklist', { request: { ipPattern: newIp, reason: newReason || null, expiresAt: expiresAt } }); setIsAddOpen(false); setNewIp(''); setNewReason(''); setNewExpires(''); loadBlacklist(); } catch (e) { console.error('Failed to add to blacklist', e); const errorMsg = String(e); if (errorMsg.includes('UNIQUE constraint')) { alert(t('security.blacklist.error_duplicate') || 'This IP is already in the blacklist'); } else if (errorMsg.includes('Invalid IP pattern')) { alert(t('security.blacklist.error_invalid_ip') || 'Invalid IP format. Please use IP address or CIDR notation (e.g., 192.168.1.0/24)'); } else { alert(t('security.blacklist.error_add_failed') || 'Failed to add IP: ' + e); } } }; const handleRemove = async (ipPattern: string) => { // 乐观更新:立即从UI中移除 setEntries(prev => prev.filter(e => e.ip_pattern !== ipPattern)); try { await invoke('remove_ip_from_blacklist', { ipPattern: ipPattern }); } catch (e) { console.error('Failed to remove from blacklist', e); // 如果删除失败,重新加载数据恢复UI loadBlacklist(); } }; const filteredEntries = entries.filter(e => e.ip_pattern.includes(search) || (e.reason && e.reason.toLowerCase().includes(search.toLowerCase())) ); return (
setSearch(e.target.value)} />
{filteredEntries.map(entry => (

{entry.ip_pattern}

{entry.reason && (

{entry.reason}

)}
{t('security.blacklist.added_at')}: {new Date(entry.added_at * 1000).toLocaleString()} {entry.expires_at && ( {t('security.blacklist.expires_at')}: {new Date(entry.expires_at * 1000).toLocaleString()} )}
))} {!loading && filteredEntries.length === 0 && (
{t('security.blacklist.no_data')}
)}
{/* Add Modal */} {isAddOpen && (

{t('security.blacklist.add_title')}

setNewIp(e.target.value)} />
setNewReason(e.target.value)} />
setNewExpires(e.target.value)} />
)}
); };