1686 lines
63 KiB
JavaScript
1686 lines
63 KiB
JavaScript
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;
|