Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m49s
159 lines
5.6 KiB
JavaScript
159 lines
5.6 KiB
JavaScript
/**
|
||
* Роуты для сбора статистики ресурсов (RAM, CPU, HDD) с MikroTik RouterOS.
|
||
* GET /api/resources/stats — system/resource по каждому jumphost/home с учётами MikroTik.
|
||
*/
|
||
|
||
const { sendError } = require('../middleware/errorHandler');
|
||
const { decrypt } = require('../utils/encryption');
|
||
const { readServersFromS3 } = require('./serversRoutes');
|
||
const { createRosClient } = require('../services/mikrotikApplyService');
|
||
|
||
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 };
|
||
}
|
||
|
||
/**
|
||
* Парсинг system/resource в нормализованный объект для UI.
|
||
* MikroTik REST возвращает: total-memory, free-memory (bytes), cpu-load (%),
|
||
* total-hdd-space, free-hdd-space (bytes), uptime, version, board-name и др.
|
||
*/
|
||
function parseResource(raw) {
|
||
if (!raw || typeof raw !== 'object') return null;
|
||
const num = (v) => (v != null && v !== '' ? parseInt(String(v), 10) : null);
|
||
const totalMemory = num(raw['total-memory']);
|
||
const freeMemory = num(raw['free-memory']);
|
||
const usedMemory = totalMemory != null && freeMemory != null ? totalMemory - freeMemory : null;
|
||
const totalHdd = num(raw['total-hdd-space']);
|
||
const freeHdd = num(raw['free-hdd-space']);
|
||
const usedHdd = totalHdd != null && freeHdd != null ? totalHdd - freeHdd : null;
|
||
const cpuLoad = raw['cpu-load'] != null ? parseFloat(String(raw['cpu-load'])) : null;
|
||
|
||
return {
|
||
// RAM (bytes и проценты)
|
||
totalMemory: totalMemory,
|
||
freeMemory: freeMemory,
|
||
usedMemory: usedMemory,
|
||
memoryUsagePercent:
|
||
totalMemory != null && totalMemory > 0 && usedMemory != null
|
||
? Math.round((usedMemory / totalMemory) * 100)
|
||
: null,
|
||
// CPU (%)
|
||
cpuLoad: cpuLoad != null ? Math.round(cpuLoad) : null,
|
||
cpuCount: num(raw['cpu-count']) ?? null,
|
||
cpuFrequency: raw['cpu-frequency'] != null ? String(raw['cpu-frequency']) : null,
|
||
cpu: raw.cpu != null ? String(raw.cpu) : null,
|
||
// HDD (bytes и проценты)
|
||
totalHddSpace: totalHdd,
|
||
freeHddSpace: freeHdd,
|
||
usedHddSpace: usedHdd,
|
||
hddUsagePercent:
|
||
totalHdd != null && totalHdd > 0 && usedHdd != null
|
||
? Math.round((usedHdd / totalHdd) * 100)
|
||
: null,
|
||
// прочее
|
||
uptime: raw.uptime != null ? String(raw.uptime) : null,
|
||
version: raw.version != null ? String(raw.version) : null,
|
||
boardName: raw['board-name'] != null ? String(raw['board-name']) : null,
|
||
architectureName: raw['architecture-name'] != null ? String(raw['architecture-name']) : null,
|
||
platform: raw.platform != null ? String(raw.platform) : null,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Внутренняя функция: возвращает данные по ресурсам роутеров (без HTTP).
|
||
*/
|
||
async function getResourceStatsData() {
|
||
const servers = await readServersFromS3();
|
||
const routers = (Array.isArray(servers) ? servers : []).filter(
|
||
(s) =>
|
||
s &&
|
||
(String(s.type || '').toLowerCase() === 'jumphost' ||
|
||
String(s.type || '').toLowerCase() === 'home')
|
||
);
|
||
|
||
const results = await Promise.all(
|
||
routers.map(async (server) => {
|
||
const serverId = server.id || server.dns || server.ip;
|
||
const name = server.name || server.dns || server.ip || serverId;
|
||
const host = server.mikrotikHost || server.ip || server.dns;
|
||
|
||
const creds = getMikrotikCredentials(server);
|
||
if (!creds) {
|
||
return {
|
||
serverId,
|
||
name,
|
||
host,
|
||
groupKey: server.groupKey || null,
|
||
error: 'MikroTik API не настроен или нет пароля',
|
||
resource: null,
|
||
};
|
||
}
|
||
try {
|
||
const client = createRosClient(creds);
|
||
const apiRes = await client.print('system/resource');
|
||
const raw = apiRes?.data;
|
||
const single = Array.isArray(raw) ? raw[0] : raw;
|
||
const resource = parseResource(single);
|
||
return {
|
||
serverId,
|
||
name,
|
||
host,
|
||
groupKey: server.groupKey || null,
|
||
error: null,
|
||
resource,
|
||
};
|
||
} catch (err) {
|
||
const msg = err.response?.data?.message || err.message || 'Ошибка запроса к RouterOS';
|
||
return {
|
||
serverId,
|
||
name,
|
||
host,
|
||
groupKey: server.groupKey || null,
|
||
error: msg,
|
||
resource: null,
|
||
};
|
||
}
|
||
})
|
||
);
|
||
|
||
return { routers: results };
|
||
}
|
||
|
||
/**
|
||
* GET /api/resources/stats
|
||
* По каждому jumphost/home с MikroTik — запрос system/resource, ответ с группировкой по роутерам.
|
||
*/
|
||
async function getResourceStats(req, res) {
|
||
try {
|
||
const data = await getResourceStatsData();
|
||
return res.json(data);
|
||
} catch (error) {
|
||
console.error('getResourceStats:', error);
|
||
return sendError(
|
||
res,
|
||
500,
|
||
error.message || 'Ошибка сбора статистики ресурсов',
|
||
'E_RESOURCE_STATS'
|
||
);
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
getResourceStats,
|
||
getResourceStatsData,
|
||
};
|