feat(EasySwitchManager, FilterManager): implement location-based ping requests to refresh data on route changes
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m56s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m56s
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import api from './lib/api.js';
|
import api from './lib/api.js';
|
||||||
import { usePing } from './contexts/PingContext.jsx';
|
import { usePing } from './contexts/PingContext.jsx';
|
||||||
import {
|
import {
|
||||||
@@ -52,6 +53,7 @@ function EasySwitchManager() {
|
|||||||
const [groupByTags, setGroupByTags] = useState(true); // Группировка по тегам
|
const [groupByTags, setGroupByTags] = useState(true); // Группировка по тегам
|
||||||
// Единый источник пингов (кеш + API) — общий с страницей фильтров
|
// Единый источник пингов (кеш + API) — общий с страницей фильтров
|
||||||
const { pingMap, requestPings } = usePing();
|
const { pingMap, requestPings } = usePing();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
@@ -89,6 +91,25 @@ function EasySwitchManager() {
|
|||||||
initData();
|
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) => {
|
const loadDataWithParams = async (communities, inventory, ncGatewaysParam) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
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 api from './lib/api.js';
|
||||||
import ConfirmDialog from './components/ConfirmDialog.jsx';
|
import ConfirmDialog from './components/ConfirmDialog.jsx';
|
||||||
import FormModal from './components/FormModal.jsx';
|
import FormModal from './components/FormModal.jsx';
|
||||||
@@ -895,16 +895,19 @@ function FilterManager() {
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Запрашиваем пинги для gateways выбранного сервера через единый контекст (кеш общий с Easy Switch)
|
const location = useLocation();
|
||||||
|
|
||||||
|
// Запрашиваем пинги для gateways выбранного сервера; при переходе на раздел Фильтров обновляем (устаревшие по TTL перезапросятся)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedServer || !selectedServerGateways?.length) return;
|
if (!selectedServer || !selectedServerGateways?.length) return;
|
||||||
|
if (location.pathname !== '/filters') return;
|
||||||
const routerId = getRouterIdForServer(selectedServer);
|
const routerId = getRouterIdForServer(selectedServer);
|
||||||
if (!routerId) return;
|
if (!routerId) return;
|
||||||
const tasks = selectedServerGateways
|
const tasks = selectedServerGateways
|
||||||
.filter((gw) => gw.ip)
|
.filter((gw) => gw.ip)
|
||||||
.map((gw) => ({ routerId, gatewayIp: gw.ip }));
|
.map((gw) => ({ routerId, gatewayIp: gw.ip }));
|
||||||
requestPings(tasks);
|
requestPings(tasks);
|
||||||
}, [selectedServer, selectedServerGateways, requestPings]);
|
}, [location.pathname, selectedServer, selectedServerGateways, requestPings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Загружаем базовую AS из настроек
|
// Загружаем базовую AS из настроек
|
||||||
|
|||||||
@@ -4,16 +4,20 @@ import api from '../lib/api.js';
|
|||||||
/**
|
/**
|
||||||
* Единая точка входа для пингов до шлюзов (MikroTik).
|
* Единая точка входа для пингов до шлюзов (MikroTik).
|
||||||
* Кеш: ключ `${routerId}:${gatewayIp}` → number | null (мс или ошибка).
|
* Кеш: ключ `${routerId}:${gatewayIp}` → number | null (мс или ошибка).
|
||||||
|
* TTL: после истечения записи перезапрашиваются при переходе на раздел.
|
||||||
* Используется и на Easy Switch, и на странице фильтров (карточки gateway).
|
* Используется и на Easy Switch, и на странице фильтров (карточки gateway).
|
||||||
*/
|
*/
|
||||||
const PingContext = createContext(null);
|
const PingContext = createContext(null);
|
||||||
|
|
||||||
const PING_COUNT = 5;
|
const PING_COUNT = 5;
|
||||||
|
/** Время жизни кеша пинга (мс). После истечения при открытии раздела данные перезапрашиваются. */
|
||||||
|
const PING_CACHE_TTL_MS = 2 * 60 * 1000; // 2 минуты
|
||||||
|
|
||||||
export function PingProvider({ children }) {
|
export function PingProvider({ children }) {
|
||||||
const [pingMap, setPingMap] = useState({});
|
const [pingMap, setPingMap] = useState({});
|
||||||
const inFlightRef = useRef(new Set());
|
const inFlightRef = useRef(new Set());
|
||||||
const pingMapRef = useRef(pingMap);
|
const pingMapRef = useRef(pingMap);
|
||||||
|
const timestampsRef = useRef({}); // key → Date.now() когда запись установлена
|
||||||
pingMapRef.current = pingMap;
|
pingMapRef.current = pingMap;
|
||||||
|
|
||||||
const getKey = (routerId, gatewayIp) => {
|
const getKey = (routerId, gatewayIp) => {
|
||||||
@@ -21,6 +25,14 @@ export function PingProvider({ children }) {
|
|||||||
return `${routerId}:${gatewayIp}`;
|
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 getPing = useCallback((routerId, gatewayIp) => {
|
||||||
const key = getKey(routerId, gatewayIp);
|
const key = getKey(routerId, gatewayIp);
|
||||||
if (!key) return undefined;
|
if (!key) return undefined;
|
||||||
@@ -29,12 +41,11 @@ export function PingProvider({ children }) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Запросить пинги для списка пар (routerId, gatewayIp).
|
* Запросить пинги для списка пар (routerId, gatewayIp).
|
||||||
* Уже закешированные или запрашиваемые ключи пропускаются. Один вызов API на ключ.
|
* Пропускаются ключи с валидным кешем (не истёк TTL) и уже запрашиваемые.
|
||||||
* @param tasks Array<{ routerId, gatewayIp }>
|
* @param tasks Array<{ routerId, gatewayIp }>
|
||||||
*/
|
*/
|
||||||
const requestPings = useCallback((tasks) => {
|
const requestPings = useCallback((tasks) => {
|
||||||
if (!Array.isArray(tasks) || tasks.length === 0) return;
|
if (!Array.isArray(tasks) || tasks.length === 0) return;
|
||||||
const cache = pingMapRef.current;
|
|
||||||
|
|
||||||
const toRequest = [];
|
const toRequest = [];
|
||||||
tasks.forEach(({ routerId, gatewayIp }) => {
|
tasks.forEach(({ routerId, gatewayIp }) => {
|
||||||
@@ -42,7 +53,7 @@ export function PingProvider({ children }) {
|
|||||||
if (!routerId || !ip) return;
|
if (!routerId || !ip) return;
|
||||||
const key = getKey(routerId, ip);
|
const key = getKey(routerId, ip);
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
if (Object.prototype.hasOwnProperty.call(cache, key)) return;
|
if (isCacheValid(key)) return;
|
||||||
if (inFlightRef.current.has(key)) return;
|
if (inFlightRef.current.has(key)) return;
|
||||||
inFlightRef.current.add(key);
|
inFlightRef.current.add(key);
|
||||||
toRequest.push({ routerId, ip, key });
|
toRequest.push({ routerId, ip, key });
|
||||||
@@ -57,17 +68,19 @@ export function PingProvider({ children }) {
|
|||||||
})
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
const value = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
const value = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
||||||
|
timestampsRef.current[key] = Date.now();
|
||||||
setPingMap((prev) => ({ ...prev, [key]: value }));
|
setPingMap((prev) => ({ ...prev, [key]: value }));
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn('PingContext: failed to fetch ping for', routerId, ip, e?.message || e);
|
console.warn('PingContext: failed to fetch ping for', routerId, ip, e?.message || e);
|
||||||
|
timestampsRef.current[key] = Date.now();
|
||||||
setPingMap((prev) => ({ ...prev, [key]: null }));
|
setPingMap((prev) => ({ ...prev, [key]: null }));
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
inFlightRef.current.delete(key);
|
inFlightRef.current.delete(key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, []);
|
}, [isCacheValid]);
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
pingMap,
|
pingMap,
|
||||||
|
|||||||
Reference in New Issue
Block a user