feat(PingServices): implement UI settings for ping services configuration and enhance ping functionality through RouterOS
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m34s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m34s
This commit is contained in:
@@ -294,81 +294,21 @@ async function applyMikrotikConfig(req, res) {
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mikrotik/ping
|
||||
* Пингует внешний ресурс через указанный MikroTik (jumphost),
|
||||
* по возможности привязываясь к интерфейсу, соответствующему gateway.
|
||||
*
|
||||
* Body:
|
||||
* - serverId: ID/имя сервера из servers.json (обязательно)
|
||||
* - gatewayIp?: IP шлюза (remote IP туннеля или прямой gateway)
|
||||
* - interfaceName?: имя интерфейса RouterOS (если знаем его заранее)
|
||||
* - target?: адрес для ping (по умолчанию www.gstatic.com)
|
||||
* - count?: количество пакетов (по умолчанию 5)
|
||||
* Выполнить один пинг через RouterOS (без HTTP, без кеша).
|
||||
* @returns Promise<{ avgMs, minMs, maxMs, loss }>
|
||||
*/
|
||||
async function pingViaInterface(req, res) {
|
||||
try {
|
||||
let {
|
||||
serverId,
|
||||
gatewayIp,
|
||||
interfaceName,
|
||||
target,
|
||||
count = 5,
|
||||
} = req.body || {};
|
||||
|
||||
if (!serverId) {
|
||||
return sendError(res, 400, 'serverId is required', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
const uiSettings = await loadUiSettings();
|
||||
|
||||
// Цель пинга: из тела запроса, иначе из настроек (Домен для пинга), иначе по умолчанию
|
||||
if (target == null || String(target).trim() === '') {
|
||||
const pingDomain = (uiSettings.pingDomain && String(uiSettings.pingDomain).trim()) || '';
|
||||
target = pingDomain || 'www.gstatic.com';
|
||||
} else {
|
||||
target = String(target).trim();
|
||||
}
|
||||
|
||||
const pingCacheMinutes = Math.max(0, parseInt(uiSettings.pingCacheMinutes, 10) || 0);
|
||||
const cacheKey = pingCacheMinutes > 0 ? pingCacheKey(serverId, gatewayIp, target) : null;
|
||||
|
||||
if (cacheKey) {
|
||||
try {
|
||||
const data = await readS3TextObject(cacheKey).catch(() => null);
|
||||
if (data?.body) {
|
||||
const cached = JSON.parse(data.body);
|
||||
const cachedAt = typeof cached.cachedAt === 'number' ? cached.cachedAt : 0;
|
||||
const ttlMs = pingCacheMinutes * 60 * 1000;
|
||||
if (cachedAt && Date.now() - cachedAt < ttlMs) {
|
||||
return res.json({
|
||||
ok: true,
|
||||
avgMs: cached.avgMs,
|
||||
minMs: cached.minMs,
|
||||
maxMs: cached.maxMs,
|
||||
loss: cached.loss,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// кеш протух или невалиден — выполняем реальный пинг
|
||||
}
|
||||
}
|
||||
|
||||
const servers = await readServersFromS3();
|
||||
const server = servers.find((s) => (s.id || s.dns || s.ip) === serverId);
|
||||
if (!server || server.type !== 'jumphost') {
|
||||
return sendError(res, 400, 'Jumphost server not found', 'E_NOT_FOUND');
|
||||
}
|
||||
|
||||
const creds = getMikrotikCredentials(server);
|
||||
if (!creds) {
|
||||
return sendError(res, 400, 'MikroTik credentials not configured for this server', 'E_CREDENTIALS');
|
||||
}
|
||||
|
||||
const client = createRosClient(creds);
|
||||
|
||||
// Пытаемся найти интерфейс по gatewayIp и serverId, если interfaceName не передан
|
||||
let ifaceName = interfaceName || null;
|
||||
async function runPingViaRouter(serverId, gatewayIp, target, count = 5, interfaceName = null) {
|
||||
const servers = await readServersFromS3();
|
||||
const server = servers.find((s) => (s.id || s.dns || s.ip) === serverId);
|
||||
if (!server || server.type !== 'jumphost') {
|
||||
throw new Error('Jumphost server not found');
|
||||
}
|
||||
const creds = getMikrotikCredentials(server);
|
||||
if (!creds) {
|
||||
throw new Error('MikroTik credentials not configured for this server');
|
||||
}
|
||||
const client = createRosClient(creds);
|
||||
let ifaceName = interfaceName || null;
|
||||
|
||||
if (!ifaceName && gatewayIp) {
|
||||
const config = await loadNetworkConfig();
|
||||
@@ -520,18 +460,72 @@ async function pingViaInterface(req, res) {
|
||||
? Number(String(summary['packet-loss']).replace('%', ''))
|
||||
: null;
|
||||
|
||||
const result = { ok: true, avgMs, minMs, maxMs, loss };
|
||||
return { ok: true, avgMs, minMs, maxMs, loss };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mikrotik/ping
|
||||
* Пингует внешний ресурс через указанный MikroTik (jumphost),
|
||||
* по возможности привязываясь к интерфейсу, соответствующему gateway.
|
||||
*
|
||||
* Body:
|
||||
* - serverId: ID/имя сервера из servers.json (обязательно)
|
||||
* - gatewayIp?: IP шлюза (remote IP туннеля или прямой gateway)
|
||||
* - interfaceName?: имя интерфейса RouterOS (если знаем его заранее)
|
||||
* - target?: адрес для ping (по умолчанию www.gstatic.com)
|
||||
* - count?: количество пакетов (по умолчанию 5)
|
||||
*/
|
||||
async function pingViaInterface(req, res) {
|
||||
try {
|
||||
let {
|
||||
serverId,
|
||||
gatewayIp,
|
||||
interfaceName,
|
||||
target,
|
||||
count = 5,
|
||||
} = req.body || {};
|
||||
|
||||
if (!serverId) {
|
||||
return sendError(res, 400, 'serverId is required', 'E_BAD_REQUEST');
|
||||
}
|
||||
const uiSettings = await loadUiSettings();
|
||||
if (target == null || String(target).trim() === '') {
|
||||
const pingDomain = (uiSettings.pingDomain && String(uiSettings.pingDomain).trim()) || '';
|
||||
target = pingDomain || 'www.gstatic.com';
|
||||
} else {
|
||||
target = String(target).trim();
|
||||
}
|
||||
const pingCacheMinutes = Math.max(0, parseInt(uiSettings.pingCacheMinutes, 10) || 0);
|
||||
const cacheKey = pingCacheMinutes > 0 ? pingCacheKey(serverId, gatewayIp, target) : null;
|
||||
if (cacheKey) {
|
||||
try {
|
||||
const data = await readS3TextObject(cacheKey).catch(() => null);
|
||||
if (data?.body) {
|
||||
const cached = JSON.parse(data.body);
|
||||
const cachedAt = typeof cached.cachedAt === 'number' ? cached.cachedAt : 0;
|
||||
const ttlMs = pingCacheMinutes * 60 * 1000;
|
||||
if (cachedAt && Date.now() - cachedAt < ttlMs) {
|
||||
return res.json({
|
||||
ok: true,
|
||||
avgMs: cached.avgMs,
|
||||
minMs: cached.minMs,
|
||||
maxMs: cached.maxMs,
|
||||
loss: cached.loss,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
const result = await runPingViaRouter(serverId, gatewayIp, target, count, interfaceName);
|
||||
if (cacheKey) {
|
||||
writeS3JsonObject(cacheKey, {
|
||||
avgMs,
|
||||
minMs,
|
||||
maxMs,
|
||||
loss,
|
||||
avgMs: result.avgMs,
|
||||
minMs: result.minMs,
|
||||
maxMs: result.maxMs,
|
||||
loss: result.loss,
|
||||
cachedAt: Date.now(),
|
||||
}).catch((err) => console.warn('[mikrotik][pingViaInterface] cache write failed:', err?.message));
|
||||
}
|
||||
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
const msg = error.response?.data?.message || error.message || 'Ping failed';
|
||||
@@ -845,4 +839,5 @@ module.exports = {
|
||||
runScript,
|
||||
tracerouteViaGateway,
|
||||
pingViaInterface,
|
||||
runPingViaRouter,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user