From ef7a4817c938cf3e171caa35dfe2096b6321bdb8 Mon Sep 17 00:00:00 2001 From: shats Date: Sat, 21 Feb 2026 19:09:11 +0700 Subject: [PATCH] feat(FirewallPage): implement IP info loading from ipinfo.io for subnet analysis suggestions --- frontend/src/FirewallPage.jsx | 76 +++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/frontend/src/FirewallPage.jsx b/frontend/src/FirewallPage.jsx index e927eb6..532ea52 100644 --- a/frontend/src/FirewallPage.jsx +++ b/frontend/src/FirewallPage.jsx @@ -229,6 +229,8 @@ export default function FirewallPage() { const [analysisResult, setAnalysisResult] = useState(null); const [applyConfirmOpen, setApplyConfirmOpen] = useState(false); const [applying, setApplying] = useState(false); + const [ipInfoMap, setIpInfoMap] = useState({}); + const [ipInfoLoading, setIpInfoLoading] = useState(false); const routerServers = useMemo( () => @@ -290,8 +292,41 @@ export default function FirewallPage() { setMinCountN(n); const result = analyzeBanList(data.ban, n); setAnalysisResult(result); + setIpInfoMap({}); }; + // Подгрузка AS/орг с ipinfo.io для подсетей из анализа (лимит 30, с задержкой между запросами) + useEffect(() => { + const suggestions = analysisResult?.suggestions; + if (!suggestions?.length) return; + const maxFetch = 30; + const slice = suggestions.slice(0, maxFetch); + let cancelled = false; + setIpInfoLoading(true); + (async () => { + for (let i = 0; i < slice.length && !cancelled; i++) { + const s = slice[i]; + const firstIp = s.subnet.split('/')[0]; + try { + const res = await fetch(`https://ipinfo.io/${firstIp}/json`, { method: 'GET' }); + if (!res.ok) continue; + const info = await res.json(); + if (!cancelled) { + setIpInfoMap((prev) => ({ + ...prev, + [s.subnet]: { org: info.org, country: info.country, city: info.city }, + })); + } + } catch (_) { + if (!cancelled) setIpInfoMap((prev) => ({ ...prev, [s.subnet]: null })); + } + if (i < slice.length - 1) await new Promise((r) => setTimeout(r, 180)); + } + if (!cancelled) setIpInfoLoading(false); + })(); + return () => { cancelled = true; }; + }, [analysisResult?.suggestions]); + const handleApplySummary = async () => { if (!serverId || !analysisResult?.suggestions?.length) return; setApplying(true); @@ -422,6 +457,7 @@ export default function FirewallPage() { setAnalysisOpen(false); setAnalysisResult(null); setApplyConfirmOpen(false); + setIpInfoMap({}); }} title="Анализ списка ban" size="lg" @@ -455,6 +491,9 @@ export default function FirewallPage() { <>

Найдено {analysisResult.suggestions.length} подсетей /24 для замены: удалить одиночные IP и добавить одну запись на подсеть. + {ipInfoLoading && ( + (загрузка AS/хостер с ipinfo.io…) + )}

@@ -462,17 +501,40 @@ export default function FirewallPage() { + + - {analysisResult.suggestions.map((s) => ( - - - - - - ))} + {analysisResult.suggestions.map((s) => { + const firstIp = s.subnet.split('/')[0]; + const info = ipInfoMap[s.subnet]; + return ( + + + + + + + + ); + })}
Подсеть Кол-во IPAS / ХостерСсылка Действие
{s.subnet}{s.count}Удалить {s.count} записей → добавить 1
{s.subnet}{s.count} + {info === undefined + ? (ipInfoLoading ? '…' : '—') + : info + ? (info.org || info.country ? [info.org, info.country].filter(Boolean).join(' · ') : '—') + : '—'} + + + ipinfo.io + + Удалить {s.count} записей → добавить 1