From 17ab2a30ec3d368cb331f861e4ee20fbde86b502 Mon Sep 17 00:00:00 2001 From: shats Date: Tue, 17 Feb 2026 00:05:30 +0700 Subject: [PATCH] feat(PingServices): add caching functionality for ping service results and enhance settings modal for cache duration configuration --- backend/routes/miscRoutes.js | 42 +++++++++++++++++++++-- frontend/src/components/SettingsModal.jsx | 16 +++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/backend/routes/miscRoutes.js b/backend/routes/miscRoutes.js index eca5e56..391fd05 100644 --- a/backend/routes/miscRoutes.js +++ b/backend/routes/miscRoutes.js @@ -2,7 +2,7 @@ * Разные утилитарные роуты (auto-urls, history, s3 meta, availability, etc.) */ -const { s3, BUCKET_NAME, writeS3TextObject, headMeta, streamToString } = require('../services/s3Service'); +const { s3, BUCKET_NAME, writeS3TextObject, writeS3JsonObject, readS3TextObject, headMeta, streamToString } = require('../services/s3Service'); const { sendError, sendOk } = require('../middleware/errorHandler'); const { resourceToKey, toIso } = require('../utils/helpers'); const { GetObjectCommand, PutObjectCommand, ListObjectVersionsCommand, CopyObjectCommand } = require('@aws-sdk/client-s3'); @@ -351,6 +351,8 @@ async function loadUiSettingsSync() { } } +const PING_SERVICES_CACHE_KEY_PREFIX = 'ping-services/cache_'; + // GET /api/ping-services — RTT до Google, Cloudflare, Yandex, Instagram (веб или через RouterOS по настройке) async function getPingServices(req, res) { const fallbackPayload = { @@ -362,6 +364,36 @@ async function getPingServices(req, res) { try { const uiSettings = await loadUiSettingsSync(); const viaRouter = String(uiSettings.pingServicesSource || 'web').toLowerCase() === 'router'; + const cacheSeconds = Math.max(0, parseInt(uiSettings.pingServicesCacheSeconds, 10) || 0); + + let serverId = null; + let gatewayIp = null; + if (viaRouter) { + const { readServersFromS3 } = require('./serversRoutes'); + const servers = await readServersFromS3(); + const routerServers = servers.filter( + (s) => s && (String(s.type || '').toLowerCase() === 'jumphost' || String(s.type || '').toLowerCase() === 'home') + ); + serverId = (uiSettings.pingServicesServerId && String(uiSettings.pingServicesServerId).trim()) || (routerServers[0] && (routerServers[0].id || routerServers[0].dns || routerServers[0].ip)); + gatewayIp = (uiSettings.pingServicesGatewayIp && String(uiSettings.pingServicesGatewayIp).trim()) || null; + } + + const cacheKey = cacheSeconds > 0 + ? PING_SERVICES_CACHE_KEY_PREFIX + (viaRouter ? 'router' : 'web') + '_' + String(serverId || '').replace(/[^a-zA-Z0-9.-]/g, '_') + '_' + String(gatewayIp || '').replace(/[^a-zA-Z0-9.]/g, '_') + : 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 = cacheSeconds * 1000; + if (cachedAt && Date.now() - cachedAt < ttlMs && cached.byId && typeof cached.byId === 'object') { + return res.json(cached.byId); + } + } + } catch (_) {} + } if (viaRouter) { const { runPingViaRouter } = require('./mikrotikConfigRoutes'); @@ -370,8 +402,6 @@ async function getPingServices(req, res) { const routerServers = servers.filter( (s) => s && (String(s.type || '').toLowerCase() === 'jumphost' || String(s.type || '').toLowerCase() === 'home') ); - const serverId = (uiSettings.pingServicesServerId && String(uiSettings.pingServicesServerId).trim()) || (routerServers[0] && (routerServers[0].id || routerServers[0].dns || routerServers[0].ip)); - const gatewayIp = (uiSettings.pingServicesGatewayIp && String(uiSettings.pingServicesGatewayIp).trim()) || null; let gatewayIpResolved = gatewayIp; if (!gatewayIpResolved && serverId && routerServers.length > 0) { const server = routerServers.find((s) => (s.id || s.dns || s.ip) === serverId) || routerServers[0]; @@ -397,6 +427,9 @@ async function getPingServices(req, res) { ); const byId = {}; results.forEach((r) => { byId[r.id] = r; }); + if (cacheKey) { + writeS3JsonObject(cacheKey, { byId, cachedAt: Date.now() }).catch((err) => console.warn('[ping-services] cache write failed:', err?.message)); + } return res.json(byId); } @@ -408,6 +441,9 @@ async function getPingServices(req, res) { ); const byId = {}; results.forEach((r) => { byId[r.id] = r; }); + if (cacheKey) { + writeS3JsonObject(cacheKey, { byId, cachedAt: Date.now() }).catch((err) => console.warn('[ping-services] cache write failed:', err?.message)); + } res.json(byId); } catch (e) { console.error('ping-services error', e); diff --git a/frontend/src/components/SettingsModal.jsx b/frontend/src/components/SettingsModal.jsx index bbba756..8cacb65 100644 --- a/frontend/src/components/SettingsModal.jsx +++ b/frontend/src/components/SettingsModal.jsx @@ -26,6 +26,7 @@ export default function SettingsModal({ open, onClose }) { const [pingServicesSource, setPingServicesSource] = useState('web'); const [pingServicesServerId, setPingServicesServerId] = useState(''); const [pingServicesGatewayIp, setPingServicesGatewayIp] = useState(''); + const [pingServicesCacheSeconds, setPingServicesCacheSeconds] = useState(''); const [serversList, setServersList] = useState([]); const routerServersForPing = useMemo(() => { @@ -58,6 +59,7 @@ export default function SettingsModal({ open, onClose }) { setPingServicesSource(String(data?.pingServicesSource || 'web').toLowerCase() === 'router' ? 'router' : 'web'); setPingServicesServerId(String(data?.pingServicesServerId || '').trim()); setPingServicesGatewayIp(String(data?.pingServicesGatewayIp || '').trim()); + setPingServicesCacheSeconds(data?.pingServicesCacheSeconds != null ? String(data.pingServicesCacheSeconds) : ''); const e = settingsRes?.headers?.etag || settingsRes?.headers?.ETag || ''; setEtag(e ? String(e) : ''); setServersList(Array.isArray(serversRes?.data) ? serversRes.data : []); @@ -126,6 +128,7 @@ export default function SettingsModal({ open, onClose }) { pingServicesSource: pingServicesSource === 'router' ? 'router' : 'web', pingServicesServerId: String(pingServicesServerId || '').trim(), pingServicesGatewayIp: String(pingServicesGatewayIp || '').trim(), + pingServicesCacheSeconds: Math.max(0, parseInt(pingServicesCacheSeconds, 10) || 0), }; const payload = { settings: mergedSettings, @@ -265,6 +268,19 @@ export default function SettingsModal({ open, onClose }) { «Веб» — задержка до 8.8.8.8, 1.1.1.1 и др. с сервера. «Роутер» — пинг через выбранный MikroTik (jumphost). +
+ +
{pingServicesSource === 'router' && (