feat(NetworkMapDashboard, SettingsPage): implement ping cache duration settings for network map and enhance ping request handling
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m5s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m5s
This commit is contained in:
@@ -29,6 +29,8 @@ export default function NetworkMapDashboard() {
|
||||
const [servers, setServers] = useState([]);
|
||||
const [connections, setConnections] = useState([]);
|
||||
const [pingMap, setPingMap] = useState({});
|
||||
const pingTimestampsRef = useRef({});
|
||||
const [pingCacheSeconds, setPingCacheSeconds] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [pingLoading, setPingLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -39,13 +41,20 @@ export default function NetworkMapDashboard() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [serversRes, configRes] = await Promise.all([
|
||||
const [serversRes, configRes, uiSettingsRes] = await Promise.all([
|
||||
api.get('/servers'),
|
||||
api.get('/network-config').catch(() => ({ data: null })),
|
||||
api.get('/ui-settings').catch(() => ({ data: {} })),
|
||||
]);
|
||||
const serversList = Array.isArray(serversRes?.data) ? serversRes.data : [];
|
||||
const config = configRes?.data || {};
|
||||
const tunnelInterfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : [];
|
||||
const uiSettings = uiSettingsRes?.data || {};
|
||||
const ttlSeconds = Math.max(
|
||||
0,
|
||||
parseInt(uiSettings.networkMapPingCacheSeconds, 10) || 0
|
||||
);
|
||||
setPingCacheSeconds(ttlSeconds);
|
||||
|
||||
const getServer = (serverId) =>
|
||||
serversList.find((s) => s.id === serverId || s.ip === serverId || s.dns === serverId);
|
||||
@@ -88,7 +97,7 @@ export default function NetworkMapDashboard() {
|
||||
pingAbortRef.current = false;
|
||||
setPingLoading(true);
|
||||
const key = (a, b) => `${a}:${b}`;
|
||||
const newMap = {};
|
||||
const ttlMs = Math.max(0, (pingCacheSeconds || 0) * 1000);
|
||||
|
||||
const tasks = [];
|
||||
connections.forEach((c) => {
|
||||
@@ -102,24 +111,42 @@ export default function NetworkMapDashboard() {
|
||||
const canPingTo =
|
||||
srcTo && ['jumphost', 'home'].includes(String(srcTo.type || '').toLowerCase()) && c.internalToFrom;
|
||||
|
||||
// Пингуем по ВНУТРЕННИМ адресам туннеля из /network-config:
|
||||
// server1 -> remoteIp, server2 -> localIp. serverId — ключ (id/dns/ip) как в servers.json.
|
||||
// Проверка кеша: если TTL не истёк, не запускаем новый запрос
|
||||
const keyFromTo = key(from, to);
|
||||
const keyToFrom = key(to, from);
|
||||
const now = Date.now();
|
||||
if (ttlMs > 0) {
|
||||
const tsFrom = pingTimestampsRef.current[keyFromTo];
|
||||
if (tsFrom && now - tsFrom < ttlMs) {
|
||||
// уже есть актуальное значение для from->to
|
||||
} else if (!canPingFrom) {
|
||||
// если пинговать нельзя, но кеша нет — оставляем как есть
|
||||
}
|
||||
const tsTo = pingTimestampsRef.current[keyToFrom];
|
||||
if (tsTo && now - tsTo < ttlMs) {
|
||||
// уже есть актуальное значение для to->from
|
||||
} else if (!canPingTo) {
|
||||
// нельзя пинговать и кеша нет — пропускаем
|
||||
}
|
||||
}
|
||||
|
||||
// Пингуем по ВНУТРЕННИМ адресам туннеля из /network-config
|
||||
if (canPingFrom) {
|
||||
tasks.push(async () => {
|
||||
if (pingAbortRef.current) return;
|
||||
try {
|
||||
const { data } = await api.post('/mikrotik/ping', {
|
||||
serverId: c.fromKey,
|
||||
// ВАЖНО: передаём внутренний IP и как target, и как gatewayIp,
|
||||
// чтобы backend мог по gatewayIp найти нужный tunnel interface.
|
||||
target: c.internalFromTo,
|
||||
gatewayIp: c.internalFromTo,
|
||||
count: 3,
|
||||
});
|
||||
const ms = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
||||
return { key: key(from, to), ms };
|
||||
pingTimestampsRef.current[keyFromTo] = Date.now();
|
||||
setPingMap((prev) => ({ ...prev, [keyFromTo]: ms }));
|
||||
} catch {
|
||||
return { key: key(from, to), ms: null };
|
||||
pingTimestampsRef.current[keyFromTo] = Date.now();
|
||||
setPingMap((prev) => ({ ...prev, [keyFromTo]: null }));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -130,29 +157,25 @@ export default function NetworkMapDashboard() {
|
||||
try {
|
||||
const { data } = await api.post('/mikrotik/ping', {
|
||||
serverId: c.toKey,
|
||||
// Аналогично для обратного направления
|
||||
target: c.internalToFrom,
|
||||
gatewayIp: c.internalToFrom,
|
||||
count: 3,
|
||||
});
|
||||
const ms = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
||||
return { key: key(to, from), ms };
|
||||
pingTimestampsRef.current[keyToFrom] = Date.now();
|
||||
setPingMap((prev) => ({ ...prev, [keyToFrom]: ms }));
|
||||
} catch {
|
||||
return { key: key(to, from), ms: null };
|
||||
pingTimestampsRef.current[keyToFrom] = Date.now();
|
||||
setPingMap((prev) => ({ ...prev, [keyToFrom]: null }));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const results = await runWithLimit(tasks, 4);
|
||||
await runWithLimit(tasks, 4);
|
||||
if (pingAbortRef.current) return;
|
||||
results.forEach((r) => {
|
||||
const v = r?.value;
|
||||
if (v && v.key != null) newMap[v.key] = v.ms ?? null;
|
||||
});
|
||||
setPingMap((prev) => ({ ...prev, ...newMap }));
|
||||
setPingLoading(false);
|
||||
}, [connections]);
|
||||
}, [connections, servers, pingCacheSeconds]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
|
||||
@@ -68,6 +68,7 @@ export default function SettingsPage() {
|
||||
const [baseAS, setBaseAS] = useState('65001');
|
||||
const [pingDomain, setPingDomain] = useState('');
|
||||
const [pingCacheMinutes, setPingCacheMinutes] = useState('');
|
||||
const [networkMapPingCacheSeconds, setNetworkMapPingCacheSeconds] = useState('');
|
||||
const [ptrZoneReplaceFrom, setPtrZoneReplaceFrom] = useState('');
|
||||
const [ptrZoneReplaceTo, setPtrZoneReplaceTo] = useState('');
|
||||
const [pingServicesSource, setPingServicesSource] = useState('web');
|
||||
@@ -206,6 +207,11 @@ export default function SettingsPage() {
|
||||
setPingCacheMinutes(
|
||||
data?.pingCacheMinutes != null ? String(data.pingCacheMinutes) : ''
|
||||
);
|
||||
setNetworkMapPingCacheSeconds(
|
||||
data?.networkMapPingCacheSeconds != null
|
||||
? String(data.networkMapPingCacheSeconds)
|
||||
: ''
|
||||
);
|
||||
setPtrZoneReplaceFrom(String(data?.ptrZoneReplaceFrom || ''));
|
||||
setPtrZoneReplaceTo(String(data?.ptrZoneReplaceTo || ''));
|
||||
setPingServicesSource(
|
||||
@@ -300,6 +306,10 @@ export default function SettingsPage() {
|
||||
baseAS: String(baseAS || '65001').trim(),
|
||||
pingDomain: String(pingDomain || '').trim(),
|
||||
pingCacheMinutes: Math.max(0, parseInt(pingCacheMinutes, 10) || 0),
|
||||
networkMapPingCacheSeconds: Math.max(
|
||||
0,
|
||||
parseInt(networkMapPingCacheSeconds, 10) || 0
|
||||
),
|
||||
ptrZoneReplaceFrom: String(ptrZoneReplaceFrom || '').trim(),
|
||||
ptrZoneReplaceTo: String(ptrZoneReplaceTo || '').trim(),
|
||||
pingServicesSource:
|
||||
@@ -528,6 +538,19 @@ export default function SettingsPage() {
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
label="Кеш пингов на карте сети (сек)"
|
||||
name="networkMapPingCacheSeconds"
|
||||
type="number"
|
||||
value={networkMapPingCacheSeconds}
|
||||
onChange={setNetworkMapPingCacheSeconds}
|
||||
placeholder="0"
|
||||
helpText="0 — без кеша. Иначе пинги между серверами на карте сети кешируются на указанный срок."
|
||||
disabled={saving}
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user