module ADN module SubcomandoDBNodo # Comando: nodo:crear [OPCIONES] - Crear nuevo nodo def comando_nodo_crear(args) opts = { nombre: nil, ip: nil, tipo: 'servidor', descripcion: nil, activo: true } parser = OptionParser.new do |opt| opt.banner = "Uso: adn db nodo:crear [OPCIONES]" opt.on("--nombre NOMBRE", "Nombre único del nodo (requerido)") do |n| opts[:nombre] = n end opt.on("--ip DIRECCION", "Dirección IP (opcional)") do |i| opts[:ip] = i end opt.on("--tipo TIPO", "Tipo (servidor, hipervisor, vm, pc)") do |t| opts[:tipo] = t end opt.on("--descripcion TEXTO", "Descripción del nodo") do |d| opts[:descripcion] = d end opt.on("--inactivo", "Crear nodo como inactivo") do opts[:activo] = false end opt.on("--ambito NOMBRE_O_ID", "Ámbito Padre (Opcional)") do |a| opts[:ambito] = a end opt.on("-h", "--help", "Mostrar ayuda") do puts opt exit end end begin parser.parse!(args) rescue OptionParser::InvalidOption => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" puts parser exit 1 end unless opts[:nombre] puts "#{Color::RED}✗ Error: --nombre es requerido#{Color::RESET}" puts parser exit 1 end puts "#{Color::CYAN}🖥️ Creando nodo '#{opts[:nombre]}'...#{Color::RESET}" BitacorasDB::BitacoraDB.with_connection do |db| begin ambito_id = nil if opts[:ambito] ambito = db.get_ambito(opts[:ambito]) if ambito.nil? puts "#{Color::RED}✗ Error: Ámbito '#{opts[:ambito]}' no encontrado.#{Color::RESET}" exit 1 end ambito_id = ambito['id'] end nodo = db.create_nodo( nombre: opts[:nombre], ip: opts[:ip], tipo: opts[:tipo], descripcion: opts[:descripcion], activo: opts[:activo], ambito_id: ambito_id ) puts "#{Color::GREEN}✅ Nodo creado exitosamente#{Color::RESET}" puts "" puts " #{Color::BOLD}ID:#{Color::RESET} #{nodo['id']}" puts " #{Color::BOLD}Nombre:#{Color::RESET} #{nodo['nombre']}" puts " #{Color::BOLD}Ámbito:#{Color::RESET} #{opts[:ambito] ? ambito['nombre'] : '(sin ámbito)'}" puts " #{Color::BOLD}IP:#{Color::RESET} #{nodo['ip'] || '(sin IP)'}" puts " #{Color::BOLD}Tipo:#{Color::RESET} #{nodo['tipo']}" puts " #{Color::BOLD}Activo:#{Color::RESET} #{nodo['activo'] ? 'Sí' : 'No'}" puts " #{Color::BOLD}Descripción:#{Color::RESET} #{nodo['descripcion'] || '(sin descripción)'}" puts " #{Color::BOLD}Creado:#{Color::RESET} #{nodo['created_at']}" rescue PG::UniqueViolation puts "#{Color::YELLOW}⚠ Ya existe un nodo con el nombre '#{opts[:nombre]}'#{Color::RESET}" puts " Usa #{Color::DIM}adn db nodo:listar#{Color::RESET} para ver nodos existentes" rescue => e puts "#{Color::RED}✗ Error creando nodo: #{e.message}#{Color::RESET}" end end end # Comando: nodo:listar [OPCIONES] - Listar nodos del sistema def comando_nodo_listar(args) opts = { activo: nil, tipo: nil } parser = OptionParser.new do |opt| opt.banner = "Uso: adn db nodo:listar [OPCIONES]" opt.on("--activo", "Mostrar solo nodos activos") do opts[:activo] = true end opt.on("--inactivo", "Mostrar solo nodos inactivos") do opts[:activo] = false end opt.on("--tipo TIPO", "Filtrar por tipo (servidor, hipervisor, vm, pc)") do |t| opts[:tipo] = t end opt.on("--ambito NOMBRE_O_ID", "Filtrar por ámbito") do |a| opts[:ambito] = a end opt.on("-h", "--help", "Mostrar ayuda") do puts opt exit end end begin parser.parse!(args) rescue OptionParser::InvalidOption => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" puts parser exit 1 end puts "#{Color::CYAN}🖥️ Listando nodos...#{Color::RESET}" BitacorasDB::BitacoraDB.with_connection do |db| ambito_id = nil if opts[:ambito] ambito = db.get_ambito(opts[:ambito]) if ambito ambito_id = ambito['id'] else puts "#{Color::RED}✗ Error: Ámbito '#{opts[:ambito]}' no encontrado.#{Color::RESET}" exit 1 end end nodos = db.list_nodos(activo: opts[:activo], tipo: opts[:tipo], ambito_id: ambito_id, limit: 100) if nodos.empty? puts "#{Color::YELLOW}⚠ No se encontraron nodos#{Color::RESET}" return end puts "" puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Nombre".ljust(20)} #{"Ámbito".ljust(15)} #{"IP".ljust(15)} #{"Tipo".ljust(12)} #{"Activo".ljust(6)} #{"Descripción".ljust(30)}#{Color::RESET}" puts "#{"─" * 5} #{"─" * 20} #{"─" * 15} #{"─" * 15} #{"─" * 12} #{"─" * 6} #{"─" * 30}" nodos.each do |n| id = n['id'].to_s nombre = n['nombre'] ambito_n = (n['ambito_nombre'] || '-').to_s ambito_n = ambito_n[0..12] + '...' if ambito_n.length > 15 ip = n['ip'] || '(sin IP)' tipo = n['tipo'] activo = n['activo'] ? '✅' : '❌' desc = n['descripcion'] || '' desc = desc[0..27] + '...' if desc.length > 30 puts "#{id.ljust(5)} #{nombre.ljust(20)} #{ambito_n.ljust(15)} #{ip.ljust(15)} #{tipo.ljust(12)} #{activo.ljust(6)} #{desc.ljust(30)}" end puts "" puts "Total: #{nodos.length} nodos" # Estadísticas por tipo tipos = nodos.group_by { |n| n['tipo'] } puts "#{Color::DIM}Tipos: #{tipos.map { |t, ns| "#{t}: #{ns.length}" }.join(', ')}#{Color::RESET}" end end # Comando: nodo:actualizar NOMBRE [OPCIONES] - Actualizar nodo existente def comando_nodo_actualizar(args) return if args.empty? nombre = args.shift opts = { ip: nil, tipo: nil, descripcion: nil, activo: nil, ambito: nil } parser = OptionParser.new do |opt| opt.banner = "Uso: adn db nodo:actualizar NOMBRE [OPCIONES]" opt.on("--ip DIRECCION", "Nueva dirección IP") do |i| opts[:ip] = i end opt.on("--tipo TIPO", "Nuevo tipo (servidor, hipervisor, vm, pc)") do |t| opts[:tipo] = t end opt.on("--descripcion TEXTO", "Nueva descripción") do |d| opts[:descripcion] = d end opt.on("--activo", "Marcar como activo") do opts[:activo] = true end opt.on("--inactivo", "Marcar como inactivo") do opts[:activo] = false end opt.on("--ambito NOMBRE_O_ID", "Nuevo ámbito") do |a| opts[:ambito] = a end opt.on("-h", "--help", "Mostrar ayuda") do puts opt exit end end begin parser.parse!(args) rescue OptionParser::InvalidOption => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" puts parser exit 1 end puts "#{Color::CYAN}🔄 Actualizando nodo '#{nombre}'...#{Color::RESET}" BitacorasDB::BitacoraDB.with_connection do |db| begin ambito_id = nil if opts[:ambito] ambito = db.get_ambito(opts[:ambito]) if ambito.nil? puts "#{Color::RED}✗ Error: Ámbito '#{opts[:ambito]}' no encontrado.#{Color::RESET}" exit 1 end ambito_id = ambito['id'] end nodo = db.update_nodo( nombre: nombre, ip: opts[:ip], tipo: opts[:tipo], descripcion: opts[:descripcion], activo: opts[:activo], ambito_id: ambito_id ) if nodo.nil? puts "#{Color::YELLOW}⚠ No se encontró el nodo '#{nombre}'#{Color::RESET}" exit 1 end puts "#{Color::GREEN}✅ Nodo actualizado exitosamente#{Color::RESET}" puts "" puts " #{Color::BOLD}ID:#{Color::RESET} #{nodo['id']}" puts " #{Color::BOLD}Nombre:#{Color::RESET} #{nodo['nombre']}" puts " #{Color::BOLD}Ámbito:#{Color::RESET} #{ambito_id ? opts[:ambito] : '(sin ámbito)'}" puts " #{Color::BOLD}IP:#{Color::RESET} #{nodo['ip'] || '(sin IP)'}" puts " #{Color::BOLD}Tipo:#{Color::RESET} #{nodo['tipo']}" puts " #{Color::BOLD}Activo:#{Color::RESET} #{nodo['activo'] ? 'Sí' : 'No'}" puts " #{Color::BOLD}Descripción:#{Color::RESET} #{nodo['descripcion'] || '(sin descripción)'}" puts " #{Color::BOLD}Actualizado:#{Color::RESET} #{nodo['updated_at']}" rescue => e puts "#{Color::RED}✗ Error actualizando nodo: #{e.message}#{Color::RESET}" end end end end end