refactor(TrafficRoutes): remove deprecated interface statistics and speed test functions; streamline MikroTik credentials retrieval
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m27s

This commit is contained in:
2026-02-17 23:22:38 +07:00
parent 5c03ec7bd0
commit 99b346fd94
-311
View File
@@ -8,125 +8,6 @@ const { decrypt } = require('../utils/encryption');
const { readServersFromS3 } = require('./serversRoutes');
const { createRosClient } = require('../services/mikrotikApplyService');
/** Получить MikroTik credentials из сервера (jumphost или home) */
function getMikrotikCredentials(server) {
if (!server || (server.type !== 'jumphost' && server.type !== 'home')) return null;
const host = server.mikrotikHost || server.ip || server.dns;
if (!host) return null;
const port = server.mikrotikPort;
const restPort = port === 80 || port === 443 || port === 8443 ? port : 80;
const user = server.mikrotikUser || 'admin';
let password = '';
if (server.encryptedMikrotikPassword) {
try {
password = decrypt(server.encryptedMikrotikPassword);
} catch (_) {
return null;
}
}
return { host, port: restPort, user, password, secure: false };
}
/**
* GET /api/traffic/interface-stats
* Возвращает по каждому jumphost список интерфейсов с rx-byte, tx-byte (из RouterOS REST /interface).
*/
async function getInterfaceStats(req, res) {
try {
const servers = await readServersFromS3();
const jumphosts = (Array.isArray(servers) ? servers : []).filter(
(s) =>
s &&
(String(s.type || '').toLowerCase() === 'jumphost' ||
String(s.type || '').toLowerCase() === 'home')
);
const results = await Promise.all(
jumphosts.map(async (server) => {
const creds = getMikrotikCredentials(server);
if (!creds) {
return {
serverId: server.id,
name: server.name || server.dns || server.ip || server.id,
host: server.mikrotikHost || server.ip || server.dns,
error: 'MikroTik API не настроен или нет пароля',
interfaces: [],
};
}
try {
const client = createRosClient(creds);
const apiRes = await client.print('interface');
const raw = apiRes?.data;
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
const interfaces = list
.map((iface) => {
const rx =
iface['rx-byte'] != null
? parseInt(String(iface['rx-byte']), 10)
: 0;
const tx =
iface['tx-byte'] != null
? parseInt(String(iface['tx-byte']), 10)
: 0;
const total = rx + tx;
return {
name: iface.name != null ? String(iface.name) : '',
type: iface.type != null ? String(iface.type) : '',
rxBytes: Number.isNaN(rx) ? 0 : rx,
txBytes: Number.isNaN(tx) ? 0 : tx,
totalBytes: Number.isNaN(total) ? 0 : total,
};
})
.filter((i) => i.name && i.totalBytes >= 0);
return {
serverId: server.id,
name: server.name || server.dns || server.ip || server.id,
host: server.mikrotikHost || server.ip || server.dns,
interfaces,
};
} catch (err) {
const msg =
err.response?.data?.message ||
err.message ||
'Ошибка запроса к RouterOS';
return {
serverId: server.id,
name: server.name || server.dns || server.ip || server.id,
host: server.mikrotikHost || server.ip || server.dns,
error: msg,
interfaces: [],
};
}
})
);
return res.json({ jumphosts: results });
} catch (error) {
console.error('getInterfaceStats:', error);
return sendError(
res,
500,
error.message || 'Ошибка сбора статистики трафика',
'E_TRAFFIC'
);
}
}
module.exports = {
getInterfaceStats,
};
/**
* Роуты для сбора статистики трафика с MikroTik RouterOS (интерфейсы по jumphost).
* GET /api/traffic/interface-stats — статистика rx/tx по каждому интерфейсу для всех jumphost.
*/
const { sendError } = require('../middleware/errorHandler');
const { decrypt } = require('../utils/encryption');
const { readServersFromS3 } = require('./serversRoutes');
const { createRosClient } = require('../services/mikrotikApplyService');
const { readS3TextObject, writeS3JsonObject } = require('../services/s3Service');
/** Получить MikroTik credentials из сервера (jumphost или home) */
function getMikrotikCredentials(server) {
if (!server || (server.type !== 'jumphost' && server.type !== 'home')) return null;
@@ -242,198 +123,6 @@ async function getInterfaceStats(req, res) {
}
}
/**
* POST /api/traffic/interface-speed-test
* Замеряет среднюю скорость по выбранному интерфейсу RouterOS на jumphost/home.
*
* Body:
* - serverId (обязательный) — id/dns/ip сервера из servers.json
* - interfaceName (обязательный) — имя интерфейса в RouterOS (как в /interface)
* - durationSeconds? — время замера (по умолчанию из ui-settings.interfaceSpeedTestDurationSeconds или 10 сек)
*
* Использует счётчики rx-byte/tx-byte: берёт значение в начале и в конце интервала
* и делит дельту на время замера.
*/
async function measureInterfaceSpeed(req, res) {
try {
const { serverId, interfaceName } = req.body || {};
let { durationSeconds } = req.body || {};
if (!serverId || !interfaceName) {
return sendError(
res,
400,
'serverId и interfaceName обязательны',
'E_BAD_REQUEST'
);
}
const uiSettings = await loadUiSettings();
const defaultDuration = Math.max(
1,
Math.min(
600,
parseInt(uiSettings.interfaceSpeedTestDurationSeconds, 10) || 10
)
);
durationSeconds = Math.max(
1,
Math.min(
600,
parseInt(durationSeconds != null ? durationSeconds : defaultDuration, 10) ||
defaultDuration
)
);
const cacheMinutes = Math.max(
0,
parseInt(uiSettings.interfaceSpeedTestCacheMinutes, 10) || 0
);
const cacheKey =
cacheMinutes > 0
? interfaceSpeedCacheKey(serverId, interfaceName)
: null;
if (cacheKey) {
try {
const raw = await readS3TextObject(cacheKey).catch(() => null);
if (raw?.body) {
const cached = JSON.parse(raw.body);
const cachedAt =
typeof cached.cachedAt === 'number' ? cached.cachedAt : 0;
const ttlMs = cacheMinutes * 60 * 1000;
if (cachedAt && Date.now() - cachedAt < ttlMs) {
return res.json({ ...cached, cached: true });
}
}
} catch (_) {
// Если кеш не прочитался — просто продолжаем без него
}
}
const servers = await readServersFromS3();
const server =
(Array.isArray(servers) ? servers : []).find(
(s) => (s.id || s.dns || s.ip) === serverId
) || null;
if (
!server ||
(server.type !== 'jumphost' &&
String(server.type || '').toLowerCase() !== 'home')
) {
return sendError(
res,
400,
'Сервер (jumphost или home) не найден',
'E_NOT_FOUND'
);
}
const creds = getMikrotikCredentials(server);
if (!creds) {
return sendError(
res,
400,
'MikroTik API не настроен или нет пароля',
'E_CREDENTIALS'
);
}
const client = createRosClient(creds);
async function readOne() {
const apiRes = await client.print('interface');
const raw = apiRes?.data;
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
const iface = list.find(
(i) =>
i &&
i.name != null &&
String(i.name).trim() === String(interfaceName).trim()
);
if (!iface) {
return null;
}
const rx =
iface['rx-byte'] != null
? parseInt(String(iface['rx-byte']), 10)
: 0;
const tx =
iface['tx-byte'] != null
? parseInt(String(iface['tx-byte']), 10)
: 0;
return {
rxBytes: Number.isNaN(rx) ? 0 : rx,
txBytes: Number.isNaN(tx) ? 0 : tx,
};
}
const notFoundMessage = `Интерфейс "${interfaceName}" не найден на RouterOS (${serverId})`;
const start = await readOne();
if (!start) {
return sendError(res, 400, notFoundMessage, 'E_IFACE_NOT_FOUND');
}
await sleep(durationSeconds * 1000);
const end = await readOne();
if (!end) {
return sendError(res, 400, notFoundMessage, 'E_IFACE_NOT_FOUND');
}
const rxBytesDelta = Math.max(0, (end.rxBytes || 0) - (start.rxBytes || 0));
const txBytesDelta = Math.max(0, (end.txBytes || 0) - (start.txBytes || 0));
const totalBytesDelta = rxBytesDelta + txBytesDelta;
const duration = durationSeconds || 1;
const rxBps = rxBytesDelta * 8 / duration;
const txBps = txBytesDelta * 8 / duration;
const totalBps = totalBytesDelta * 8 / duration;
const payload = {
ok: true,
serverId,
interfaceName,
durationSeconds: duration,
rxBytesStart: start.rxBytes,
txBytesStart: start.txBytes,
rxBytesEnd: end.rxBytes,
txBytesEnd: end.txBytes,
rxBytesDelta,
txBytesDelta,
totalBytesDelta,
rxBps,
txBps,
totalBps,
cached: false,
};
if (cacheKey) {
writeS3JsonObject(cacheKey, {
...payload,
cached: false,
cachedAt: Date.now(),
}).catch((err) =>
console.warn(
'[traffic][interface-speed-test] cache write failed:',
err?.message || err
)
);
}
return res.json(payload);
} catch (error) {
console.error('measureInterfaceSpeed:', error);
return sendError(
res,
500,
error?.message || 'Ошибка замера скорости по интерфейсу',
'E_IFACE_SPEED'
);
}
}
module.exports = {
getInterfaceStats,
measureInterfaceSpeed,
};