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.)
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
@@ -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).
|
||||
</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' && (
|
||||
<div className="row g-2">
|
||||
<div className="col-md-6">
|
||||
|
||||
Reference in New Issue
Block a user