feat(NetworkMapScheduler): integrate network map scheduler functionality with API routes and frontend components for enhanced network monitoring
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m53s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m53s
This commit is contained in:
@@ -1155,4 +1155,5 @@ module.exports = {
|
||||
pingViaInterface,
|
||||
runPingViaRouter,
|
||||
speedTestViaTunnel,
|
||||
loadNetworkConfig,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Роуты планировщика карты сети: настройки, логи, ручной запуск, кеш.
|
||||
*/
|
||||
|
||||
const { sendError, sendOk } = require('../middleware/errorHandler');
|
||||
const { readS3TextObject, writeS3JsonObject } = require('../services/s3Service');
|
||||
const networkMapScheduler = require('../services/networkMapScheduler');
|
||||
|
||||
const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json';
|
||||
const NETWORK_MAP_CACHE_KEY = 'network-map-cache/latest.json';
|
||||
|
||||
const SCHEDULER_KEYS = [
|
||||
'networkMapSchedulerEnabled',
|
||||
'networkMapSchedulerIntervalMinutes',
|
||||
'networkMapSchedulerRunPing',
|
||||
'networkMapSchedulerRunSpeedTest',
|
||||
'networkMapSchedulerLogMaxLines',
|
||||
];
|
||||
|
||||
async function readUiSettings() {
|
||||
try {
|
||||
const data = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||
const parsed = JSON.parse(data?.body || '{}');
|
||||
return parsed && typeof parsed === 'object' ? parsed : {};
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /api/scheduler/network-map/settings */
|
||||
async function getNetworkMapSchedulerSettings(req, res) {
|
||||
try {
|
||||
const ui = await readUiSettings();
|
||||
const settings = networkMapScheduler.getSettingsFromUiSettings(ui);
|
||||
return res.json(settings);
|
||||
} catch (e) {
|
||||
console.error('[scheduler] getSettings', e);
|
||||
return sendError(res, 500, 'Не удалось прочитать настройки', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** PATCH /api/scheduler/network-map/settings — обновить только ключи планировщика в ui-settings */
|
||||
async function patchNetworkMapSchedulerSettings(req, res) {
|
||||
try {
|
||||
const body = req.body || {};
|
||||
const current = await readUiSettings();
|
||||
for (const key of SCHEDULER_KEYS) {
|
||||
if (Object.prototype.hasOwnProperty.call(body, key)) {
|
||||
current[key] = body[key];
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(body, 'networkMapSchedulerLogMaxLines')) {
|
||||
networkMapScheduler.setLogMaxLines(body.networkMapSchedulerLogMaxLines);
|
||||
}
|
||||
await writeS3JsonObject(UI_SETTINGS_KEY, current);
|
||||
const settings = networkMapScheduler.getSettingsFromUiSettings(current);
|
||||
return res.json(settings);
|
||||
} catch (e) {
|
||||
console.error('[scheduler] patchSettings', e);
|
||||
return sendError(res, 500, 'Не удалось сохранить настройки', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /api/scheduler/network-map/logs */
|
||||
async function getNetworkMapSchedulerLogs(req, res) {
|
||||
try {
|
||||
const lines = networkMapScheduler.getLogs();
|
||||
return res.json({ lines });
|
||||
} catch (e) {
|
||||
console.error('[scheduler] getLogs', e);
|
||||
return sendError(res, 500, 'Не удалось прочитать логи', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/scheduler/network-map/run-now — один запуск по текущим настройкам */
|
||||
async function runNetworkMapSchedulerNow(req, res) {
|
||||
try {
|
||||
const port = Number(process.env.PORT) || 3001;
|
||||
const baseUrl = `http://127.0.0.1:${port}`;
|
||||
const ui = await readUiSettings();
|
||||
const settings = networkMapScheduler.getSettingsFromUiSettings(ui);
|
||||
|
||||
networkMapScheduler.runJob({
|
||||
baseUrl,
|
||||
runPing: settings.runPing,
|
||||
runSpeedTest: settings.runSpeedTest,
|
||||
}).catch((err) => {
|
||||
console.error('[scheduler] run-now failed', err);
|
||||
});
|
||||
|
||||
return sendOk(res, { message: 'Запуск выполняется в фоне' });
|
||||
} catch (e) {
|
||||
console.error('[scheduler] run-now', e);
|
||||
return sendError(res, 500, 'Не удалось запустить', 'E_SCHEDULER');
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /api/network-map-cache — кеш пингов и скоростей для карты сети */
|
||||
async function getNetworkMapCache(req, res) {
|
||||
try {
|
||||
const data = await readS3TextObject(NETWORK_MAP_CACHE_KEY).catch(() => null);
|
||||
if (!data?.body) {
|
||||
return res.json({ pingMap: {}, speedMap: {}, updatedAt: null });
|
||||
}
|
||||
const parsed = JSON.parse(data.body);
|
||||
return res.json({
|
||||
pingMap: parsed.pingMap && typeof parsed.pingMap === 'object' ? parsed.pingMap : {},
|
||||
speedMap: parsed.speedMap && typeof parsed.speedMap === 'object' ? parsed.speedMap : {},
|
||||
updatedAt: typeof parsed.updatedAt === 'number' ? parsed.updatedAt : null,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[network-map-cache] get', e);
|
||||
return res.json({ pingMap: {}, speedMap: {}, updatedAt: null });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNetworkMapSchedulerSettings,
|
||||
patchNetworkMapSchedulerSettings,
|
||||
getNetworkMapSchedulerLogs,
|
||||
runNetworkMapSchedulerNow,
|
||||
getNetworkMapCache,
|
||||
};
|
||||
<|tool▁calls▁begin|><|tool▁call▁begin|>
|
||||
StrReplace
|
||||
+21
-7
@@ -31,6 +31,8 @@ const mikrotikConfigRoutes = require('./routes/mikrotikConfigRoutes');
|
||||
const mikrotikBackupRoutes = require('./routes/mikrotikBackupRoutes');
|
||||
const trafficRoutes = require('./routes/trafficRoutes');
|
||||
const { initMikrotikBackupScheduler } = require('./services/mikrotikBackupScheduler');
|
||||
const { initNetworkMapScheduler } = require('./services/networkMapScheduler');
|
||||
const schedulerRoutes = require('./routes/schedulerRoutes');
|
||||
|
||||
const app = express();
|
||||
const port = Number(process.env.PORT) || 3001;
|
||||
@@ -476,6 +478,15 @@ app.get('/api/mikrotik/backups/item', mikrotikBackupRoutes.getBackup);
|
||||
app.post('/api/mikrotik/backups/diff', mikrotikBackupRoutes.diffBackups);
|
||||
app.post('/api/mikrotik/backups/run', writeLimiter, mikrotikBackupRoutes.runBackupNow);
|
||||
|
||||
// === NETWORK MAP CACHE (для быстрой загрузки карты сети) ===
|
||||
app.get('/api/network-map-cache', schedulerRoutes.getNetworkMapCache);
|
||||
|
||||
// === SCHEDULER (карта сети: пинг и скорость по расписанию) ===
|
||||
app.get('/api/scheduler/network-map/settings', schedulerRoutes.getNetworkMapSchedulerSettings);
|
||||
app.patch('/api/scheduler/network-map/settings', writeLimiter, schedulerRoutes.patchNetworkMapSchedulerSettings);
|
||||
app.get('/api/scheduler/network-map/logs', schedulerRoutes.getNetworkMapSchedulerLogs);
|
||||
app.post('/api/scheduler/network-map/run-now', writeLimiter, schedulerRoutes.runNetworkMapSchedulerNow);
|
||||
|
||||
// === MIKROTIK VALIDATION ===
|
||||
app.post('/api/mikrotik/validate', async (req, res) => {
|
||||
const { config } = req.body;
|
||||
@@ -503,12 +514,15 @@ app.use(errorHandler);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
try {
|
||||
initMikrotikBackupScheduler(logger);
|
||||
} catch (e) {
|
||||
logger.error({ component: 'mikrotik-backup', err: e && e.message }, 'Failed to start backup scheduler');
|
||||
}
|
||||
try {
|
||||
initNetworkMapScheduler(logger, port);
|
||||
} catch (e) {
|
||||
logger.error({ component: 'network-map-scheduler', err: e && e.message }, 'Failed to start network map scheduler');
|
||||
}
|
||||
});
|
||||
|
||||
// Запускаем планировщик автоматического бэкапа MikroTik (если включен через ENV)
|
||||
try {
|
||||
initMikrotikBackupScheduler(logger);
|
||||
} catch (e) {
|
||||
logger.error({ component: 'mikrotik-backup', err: e && e.message }, 'Failed to start backup scheduler');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
/**
|
||||
* Планировщик пингов и замеров скорости для карты сети.
|
||||
* Периодически вызывает пинг и speed-test по связям из network-config,
|
||||
* сохраняет результаты в S3 (кеш по рёбрам + агрегат network-map-cache/latest.json).
|
||||
* Логи пишет в кольцевой буфер для просмотра в UI.
|
||||
*/
|
||||
|
||||
const { readServersFromS3 } = require('../routes/serversRoutes');
|
||||
const { loadNetworkConfig } = require('../routes/mikrotikConfigRoutes');
|
||||
const { readS3TextObject, writeS3JsonObject } = require('./s3Service');
|
||||
|
||||
const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json';
|
||||
const NETWORK_MAP_CACHE_KEY = 'network-map-cache/latest.json';
|
||||
|
||||
const DEFAULT_INTERVAL_MINUTES = 5;
|
||||
const DEFAULT_LOG_MAX_LINES = 500;
|
||||
|
||||
/** Кольцевой буфер логов */
|
||||
class LogBuffer {
|
||||
constructor(maxLines = 500) {
|
||||
this.maxLines = Math.max(100, maxLines);
|
||||
this.lines = [];
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
push(line) {
|
||||
const ts = new Date().toISOString();
|
||||
const entry = `${ts} ${line}`;
|
||||
if (this.lines.length < this.maxLines) {
|
||||
this.lines.push(entry);
|
||||
} else {
|
||||
this.lines[this.index] = entry;
|
||||
this.index = (this.index + 1) % this.maxLines;
|
||||
}
|
||||
}
|
||||
|
||||
getLines() {
|
||||
if (this.lines.length < this.maxLines) return [...this.lines];
|
||||
return [...this.lines.slice(this.index), ...this.lines.slice(0, this.index)];
|
||||
}
|
||||
|
||||
setMaxLines(n) {
|
||||
this.maxLines = Math.max(100, n);
|
||||
if (this.lines.length > this.maxLines) {
|
||||
this.lines = this.getLines().slice(-this.maxLines);
|
||||
this.index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logBuffer = new LogBuffer(DEFAULT_LOG_MAX_LINES);
|
||||
|
||||
async function loadUiSettings() {
|
||||
try {
|
||||
const data = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||
const parsed = JSON.parse(data?.body || '{}');
|
||||
return parsed && typeof parsed === 'object' ? parsed : {};
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/** Построить список связей как в NetworkMapDashboard (tunnelInterfaces → connections) */
|
||||
async function buildConnections() {
|
||||
const [serversList, config, uiSettings] = await Promise.all([
|
||||
readServersFromS3(),
|
||||
loadNetworkConfig(),
|
||||
loadUiSettings(),
|
||||
]);
|
||||
const servers = Array.isArray(serversList) ? serversList : [];
|
||||
const tunnelInterfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : [];
|
||||
|
||||
const getServer = (serverId) =>
|
||||
servers.find((s) => s.id === serverId || s.ip === serverId || s.dns === serverId);
|
||||
|
||||
const connList = [];
|
||||
for (const iface of tunnelInterfaces) {
|
||||
if (!iface.serverId || !iface.serverId2) continue;
|
||||
const s1 = getServer(iface.serverId);
|
||||
const s2 = getServer(iface.serverId2);
|
||||
if (!s1 || !s2 || s1.ip === s2.ip) continue;
|
||||
const s1Key = s1.id || s1.dns || s1.ip;
|
||||
const s2Key = s2.id || s2.dns || s2.ip;
|
||||
if (!s1Key || !s2Key) continue;
|
||||
const isJumphost1 = s1 && ['jumphost', 'home'].includes(String(s1.type || '').toLowerCase());
|
||||
const isJumphost2 = s2 && ['jumphost', 'home'].includes(String(s2.type || '').toLowerCase());
|
||||
connList.push({
|
||||
from: s1.ip,
|
||||
to: s2.ip,
|
||||
fromKey: s1Key,
|
||||
toKey: s2Key,
|
||||
internalFromTo: iface.remoteIp || null,
|
||||
internalToFrom: iface.localIp || null,
|
||||
speedTestServerId: isJumphost1 ? s1Key : isJumphost2 ? s2Key : null,
|
||||
interfaceName: iface.name || null,
|
||||
});
|
||||
}
|
||||
return { connections: connList, servers, uiSettings };
|
||||
}
|
||||
|
||||
function edgePingKey(a, b) {
|
||||
return [String(a), String(b)].sort().join(':');
|
||||
}
|
||||
|
||||
function speedKey(key1, key2) {
|
||||
return [String(key1), String(key2)].sort().join(':');
|
||||
}
|
||||
|
||||
/** Параллельно с лимитом */
|
||||
async function runWithLimit(tasks, limit = 4) {
|
||||
const results = [];
|
||||
let index = 0;
|
||||
async function runNext() {
|
||||
const i = index++;
|
||||
if (i >= tasks.length) return;
|
||||
try {
|
||||
results[i] = { value: await tasks[i]() };
|
||||
} catch (err) {
|
||||
results[i] = { error: err };
|
||||
}
|
||||
await runNext();
|
||||
}
|
||||
const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => runNext());
|
||||
await Promise.all(workers);
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Один тест на ноду в момент времени */
|
||||
async function runWithOnePerKey(tasks, getKey) {
|
||||
const n = tasks.length;
|
||||
const keyToIndices = new Map();
|
||||
for (let i = 0; i < n; i++) {
|
||||
const k = String(getKey(i));
|
||||
if (!keyToIndices.has(k)) keyToIndices.set(k, []);
|
||||
keyToIndices.get(k).push(i);
|
||||
}
|
||||
const results = [];
|
||||
for (let i = 0; i < n; i++) results[i] = null;
|
||||
const keys = Array.from(keyToIndices.keys());
|
||||
let round = 0;
|
||||
while (true) {
|
||||
const batch = [];
|
||||
for (const k of keys) {
|
||||
const indices = keyToIndices.get(k);
|
||||
if (round < indices.length) batch.push(indices[round]);
|
||||
}
|
||||
if (batch.length === 0) break;
|
||||
await Promise.all(
|
||||
batch.map(async (i) => {
|
||||
try {
|
||||
results[i] = { value: await tasks[i]() };
|
||||
} catch (err) {
|
||||
results[i] = { error: err };
|
||||
}
|
||||
})
|
||||
);
|
||||
round++;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить одну итерацию: пинг и speed-test по связям, записать кеш и логи.
|
||||
* @param {object} opts - { baseUrl, runPing, runSpeedTest, log }
|
||||
*/
|
||||
async function runJob(opts) {
|
||||
const { baseUrl, runPing = true, runSpeedTest = true, log = (msg) => logBuffer.push(msg) } = opts;
|
||||
const pingMap = {};
|
||||
const speedMap = {};
|
||||
|
||||
try {
|
||||
const { connections, servers } = await buildConnections();
|
||||
if (connections.length === 0) {
|
||||
log('Нет связей в network-config (tunnelInterfaces).');
|
||||
await writeCache(pingMap, speedMap);
|
||||
return { pingMap, speedMap };
|
||||
}
|
||||
|
||||
log(`Связей: ${connections.length}. Пинг: ${runPing ? 'да' : 'нет'}, Скорость: ${runSpeedTest ? 'да' : 'нет'}`);
|
||||
|
||||
if (runPing) {
|
||||
const pingTasks = [];
|
||||
for (const c of connections) {
|
||||
const from = String(c.from);
|
||||
const to = String(c.to);
|
||||
if (!from || !to || from === to) continue;
|
||||
const srcFrom = servers.find((s) => s.ip === from);
|
||||
const srcTo = servers.find((s) => s.ip === to);
|
||||
const canPingFrom = srcFrom && ['jumphost', 'home'].includes(String(srcFrom.type || '').toLowerCase()) && c.internalFromTo;
|
||||
const canPingTo = srcTo && ['jumphost', 'home'].includes(String(srcTo.type || '').toLowerCase()) && c.internalToFrom;
|
||||
|
||||
if (canPingFrom) {
|
||||
pingTasks.push(async () => {
|
||||
const ekey = edgePingKey(from, to);
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/mikrotik/ping`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serverId: c.fromKey,
|
||||
target: c.internalFromTo,
|
||||
gatewayIp: c.internalFromTo,
|
||||
count: 3,
|
||||
}),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const ms = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
||||
pingMap[ekey] = ms;
|
||||
log(`Пинг ${from} → ${to}: ${ms != null ? ms + ' ms' : 'ошибка'}`);
|
||||
return ms;
|
||||
} catch (e) {
|
||||
pingMap[ekey] = null;
|
||||
log(`Пинг ${from} → ${to}: ошибка ${e?.message || e}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} else if (canPingTo) {
|
||||
pingTasks.push(async () => {
|
||||
const ekey = edgePingKey(from, to);
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/mikrotik/ping`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serverId: c.toKey,
|
||||
target: c.internalToFrom,
|
||||
gatewayIp: c.internalToFrom,
|
||||
count: 3,
|
||||
}),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const ms = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
||||
pingMap[ekey] = ms;
|
||||
log(`Пинг ${to} → ${from}: ${ms != null ? ms + ' ms' : 'ошибка'}`);
|
||||
return ms;
|
||||
} catch (e) {
|
||||
pingMap[ekey] = null;
|
||||
log(`Пинг ${to} → ${from}: ошибка ${e?.message || e}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
await runWithLimit(pingTasks, 4);
|
||||
}
|
||||
|
||||
if (runSpeedTest) {
|
||||
const withSpeed = connections.filter((c) => c.speedTestServerId && c.interfaceName);
|
||||
const speedTasks = withSpeed.map((c) => async () => {
|
||||
const key = speedKey(c.fromKey, c.toKey);
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/mikrotik/speed-test`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serverId: c.speedTestServerId,
|
||||
interfaceName: c.interfaceName,
|
||||
}),
|
||||
signal: (() => {
|
||||
const c = new AbortController();
|
||||
setTimeout(() => c.abort(), 120000);
|
||||
return c.signal;
|
||||
})(),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (data?.ok && (data.tcpDownloadBps != null || data.tcpUploadBps != null)) {
|
||||
speedMap[key] = {
|
||||
tcpDownloadBps: data.tcpDownloadBps,
|
||||
tcpUploadBps: data.tcpUploadBps,
|
||||
cached: data.cached === true,
|
||||
durationSeconds: data.durationSeconds,
|
||||
};
|
||||
const down = data.tcpDownloadBps != null ? (data.tcpDownloadBps / 1e6).toFixed(1) : '—';
|
||||
const up = data.tcpUploadBps != null ? (data.tcpUploadBps / 1e6).toFixed(1) : '—';
|
||||
log(`Скорость ${c.interfaceName}: ↓${down} ↑${up} Mbps`);
|
||||
return speedMap[key];
|
||||
}
|
||||
speedMap[key] = null;
|
||||
log(`Скорость ${c.interfaceName}: нет данных`);
|
||||
return null;
|
||||
} catch (e) {
|
||||
speedMap[key] = null;
|
||||
log(`Скорость ${c.interfaceName}: ошибка ${e?.message || e}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
await runWithOnePerKey(speedTasks, (i) => withSpeed[i].speedTestServerId);
|
||||
}
|
||||
|
||||
await writeCache(pingMap, speedMap);
|
||||
log('Кеш карты сети обновлён.');
|
||||
} catch (err) {
|
||||
log(`Ошибка: ${err?.message || err}`);
|
||||
}
|
||||
|
||||
return { pingMap, speedMap };
|
||||
}
|
||||
|
||||
async function writeCache(pingMap, speedMap) {
|
||||
try {
|
||||
await writeS3JsonObject(NETWORK_MAP_CACHE_KEY, {
|
||||
pingMap: pingMap || {},
|
||||
speedMap: speedMap || {},
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
} catch (e) {
|
||||
logBuffer.push(`Запись кеша в S3 не удалась: ${e?.message || e}`);
|
||||
}
|
||||
}
|
||||
|
||||
let schedulerTimer = null;
|
||||
|
||||
function getSettingsFromUiSettings(uiSettings) {
|
||||
return {
|
||||
enabled: Boolean(uiSettings.networkMapSchedulerEnabled),
|
||||
intervalMinutes: Math.max(1, Math.min(1440, parseInt(uiSettings.networkMapSchedulerIntervalMinutes, 10) || DEFAULT_INTERVAL_MINUTES)),
|
||||
runPing: uiSettings.networkMapSchedulerRunPing !== false,
|
||||
runSpeedTest: uiSettings.networkMapSchedulerRunSpeedTest !== false,
|
||||
logMaxLines: Math.max(100, Math.min(2000, parseInt(uiSettings.networkMapSchedulerLogMaxLines, 10) || DEFAULT_LOG_MAX_LINES)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить планировщик (setInterval).
|
||||
* @param {object} logger - pino logger
|
||||
* @param {number} port - порт сервера для вызова API
|
||||
*/
|
||||
function initNetworkMapScheduler(logger, port) {
|
||||
const log = logger || console;
|
||||
const baseUrl = `http://127.0.0.1:${port}`;
|
||||
|
||||
async function tick() {
|
||||
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;
|
||||
|
||||
logBuffer.setMaxLines(settings.logMaxLines);
|
||||
logBuffer.push('--- Запуск по расписанию ---');
|
||||
runJob({
|
||||
baseUrl,
|
||||
runPing: settings.runPing,
|
||||
runSpeedTest: settings.runSpeedTest,
|
||||
log: (msg) => {
|
||||
logBuffer.push(msg);
|
||||
log.info({ component: 'network-map-scheduler' }, msg);
|
||||
},
|
||||
}).catch((err) => {
|
||||
logBuffer.push(`Ошибка: ${err?.message || err}`);
|
||||
log.error({ component: 'network-map-scheduler', err: err?.message }, 'Scheduler run failed');
|
||||
});
|
||||
}
|
||||
|
||||
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: 'network-map-scheduler' }, 'Scheduler disabled in settings');
|
||||
return;
|
||||
}
|
||||
logBuffer.setMaxLines(settings.logMaxLines);
|
||||
const intervalMs = settings.intervalMinutes * 60 * 1000;
|
||||
log.info(
|
||||
{ component: 'network-map-scheduler', intervalMinutes: settings.intervalMinutes },
|
||||
'Network map scheduler started (first run in %d min)',
|
||||
settings.intervalMinutes
|
||||
);
|
||||
schedulerTimer = setInterval(tick, intervalMs);
|
||||
}
|
||||
|
||||
schedule();
|
||||
}
|
||||
|
||||
function stopNetworkMapScheduler() {
|
||||
if (schedulerTimer) {
|
||||
clearInterval(schedulerTimer);
|
||||
schedulerTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getLogs() {
|
||||
return logBuffer.getLines();
|
||||
}
|
||||
|
||||
function getLogMaxLines() {
|
||||
return logBuffer.maxLines;
|
||||
}
|
||||
|
||||
function setLogMaxLines(n) {
|
||||
const num = Math.max(100, Math.min(2000, parseInt(n, 10) || 500));
|
||||
logBuffer.setMaxLines(num);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runJob,
|
||||
initNetworkMapScheduler,
|
||||
stopNetworkMapScheduler,
|
||||
getLogs,
|
||||
getLogMaxLines,
|
||||
setLogMaxLines,
|
||||
getSettingsFromUiSettings,
|
||||
loadUiSettings: loadUiSettings,
|
||||
buildConnections,
|
||||
LogBuffer,
|
||||
};
|
||||
Reference in New Issue
Block a user