feat(UptimeMonitor): implement uptime cache retrieval and update logic for improved monitoring performance
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m36s

This commit is contained in:
2026-02-22 18:36:02 +07:00
parent 64d9178784
commit 1be7e3bb22
4 changed files with 154 additions and 34 deletions
+75
View File
@@ -22,6 +22,7 @@ const NETWORK_CONFIG_KEY = 'network-config.json';
const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json';
const PING_CACHE_PREFIX = 'ping-cache/';
const SPEEDTEST_CACHE_PREFIX = 'speed-test-cache/';
const UPTIME_CACHE_KEY = 'uptime-monitor-cache/latest.json';
/** Загрузить UI-настройки из S3 (для pingDomain, pingCacheMinutes и др.) */
async function loadUiSettings() {
@@ -244,11 +245,64 @@ async function testMikrotikConnection(req, res) {
}
}
/** Записать результат проверки в кеш Uptime Monitor (S3). Не блокирует ответ. */
function updateUptimeCache(serverId, entry) {
const { ok, lastCheckTs, ms } = entry;
return readS3TextObject(UPTIME_CACHE_KEY)
.catch(() => ({ body: '{}' }))
.then((data) => {
let cache = { results: {}, updatedAt: null };
try {
const parsed = JSON.parse(data?.body || '{}');
if (parsed && typeof parsed === 'object' && parsed.results) cache = parsed;
} catch (_) {}
cache.results[serverId] = { ok, lastCheckTs, ms };
cache.updatedAt = Date.now();
return writeS3JsonObject(UPTIME_CACHE_KEY, cache);
})
.catch((e) => console.warn('[uptime] cache write failed:', e?.message));
}
/**
* GET /api/uptime/cache
* Возвращает закешированные результаты проверок, если кеш младше TTL из настроек.
*/
async function getUptimeCache(req, res) {
try {
const uiSettings = await loadUiSettings();
const ttlSec = Math.max(0, parseInt(uiSettings.uptimeMonitorCacheSeconds, 10) || 120);
if (ttlSec === 0) {
return res.json({ results: {}, updatedAt: null });
}
const data = await readS3TextObject(UPTIME_CACHE_KEY).catch(() => null);
if (!data?.body) {
return res.json({ results: {}, updatedAt: null });
}
let cache = { results: {}, updatedAt: null };
try {
const parsed = JSON.parse(data.body);
if (parsed && typeof parsed === 'object') cache = parsed;
} catch (_) {}
const now = Date.now();
if (!cache.updatedAt || now - cache.updatedAt > ttlSec * 1000) {
return res.json({ results: {}, updatedAt: null });
}
return res.json({
results: cache.results && typeof cache.results === 'object' ? cache.results : {},
updatedAt: cache.updatedAt,
});
} catch (e) {
console.error('[uptime] getUptimeCache', e);
return res.json({ results: {}, updatedAt: null });
}
}
/**
* POST /api/uptime/check
* Проверка доступности одного сервера (jumphost/home). Тип проверки берётся из ui-settings.uptimeMonitorCheckType.
* Body: { serverId }
* Returns: { ok: boolean, ms?: number }
* Результат пишется в кеш (S3) для отображения при заходе на страницу.
*/
async function uptimeCheck(req, res) {
const t0 = Date.now();
@@ -280,6 +334,8 @@ async function uptimeCheck(req, res) {
await client.print('system/resource');
ok = true;
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok, lastCheckTs, ms });
return res.json({ ok, ms });
}
@@ -293,6 +349,8 @@ async function uptimeCheck(req, res) {
);
if (!iface || (!iface.remoteIp && !iface.localIp)) {
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms, error: 'Нет туннеля с внутренним адресом для этого сервера' });
}
const target = iface.serverId === serverId || iface.serverId === server.ip || iface.serverId === server.dns
@@ -301,15 +359,21 @@ async function uptimeCheck(req, res) {
const gatewayIp = target;
if (!target) {
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms, error: 'Нет целевого адреса для пинга' });
}
try {
const result = await runPingViaRouter(serverId, gatewayIp, target, 3);
ms = Date.now() - t0;
ok = typeof result.avgMs === 'number';
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok, lastCheckTs, ms: ok ? result.avgMs : ms });
return res.json({ ok, ms: ok ? result.avgMs : ms });
} catch (err) {
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms, error: err?.message || String(err) });
}
}
@@ -324,11 +388,15 @@ async function uptimeCheck(req, res) {
const sourceServer = others[0];
if (!sourceServer) {
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms, error: 'Нет другого jumphost для внешнего пинга' });
}
const targetIp = server.ip || server.extIp;
if (!targetIp) {
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms, error: 'У сервера нет внешнего IP' });
}
const sourceId = sourceServer.id || sourceServer.dns || sourceServer.ip;
@@ -336,14 +404,20 @@ async function uptimeCheck(req, res) {
const result = await runPingViaRouter(sourceId, null, targetIp, 3);
ms = Date.now() - t0;
ok = typeof result.avgMs === 'number';
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok, lastCheckTs, ms: ok ? result.avgMs : ms });
return res.json({ ok, ms: ok ? result.avgMs : ms });
} catch (err) {
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms, error: err?.message || String(err) });
}
}
ms = Date.now() - t0;
const lastCheckTs = Date.now();
updateUptimeCache(serverId, { ok: false, lastCheckTs, ms });
return res.json({ ok: false, ms });
} catch (error) {
const ms = Date.now() - t0;
@@ -1462,4 +1536,5 @@ module.exports = {
getAddressLists,
applyAddressListSummary,
uptimeCheck,
getUptimeCache,
};
+1
View File
@@ -471,6 +471,7 @@ app.get('/api/mikrotik/address-lists', mikrotikConfigRoutes.getAddressLists);
app.post('/api/mikrotik/address-lists/apply-summary', writeLimiter, mikrotikConfigRoutes.applyAddressListSummary);
// === UPTIME MONITOR (проверка доступности: http / internal-ping / external-ping из настроек) ===
app.get('/api/uptime/cache', mikrotikConfigRoutes.getUptimeCache);
app.post('/api/uptime/check', writeLimiter, mikrotikConfigRoutes.uptimeCheck);
// === TRAFFIC STATS (MikroTik interfaces by jumphost) ===