feat(PingServicesScheduler): implement ping services scheduler endpoints and cache refresh logic for improved service monitoring
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m29s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m29s
This commit is contained in:
@@ -368,6 +368,88 @@ async function loadUiSettingsSync() {
|
||||
|
||||
const PING_SERVICES_CACHE_KEY_PREFIX = 'ping-services/cache_';
|
||||
|
||||
/**
|
||||
* Обновить кеш пинг-сервисов: выполнить пинги по списку из настроек и записать результат в S3.
|
||||
* Используется планировщиком и при промахе кеша в getPingServices.
|
||||
* @returns {Promise<Record<string, { id: string, name: string, host: string, ms: number|null }>>} byId
|
||||
*/
|
||||
async function refreshPingServicesCache() {
|
||||
const uiSettings = await loadUiSettingsSync();
|
||||
const servicesList = getPingServicesListFromSettings(uiSettings);
|
||||
const fallbackPayload = {};
|
||||
servicesList.forEach((s) => { fallbackPayload[s.id] = { id: s.id, name: s.name, host: s.host, ms: null }; });
|
||||
|
||||
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 (viaRouter) {
|
||||
const { runPingViaRouter } = require('./mikrotikConfigRoutes');
|
||||
const { readServersFromS3 } = require('./serversRoutes');
|
||||
const servers = await readServersFromS3();
|
||||
const routerServers = servers.filter(
|
||||
(s) => s && (String(s.type || '').toLowerCase() === 'jumphost' || String(s.type || '').toLowerCase() === 'home')
|
||||
);
|
||||
let gatewayIpResolved = gatewayIp;
|
||||
if (!gatewayIpResolved && serverId && routerServers.length > 0) {
|
||||
const server = routerServers.find((s) => (s.id || s.dns || s.ip) === serverId) || routerServers[0];
|
||||
const gateways = Array.isArray(server.gateways) ? server.gateways : [];
|
||||
const primary = gateways.find((g) => g && g.primary) || gateways[0];
|
||||
gatewayIpResolved = primary && (primary.ip || primary.remoteIp) ? (primary.ip || primary.remoteIp) : null;
|
||||
}
|
||||
if (!serverId) {
|
||||
console.warn('[ping-services] router mode: no serverId (no jumphost/home in settings or in servers list)');
|
||||
return fallbackPayload;
|
||||
}
|
||||
const results = await Promise.all(
|
||||
servicesList.map(async (svc) => {
|
||||
try {
|
||||
const result = await runPingViaRouter(serverId, gatewayIpResolved || null, svc.host, 3);
|
||||
const ms = typeof result.avgMs === 'number' ? Math.round(result.avgMs) : null;
|
||||
return { id: svc.id, name: svc.name, host: svc.host, ms };
|
||||
} catch (err) {
|
||||
console.warn('[ping-services] runPingViaRouter failed for', svc.host, err?.message || err);
|
||||
return { id: svc.id, name: svc.name, host: svc.host, ms: null };
|
||||
}
|
||||
})
|
||||
);
|
||||
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 byId;
|
||||
}
|
||||
|
||||
const results = await Promise.all(
|
||||
servicesList.map(async (svc) => {
|
||||
const ms = await measureTcpRtt(svc.host, svc.port, 6000);
|
||||
return { id: svc.id, name: svc.name, host: svc.host, ms };
|
||||
})
|
||||
);
|
||||
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 byId;
|
||||
}
|
||||
|
||||
// GET /api/ping-services-list — список сервисов для пинга из настроек (для дашборда и редактора)
|
||||
async function getPingServicesList(req, res) {
|
||||
try {
|
||||
@@ -420,56 +502,8 @@ async function getPingServices(req, res) {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (viaRouter) {
|
||||
const { runPingViaRouter } = require('./mikrotikConfigRoutes');
|
||||
const { readServersFromS3 } = require('./serversRoutes');
|
||||
const servers = await readServersFromS3();
|
||||
const routerServers = servers.filter(
|
||||
(s) => s && (String(s.type || '').toLowerCase() === 'jumphost' || String(s.type || '').toLowerCase() === 'home')
|
||||
);
|
||||
let gatewayIpResolved = gatewayIp;
|
||||
if (!gatewayIpResolved && serverId && routerServers.length > 0) {
|
||||
const server = routerServers.find((s) => (s.id || s.dns || s.ip) === serverId) || routerServers[0];
|
||||
const gateways = Array.isArray(server.gateways) ? server.gateways : [];
|
||||
const primary = gateways.find((g) => g && g.primary) || gateways[0];
|
||||
gatewayIpResolved = primary && (primary.ip || primary.remoteIp) ? (primary.ip || primary.remoteIp) : null;
|
||||
}
|
||||
if (!serverId) {
|
||||
console.warn('[ping-services] router mode: no serverId (no jumphost/home in settings or in servers list)');
|
||||
return res.json(fallbackPayload);
|
||||
}
|
||||
const results = await Promise.all(
|
||||
servicesList.map(async (svc) => {
|
||||
try {
|
||||
const result = await runPingViaRouter(serverId, gatewayIpResolved || null, svc.host, 3);
|
||||
const ms = typeof result.avgMs === 'number' ? Math.round(result.avgMs) : null;
|
||||
return { id: svc.id, name: svc.name, host: svc.host, ms };
|
||||
} catch (err) {
|
||||
console.warn('[ping-services] runPingViaRouter failed for', svc.host, err?.message || err);
|
||||
return { id: svc.id, name: svc.name, host: svc.host, ms: null };
|
||||
}
|
||||
})
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
const results = await Promise.all(
|
||||
servicesList.map(async (svc) => {
|
||||
const ms = await measureTcpRtt(svc.host, svc.port, 6000);
|
||||
return { id: svc.id, name: svc.name, host: svc.host, ms };
|
||||
})
|
||||
);
|
||||
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);
|
||||
const byId = await refreshPingServicesCache();
|
||||
return res.json(byId);
|
||||
} catch (e) {
|
||||
console.error('ping-services error', e);
|
||||
const fallback = {};
|
||||
@@ -695,5 +729,6 @@ module.exports = {
|
||||
postUiSettings,
|
||||
getPingServicesList,
|
||||
getPingServices,
|
||||
refreshPingServicesCache,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
const { sendError, sendOk } = require('../middleware/errorHandler');
|
||||
const { readS3TextObject, writeS3JsonObject } = require('../services/s3Service');
|
||||
const networkMapScheduler = require('../services/networkMapScheduler');
|
||||
const pingServicesScheduler = require('../services/pingServicesScheduler');
|
||||
|
||||
const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json';
|
||||
const NETWORK_MAP_CACHE_KEY = 'network-map-cache/latest.json';
|
||||
@@ -17,6 +18,11 @@ const SCHEDULER_KEYS = [
|
||||
'networkMapSchedulerLogMaxLines',
|
||||
];
|
||||
|
||||
const PING_SERVICES_SCHEDULER_KEYS = [
|
||||
'pingServicesSchedulerEnabled',
|
||||
'pingServicesSchedulerIntervalMinutes',
|
||||
];
|
||||
|
||||
async function readUiSettings() {
|
||||
try {
|
||||
const data = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||
@@ -124,10 +130,61 @@ async function getNetworkMapCache(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// === Ping services scheduler ===
|
||||
|
||||
/** GET /api/scheduler/ping-services/settings */
|
||||
async function getPingServicesSchedulerSettings(req, res) {
|
||||
try {
|
||||
const ui = await readUiSettings();
|
||||
const settings = pingServicesScheduler.getSettingsFromUiSettings(ui);
|
||||
const nextRunAt = pingServicesScheduler.getNextRunAt(settings);
|
||||
return res.json({ ...settings, nextRunAt: nextRunAt || undefined });
|
||||
} catch (e) {
|
||||
console.error('[scheduler] getPingServicesSettings', e);
|
||||
return sendError(res, 500, 'Не удалось прочитать настройки', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** PATCH /api/scheduler/ping-services/settings */
|
||||
async function patchPingServicesSchedulerSettings(req, res) {
|
||||
try {
|
||||
const body = req.body || {};
|
||||
const current = await readUiSettings();
|
||||
for (const key of PING_SERVICES_SCHEDULER_KEYS) {
|
||||
if (Object.prototype.hasOwnProperty.call(body, key)) {
|
||||
current[key] = body[key];
|
||||
}
|
||||
}
|
||||
await writeS3JsonObject(UI_SETTINGS_KEY, current);
|
||||
const settings = pingServicesScheduler.getSettingsFromUiSettings(current);
|
||||
return res.json(settings);
|
||||
} catch (e) {
|
||||
console.error('[scheduler] patchPingServicesSettings', e);
|
||||
return sendError(res, 500, 'Не удалось сохранить настройки', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/scheduler/ping-services/run-now */
|
||||
async function runPingServicesSchedulerNow(req, res) {
|
||||
try {
|
||||
const { refreshPingServicesCache } = require('./miscRoutes');
|
||||
refreshPingServicesCache().catch((err) => {
|
||||
console.error('[scheduler] ping-services run-now failed', err);
|
||||
});
|
||||
return sendOk(res, { message: 'Обновление кеша пинг-сервисов запущено в фоне' });
|
||||
} catch (e) {
|
||||
console.error('[scheduler] ping-services run-now', e);
|
||||
return sendError(res, 500, 'Не удалось запустить', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNetworkMapSchedulerSettings,
|
||||
patchNetworkMapSchedulerSettings,
|
||||
getNetworkMapSchedulerLogs,
|
||||
runNetworkMapSchedulerNow,
|
||||
getNetworkMapCache,
|
||||
getPingServicesSchedulerSettings,
|
||||
patchPingServicesSchedulerSettings,
|
||||
runPingServicesSchedulerNow,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user