/** * Роуты для сбора статистики трафика с 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'); /** Получить 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 }; } // UI-настройки (общие с остальным UI) const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json'; const INTERFACE_SPEED_CACHE_PREFIX = 'interface-speed-cache/'; async function loadUiSettings() { try { const data = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' })); const parsed = JSON.parse(data?.body || '{}'); return parsed && typeof parsed === 'object' ? parsed : {}; } catch (_) { return {}; } } function interfaceSpeedCacheKey(serverId, interfaceName) { const key = `${serverId || ''}|${interfaceName || ''}`; // Хэш не нужен: ключи короткие, но на всякий случай экранируем странные символы return ( INTERFACE_SPEED_CACHE_PREFIX + String(key) .trim() .replace(/[^a-zA-Z0-9_.:-]+/g, '_') + '.json' ); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * 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, };