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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m27s
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user