feat(FirewallPage): implement IP info loading from ipinfo.io for subnet analysis suggestions
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m6s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m6s
This commit is contained in:
@@ -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() {
|
||||
<>
|
||||
<p className="mb-2">
|
||||
Найдено <strong>{analysisResult.suggestions.length}</strong> подсетей /24 для замены: удалить одиночные IP и добавить одну запись на подсеть.
|
||||
{ipInfoLoading && (
|
||||
<span className="ms-2 text-muted small">(загрузка AS/хостер с ipinfo.io…)</span>
|
||||
)}
|
||||
</p>
|
||||
<div className="table-responsive">
|
||||
<table className="table table-sm table-vcenter mb-0">
|
||||
@@ -462,17 +501,40 @@ export default function FirewallPage() {
|
||||
<tr>
|
||||
<th>Подсеть</th>
|
||||
<th className="text-end">Кол-во IP</th>
|
||||
<th>AS / Хостер</th>
|
||||
<th>Ссылка</th>
|
||||
<th>Действие</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{analysisResult.suggestions.map((s) => (
|
||||
<tr key={s.subnet}>
|
||||
<td className="font-monospace">{s.subnet}</td>
|
||||
<td className="text-end">{s.count}</td>
|
||||
<td className="text-muted small">Удалить {s.count} записей → добавить 1</td>
|
||||
</tr>
|
||||
))}
|
||||
{analysisResult.suggestions.map((s) => {
|
||||
const firstIp = s.subnet.split('/')[0];
|
||||
const info = ipInfoMap[s.subnet];
|
||||
return (
|
||||
<tr key={s.subnet}>
|
||||
<td className="font-monospace">{s.subnet}</td>
|
||||
<td className="text-end">{s.count}</td>
|
||||
<td className="small">
|
||||
{info === undefined
|
||||
? (ipInfoLoading ? '…' : '—')
|
||||
: info
|
||||
? (info.org || info.country ? [info.org, info.country].filter(Boolean).join(' · ') : '—')
|
||||
: '—'}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href={`https://ipinfo.io/${firstIp}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary"
|
||||
>
|
||||
ipinfo.io
|
||||
</a>
|
||||
</td>
|
||||
<td className="text-muted small">Удалить {s.count} записей → добавить 1</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user