[ADN] Commit final DB-First - Actualización completa de documentación, herramientas y componentes
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# adn/tools/cli/nodos.rb — Subcomando para gestión y consulta de nodos
|
||||
# ======================================================================
|
||||
# Uso:
|
||||
# ./adn/tools/run nodos listar [--tipo servidor|vm|pc]
|
||||
# ./adn/tools/run nodos agrupar [--formato arbol|tabla]
|
||||
# ./adn/tools/run nodos buscar <término>
|
||||
|
||||
require 'optparse'
|
||||
require_relative '../core/colores'
|
||||
|
||||
module ADN
|
||||
class SubcomandoNodos
|
||||
def initialize(args, logger)
|
||||
@args = args
|
||||
@logger = logger
|
||||
@opciones = {}
|
||||
end
|
||||
|
||||
def ejecutar
|
||||
if @args.empty?
|
||||
mostrar_ayuda
|
||||
return
|
||||
end
|
||||
|
||||
accion = @args.shift
|
||||
|
||||
case accion
|
||||
when 'agrupar'
|
||||
agrupar_nodos
|
||||
when 'listar'
|
||||
listar_nodos
|
||||
when 'buscar'
|
||||
buscar_nodos
|
||||
when 'ayuda', 'help', '-h', '--help'
|
||||
mostrar_ayuda
|
||||
else
|
||||
puts "#{Color::RED}✗ Acción desconocida: '#{accion}'#{Color::RESET}"
|
||||
mostrar_ayuda
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mostrar_ayuda
|
||||
puts <<~AYUDA
|
||||
#{Color::CYAN}🖥️ Gestión de Nodos#{Color::RESET}
|
||||
|
||||
#{Color::YELLOW}Uso:#{Color::RESET} ./adn/tools/run nodos <acción> [opciones]
|
||||
|
||||
#{Color::YELLOW}Acciones:#{Color::RESET}
|
||||
agrupar [--servidor SRV] Agrupar VMs por servidor anfitrión
|
||||
listar [--tipo TIPO] Listar todos los nodos (fichas .md)
|
||||
buscar <término> Buscar nodos por nombre o contenido
|
||||
|
||||
#{Color::YELLOW}Opciones de 'agrupar':#{Color::RESET}
|
||||
--servidor SRV Filtrar solo VMs de un servidor específico
|
||||
|
||||
#{Color::YELLOW}Opciones de 'listar':#{Color::RESET}
|
||||
--tipo TIPO Filtrar por prefijo (srv, srvv, pcv, pc, cam, dc, sql)
|
||||
|
||||
#{Color::YELLOW}Ejemplos:#{Color::RESET}
|
||||
./adn/tools/run nodos agrupar
|
||||
./adn/tools/run nodos agrupar --servidor srv-pmox1
|
||||
./adn/tools/run nodos listar
|
||||
./adn/tools/run nodos listar --tipo srvv
|
||||
./adn/tools/run nodos buscar dasuten
|
||||
AYUDA
|
||||
end
|
||||
|
||||
# ─── Agrupar ────────────────────────────────────────────────────────
|
||||
|
||||
def agrupar_nodos
|
||||
opts = { servidor: nil }
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.on('--servidor SRV', 'Filtrar por servidor anfitrión') { |s| opts[:servidor] = s }
|
||||
end
|
||||
parser.order!(@args)
|
||||
|
||||
puts "#{Color::CYAN}📋 Agrupando nodos por Servidor Host#{Color::RESET}"
|
||||
puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}"
|
||||
|
||||
servidores = Hash.new { |h, k| h[k] = [] }
|
||||
sin_servidor = []
|
||||
|
||||
Dir.glob(File.join(ADN::NODOS_DIR, '*.md')).each do |archivo|
|
||||
nodo_nombre = File.basename(archivo, '.md')
|
||||
next if nodo_nombre.start_with?('_')
|
||||
|
||||
host = detectar_host(archivo)
|
||||
|
||||
if host
|
||||
servidores[host] << nodo_nombre
|
||||
elsif !es_nodo_fisico?(nodo_nombre)
|
||||
sin_servidor << nodo_nombre
|
||||
end
|
||||
end
|
||||
|
||||
# Filtrar por servidor si se especificó
|
||||
if opts[:servidor]
|
||||
srv = opts[:servidor]
|
||||
unless servidores.key?(srv)
|
||||
puts "#{Color::YELLOW}⚠ No se encontraron VMs para el servidor '#{srv}'#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
mostrar_servidor(srv, servidores[srv])
|
||||
else
|
||||
servidores.keys.sort.each { |srv| mostrar_servidor(srv, servidores[srv]) }
|
||||
|
||||
if sin_servidor.any?
|
||||
puts "#{Color::YELLOW}❓ Nodos sin servidor anfitrión identificable:#{Color::RESET}"
|
||||
sin_servidor.sort.each { |nodo| puts " ├── 💻 #{nodo}" }
|
||||
puts ""
|
||||
end
|
||||
end
|
||||
|
||||
total_vms = servidores.values.flatten.length
|
||||
puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}"
|
||||
puts "#{Color::DIM}Total: #{servidores.keys.length} servidores, #{total_vms} VMs#{Color::RESET}"
|
||||
end
|
||||
|
||||
def mostrar_servidor(srv, vms)
|
||||
puts "#{Color::GREEN}🏢 #{srv}#{Color::RESET} (#{vms.length} VMs)"
|
||||
vms.sort.each { |vm| puts " ├── 💻 #{vm}" }
|
||||
puts ""
|
||||
end
|
||||
|
||||
def detectar_host(archivo)
|
||||
contenido = File.read(archivo, encoding: 'UTF-8')
|
||||
host = nil
|
||||
|
||||
contenido.split("\n").each do |linea|
|
||||
# Formato lista o tabla: **Padre/Host**: `srv-dasu`
|
||||
if linea.match(/\*\*(?:Padre\/Host|Host Anfitrión|Host|Servidor(?: Anfitrión)?|Hipervisor|IP \/ Ubicación)\*\*\s*[:\|]?\s*`?([a-zA-Z0-9\-]+)`?/) ||
|
||||
linea.match(/\*\*(?:Padre\/Host|Host Anfitrión|Host|Servidor(?: Anfitrión)?|Hipervisor|IP \/ Ubicación)\*\*\s*[:\|]?\s*([^`\s|]+)/)
|
||||
candidato = ($1 || $2).strip.gsub(/`/, '')
|
||||
return candidato if candidato.start_with?('srv-')
|
||||
end
|
||||
|
||||
# Texto: "dependiente del host físico `srv-dasu`"
|
||||
if linea.match(/host(?: físico)?\s+`?([a-zA-Z0-9\-]+)`?/)
|
||||
candidato = $1.strip
|
||||
return candidato if candidato.start_with?('srv-')
|
||||
end
|
||||
end
|
||||
|
||||
host
|
||||
end
|
||||
|
||||
def es_nodo_fisico?(nombre)
|
||||
nombre.start_with?('srv-') || nombre.start_with?('pc-') ||
|
||||
nombre == 'dtic-bitacoras' || nombre == 'cam-hikvision-24'
|
||||
end
|
||||
|
||||
# ─── Listar ─────────────────────────────────────────────────────────
|
||||
|
||||
def listar_nodos
|
||||
opts = { tipo: nil }
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.on('--tipo TIPO', 'Filtrar por prefijo (srv, srvv, pcv, pc, cam, dc, sql)') { |t| opts[:tipo] = t }
|
||||
end
|
||||
parser.order!(@args)
|
||||
|
||||
puts "#{Color::CYAN}📋 Listando fichas de nodos#{Color::RESET}"
|
||||
puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}"
|
||||
|
||||
archivos = Dir.glob(File.join(ADN::NODOS_DIR, '*.md')).sort
|
||||
nodos = archivos.map { |a| File.basename(a, '.md') }
|
||||
nodos.reject! { |n| n.start_with?('_') }
|
||||
|
||||
if opts[:tipo]
|
||||
nodos.select! { |n| n.start_with?(opts[:tipo]) }
|
||||
end
|
||||
|
||||
if nodos.empty?
|
||||
puts "#{Color::YELLOW}⚠ No se encontraron nodos#{opts[:tipo] ? " con prefijo '#{opts[:tipo]}'" : ''}#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
nodos.each do |nodo|
|
||||
archivo = File.join(ADN::NODOS_DIR, "#{nodo}.md")
|
||||
contenido = File.read(archivo, encoding: 'UTF-8')
|
||||
|
||||
# Extraer rol si existe
|
||||
rol = '-'
|
||||
if contenido.match(/\*\*Rol(?:\s*Principal)?\*\*\s*[:\|]\s*([^\|\n]+)/)
|
||||
rol = $1.strip[0..40]
|
||||
end
|
||||
|
||||
puts " 💻 #{nodo.ljust(22)} #{Color::DIM}#{rol}#{Color::RESET}"
|
||||
end
|
||||
|
||||
puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}"
|
||||
puts "#{Color::DIM}Total: #{nodos.length} nodos#{Color::RESET}"
|
||||
end
|
||||
|
||||
# ─── Buscar ─────────────────────────────────────────────────────────
|
||||
|
||||
def buscar_nodos
|
||||
termino = @args.join(' ')
|
||||
if termino.empty?
|
||||
puts "#{Color::RED}✗ Se requiere un término de búsqueda#{Color::RESET}"
|
||||
puts " Uso: ./adn/tools/run nodos buscar <término>"
|
||||
return
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}🔍 Buscando nodos con '#{termino}'#{Color::RESET}"
|
||||
puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}"
|
||||
|
||||
resultados = []
|
||||
Dir.glob(File.join(ADN::NODOS_DIR, '*.md')).each do |archivo|
|
||||
nodo = File.basename(archivo, '.md')
|
||||
contenido = File.read(archivo, encoding: 'UTF-8')
|
||||
|
||||
if nodo.include?(termino.downcase) || contenido.downcase.include?(termino.downcase)
|
||||
# Buscar línea con contexto
|
||||
lineas = contenido.split("\n")
|
||||
contexto = lineas.find { |l| l.downcase.include?(termino.downcase) }
|
||||
contexto = contexto ? contexto.strip[0..60] : '-'
|
||||
resultados << { nodo: nodo, contexto: contexto }
|
||||
end
|
||||
end
|
||||
|
||||
if resultados.empty?
|
||||
puts "#{Color::YELLOW}⚠ No se encontraron nodos que coincidan con '#{termino}'#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
resultados.each do |r|
|
||||
puts " 💻 #{r[:nodo].ljust(22)} #{Color::DIM}#{r[:contexto]}#{Color::RESET}"
|
||||
end
|
||||
|
||||
puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}"
|
||||
puts "#{Color::DIM}Total: #{resultados.length} resultados#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user