feat(InterfaceSpeedTest, MikrotikConfigRoutes): enhance speed test functionality with cache handling and force refresh options
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m53s

This commit is contained in:
2026-02-18 12:24:51 +07:00
parent 8b362f0f55
commit fbe9a07727
2 changed files with 63 additions and 29 deletions
+34 -27
View File
@@ -851,7 +851,7 @@ function parseSpeedToBps(val) {
*/
async function speedTestViaTunnel(req, res) {
try {
const { serverId, interfaceName } = req.body || {};
const { serverId, interfaceName, cacheOnly, forceRefresh } = req.body || {};
let { durationSeconds } = req.body || {};
if (!serverId || !interfaceName) {
@@ -864,6 +864,39 @@ async function speedTestViaTunnel(req, res) {
}
const uiSettings = await loadUiSettings();
const cacheMinutes = Math.max(
0,
parseInt(uiSettings.interfaceSpeedTestCacheMinutes, 10) || 0
);
const cacheKey =
cacheMinutes > 0
? speedTestCacheKey(serverId, interfaceName)
: null;
// При выборе сервера/интерфейса — только кеш. При нажатии «Замерить» — принудительно новый замер (forceRefresh).
const skipCacheRead = forceRefresh === true;
if (!skipCacheRead && cacheKey) {
try {
const raw = await readS3TextObject(cacheKey).catch(() => null);
if (raw?.body) {
const cached = JSON.parse(raw.body);
const cachedAt =
typeof cached.cachedAt === 'number' ? cached.cachedAt : 0;
const ttlMs = cacheMinutes * 60 * 1000;
if (cachedAt && Date.now() - cachedAt < ttlMs) {
return res.json({ ...cached, cached: true });
}
}
} catch (_) {
// Если кеш не прочитался — продолжаем без него
}
}
if (cacheOnly === true) {
return res.json({ ok: false, notInCache: true });
}
const defaultDuration = Math.max(
1,
Math.min(
@@ -882,32 +915,6 @@ async function speedTestViaTunnel(req, res) {
)
);
const cacheMinutes = Math.max(
0,
parseInt(uiSettings.interfaceSpeedTestCacheMinutes, 10) || 0
);
const cacheKey =
cacheMinutes > 0
? speedTestCacheKey(serverId, interfaceName)
: null;
if (cacheKey) {
try {
const raw = await readS3TextObject(cacheKey).catch(() => null);
if (raw?.body) {
const cached = JSON.parse(raw.body);
const cachedAt =
typeof cached.cachedAt === 'number' ? cached.cachedAt : 0;
const ttlMs = cacheMinutes * 60 * 1000;
if (cachedAt && Date.now() - cachedAt < ttlMs) {
return res.json({ ...cached, cached: true });
}
}
} catch (_) {
// Если кеш не прочитался — продолжаем без него
}
}
const config = await loadNetworkConfig();
const tunnelIfaces = Array.isArray(config.tunnelInterfaces)
? config.tunnelInterfaces
+29 -2
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import api from './lib/api.js';
import { useNotify } from './components/NotifyProvider.jsx';
import PageHeader from './components/PageHeader.jsx';
@@ -167,6 +167,33 @@ export default function InterfaceSpeedTest() {
return `${mbps.toFixed(2)} Мбит/с`;
};
/** Запросить только кеш (без замера). При выборе сервера/интерфейса показываем кеш, если есть. */
const fetchCachedResult = useCallback(async () => {
if (!serverId || !selectedInterfaceName) {
setSpeedResult(null);
return;
}
try {
const res = await api.post('/mikrotik/speed-test', {
serverId,
interfaceName: selectedInterfaceName,
cacheOnly: true,
});
const data = res?.data || {};
if (data.ok !== false && !data.notInCache && (data.tcpDownloadBps != null || data.tcpUploadBps != null)) {
setSpeedResult(data);
} else {
setSpeedResult(null);
}
} catch {
setSpeedResult(null);
}
}, [serverId, selectedInterfaceName]);
useEffect(() => {
fetchCachedResult();
}, [fetchCachedResult]);
const handleRunTest = async () => {
if (!serverId) {
notify.error('Выберите сервер (jumphost/home) для замера скорости');
@@ -185,9 +212,9 @@ export default function InterfaceSpeedTest() {
serverId,
interfaceName: selectedInterfaceName,
durationSeconds: speedSettings.durationSeconds,
forceRefresh: true,
};
const res = await api.post('/mikrotik/speed-test', body, {
// speed-test может занимать дольше стандартных 30 секунд
timeout: 120000,
});
const data = res?.data || {};