feat(UptimeMonitor): add uptime monitor scheduler settings and endpoints for improved monitoring functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m47s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m47s
This commit is contained in:
@@ -6,6 +6,7 @@ const { sendError, sendOk } = require('../middleware/errorHandler');
|
||||
const { readS3TextObject, writeS3JsonObject } = require('../services/s3Service');
|
||||
const networkMapScheduler = require('../services/networkMapScheduler');
|
||||
const pingServicesScheduler = require('../services/pingServicesScheduler');
|
||||
const uptimeMonitorScheduler = require('../services/uptimeMonitorScheduler');
|
||||
|
||||
const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json';
|
||||
const NETWORK_MAP_CACHE_KEY = 'network-map-cache/latest.json';
|
||||
@@ -23,6 +24,11 @@ const PING_SERVICES_SCHEDULER_KEYS = [
|
||||
'pingServicesSchedulerIntervalMinutes',
|
||||
];
|
||||
|
||||
const UPTIME_MONITOR_SCHEDULER_KEYS = [
|
||||
'uptimeMonitorSchedulerEnabled',
|
||||
'uptimeMonitorSchedulerIntervalMinutes',
|
||||
];
|
||||
|
||||
async function readUiSettings() {
|
||||
try {
|
||||
const data = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||
@@ -178,6 +184,66 @@ async function runPingServicesSchedulerNow(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// === Uptime monitor scheduler ===
|
||||
|
||||
/** GET /api/scheduler/uptime-monitor/settings */
|
||||
async function getUptimeMonitorSchedulerSettings(req, res) {
|
||||
try {
|
||||
const ui = await readUiSettings();
|
||||
const settings = uptimeMonitorScheduler.getSettingsFromUiSettings(ui);
|
||||
const nextRunAt = uptimeMonitorScheduler.getNextRunAt(settings);
|
||||
return res.json({ ...settings, nextRunAt: nextRunAt || undefined });
|
||||
} catch (e) {
|
||||
console.error('[scheduler] getUptimeMonitorSettings', e);
|
||||
return sendError(res, 500, 'Не удалось прочитать настройки', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** PATCH /api/scheduler/uptime-monitor/settings */
|
||||
async function patchUptimeMonitorSchedulerSettings(req, res) {
|
||||
try {
|
||||
const body = req.body || {};
|
||||
const current = await readUiSettings();
|
||||
for (const key of UPTIME_MONITOR_SCHEDULER_KEYS) {
|
||||
if (Object.prototype.hasOwnProperty.call(body, key)) {
|
||||
current[key] = body[key];
|
||||
}
|
||||
}
|
||||
await writeS3JsonObject(UI_SETTINGS_KEY, current);
|
||||
const settings = uptimeMonitorScheduler.getSettingsFromUiSettings(current);
|
||||
return res.json(settings);
|
||||
} catch (e) {
|
||||
console.error('[scheduler] patchUptimeMonitorSettings', e);
|
||||
return sendError(res, 500, 'Не удалось сохранить настройки', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/scheduler/uptime-monitor/run-now */
|
||||
async function runUptimeMonitorSchedulerNow(req, res) {
|
||||
try {
|
||||
const port = Number(process.env.PORT) || 3001;
|
||||
const baseUrl = `http://127.0.0.1:${port}`;
|
||||
const resServers = await fetch(`${baseUrl}/api/servers`).catch(() => null);
|
||||
const data = resServers?.ok ? await resServers.json() : [];
|
||||
const servers = Array.isArray(data) ? data : [];
|
||||
const jumphosts = servers.filter(
|
||||
(s) => s && ['jumphost', 'home'].includes(String(s.type || '').toLowerCase())
|
||||
);
|
||||
const serverIds = jumphosts.map((s) => s.id || s.dns || s.ip).filter(Boolean);
|
||||
for (const serverId of serverIds) {
|
||||
fetch(`${baseUrl}/api/uptime/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ serverId }),
|
||||
}).catch((err) => console.warn('[scheduler] uptime check', serverId, err?.message));
|
||||
}
|
||||
return sendOk(res, { message: 'Проверки Uptime Monitor запущены в фоне' });
|
||||
} catch (e) {
|
||||
console.error('[scheduler] uptime-monitor run-now', e);
|
||||
return sendError(res, 500, 'Не удалось запустить', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNetworkMapSchedulerSettings,
|
||||
patchNetworkMapSchedulerSettings,
|
||||
@@ -187,4 +253,7 @@ module.exports = {
|
||||
getPingServicesSchedulerSettings,
|
||||
patchPingServicesSchedulerSettings,
|
||||
runPingServicesSchedulerNow,
|
||||
getUptimeMonitorSchedulerSettings,
|
||||
patchUptimeMonitorSchedulerSettings,
|
||||
runUptimeMonitorSchedulerNow,
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ const trafficRoutes = require('./routes/trafficRoutes');
|
||||
const { initMikrotikBackupScheduler } = require('./services/mikrotikBackupScheduler');
|
||||
const { initNetworkMapScheduler } = require('./services/networkMapScheduler');
|
||||
const { initPingServicesScheduler } = require('./services/pingServicesScheduler');
|
||||
const { initUptimeMonitorScheduler } = require('./services/uptimeMonitorScheduler');
|
||||
const schedulerRoutes = require('./routes/schedulerRoutes');
|
||||
|
||||
const app = express();
|
||||
@@ -499,6 +500,11 @@ app.get('/api/scheduler/ping-services/settings', schedulerRoutes.getPingServices
|
||||
app.patch('/api/scheduler/ping-services/settings', writeLimiter, schedulerRoutes.patchPingServicesSchedulerSettings);
|
||||
app.post('/api/scheduler/ping-services/run-now', writeLimiter, schedulerRoutes.runPingServicesSchedulerNow);
|
||||
|
||||
// === SCHEDULER (Uptime Monitor: проверки по расписанию, по умолчанию каждые 2 мин) ===
|
||||
app.get('/api/scheduler/uptime-monitor/settings', schedulerRoutes.getUptimeMonitorSchedulerSettings);
|
||||
app.patch('/api/scheduler/uptime-monitor/settings', writeLimiter, schedulerRoutes.patchUptimeMonitorSchedulerSettings);
|
||||
app.post('/api/scheduler/uptime-monitor/run-now', writeLimiter, schedulerRoutes.runUptimeMonitorSchedulerNow);
|
||||
|
||||
// === MIKROTIK VALIDATION ===
|
||||
app.post('/api/mikrotik/validate', async (req, res) => {
|
||||
const { config } = req.body;
|
||||
@@ -541,5 +547,10 @@ app.listen(port, () => {
|
||||
} catch (e) {
|
||||
logger.error({ component: 'ping-services-scheduler', err: e && e.message }, 'Failed to start ping services scheduler');
|
||||
}
|
||||
try {
|
||||
initUptimeMonitorScheduler(logger, port);
|
||||
} catch (e) {
|
||||
logger.error({ component: 'uptime-monitor-scheduler', err: e && e.message }, 'Failed to start uptime monitor scheduler');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Планировщик проверок Uptime Monitor.
|
||||
* Периодически вызывает POST /api/uptime/check для каждого jumphost/home и обновляет кеш в S3.
|
||||
* После перезапуска контейнера кеш обновляется по расписанию без необходимости открывать страницу.
|
||||
*/
|
||||
|
||||
const { readS3TextObject } = require('./s3Service');
|
||||
|
||||
const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json';
|
||||
|
||||
const DEFAULT_INTERVAL_MINUTES = 2;
|
||||
const PARALLEL_CHECKS = 2;
|
||||
|
||||
let schedulerTimer = null;
|
||||
let lastScheduledRunAt = null;
|
||||
|
||||
function getSettingsFromUiSettings(uiSettings) {
|
||||
return {
|
||||
enabled: uiSettings.uptimeMonitorSchedulerEnabled !== false,
|
||||
intervalMinutes: Math.max(1, Math.min(1440, parseInt(uiSettings.uptimeMonitorSchedulerIntervalMinutes, 10) || DEFAULT_INTERVAL_MINUTES)),
|
||||
};
|
||||
}
|
||||
|
||||
function getNextRunAt(settings) {
|
||||
if (!settings || !settings.enabled) return null;
|
||||
const intervalMs = (settings.intervalMinutes || DEFAULT_INTERVAL_MINUTES) * 60 * 1000;
|
||||
const from = lastScheduledRunAt || Date.now();
|
||||
return from + intervalMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить планировщик Uptime Monitor.
|
||||
* @param {object} logger - pino logger
|
||||
* @param {number} port - порт приложения для вызова API
|
||||
*/
|
||||
function initUptimeMonitorScheduler(logger, port) {
|
||||
const log = logger || console;
|
||||
const baseUrl = `http://127.0.0.1:${port}`;
|
||||
|
||||
async function runChecks(serverIds) {
|
||||
const runOne = async (serverId) => {
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/uptime/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ serverId }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
log.warn({ component: 'uptime-monitor-scheduler', serverId, status: res.status }, 'Uptime check failed');
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn({ component: 'uptime-monitor-scheduler', serverId, err: err?.message }, 'Uptime check error');
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < serverIds.length; i += PARALLEL_CHECKS) {
|
||||
const chunk = serverIds.slice(i, i + PARALLEL_CHECKS);
|
||||
await Promise.all(chunk.map(runOne));
|
||||
}
|
||||
}
|
||||
|
||||
async function tick() {
|
||||
lastScheduledRunAt = Date.now();
|
||||
let uiSettings;
|
||||
try {
|
||||
const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||
uiSettings = JSON.parse(raw?.body || '{}') || {};
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
const settings = getSettingsFromUiSettings(uiSettings);
|
||||
if (!settings.enabled) return;
|
||||
|
||||
let servers = [];
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/servers`);
|
||||
const data = res.ok ? await res.json() : [];
|
||||
servers = Array.isArray(data) ? data : [];
|
||||
} catch (err) {
|
||||
log.error({ component: 'uptime-monitor-scheduler', err: err?.message }, 'Failed to fetch servers');
|
||||
return;
|
||||
}
|
||||
|
||||
const jumphosts = servers.filter(
|
||||
(s) => s && ['jumphost', 'home'].includes(String(s.type || '').toLowerCase())
|
||||
);
|
||||
const serverIds = jumphosts
|
||||
.map((s) => s.id || s.dns || s.ip)
|
||||
.filter(Boolean);
|
||||
|
||||
if (serverIds.length === 0) {
|
||||
log.info({ component: 'uptime-monitor-scheduler' }, 'No jumphost/home servers to check');
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(
|
||||
{ component: 'uptime-monitor-scheduler', count: serverIds.length },
|
||||
'Running uptime monitor checks for %d server(s)',
|
||||
serverIds.length
|
||||
);
|
||||
await runChecks(serverIds);
|
||||
}
|
||||
|
||||
async function schedule() {
|
||||
let uiSettings;
|
||||
try {
|
||||
const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||
uiSettings = JSON.parse(raw?.body || '{}') || {};
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
const settings = getSettingsFromUiSettings(uiSettings);
|
||||
if (!settings.enabled) {
|
||||
log.info({ component: 'uptime-monitor-scheduler' }, 'Uptime monitor scheduler disabled in settings');
|
||||
return;
|
||||
}
|
||||
const intervalMs = settings.intervalMinutes * 60 * 1000;
|
||||
log.info(
|
||||
{ component: 'uptime-monitor-scheduler', intervalMinutes: settings.intervalMinutes },
|
||||
'Uptime monitor scheduler started (first run in 20 s, then every %d min)',
|
||||
settings.intervalMinutes
|
||||
);
|
||||
schedulerTimer = setInterval(tick, intervalMs);
|
||||
setTimeout(() => tick(), 20 * 1000);
|
||||
}
|
||||
|
||||
schedule();
|
||||
}
|
||||
|
||||
function stopUptimeMonitorScheduler() {
|
||||
if (schedulerTimer) {
|
||||
clearInterval(schedulerTimer);
|
||||
schedulerTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initUptimeMonitorScheduler,
|
||||
stopUptimeMonitorScheduler,
|
||||
getSettingsFromUiSettings,
|
||||
getNextRunAt,
|
||||
};
|
||||
Reference in New Issue
Block a user