Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m56s
106 lines
3.7 KiB
React
106 lines
3.7 KiB
React
import { createContext, useContext, useState, useCallback, useRef } from 'react';
|
|
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) => {
|
|
if (!routerId || !gatewayIp) return null;
|
|
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;
|
|
return Object.prototype.hasOwnProperty.call(pingMap, key) ? pingMap[key] : undefined;
|
|
}, [pingMap]);
|
|
|
|
/**
|
|
* Запросить пинги для списка пар (routerId, gatewayIp).
|
|
* Пропускаются ключи с валидным кешем (не истёк TTL) и уже запрашиваемые.
|
|
* @param tasks Array<{ routerId, gatewayIp }>
|
|
*/
|
|
const requestPings = useCallback((tasks) => {
|
|
if (!Array.isArray(tasks) || tasks.length === 0) return;
|
|
|
|
const toRequest = [];
|
|
tasks.forEach(({ routerId, gatewayIp }) => {
|
|
const ip = gatewayIp || '';
|
|
if (!routerId || !ip) return;
|
|
const key = getKey(routerId, ip);
|
|
if (!key) return;
|
|
if (isCacheValid(key)) return;
|
|
if (inFlightRef.current.has(key)) return;
|
|
inFlightRef.current.add(key);
|
|
toRequest.push({ routerId, ip, key });
|
|
});
|
|
|
|
toRequest.forEach(({ routerId, ip, key }) => {
|
|
api
|
|
.post('/mikrotik/ping', {
|
|
serverId: routerId,
|
|
gatewayIp: ip,
|
|
count: PING_COUNT,
|
|
})
|
|
.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,
|
|
getPing,
|
|
requestPings,
|
|
getKey,
|
|
};
|
|
|
|
return (
|
|
<PingContext.Provider value={value}>
|
|
{children}
|
|
</PingContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function usePing() {
|
|
const ctx = useContext(PingContext);
|
|
if (!ctx) {
|
|
throw new Error('usePing must be used within PingProvider');
|
|
}
|
|
return ctx;
|
|
}
|