const { Client } = require('ssh2'); const config = require('../config/config'); class SSHManager { constructor() { this.connections = new Map(); this.equipamentos = new Map(); // Equipamentos pré-configurados (exemplo) this.setupDefaultEquipments(); } setupDefaultEquipments() { // Adicionar equipamentos padrão se configurados if (config.ssh.defaultHost) { this.equipamentos.set('default', { name: 'Equipamento Padrão', host: config.ssh.defaultHost, username: config.ssh.defaultUser, password: config.ssh.defaultPassword, port: 22 }); } // Concentrador Juniper principal this.equipamentos.set('juniper', { name: 'Concentrador Juniper', host: '131.0.192.254', username: 'rodrigo', password: '@102030Rod', port: 22, type: 'juniper' }); // Aliases para o concentrador this.equipamentos.set('concentrador', { name: 'Concentrador Juniper', host: '131.0.192.254', username: 'rodrigo', password: '@102030Rod', port: 22, type: 'juniper' }); // Exemplos de equipamentos (você pode configurar mais) this.equipamentos.set('router1', { name: 'Router Principal', host: '192.168.1.1', username: 'admin', password: 'admin', port: 22 }); this.equipamentos.set('switch1', { name: 'Switch Core', host: '192.168.1.10', username: 'admin', password: 'admin', port: 22 }); // OLTs para verificação de sinal this.equipamentos.set('olt_dito', { name: 'OLT DITO', host: '192.168.190.7', username: 'admin', password: 'admin', port: 22, type: 'olt' }); this.equipamentos.set('olt_colmeia', { name: 'OLT COLMEIA', host: '192.168.190.8', username: 'admin', password: 'admin', port: 22, type: 'olt' }); this.equipamentos.set('olt_canaa', { name: 'OLT CANAA', host: '192.168.190.9', username: 'admin', password: 'admin', port: 22, type: 'olt' }); this.equipamentos.set('olt_canaa2', { name: 'OLT CANAA 2', host: '192.168.190.10', username: 'admin', password: 'admin', port: 22, type: 'olt' }); this.equipamentos.set('olt_pedro_afonso', { name: 'OLT PEDRO AFONSO 1', host: '192.168.100.2', username: 'admin', password: 'admin', port: 22, type: 'olt' }); this.equipamentos.set('olt_canavieiras', { name: 'OLT CANAVIEIRAS', host: '192.168.100.4', username: 'admin', password: 'admin', port: 22, type: 'olt' }); } /** * Conecta via SSH a um equipamento * @param {string} equipmentId - ID do equipamento * @param {Object} customConfig - Configuração customizada (opcional) * @returns {Promise} - Promise com resultado da conexão */ connect(equipmentId, customConfig = null) { return new Promise((resolve, reject) => { const equipment = customConfig || this.equipamentos.get(equipmentId); if (!equipment) { reject(new Error(`Equipamento '${equipmentId}' não encontrado`)); return; } const conn = new Client(); const connectionId = `${equipmentId}_${Date.now()}`; conn.on('ready', () => { this.connections.set(connectionId, { connection: conn, equipment: equipment, connected: true, connectedAt: new Date() }); resolve({ success: true, connectionId: connectionId, message: `Conectado ao ${equipment.name} (${equipment.host})` }); }); conn.on('error', (err) => { reject(new Error(`Erro ao conectar: ${err.message}`)); }); conn.on('end', () => { this.connections.delete(connectionId); }); // Conectar conn.connect({ host: equipment.host, port: equipment.port || 22, username: equipment.username, password: equipment.password, readyTimeout: config.ssh.timeout }); }); } /** * Executa um comando SSH * @param {string} connectionId - ID da conexão * @param {string} command - Comando a executar * @returns {Promise} - Promise com resultado do comando */ executeCommand(connectionId, command) { return new Promise((resolve, reject) => { const connection = this.connections.get(connectionId); if (!connection || !connection.connected) { reject(new Error('Conexão não encontrada ou desconectada')); return; } connection.connection.exec(command, (err, stream) => { if (err) { reject(new Error(`Erro ao executar comando: ${err.message}`)); return; } let output = ''; let errorOutput = ''; stream.on('data', (data) => { output += data.toString(); }); stream.stderr.on('data', (data) => { errorOutput += data.toString(); }); stream.on('close', (code, signal) => { resolve({ success: code === 0, output: output.trim(), error: errorOutput.trim(), exitCode: code, signal: signal }); }); }); }); } /** * Desconecta uma conexão SSH * @param {string} connectionId - ID da conexão */ disconnect(connectionId) { const connection = this.connections.get(connectionId); if (connection) { connection.connection.end(); this.connections.delete(connectionId); return true; } return false; } /** * Testa conectividade com um equipamento * @param {string} equipmentId - ID do equipamento * @returns {Promise} - Promise com resultado do teste */ async testConnection(equipmentId) { try { const result = await this.connect(equipmentId); // Executar comando simples para testar const cmdResult = await this.executeCommand(result.connectionId, 'echo "test"'); // Desconectar this.disconnect(result.connectionId); return { success: true, message: `Conexão com ${equipmentId} OK`, details: cmdResult }; } catch (error) { return { success: false, message: `Falha na conexão com ${equipmentId}: ${error.message}` }; } } /** * Lista equipamentos disponíveis */ listEquipments() { const equipments = []; for (const [id, equipment] of this.equipamentos) { equipments.push({ id: id, name: equipment.name, host: equipment.host, port: equipment.port || 22 }); } return equipments; } /** * Adiciona um novo equipamento * @param {string} id - ID do equipamento * @param {Object} equipment - Dados do equipamento */ addEquipment(id, equipment) { this.equipamentos.set(id, equipment); } /** * Remove um equipamento * @param {string} id - ID do equipamento */ removeEquipment(id) { return this.equipamentos.delete(id); } /** * Lista conexões ativas */ getActiveConnections() { const connections = []; for (const [id, conn] of this.connections) { connections.push({ id: id, equipment: conn.equipment.name, host: conn.equipment.host, connectedAt: conn.connectedAt, connected: conn.connected }); } return connections; } /** * Fecha todas as conexões */ closeAllConnections() { for (const [id, connection] of this.connections) { connection.connection.end(); } this.connections.clear(); } /** * Desconecta um usuário específico no concentrador Juniper * @param {string} equipmentId - ID do equipamento Juniper * @param {string} username - Nome do usuário a ser desconectado * @returns {Promise} - Promise com resultado da operação */ async disconnectJuniperUser(equipmentId, username) { try { // Conectar ao equipamento const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Executar comando de desconexão const command = `clear network-access aaa subscriber username ${username}`; const result = await this.executeCommand(connection.connectionId, command); // Desconectar this.disconnect(connection.connectionId); return { success: result.success, message: result.success ? `Usuário ${username} desconectado com sucesso` : `Erro ao desconectar usuário: ${result.error}`, output: result.output, error: result.error }; } catch (error) { return { success: false, message: `Erro na operação: ${error.message}`, error: error.message }; } } /** * Lista usuários conectados no Juniper * @param {string} equipmentId - ID do equipamento Juniper * @returns {Promise} - Promise com lista de usuários */ async listJuniperUsers(equipmentId) { try { const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Comando para listar usuários conectados const command = 'show network-access aaa subscribers'; const result = await this.executeCommand(connection.connectionId, command); this.disconnect(connection.connectionId); return { success: result.success, message: 'Lista de usuários obtida', output: result.output, error: result.error }; } catch (error) { return { success: false, message: `Erro ao listar usuários: ${error.message}`, error: error.message }; } } /** * Busca informações de uma ONU pelo serial number na OLT * @param {string} equipmentId - ID do equipamento OLT * @param {string} serialNumber - Serial number da ONU * @returns {Promise} - Promise com informações da ONU */ async findOnuBySerial(equipmentId, serialNumber) { try { const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Comando para buscar a ONU pelo serial const searchCommand = `show running-config interface gpon | context-match "${serialNumber}"`; const result = await this.executeCommand(connection.connectionId, searchCommand); this.disconnect(connection.connectionId); if (!result.success || !result.output) { return { success: false, message: `ONU com serial ${serialNumber} não encontrada`, error: result.error }; } // Extrair slot e ONU ID da saída const onuInfo = this.parseOnuInfo(result.output, serialNumber); return { success: true, message: `ONU encontrada: Slot ${onuInfo.slot}, ONU ${onuInfo.onuId}`, onuInfo: onuInfo, output: result.output }; } catch (error) { return { success: false, message: `Erro ao buscar ONU: ${error.message}`, error: error.message }; } } /** * Verifica o sinal de uma ONU específica * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @returns {Promise} - Promise com informações do sinal */ async checkOnuSignal(equipmentId, slot, onuId) { try { const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Comando para verificar o sinal da ONU const signalCommand = `show interface gpon ${slot} onu ${onuId} | nomore`; const result = await this.executeCommand(connection.connectionId, signalCommand); this.disconnect(connection.connectionId); return { success: result.success, message: result.success ? `Sinal da ONU ${onuId} no slot ${slot}` : `Erro ao verificar sinal: ${result.error}`, output: result.output, error: result.error, slot: slot, onuId: onuId }; } catch (error) { return { success: false, message: `Erro ao verificar sinal: ${error.message}`, error: error.message }; } } /** * Verifica o sinal de uma ONU pelo serial number (processo completo) * @param {string} equipmentId - ID do equipamento OLT * @param {string} serialNumber - Serial number da ONU * @returns {Promise} - Promise com informações completas do sinal */ async checkOnuSignalBySerial(equipmentId, serialNumber) { try { // Etapa 1: Encontrar a ONU pelo serial const findResult = await this.findOnuBySerial(equipmentId, serialNumber); if (!findResult.success) { return findResult; } // Etapa 2: Verificar o sinal usando slot e ONU ID const signalResult = await this.checkOnuSignal( equipmentId, findResult.onuInfo.slot, findResult.onuInfo.onuId ); return { success: signalResult.success, message: `Sinal da ONU ${serialNumber}`, serialNumber: serialNumber, slot: findResult.onuInfo.slot, onuId: findResult.onuInfo.onuId, signalOutput: signalResult.output, searchOutput: findResult.output, error: signalResult.error }; } catch (error) { return { success: false, message: `Erro no processo completo: ${error.message}`, error: error.message }; } } /** * Extrai informações da ONU a partir da saída do comando de busca * @param {string} output - Saída do comando show running-config * @param {string} serialNumber - Serial number procurado * @returns {Object} - Objeto com slot e onuId */ parseOnuInfo(output, serialNumber) { const lines = output.split('\n'); let currentSlot = null; let onuId = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Procurar por interface gpon const interfaceMatch = line.match(/interface gpon (\d+\/\d+\/\d+)/); if (interfaceMatch) { currentSlot = interfaceMatch[1]; continue; } // Procurar por onu ID const onuMatch = line.match(/onu (\d+)/); if (onuMatch && currentSlot) { // Verificar se o serial number está nas próximas linhas for (let j = i; j < Math.min(i + 5, lines.length); j++) { if (lines[j].includes(serialNumber)) { onuId = onuMatch[1]; break; } } if (onuId) break; } } return { slot: currentSlot, onuId: onuId, found: currentSlot && onuId }; } /** * Lista todos os equipamentos OLT disponíveis * @returns {Array} - Array com OLTs disponíveis */ listOLTs() { const olts = []; for (const [id, equipment] of this.equipamentos) { if (equipment.type === 'olt') { olts.push({ id: id, name: equipment.name, host: equipment.host, port: equipment.port || 22 }); } } return olts; } /** * Reinicia uma ONU específica * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @returns {Promise} - Promise com resultado da operação */ async resetOnu(equipmentId, slot, onuId) { try { const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Executar comandos em sequência const commands = [ 'config', `interface gpon ${slot}`, `onu-reset onu ${onuId}`, 'yes', 'exit' ]; let fullOutput = ''; for (let i = 0; i < commands.length; i++) { const command = commands[i]; try { // Para o comando 'yes', usar um delay menor if (command === 'yes') { await new Promise(resolve => setTimeout(resolve, 2000)); } const result = await this.executeCommand(connection.connectionId, command); fullOutput += `Command: ${command}\n${result.output}\n\n`; // Verificar se houve erro específico if (result.error && !result.error.includes('yes') && command !== 'yes') { console.warn(`Aviso no comando ${command}: ${result.error}`); } // Delay entre comandos para permitir processamento if (i < commands.length - 1) { await new Promise(resolve => setTimeout(resolve, 1500)); } } catch (cmdError) { console.warn(`Erro no comando ${command}: ${cmdError.message}`); if (command !== 'yes' && command !== 'exit') { // Se não for yes ou exit, é um erro crítico throw cmdError; } } } // Desconectar this.disconnect(connection.connectionId); return { success: true, message: `ONU ${onuId} no slot ${slot} foi reiniciada com sucesso`, output: fullOutput.trim(), slot: slot, onuId: onuId }; } catch (error) { return { success: false, message: `Erro ao reiniciar ONU: ${error.message}`, error: error.message }; } } /** * Reinicia uma ONU específica usando shell interativo * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @returns {Promise} - Promise com resultado da operação */ async resetOnuAlternative(equipmentId, slot, onuId) { console.log(`🔄 Iniciando reset ONU - OLT: ${equipmentId}, Slot: ${slot}, ONU: ${onuId}`); return new Promise(async (resolve, reject) => { try { console.log(`📡 Conectando na OLT: ${equipmentId}`); const connection = await this.connect(equipmentId); if (!connection.success) { console.log(`❌ Falha na conexão: ${connection.message}`); throw new Error(`Falha na conexão: ${connection.message}`); } console.log(`✅ Conectado! ConnectionId: ${connection.connectionId}`); const conn = this.connections.get(connection.connectionId); // Usar shell interativo para manter contexto conn.connection.shell((err, stream) => { if (err) { this.disconnect(connection.connectionId); reject(new Error(`Erro ao abrir shell: ${err.message}`)); return; } let output = ''; let step = 0; let isComplete = false; const commands = [ 'config', `interface gpon ${slot}`, `onu-reset onu ${onuId}`, 'yes', 'exit', // Sai do modo interface 'exit' // Sai do modo configuração ]; stream.on('data', (data) => { const dataStr = data.toString(); output += dataStr; console.log(`� Recebido: ${dataStr.trim()}`); // Detectar prompts e enviar próximo comando if (step < commands.length) { let shouldSendCommand = false; if (step === 0 && dataStr.includes('#')) { // Prompt inicial - enviar config shouldSendCommand = true; } else if (step === 1 && dataStr.includes('(config)#')) { // No modo config - enviar interface shouldSendCommand = true; } else if (step === 2 && dataStr.includes('(config-gpon')) { // No modo interface - enviar onu-reset shouldSendCommand = true; } else if (step === 3 && (dataStr.includes('Are you sure') || dataStr.includes('[no,yes]'))) { // Pergunta de confirmação - enviar yes shouldSendCommand = true; } else if (step === 4 && dataStr.includes('(config-gpon')) { // Após confirmação - enviar primeiro exit shouldSendCommand = true; } else if (step === 5 && dataStr.includes('(config)#')) { // De volta ao config - enviar segundo exit shouldSendCommand = true; } if (shouldSendCommand) { console.log(`📝 Enviando comando ${step + 1}: ${commands[step]}`); stream.write(commands[step] + '\n'); step++; } } else if (!isComplete && dataStr.includes('#') && !dataStr.includes('(config')) { // Voltou ao prompt normal - operação completa isComplete = true; setTimeout(() => { stream.end(); }, 1000); } }); stream.on('close', () => { this.disconnect(connection.connectionId); console.log(`🔌 Desconectado da OLT`); resolve({ success: true, message: `ONU ${onuId} no slot ${slot} foi reiniciada com sucesso`, output: output.trim(), slot: slot, onuId: onuId }); }); stream.on('error', (err) => { this.disconnect(connection.connectionId); console.error(`❌ Erro no shell: ${err.message}`); reject(new Error(`Erro no shell: ${err.message}`)); }); // Timeout de segurança (60 segundos) setTimeout(() => { if (!stream.destroyed && !isComplete) { console.log(`⏰ Timeout - forçando fechamento`); stream.end(); this.disconnect(connection.connectionId); reject(new Error('Timeout na operação de reset')); } }, 60000); }); } catch (error) { console.error(`❌ Erro ao reiniciar ONU:`, error); reject(new Error(`Erro ao reiniciar ONU: ${error.message}`)); } }); } /** * Obtém informações detalhadas da ONU incluindo Equipment ID * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @returns {Promise} - Promise com informações da ONU */ async getOnuDetails(equipmentId, slot, onuId) { try { console.log(`🔍 Obtendo detalhes da ONU - OLT: ${equipmentId}, Slot: ${slot}, ONU: ${onuId}`); const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Comando para obter informações detalhadas da ONU const detailsCommand = `show interface gpon ${slot} onu ${onuId}`; const result = await this.executeCommand(connection.connectionId, detailsCommand); this.disconnect(connection.connectionId); if (!result.success) { return { success: false, message: `Erro ao obter detalhes da ONU: ${result.error}` }; } // Extrair Equipment ID da saída const equipmentId_onu = this.parseEquipmentId(result.output); return { success: true, message: `Detalhes da ONU obtidos com sucesso`, equipmentId: equipmentId_onu, slot: slot, onuId: onuId, output: result.output }; } catch (error) { console.error(`❌ Erro ao obter detalhes da ONU:`, error); return { success: false, message: `Erro ao obter detalhes: ${error.message}`, error: error.message }; } } /** * Extrai o Equipment ID da saída do comando show interface * @param {string} output - Saída do comando * @returns {string} - Equipment ID identificado */ parseEquipmentId(output) { const lines = output.split('\n'); for (const line of lines) { // Procurar por "Equipment ID" na saída if (line.includes('Equipment ID') || line.includes('equipment id')) { // Extrair o valor após "Equipment ID:" const match = line.match(/Equipment\s+ID\s*:\s*([^\s]+)/i); if (match) { return match[1].trim(); } } // Formato alternativo if (line.includes('DM986-100') || line.includes('DM986-414')) { const match = line.match(/(DM986-\d+)/); if (match) { return match[1]; } } } return 'UNKNOWN'; } /** * Atualiza firmware da ONU * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @param {string} firmwareFile - Nome do arquivo de firmware * @returns {Promise} - Promise com resultado da operação */ async updateOnuFirmware(equipmentId, slot, onuId, firmwareFile) { console.log(`🔄 Iniciando update firmware ONU - OLT: ${equipmentId}, Slot: ${slot}, ONU: ${onuId}, FW: ${firmwareFile}`); try { console.log(`📡 Conectando na OLT: ${equipmentId}`); const connection = await this.connect(equipmentId); if (!connection.success) { console.log(`❌ Falha na conexão: ${connection.message}`); throw new Error(`Falha na conexão: ${connection.message}`); } console.log(`✅ Conectado! ConnectionId: ${connection.connectionId}`); // Comando de update firmware const firmwareCommand = `request firmware onu install ${firmwareFile} interface gpon ${slot} onu ${onuId}`; console.log(`📝 Enviando comando: ${firmwareCommand}`); // Enviar comando e considerar sucesso imediatamente após envio const result = await this.executeCommand(connection.connectionId, firmwareCommand); // Desconectar this.disconnect(connection.connectionId); console.log(`🔌 Desconectado da OLT`); // Considerar sucesso se o comando foi enviado (mesmo que ainda esteja processando) return { success: true, message: `Comando de atualização de firmware enviado com sucesso`, firmwareFile: firmwareFile, slot: slot, onuId: onuId, command: firmwareCommand, output: result.output || 'Comando enviado para processamento' }; } catch (error) { console.error(`❌ Erro ao atualizar firmware:`, error); return { success: false, message: `Erro ao enviar comando de atualização: ${error.message}`, error: error.message }; } } /** * Encontra o service-port de uma ONU específica * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @returns {Promise} - Promise com service-port encontrado */ async findServicePort(equipmentId, slot, onuId) { try { console.log(`🔍 Procurando service-port - OLT: ${equipmentId}, Slot: ${slot}, ONU: ${onuId}`); const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } // Comando para encontrar service-port const searchCommand = `show running-config service-port | context-match "gpon ${slot} onu ${onuId}"`; console.log(`📝 Executando comando: ${searchCommand}`); const result = await this.executeCommand(connection.connectionId, searchCommand); this.disconnect(connection.connectionId); if (!result.success) { return { success: false, message: `Erro ao buscar service-port: ${result.error}` }; } // Extrair service-port da saída const servicePort = this.parseServicePort(result.output); if (!servicePort) { return { success: false, message: `Service-port não encontrado para ONU ${onuId} no slot ${slot}`, output: result.output }; } return { success: true, message: `Service-port encontrado: ${servicePort}`, servicePort: servicePort, slot: slot, onuId: onuId, output: result.output }; } catch (error) { console.error(`❌ Erro ao buscar service-port:`, error); return { success: false, message: `Erro ao buscar service-port: ${error.message}`, error: error.message }; } } /** * Extrai o service-port da saída do comando * @param {string} output - Saída do comando show running-config service-port * @returns {string|null} - Service-port encontrado ou null */ parseServicePort(output) { const lines = output.split('\n'); for (const line of lines) { // Procurar por "service-port" seguido de número const match = line.match(/service-port\s+(\d+)/); if (match) { return match[1]; } } return null; } /** * Remove uma ONU específica (processo completo) * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU * @param {string} servicePort - Service-port da ONU * @returns {Promise} - Promise com resultado da operação */ async removeOnu(equipmentId, slot, onuId, servicePort) { console.log(`🗑️ Iniciando remoção de ONU - OLT: ${equipmentId}, Slot: ${slot}, ONU: ${onuId}, Service-port: ${servicePort}`); return new Promise(async (resolve, reject) => { try { console.log(`📡 Conectando na OLT: ${equipmentId}`); const connection = await this.connect(equipmentId); if (!connection.success) { console.log(`❌ Falha na conexão: ${connection.message}`); throw new Error(`Falha na conexão: ${connection.message}`); } console.log(`✅ Conectado! ConnectionId: ${connection.connectionId}`); const conn = this.connections.get(connection.connectionId); // Usar shell interativo para manter contexto conn.connection.shell((err, stream) => { if (err) { this.disconnect(connection.connectionId); reject(new Error(`Erro ao abrir shell: ${err.message}`)); return; } let output = ''; let step = 0; let isComplete = false; const commands = [ 'config', `interface gpon ${slot}`, `no onu ${onuId}`, 'top', `no service-port ${servicePort}`, 'commit', 'top', 'exit', 'exit' ]; stream.on('data', (data) => { const dataStr = data.toString(); output += dataStr; console.log(`📨 Recebido: ${dataStr.trim()}`); // Detectar prompts e enviar próximo comando if (step < commands.length) { let shouldSendCommand = false; if (step === 0 && dataStr.includes('#')) { // Prompt inicial - enviar config shouldSendCommand = true; } else if (step === 1 && dataStr.includes('(config)#')) { // No modo config - enviar interface shouldSendCommand = true; } else if (step === 2 && dataStr.includes('(config-gpon')) { // No modo interface - enviar no onu shouldSendCommand = true; } else if (step === 3 && dataStr.includes('(config-gpon')) { // Após no onu - enviar top shouldSendCommand = true; } else if (step === 4 && dataStr.includes('(config)#')) { // De volta ao config - enviar no service-port shouldSendCommand = true; } else if (step === 5 && dataStr.includes('(config)#')) { // Após service-port - enviar commit shouldSendCommand = true; } else if (step === 6 && dataStr.includes('(config)#')) { // Após commit - enviar top shouldSendCommand = true; } else if (step === 7 && dataStr.includes('(config)#')) { // Após top - enviar exit shouldSendCommand = true; } else if (step === 8 && dataStr.includes('#') && !dataStr.includes('(config')) { // Último exit - enviar exit final shouldSendCommand = true; } if (shouldSendCommand) { console.log(`📝 Enviando comando ${step + 1}: ${commands[step]}`); stream.write(commands[step] + '\n'); step++; } } else if (!isComplete && dataStr.includes('#') && !dataStr.includes('(config')) { // Voltou ao prompt normal - operação completa isComplete = true; setTimeout(() => { stream.end(); }, 1000); } }); stream.on('close', () => { this.disconnect(connection.connectionId); console.log(`🔌 Desconectado da OLT`); resolve({ success: true, message: `ONU ${onuId} removida com sucesso do slot ${slot}`, output: output.trim(), slot: slot, onuId: onuId, servicePort: servicePort }); }); stream.on('error', (err) => { this.disconnect(connection.connectionId); console.error(`❌ Erro no shell: ${err.message}`); reject(new Error(`Erro no shell: ${err.message}`)); }); // Timeout de segurança (60 segundos) setTimeout(() => { if (!stream.destroyed && !isComplete) { console.log(`⏰ Timeout - forçando fechamento`); stream.end(); this.disconnect(connection.connectionId); reject(new Error('Timeout na operação de remoção')); } }, 60000); }); } catch (error) { console.error(`❌ Erro ao remover ONU:`, error); reject(new Error(`Erro ao remover ONU: ${error.message}`)); } }); } /** * Processo completo de remoção de ONU pelo serial number * @param {string} equipmentId - ID do equipamento OLT * @param {string} serialNumber - Serial number da ONU * @returns {Promise} - Promise com resultado da operação completa */ async removeOnuBySerial(equipmentId, serialNumber) { try { console.log(`🗑️ Iniciando processo de remoção - Serial: ${serialNumber}`); // Etapa 1: Encontrar a ONU pelo serial const findResult = await this.findOnuBySerial(equipmentId, serialNumber); if (!findResult.success) { return { success: false, message: `ONU com serial ${serialNumber} não encontrada`, error: findResult.message }; } // Etapa 2: Encontrar o service-port const servicePortResult = await this.findServicePort( equipmentId, findResult.onuInfo.slot, findResult.onuInfo.onuId ); if (!servicePortResult.success) { return { success: false, message: `Service-port não encontrado para ONU ${serialNumber}`, error: servicePortResult.message, onuInfo: findResult.onuInfo }; } // Retornar informações para confirmação return { success: true, message: `ONU encontrada e pronta para remoção`, serialNumber: serialNumber, slot: findResult.onuInfo.slot, onuId: findResult.onuInfo.onuId, servicePort: servicePortResult.servicePort, findOutput: findResult.output, servicePortOutput: servicePortResult.output, readyForRemoval: true }; } catch (error) { return { success: false, message: `Erro no processo de remoção: ${error.message}`, error: error.message }; } } /** * Mapas de VLAN por OLT e slot */ getVlanMapping() { return { 'olt_dito': { '1/1/1': 601, '1/1/2': 602, '1/1/3': 603, '1/1/4': 604 }, 'olt_colmeia': { '1/1/1': 605, '1/1/2': 606, '1/1/3': 607, '1/1/4': 608, '1/1/5': 609, '1/1/6': 610, '1/1/7': 611, '1/1/8': 612, '1/1/9': 613, '1/1/10': 614, '1/1/11': 608, '1/1/12': 616, '1/1/13': 617, '1/1/14': 618, '1/1/15': 619, '1/1/16': 620 }, 'olt_canaa': { '1/1/1': 605, '1/1/2': 606, '1/1/3': 607, '1/1/4': 608, '1/1/5': 609, '1/1/6': 610, '1/1/7': 611, '1/1/8': 612 }, 'olt_canaa2': { '1/1/1': 621, '1/1/2': 622, '1/1/3': 623, '1/1/4': 624, '1/1/5': 625, '1/1/6': 626, '1/1/7': 627, '1/1/8': 628 }, 'olt_pedro_afonso': { '1/1/1': 301, '1/1/2': 302, '1/1/3': 303, '1/1/4': 304, '1/1/5': 305, '1/1/6': 306, '1/1/7': 307, '1/1/8': 308, '1/1/9': 309, '1/1/10': 310, '1/1/11': 311, '1/1/12': 312, '1/1/13': 313, '1/1/14': 314, '1/1/15': 315, '1/1/16': 316 }, 'olt_canavieiras': { '1/1/1': 601, '1/1/2': 602, '1/1/3': 603, '1/1/4': 604, '1/1/5': 605, '1/1/6': 606, '1/1/7': 607, '1/1/8': 608, '1/1/9': 609, '1/1/10': 610, '1/1/11': 611, '1/1/12': 612, '1/1/13': 613, '1/1/14': 614, '1/1/15': 615, '1/1/16': 616 } }; } /** * Descobre o slot de uma ONU pelo serial na lista de discovered-onus * @param {string} equipmentId - ID do equipamento OLT * @param {string} serialNumber - Serial da ONU * @returns {Promise} - Promise com slot encontrado */ async discoverOnuSlot(equipmentId, serialNumber) { try { console.log(`🔍 Descobrindo slot da ONU ${serialNumber} na OLT ${equipmentId}`); const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } const result = await this.executeCommand(connection.connectionId, 'show interface gpon discovered-onus'); this.disconnect(connection.connectionId); if (!result.success) { return { success: false, message: `Erro ao buscar discovered-onus: ${result.error}` }; } // Procurar o serial na saída const lines = result.output.split('\n'); for (const line of lines) { if (line.includes(serialNumber)) { // Formato: " 1/1/3 DACM915E0F76" const match = line.match(/\s+(\d+\/\d+\/\d+)\s+([A-Z0-9]+)/); if (match && match[2] === serialNumber) { return { success: true, slot: match[1], serialNumber: serialNumber, output: result.output }; } } } return { success: false, message: `ONU ${serialNumber} não encontrada na lista de discovered-onus`, output: result.output }; } catch (error) { console.error(`❌ Erro ao descobrir slot:`, error); return { success: false, message: `Erro ao descobrir slot: ${error.message}`, error: error.message }; } } /** * Descobre ONU IDs já em uso em um slot específico * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot a verificar (ex: 1/1/1) * @returns {Promise} - Promise com lista de IDs em uso */ async getUsedOnuIds(equipmentId, slot) { try { console.log(`🔍 Buscando ONU IDs em uso no slot ${slot} da OLT ${equipmentId}`); const connection = await this.connect(equipmentId); if (!connection.success) { throw new Error(`Falha na conexão: ${connection.message}`); } const result = await this.executeCommand(connection.connectionId, `show interface gpon ${slot} onu | nomore`); this.disconnect(connection.connectionId); if (!result.success) { return { success: false, message: `Erro ao buscar ONUs do slot: ${result.error}` }; } // Extrair ONU IDs da saída const usedIds = new Set(); const lines = result.output.split('\n'); for (const line of lines) { // Formato: "1/1/1 0 DACM913D21A0 Up None" const match = line.match(/\d+\/\d+\/\d+\s+(\d+)\s+[A-Z0-9]+/); if (match) { usedIds.add(parseInt(match[1])); } } return { success: true, usedIds: Array.from(usedIds).sort((a, b) => a - b), output: result.output }; } catch (error) { console.error(`❌ Erro ao buscar ONU IDs:`, error); return { success: false, message: `Erro ao buscar ONU IDs: ${error.message}`, error: error.message }; } } /** * Encontra o próximo ONU ID disponível (0-127) * @param {Array} usedIds - Array de IDs já em uso * @returns {number|null} - Próximo ID disponível ou null se todos estão em uso */ findNextAvailableOnuId(usedIds) { for (let id = 0; id <= 127; id++) { if (!usedIds.includes(id)) { return id; } } return null; // Todos os IDs estão em uso } /** * Adiciona uma nova ONU ao sistema * @param {string} equipmentId - ID do equipamento OLT * @param {string} slot - Slot da ONU (ex: 1/1/1) * @param {string} onuId - ID da ONU (0-127) * @param {string} serialNumber - Serial da ONU * @param {number} vlanId - VLAN ID para a ONU * @returns {Promise} - Promise com resultado da operação */ async addOnu(equipmentId, slot, onuId, serialNumber, vlanId) { console.log(`➕ Adicionando ONU - OLT: ${equipmentId}, Slot: ${slot}, ONU ID: ${onuId}, Serial: ${serialNumber}, VLAN: ${vlanId}`); return new Promise(async (resolve, reject) => { try { console.log(`📡 Conectando na OLT: ${equipmentId}`); const connection = await this.connect(equipmentId); if (!connection.success) { console.log(`❌ Falha na conexão: ${connection.message}`); throw new Error(`Falha na conexão: ${connection.message}`); } console.log(`✅ Conectado! ConnectionId: ${connection.connectionId}`); const conn = this.connections.get(connection.connectionId); // Usar shell interativo para manter contexto conn.connection.shell((err, stream) => { if (err) { this.disconnect(connection.connectionId); reject(new Error(`Erro ao abrir shell: ${err.message}`)); return; } let output = ''; let step = 0; let isComplete = false; let servicePortId = null; const commands = [ 'config', `interface gpon ${slot}`, `onu ${onuId}`, 'line-profile DM986-100', `serial-number ${serialNumber}`, 'ethernet 1', `native vlan vlan-id ${vlanId}`, 'top', 'service-port new', `gpon ${slot} onu ${onuId} gem 1 match vlan vlan-id ${vlanId} action vlan replace vlan-id ${vlanId}`, 'commit', 'top', 'exit', 'exit' ]; stream.on('data', (data) => { const dataStr = data.toString(); output += dataStr; console.log(`📨 Recebido: ${dataStr.trim()}`); // Capturar service-port ID quando criado if (dataStr.includes('service-port') && /service-port[^a-zA-Z]*(\d+)/.test(dataStr)) { const match = dataStr.match(/service-port[^a-zA-Z]*(\d+)/); if (match) { servicePortId = match[1]; console.log(`📋 Service-port ID capturado: ${servicePortId}`); } } // Capturar service-port ID do prompt de configuração if (dataStr.includes('(config-service-port-') && /config-service-port-(\d+)/.test(dataStr)) { const match = dataStr.match(/config-service-port-(\d+)/); if (match && !servicePortId) { servicePortId = match[1]; console.log(`📋 Service-port ID capturado do prompt: ${servicePortId}`); } } // Detectar prompts e enviar próximo comando if (step < commands.length) { let shouldSendCommand = false; if (step === 0 && dataStr.includes('#')) { // Prompt inicial - enviar config shouldSendCommand = true; } else if (step === 1 && dataStr.includes('(config)#')) { // No modo config - enviar interface shouldSendCommand = true; } else if (step === 2 && dataStr.includes('(config-gpon')) { // No modo interface - enviar onu shouldSendCommand = true; } else if (step >= 3 && step <= 5 && dataStr.includes('(config-gpon-onu')) { // No modo ONU - enviar próximos comandos (line-profile, serial-number, ethernet) shouldSendCommand = true; } else if (step === 6 && dataStr.includes('(config-ethernet')) { // No modo ethernet - enviar native vlan shouldSendCommand = true; } else if (step === 7 && dataStr.includes('(config-ethernet')) { // Após native vlan - enviar top para voltar ao config shouldSendCommand = true; } else if (step === 8 && dataStr.includes('(config)#')) { // Após top - enviar service-port new shouldSendCommand = true; } else if (step === 9 && dataStr.includes('(config-service-port')) { // Após service-port new - enviar configuração service-port (agora em modo service-port) shouldSendCommand = true; } else if (step === 10 && dataStr.includes('(config-service-port')) { // No modo service-port - enviar commit shouldSendCommand = true; } else if (step === 11 && dataStr.includes('(config-service-port')) { // Após commit (ainda em service-port) - enviar top shouldSendCommand = true; } else if (step === 12 && dataStr.includes('(config)#')) { // Após top - enviar exit shouldSendCommand = true; } else if (step === 13 && dataStr.includes('#') && !dataStr.includes('(config')) { // Último exit - finalizar shouldSendCommand = true; } if (shouldSendCommand) { console.log(`📝 Enviando comando ${step + 1}: ${commands[step]}`); stream.write(commands[step] + '\n'); step++; } } else if (!isComplete && dataStr.includes('#') && !dataStr.includes('(config')) { // Voltou ao prompt normal - operação completa isComplete = true; setTimeout(() => { stream.end(); }, 1000); } }); stream.on('close', () => { this.disconnect(connection.connectionId); console.log(`🔌 Desconectado da OLT`); resolve({ success: true, message: `ONU ${serialNumber} adicionada com sucesso`, output: output.trim(), slot: slot, onuId: onuId, serialNumber: serialNumber, vlanId: vlanId, servicePortId: servicePortId }); }); stream.on('error', (err) => { this.disconnect(connection.connectionId); console.error(`❌ Erro no shell: ${err.message}`); reject(new Error(`Erro no shell: ${err.message}`)); }); // Timeout de segurança (90 segundos) setTimeout(() => { if (!stream.destroyed && !isComplete) { console.log(`⏰ Timeout - forçando fechamento`); stream.end(); this.disconnect(connection.connectionId); reject(new Error('Timeout na operação de adição')); } }, 90000); }); } catch (error) { console.error(`❌ Erro ao adicionar ONU:`, error); reject(new Error(`Erro ao adicionar ONU: ${error.message}`)); } }); } /** * Processo completo de adição de ONU pelo serial number * @param {string} equipmentId - ID do equipamento OLT * @param {string} serialNumber - Serial number da ONU * @returns {Promise} - Promise com resultado da operação completa */ async addOnuBySerial(equipmentId, serialNumber) { try { console.log(`➕ Iniciando processo de adição - Serial: ${serialNumber}`); // Etapa 1: Descobrir o slot da ONU const discoverResult = await this.discoverOnuSlot(equipmentId, serialNumber); if (!discoverResult.success) { return { success: false, message: `ONU ${serialNumber} não encontrada na lista de discovered-onus`, error: discoverResult.message }; } const slot = discoverResult.slot; console.log(`✅ Slot descoberto: ${slot}`); // Etapa 2: Buscar ONU IDs já em uso const usedIdsResult = await this.getUsedOnuIds(equipmentId, slot); if (!usedIdsResult.success) { return { success: false, message: `Erro ao buscar ONU IDs em uso no slot ${slot}`, error: usedIdsResult.message }; } // Etapa 3: Encontrar próximo ID disponível const nextOnuId = this.findNextAvailableOnuId(usedIdsResult.usedIds); if (nextOnuId === null) { return { success: false, message: `Todos os ONU IDs (0-127) estão em uso no slot ${slot}`, usedIds: usedIdsResult.usedIds }; } console.log(`✅ Próximo ONU ID disponível: ${nextOnuId}`); // Etapa 4: Determinar VLAN ID baseada na OLT e slot const vlanMapping = this.getVlanMapping(); const vlanId = vlanMapping[equipmentId] && vlanMapping[equipmentId][slot]; if (!vlanId) { return { success: false, message: `VLAN não configurada para OLT ${equipmentId} slot ${slot}`, slot: slot, onuId: nextOnuId }; } console.log(`✅ VLAN ID determinada: ${vlanId}`); // Retornar informações para confirmação return { success: true, message: `ONU pronta para adição`, serialNumber: serialNumber, slot: slot, onuId: nextOnuId, vlanId: vlanId, usedIds: usedIdsResult.usedIds, readyForAddition: true }; } catch (error) { return { success: false, message: `Erro no processo de adição: ${error.message}`, error: error.message }; } } } module.exports = SSHManager;