From fbe9a07727bb848275553794f31697b2c95cc660 Mon Sep 17 00:00:00 2001 From: shats Date: Wed, 18 Feb 2026 12:24:51 +0700 Subject: [PATCH] feat(InterfaceSpeedTest, MikrotikConfigRoutes): enhance speed test functionality with cache handling and force refresh options --- backend/routes/mikrotikConfigRoutes.js | 61 ++++++++++++++------------ frontend/src/InterfaceSpeedTest.jsx | 31 ++++++++++++- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/backend/routes/mikrotikConfigRoutes.js b/backend/routes/mikrotikConfigRoutes.js index ded9a56..74c9470 100644 --- a/backend/routes/mikrotikConfigRoutes.js +++ b/backend/routes/mikrotikConfigRoutes.js @@ -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 diff --git a/frontend/src/InterfaceSpeedTest.jsx b/frontend/src/InterfaceSpeedTest.jsx index 6c3f59b..a3a2aea 100644 --- a/frontend/src/InterfaceSpeedTest.jsx +++ b/frontend/src/InterfaceSpeedTest.jsx @@ -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 || {};