diff --git a/frontend/src/EasySwitchManager.jsx b/frontend/src/EasySwitchManager.jsx index faa2371..038ddd9 100644 --- a/frontend/src/EasySwitchManager.jsx +++ b/frontend/src/EasySwitchManager.jsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; import api from './lib/api.js'; import { usePing } from './contexts/PingContext.jsx'; import { @@ -52,6 +53,7 @@ function EasySwitchManager() { const [groupByTags, setGroupByTags] = useState(true); // Группировка по тегам // Единый источник пингов (кеш + API) — общий с страницей фильтров const { pingMap, requestPings } = usePing(); + const location = useLocation(); useEffect(() => { const initData = async () => { @@ -89,6 +91,25 @@ function EasySwitchManager() { initData(); }, []); + // При переходе на раздел Easy Switch перезапрашиваем пинги (устаревшие по TTL обновятся) + useEffect(() => { + if (location.pathname !== '/easy-switch' || !servers.length || !inventoryServers.length) return; + const pingTasks = []; + servers.forEach((server) => { + const inv = inventoryServers.find(srv => + String(srv.dns || '').trim() === server.name || + String(srv.hostName || '').trim() === server.name || + String(srv.ip || '').trim() === server.name + ); + const routerId = inv?.id || inv?.dns || inv?.ip; + if (!routerId) return; + (server.gateways || []).forEach((gw) => { + if (gw.ip) pingTasks.push({ routerId, gatewayIp: gw.ip }); + }); + }); + requestPings(pingTasks); + }, [location.pathname, servers, inventoryServers, requestPings]); + const loadDataWithParams = async (communities, inventory, ncGatewaysParam) => { setLoading(true); setError(''); diff --git a/frontend/src/FilterManager.jsx b/frontend/src/FilterManager.jsx index 1a05db0..07154a3 100644 --- a/frontend/src/FilterManager.jsx +++ b/frontend/src/FilterManager.jsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import api from './lib/api.js'; import ConfirmDialog from './components/ConfirmDialog.jsx'; import FormModal from './components/FormModal.jsx'; @@ -895,16 +895,19 @@ function FilterManager() { }); })(); - // Запрашиваем пинги для gateways выбранного сервера через единый контекст (кеш общий с Easy Switch) + const location = useLocation(); + + // Запрашиваем пинги для gateways выбранного сервера; при переходе на раздел Фильтров обновляем (устаревшие по TTL перезапросятся) useEffect(() => { if (!selectedServer || !selectedServerGateways?.length) return; + if (location.pathname !== '/filters') return; const routerId = getRouterIdForServer(selectedServer); if (!routerId) return; const tasks = selectedServerGateways .filter((gw) => gw.ip) .map((gw) => ({ routerId, gatewayIp: gw.ip })); requestPings(tasks); - }, [selectedServer, selectedServerGateways, requestPings]); + }, [location.pathname, selectedServer, selectedServerGateways, requestPings]); useEffect(() => { // Загружаем базовую AS из настроек diff --git a/frontend/src/contexts/PingContext.jsx b/frontend/src/contexts/PingContext.jsx index 360364a..3ba02f7 100644 --- a/frontend/src/contexts/PingContext.jsx +++ b/frontend/src/contexts/PingContext.jsx @@ -4,16 +4,20 @@ import api from '../lib/api.js'; /** * Единая точка входа для пингов до шлюзов (MikroTik). * Кеш: ключ `${routerId}:${gatewayIp}` → number | null (мс или ошибка). + * TTL: после истечения записи перезапрашиваются при переходе на раздел. * Используется и на Easy Switch, и на странице фильтров (карточки gateway). */ const PingContext = createContext(null); const PING_COUNT = 5; +/** Время жизни кеша пинга (мс). После истечения при открытии раздела данные перезапрашиваются. */ +const PING_CACHE_TTL_MS = 2 * 60 * 1000; // 2 минуты export function PingProvider({ children }) { const [pingMap, setPingMap] = useState({}); const inFlightRef = useRef(new Set()); const pingMapRef = useRef(pingMap); + const timestampsRef = useRef({}); // key → Date.now() когда запись установлена pingMapRef.current = pingMap; const getKey = (routerId, gatewayIp) => { @@ -21,6 +25,14 @@ export function PingProvider({ children }) { return `${routerId}:${gatewayIp}`; }; + const isCacheValid = useCallback((key) => { + const cache = pingMapRef.current; + if (!Object.prototype.hasOwnProperty.call(cache, key)) return false; + const ts = timestampsRef.current[key]; + if (ts == null) return false; + return (Date.now() - ts) < PING_CACHE_TTL_MS; + }, []); + const getPing = useCallback((routerId, gatewayIp) => { const key = getKey(routerId, gatewayIp); if (!key) return undefined; @@ -29,12 +41,11 @@ export function PingProvider({ children }) { /** * Запросить пинги для списка пар (routerId, gatewayIp). - * Уже закешированные или запрашиваемые ключи пропускаются. Один вызов API на ключ. + * Пропускаются ключи с валидным кешем (не истёк TTL) и уже запрашиваемые. * @param tasks Array<{ routerId, gatewayIp }> */ const requestPings = useCallback((tasks) => { if (!Array.isArray(tasks) || tasks.length === 0) return; - const cache = pingMapRef.current; const toRequest = []; tasks.forEach(({ routerId, gatewayIp }) => { @@ -42,7 +53,7 @@ export function PingProvider({ children }) { if (!routerId || !ip) return; const key = getKey(routerId, ip); if (!key) return; - if (Object.prototype.hasOwnProperty.call(cache, key)) return; + if (isCacheValid(key)) return; if (inFlightRef.current.has(key)) return; inFlightRef.current.add(key); toRequest.push({ routerId, ip, key }); @@ -57,17 +68,19 @@ export function PingProvider({ children }) { }) .then(({ data }) => { const value = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null; + timestampsRef.current[key] = Date.now(); setPingMap((prev) => ({ ...prev, [key]: value })); }) .catch((e) => { console.warn('PingContext: failed to fetch ping for', routerId, ip, e?.message || e); + timestampsRef.current[key] = Date.now(); setPingMap((prev) => ({ ...prev, [key]: null })); }) .finally(() => { inFlightRef.current.delete(key); }); }); - }, []); + }, [isCacheValid]); const value = { pingMap,