feat(PingServices): add caching functionality for ping service results and enhance settings modal for cache duration configuration
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m41s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m41s
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* Разные утилитарные роуты (auto-urls, history, s3 meta, availability, etc.)
|
* Разные утилитарные роуты (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 { sendError, sendOk } = require('../middleware/errorHandler');
|
||||||
const { resourceToKey, toIso } = require('../utils/helpers');
|
const { resourceToKey, toIso } = require('../utils/helpers');
|
||||||
const { GetObjectCommand, PutObjectCommand, ListObjectVersionsCommand, CopyObjectCommand } = require('@aws-sdk/client-s3');
|
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 по настройке)
|
// GET /api/ping-services — RTT до Google, Cloudflare, Yandex, Instagram (веб или через RouterOS по настройке)
|
||||||
async function getPingServices(req, res) {
|
async function getPingServices(req, res) {
|
||||||
const fallbackPayload = {
|
const fallbackPayload = {
|
||||||
@@ -362,6 +364,36 @@ async function getPingServices(req, res) {
|
|||||||
try {
|
try {
|
||||||
const uiSettings = await loadUiSettingsSync();
|
const uiSettings = await loadUiSettingsSync();
|
||||||
const viaRouter = String(uiSettings.pingServicesSource || 'web').toLowerCase() === 'router';
|
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) {
|
if (viaRouter) {
|
||||||
const { runPingViaRouter } = require('./mikrotikConfigRoutes');
|
const { runPingViaRouter } = require('./mikrotikConfigRoutes');
|
||||||
@@ -370,8 +402,6 @@ async function getPingServices(req, res) {
|
|||||||
const routerServers = servers.filter(
|
const routerServers = servers.filter(
|
||||||
(s) => s && (String(s.type || '').toLowerCase() === 'jumphost' || String(s.type || '').toLowerCase() === 'home')
|
(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;
|
let gatewayIpResolved = gatewayIp;
|
||||||
if (!gatewayIpResolved && serverId && routerServers.length > 0) {
|
if (!gatewayIpResolved && serverId && routerServers.length > 0) {
|
||||||
const server = routerServers.find((s) => (s.id || s.dns || s.ip) === serverId) || routerServers[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 = {};
|
const byId = {};
|
||||||
results.forEach((r) => { byId[r.id] = r; });
|
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);
|
return res.json(byId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,6 +441,9 @@ async function getPingServices(req, res) {
|
|||||||
);
|
);
|
||||||
const byId = {};
|
const byId = {};
|
||||||
results.forEach((r) => { byId[r.id] = r; });
|
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);
|
res.json(byId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('ping-services error', e);
|
console.error('ping-services error', e);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export default function SettingsModal({ open, onClose }) {
|
|||||||
const [pingServicesSource, setPingServicesSource] = useState('web');
|
const [pingServicesSource, setPingServicesSource] = useState('web');
|
||||||
const [pingServicesServerId, setPingServicesServerId] = useState('');
|
const [pingServicesServerId, setPingServicesServerId] = useState('');
|
||||||
const [pingServicesGatewayIp, setPingServicesGatewayIp] = useState('');
|
const [pingServicesGatewayIp, setPingServicesGatewayIp] = useState('');
|
||||||
|
const [pingServicesCacheSeconds, setPingServicesCacheSeconds] = useState('');
|
||||||
const [serversList, setServersList] = useState([]);
|
const [serversList, setServersList] = useState([]);
|
||||||
|
|
||||||
const routerServersForPing = useMemo(() => {
|
const routerServersForPing = useMemo(() => {
|
||||||
@@ -58,6 +59,7 @@ export default function SettingsModal({ open, onClose }) {
|
|||||||
setPingServicesSource(String(data?.pingServicesSource || 'web').toLowerCase() === 'router' ? 'router' : 'web');
|
setPingServicesSource(String(data?.pingServicesSource || 'web').toLowerCase() === 'router' ? 'router' : 'web');
|
||||||
setPingServicesServerId(String(data?.pingServicesServerId || '').trim());
|
setPingServicesServerId(String(data?.pingServicesServerId || '').trim());
|
||||||
setPingServicesGatewayIp(String(data?.pingServicesGatewayIp || '').trim());
|
setPingServicesGatewayIp(String(data?.pingServicesGatewayIp || '').trim());
|
||||||
|
setPingServicesCacheSeconds(data?.pingServicesCacheSeconds != null ? String(data.pingServicesCacheSeconds) : '');
|
||||||
const e = settingsRes?.headers?.etag || settingsRes?.headers?.ETag || '';
|
const e = settingsRes?.headers?.etag || settingsRes?.headers?.ETag || '';
|
||||||
setEtag(e ? String(e) : '');
|
setEtag(e ? String(e) : '');
|
||||||
setServersList(Array.isArray(serversRes?.data) ? serversRes.data : []);
|
setServersList(Array.isArray(serversRes?.data) ? serversRes.data : []);
|
||||||
@@ -126,6 +128,7 @@ export default function SettingsModal({ open, onClose }) {
|
|||||||
pingServicesSource: pingServicesSource === 'router' ? 'router' : 'web',
|
pingServicesSource: pingServicesSource === 'router' ? 'router' : 'web',
|
||||||
pingServicesServerId: String(pingServicesServerId || '').trim(),
|
pingServicesServerId: String(pingServicesServerId || '').trim(),
|
||||||
pingServicesGatewayIp: String(pingServicesGatewayIp || '').trim(),
|
pingServicesGatewayIp: String(pingServicesGatewayIp || '').trim(),
|
||||||
|
pingServicesCacheSeconds: Math.max(0, parseInt(pingServicesCacheSeconds, 10) || 0),
|
||||||
};
|
};
|
||||||
const payload = {
|
const payload = {
|
||||||
settings: mergedSettings,
|
settings: mergedSettings,
|
||||||
@@ -265,6 +268,19 @@ export default function SettingsModal({ open, onClose }) {
|
|||||||
«Веб» — задержка до 8.8.8.8, 1.1.1.1 и др. с сервера. «Роутер» — пинг через выбранный MikroTik (jumphost).
|
«Веб» — задержка до 8.8.8.8, 1.1.1.1 и др. с сервера. «Роутер» — пинг через выбранный MikroTik (jumphost).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<FormField
|
||||||
|
label="Время кеширования пингов (сек)"
|
||||||
|
name="pingServicesCacheSeconds"
|
||||||
|
type="number"
|
||||||
|
value={pingServicesCacheSeconds}
|
||||||
|
onChange={setPingServicesCacheSeconds}
|
||||||
|
placeholder="0"
|
||||||
|
helpText="0 — без кеша. Результаты пинга до Google, Cloudflare и др. будут кешироваться на указанное число секунд."
|
||||||
|
disabled={loading || saving}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{pingServicesSource === 'router' && (
|
{pingServicesSource === 'router' && (
|
||||||
<div className="row g-2">
|
<div className="row g-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user