/** * Роуты для сбора статистики ресурсов (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, }; } /** * GET /api/resources/stats * По каждому jumphost/home с MikroTik — запрос system/resource, ответ с группировкой по роутерам. */ async function getResourceStats(req, res) { try { 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 res.json({ routers: results }); } catch (error) { console.error('getResourceStats:', error); return sendError( res, 500, error.message || 'Ошибка сбора статистики ресурсов', 'E_RESOURCE_STATS' ); } } module.exports = { getResourceStats, };