[P2606] WebServer NGINX: plan actualizado, ficha nodo, HTTPS operativo
- P2606: Plan v1.1 EN EJECUCIÓN. Fases 1-3 completadas. - Ficha nodo srvv-nginx-rm.md. P2606 en adn/07_proyectos.md. - Incluye cambios acumulados de sesiones anteriores.
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'optparse'
|
||||
require_relative '../../core/colores'
|
||||
require_relative '../../core/help_formatter'
|
||||
|
||||
module ADN
|
||||
module SubcomandoDBAmbito
|
||||
# Comando: ambito:listar [OPCIONES] - Listar ámbitos
|
||||
def comando_ambito_listar(args)
|
||||
opts = { activo: nil }
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Uso: adn db ambito:listar [OPCIONES]"
|
||||
opt.on("--activo", "Mostrar solo ámbitos activos") { opts[:activo] = true }
|
||||
opt.on("--inactivo", "Mostrar solo ámbitos inactivos") { opts[:activo] = false }
|
||||
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}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}📋 Listando ámbitos...#{Color::RESET}\n\n"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
ambitos = db.list_ambitos(activo: opts[:activo])
|
||||
|
||||
if ambitos.empty?
|
||||
puts "#{Color::YELLOW}⚠ No se encontraron ámbitos.#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Nombre".ljust(20)} #{"Nodos".ljust(7)} #{"Activo".ljust(6)} #{"Padre".ljust(20)}#{Color::RESET}"
|
||||
puts "#{"─" * 5} #{"─" * 20} #{"─" * 7} #{"─" * 6} #{"─" * 20}"
|
||||
|
||||
ambitos.each do |a|
|
||||
id = a['id'].to_s.ljust(5)
|
||||
nombre = a['nombre'].to_s.ljust(20)
|
||||
count = a['count_nodos'].to_s.ljust(7)
|
||||
activo = (a['activo'] ? '✅' : '❌').ljust(6)
|
||||
padre = (a['parent_nombre'] || '-').ljust(20)
|
||||
|
||||
puts "#{id} #{nombre} #{count} #{activo} #{padre}"
|
||||
end
|
||||
puts "\nTotal: #{ambitos.length} ámbitos."
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: ambito:crear [OPCIONES] - Crear nuevo ámbito
|
||||
def comando_ambito_crear(args)
|
||||
opts = { nombre: nil, descripcion: nil, parent_id: nil }
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Uso: adn db ambito:crear --nombre NOMBRE [OPCIONES]"
|
||||
opt.on("--nombre NOMBRE", "Nombre único del ámbito (obligatorio)") { |n| opts[:nombre] = n }
|
||||
opt.on("--descripcion DESC", "Descripción del ámbito") { |d| opts[:descripcion] = d }
|
||||
opt.on("--parent-id ID", Integer, "ID del ámbito padre") { |p| opts[:parent_id] = p }
|
||||
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}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless opts[:nombre]
|
||||
puts "#{Color::RED}✗ Error: El parámetro --nombre es obligatorio.#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}📦 Creando ámbito '#{opts[:nombre]}'...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
ambito = db.create_ambito(
|
||||
nombre: opts[:nombre],
|
||||
descripcion: opts[:descripcion],
|
||||
parent_id: opts[:parent_id]
|
||||
)
|
||||
puts "#{Color::GREEN}✅ Ámbito creado exitosamente. ID: #{ambito['id']}#{Color::RESET}"
|
||||
rescue PG::UniqueViolation
|
||||
puts "#{Color::RED}✗ Error: Ya existe un ámbito con el nombre '#{opts[:nombre]}'.#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: ambito:info [NOMBRE_O_ID] - Info detallada
|
||||
def comando_ambito_info(args)
|
||||
objetivo = args.first
|
||||
if objetivo.nil? || objetivo.start_with?('-')
|
||||
puts "#{Color::YELLOW}Uso: adn db ambito:info <nombre_o_id>#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
ambito = db.get_ambito(objetivo)
|
||||
if ambito.nil?
|
||||
puts "#{Color::RED}✗ Error: Ámbito '#{objetivo}' no encontrado.#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}ℹ Info del Ámbito: #{ambito['nombre']}#{Color::RESET}\n\n"
|
||||
puts " #{Color::BOLD}ID:#{Color::RESET} #{ambito['id']}"
|
||||
puts " #{Color::BOLD}Nombre:#{Color::RESET} #{ambito['nombre']}"
|
||||
puts " #{Color::BOLD}Descripción:#{Color::RESET} #{ambito['descripcion'] || '(sin descripción)'}"
|
||||
puts " #{Color::BOLD}Padre:#{Color::RESET} #{ambito['parent_nombre'] || '(raíz)'}"
|
||||
puts " #{Color::BOLD}Activo:#{Color::RESET} #{ambito['activo'] ? 'Sí' : 'No'}"
|
||||
puts " #{Color::BOLD}Nodos:#{Color::RESET} #{ambito['count_nodos']}"
|
||||
puts ""
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: ambito:nodos [NOMBRE_O_ID] - Listar nodos de un ámbito
|
||||
def comando_ambito_nodos(args)
|
||||
objetivo = args.first
|
||||
if objetivo.nil? || objetivo.start_with?('-')
|
||||
puts "#{Color::YELLOW}Uso: adn db ambito:nodos <nombre_o_id>#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
ambito = db.get_ambito(objetivo)
|
||||
if ambito.nil?
|
||||
puts "#{Color::RED}✗ Error: Ámbito '#{objetivo}' no encontrado.#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}🖥️ Nodos del ámbito '#{ambito['nombre']}'...#{Color::RESET}\n\n"
|
||||
nodos = db.get_nodos_by_ambito(ambito['id'])
|
||||
|
||||
if nodos.empty?
|
||||
puts "#{Color::YELLOW}⚠ Este ámbito no tiene nodos asignados.#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
nodos.each do |n|
|
||||
tipo = n['tipo'].to_s.ljust(10)
|
||||
ip = (n['ip'] || '-').ljust(15)
|
||||
estado = n['activo'] ? "#{Color::GREEN}ON#{Color::RESET}" : "#{Color::RED}OFF#{Color::RESET}"
|
||||
puts " #{Color::DIM}▸#{Color::RESET} #{Color::BOLD}#{n['nombre'].to_s.ljust(20)}#{Color::RESET} [#{tipo}] IP: #{ip} (#{estado})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,13 +2,16 @@ module ADN
|
||||
module SubcomandoDBBitacora
|
||||
# Comando: bitacora:crear [FECHA] - Crear nueva bitácora diaria
|
||||
def comando_bitacora_crear(args)
|
||||
opts = { fecha: Date.today, resumen: nil }
|
||||
opts = { fecha: Date.today, resumen: nil, modo: 'P' }
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Uso: adn db bitacora:crear [FECHA] [OPCIONES]"
|
||||
opt.on("--resumen TEXTO", "Resumen inicial de la bitácora") do |r|
|
||||
opts[:resumen] = r
|
||||
end
|
||||
opt.on("--modo MODO", "Modo de la jornada: P=Presencial, R=Remoto (default: P)") do |m|
|
||||
opts[:modo] = m.upcase
|
||||
end
|
||||
opt.on("-h", "--help", "Mostrar ayuda") do
|
||||
puts opt
|
||||
exit
|
||||
@@ -17,7 +20,6 @@ module ADN
|
||||
|
||||
begin
|
||||
parser.parse!(args)
|
||||
# Si queda un argumento sin procesar, asumimos que es la fecha
|
||||
opts[:fecha] = Date.parse(args[0]) if args[0]
|
||||
rescue OptionParser::InvalidOption, Date::Error => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
@@ -29,11 +31,12 @@ module ADN
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
bitacora = db.create_bitacora(fecha: opts[:fecha], resumen: opts[:resumen])
|
||||
bitacora = db.create_bitacora(fecha: opts[:fecha], resumen: opts[:resumen], modo: opts[:modo])
|
||||
puts "#{Color::GREEN}✅ Bitácora creada exitosamente#{Color::RESET}"
|
||||
puts ""
|
||||
puts " #{Color::BOLD}ID:#{Color::RESET} #{bitacora['id']}"
|
||||
puts " #{Color::BOLD}Fecha:#{Color::RESET} #{bitacora['fecha']}"
|
||||
puts " #{Color::BOLD}Modo:#{Color::RESET} #{bitacora['modo']} (#{modo_label(bitacora['modo'])})"
|
||||
puts " #{Color::BOLD}Resumen:#{Color::RESET} #{bitacora['resumen'] || '(sin resumen)'}"
|
||||
puts " #{Color::BOLD}Creada:#{Color::RESET} #{bitacora['created_at']}"
|
||||
rescue PG::UniqueViolation
|
||||
@@ -91,16 +94,17 @@ module ADN
|
||||
end
|
||||
|
||||
puts ""
|
||||
puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Fecha".ljust(12)} #{"Resumen".ljust(40)} #{"Creada".ljust(20)}#{Color::RESET}"
|
||||
puts "#{"─" * 5} #{"─" * 12} #{"─" * 40} #{"─" * 20}"
|
||||
puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Fecha".ljust(12)} #{"Modo"} #{"Resumen".ljust(38)} #{"Creada".ljust(20)}#{Color::RESET}"
|
||||
puts "#{"─" * 5} #{"─" * 12} #{"─" * 4} #{"─" * 38} #{"─" * 20}"
|
||||
|
||||
bitacoras.each do |b|
|
||||
fecha = b['fecha']
|
||||
modo = b['modo'] || 'P'
|
||||
resumen = b['resumen'] || ''
|
||||
resumen = resumen[0..37] + '...' if resumen.length > 40
|
||||
resumen = resumen[0..35] + '...' if resumen.length > 38
|
||||
creada = Time.parse(b['created_at']).strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
puts "#{b['id'].to_s.ljust(5)} #{fecha.ljust(12)} #{resumen.ljust(40)} #{creada.ljust(20)}"
|
||||
puts "#{b['id'].to_s.ljust(5)} #{fecha.ljust(12)} [#{modo}] #{resumen.ljust(38)} #{creada.ljust(20)}"
|
||||
end
|
||||
|
||||
puts ""
|
||||
@@ -108,5 +112,8 @@ module ADN
|
||||
end
|
||||
end
|
||||
|
||||
def modo_label(modo)
|
||||
{ 'P' => 'Presencial', 'R' => 'Remoto', 'S' => 'Servicio' }[modo] || modo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+134
-32
@@ -10,18 +10,22 @@ module ADN
|
||||
modo: nil,
|
||||
es_ia: false,
|
||||
nodo: nil,
|
||||
tema: nil
|
||||
tema: nil,
|
||||
plantilla: nil,
|
||||
fecha: nil,
|
||||
ambito: nil,
|
||||
doc_path: nil
|
||||
}
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Uso: adn db evento:crear [OPCIONES]"
|
||||
opt.on("--inicio HH:MM", "Hora de inicio (requerido)") do |i|
|
||||
opt.on("--inicio HH:MM[:SS]", "Hora de inicio (requerido)") do |i|
|
||||
opts[:inicio] = i
|
||||
end
|
||||
opt.on("--descripcion TEXTO", "Descripción de la actividad (requerido)") do |d|
|
||||
opts[:descripcion] = d
|
||||
end
|
||||
opt.on("--fin HH:MM", "Hora de fin (opcional)") do |f|
|
||||
opt.on("--fin HH:MM[:SS]", "Hora de fin (opcional)") do |f|
|
||||
opts[:fin] = f
|
||||
end
|
||||
opt.on("--estado ESTADO", "Estado (⏳, ✅, ❌, ⚠️, ⏸️)") do |e|
|
||||
@@ -33,9 +37,21 @@ module ADN
|
||||
opt.on("--nodo NOMBRE", "Nombre del nodo (servidor/VM)") do |n|
|
||||
opts[:nodo] = n
|
||||
end
|
||||
opt.on("--fecha YYYY-MM-DD", "Fecha de la bitácora (default: hoy)") do |f|
|
||||
opts[:fecha] = f
|
||||
end
|
||||
opt.on("--ambito NOMBRE_O_ID", "Ámbito del evento") do |a|
|
||||
opts[:ambito] = a
|
||||
end
|
||||
opt.on("--doc RUTA", "Ruta al plan .md (ej: P2601_Dasuten/P2601.09_DASUTEN-sin-DC.md)") do |d|
|
||||
opts[:doc_path] = d
|
||||
end
|
||||
opt.on("--ia", "Marcar como generado por IA") do
|
||||
opts[:es_ia] = true
|
||||
end
|
||||
opt.on("--plantilla TIPO", "Usar plantilla (implementacion, investigacion, resolucion, documentacion, test, debug, planificacion)") do |p|
|
||||
opts[:plantilla] = p
|
||||
end
|
||||
opt.on("-h", "--help", "Mostrar ayuda") do
|
||||
puts opt
|
||||
exit
|
||||
@@ -51,12 +67,32 @@ module ADN
|
||||
end
|
||||
|
||||
# Validaciones básicas
|
||||
unless opts[:inicio] && opts[:descripcion]
|
||||
puts "#{Color::RED}✗ Error: --inicio y --descripcion son requeridos#{Color::RESET}"
|
||||
unless opts[:descripcion]
|
||||
puts "#{Color::RED}✗ Error: --descripcion es requerido#{Color::RESET}"
|
||||
puts parser
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Si no se especifica --inicio, usar hora actual
|
||||
opts[:inicio] ||= Time.now.strftime('%H:%M:%S')
|
||||
puts "#{Color::DIM}ℹ Hora de inicio: #{opts[:inicio]}#{Color::RESET}"
|
||||
|
||||
# Si se specify plantilla, mostrar y prependear al texto
|
||||
if opts[:plantilla]
|
||||
require_relative '../../templates/plantillas_eventos'
|
||||
if ADN::PlantillasEventos.existe?(opts[:plantilla])
|
||||
plantilla = ADN::PlantillasEventos.obtener(opts[:plantilla])
|
||||
puts "#{Color::CYAN}📋 Plantilla: #{opts[:plantilla]}#{Color::RESET}"
|
||||
puts plantilla
|
||||
puts ""
|
||||
puts "#{Color::YELLOW}💡 Completa la plantilla y usa --descripcion con el resultado#{Color::RESET}"
|
||||
else
|
||||
puts "#{Color::RED}✗ Tipo de plantilla desconocido: #{opts[:plantilla]}#{Color::RESET}"
|
||||
puts "#{Color::YELLOW}Disponibles: #{ADN::PlantillasEventos.lista_tipos.join(', ')}#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# Obtener modo desde jornada actual si no se especifica
|
||||
modo_resuelto = opts[:modo]
|
||||
if modo_resuelto.nil?
|
||||
@@ -79,24 +115,44 @@ module ADN
|
||||
end
|
||||
end
|
||||
|
||||
# Buscar bitácora para hoy (o crearla)
|
||||
fecha_hoy = Date.today.to_s
|
||||
bitacora = db.find_bitacora_by_fecha(fecha_hoy)
|
||||
# Resolver ámbito si se especificó
|
||||
ambito_id = nil
|
||||
if opts[:ambito]
|
||||
ambito = db.get_ambito(opts[:ambito])
|
||||
if ambito
|
||||
ambito_id = ambito['id']
|
||||
puts " #{Color::DIM}Ámbito encontrado: #{opts[:ambito]} (ID: #{ambito_id})#{Color::RESET}"
|
||||
else
|
||||
puts "#{Color::YELLOW}⚠ Ámbito '#{opts[:ambito]}' no encontrado#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
|
||||
# Determinar fecha: --fecha o hoy
|
||||
fecha_target = opts[:fecha] ? opts[:fecha] : Date.today.to_s
|
||||
if opts[:fecha]
|
||||
begin
|
||||
Date.parse(opts[:fecha])
|
||||
puts " #{Color::DIM}ℹ Fecha especificada: #{fecha_target}#{Color::RESET}"
|
||||
rescue Date::Error
|
||||
puts "#{Color::RED}✗ Formato de fecha inválido: #{opts[:fecha]}. Usar YYYY-MM-DD#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
bitacora = db.find_bitacora_by_fecha(fecha_target)
|
||||
|
||||
if bitacora.nil?
|
||||
puts " #{Color::DIM}Creando bitácora para hoy...#{Color::RESET}"
|
||||
bitacora = db.create_bitacora(fecha: fecha_hoy)
|
||||
puts " #{Color::DIM}Creando bitácora para #{fecha_target}...#{Color::RESET}"
|
||||
bitacora = db.create_bitacora(fecha: fecha_target)
|
||||
end
|
||||
|
||||
bitacora_id = bitacora['id']
|
||||
|
||||
# Obtener modo desde jornada actual si no se especificó
|
||||
if modo_resuelto.nil?
|
||||
entradas = db.list_entradas(bitacora_id: bitacora_id, limit: 10)
|
||||
jornada = entradas.find { |e| e['descripcion']&.downcase&.include?('inicio de jornada') }
|
||||
if jornada && jornada['modo']
|
||||
modo_resuelto = jornada['modo']
|
||||
puts " #{Color::DIM}ℹ Modo resuelto desde jornada: #{modo_resuelto == 'P' ? 'Presencial' : 'Remoto'}#{Color::RESET}"
|
||||
if bitacora && bitacora['modo']
|
||||
modo_resuelto = bitacora['modo']
|
||||
puts " #{Color::DIM}ℹ Modo resuelto desde jornada: #{modo_resuelto == 'P' ? 'Presencial' : (modo_resuelto == 'R' ? 'Remoto' : 'Servicio')}#{Color::RESET}"
|
||||
else
|
||||
modo_resuelto = 'P' # default
|
||||
puts " #{Color::DIM}ℹ Sin jornada activa, usando default: Presencial#{Color::RESET}"
|
||||
@@ -128,7 +184,20 @@ module ADN
|
||||
tema_id: nil # Por ahora sin tema
|
||||
)
|
||||
|
||||
# Asignar doc_path si se especificó
|
||||
if opts[:doc_path] && entrada
|
||||
db.update_entrada(entrada['id'], { doc_path: opts[:doc_path] })
|
||||
puts " #{Color::DIM}ℹ Doc vinculado: #{opts[:doc_path]}#{Color::RESET}"
|
||||
end
|
||||
|
||||
# Asignar ámbito si se especificó
|
||||
if ambito_id && entrada
|
||||
db.update_entrada(entrada['id'], { ambito_id: ambito_id })
|
||||
puts " #{Color::DIM}ℹ Ámbito asignado (ID: #{ambito_id})#{Color::RESET}"
|
||||
end
|
||||
|
||||
puts "#{Color::GREEN}✅ Entrada creada exitosamente#{Color::RESET}"
|
||||
puts " #{Color::BOLD}Fecha:#{Color::RESET} #{fecha_target}"
|
||||
puts " #{Color::BOLD}Inicio:#{Color::RESET} #{entrada['inicio']}"
|
||||
puts " #{Color::BOLD}Fin:#{Color::RESET} #{entrada['fin'] || '(sin fin)'}"
|
||||
puts " 📝 #{Color::BOLD}Descripción:#{Color::RESET} #{entrada['descripcion']}"
|
||||
@@ -141,7 +210,7 @@ module ADN
|
||||
def comando_evento_actualizar(args)
|
||||
if args.length < 1 || args[0].start_with?('-')
|
||||
puts "#{Color::YELLOW}Uso: adn db evento:actualizar <ID> [OPCIONES]#{Color::RESET}"
|
||||
puts "Opciones soportadas: --fin \"HH:MM\", --inicio \"HH:MM\", --estado \"✅\", --descripcion \"Texto\", --nodo \"Nombre\""
|
||||
puts "Opciones soportadas: --fin \"HH:MM[:SS]\", --inicio \"HH:MM[:SS]\", --estado \"✅\", --descripcion \"Texto\", --nodo \"Nombre\""
|
||||
return
|
||||
end
|
||||
|
||||
@@ -150,12 +219,14 @@ module ADN
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Opciones:"
|
||||
opt.on("--inicio HH:MM", "Nueva hora de inicio") { |i| opts[:inicio] = i }
|
||||
opt.on("--fin HH:MM", "Nueva hora de fin") { |f| opts[:fin] = f }
|
||||
opt.on("--inicio HH:MM[:SS]", "Nueva hora de inicio") { |i| opts[:inicio] = i }
|
||||
opt.on("--fin HH:MM[:SS]", "Nueva hora de fin") { |f| opts[:fin] = f }
|
||||
opt.on("--descripcion TEXTO", "Nueva descripción") { |d| opts[:descripcion] = d }
|
||||
opt.on("--estado ESTADO", "Nuevo estado (⏳, ✅, ❌, ⚠️, ⏸️)") { |e| opts[:estado] = e }
|
||||
opt.on("--modo MODO", "Modo (P=Presencial, R=Remoto)") { |m| opts[:modo] = m }
|
||||
opt.on("--nodo NOMBRE", "Nuevo nodo") { |n| opts[:nodo_raw] = n }
|
||||
opt.on("--ambito NOMBRE_O_ID", "Nuevo ámbito") { |a| opts[:ambito_raw] = a }
|
||||
opt.on("--doc RUTA", "Ruta al plan .md vinculado") { |d| opts[:doc_path] = d }
|
||||
opt.on("-h", "--help", "Mostrar ayuda") do
|
||||
puts opt
|
||||
exit
|
||||
@@ -199,6 +270,20 @@ module ADN
|
||||
opts.delete(:nodo_raw)
|
||||
end
|
||||
|
||||
if opts.key?(:ambito_raw)
|
||||
if opts[:ambito_raw].nil? || opts[:ambito_raw].empty?
|
||||
opts[:ambito_id] = nil
|
||||
else
|
||||
ambito = db.get_ambito(opts[:ambito_raw])
|
||||
if ambito
|
||||
opts[:ambito_id] = ambito['id']
|
||||
else
|
||||
puts "#{Color::YELLOW}⚠ Ámbito '#{opts[:ambito_raw]}' no encontrado#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
opts.delete(:ambito_raw)
|
||||
end
|
||||
|
||||
begin
|
||||
res = db.update_entrada(id_evento, opts)
|
||||
if res
|
||||
@@ -232,6 +317,9 @@ module ADN
|
||||
opt.on("--nodo NOMBRE", "Filtrar por nombre de nodo") do |n|
|
||||
opts[:nodo] = n
|
||||
end
|
||||
opt.on("--ambito NOMBRE_O_ID", "Filtrar por ámbito") do |a|
|
||||
opts[:ambito] = a
|
||||
end
|
||||
opt.on("--estado ESTADO", "Filtrar por estado (⏳, ✅, ❌, ⚠️, ⏸️)") do |e|
|
||||
opts[:estado] = e
|
||||
end
|
||||
@@ -276,7 +364,20 @@ module ADN
|
||||
end
|
||||
end
|
||||
|
||||
# Obtener ID de ámbito si se especificó nombre
|
||||
ambito_id = nil
|
||||
if opts[:ambito]
|
||||
ambito = db.get_ambito(opts[:ambito])
|
||||
if ambito
|
||||
ambito_id = ambito['id']
|
||||
puts " #{Color::DIM}Filtrando por ámbito: #{opts[:ambito]} (ID: #{ambito_id})#{Color::RESET}"
|
||||
else
|
||||
puts "#{Color::YELLOW}⚠ Ámbito '#{opts[:ambito]}' no encontrado#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
|
||||
entradas = db.list_entradas(
|
||||
ambito_id: ambito_id,
|
||||
nodo_id: nodo_id,
|
||||
estado: opts[:estado],
|
||||
buscar: opts[:buscar],
|
||||
@@ -296,9 +397,9 @@ module ADN
|
||||
entradas.each_with_index do |e, i|
|
||||
puts "#{Color::DIM}#{"─" * 60}#{Color::RESET}" if i > 0
|
||||
id = e['id'].to_s
|
||||
inicio = e['inicio'][0..7]
|
||||
fin = e['fin'] ? e['fin'][0..7] : '--:--'
|
||||
nodo = e['nodo_nombre'] || '(sin nodo)'
|
||||
inicio = e['inicio'] ? e['inicio'][0..7] : '--:--:--'
|
||||
fin = e['fin'] ? e['fin'][0..7] : '--:--:--'
|
||||
nodo = "#{e['ambito_nombre'] || '-'} / #{e['nodo_nombre'] || '-'}"
|
||||
estado = e['estado']
|
||||
desc = e['descripcion']
|
||||
|
||||
@@ -306,22 +407,23 @@ module ADN
|
||||
puts " #{desc}"
|
||||
end
|
||||
else
|
||||
# Formato tabla compacta (original)
|
||||
# Formato tabla compacta (original ajustado)
|
||||
puts ""
|
||||
puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Inicio".ljust(8)} #{"Fin".ljust(8)} #{"Nodo".ljust(15)} #{"Estado".ljust(3)} #{"Descripción".ljust(40)}#{Color::RESET}"
|
||||
puts "#{"─" * 5} #{"─" * 8} #{"─" * 8} #{"─" * 15} #{"─" * 3} #{"─" * 40}"
|
||||
puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Inicio".ljust(9)} #{"Fin".ljust(9)} #{"Ámbito/Nodo".ljust(25)} #{"Est".ljust(3)} #{"Descripción".ljust(35)}#{Color::RESET}"
|
||||
puts "#{"─" * 5} #{"─" * 9} #{"─" * 9} #{"─" * 25} #{"─" * 3} #{"─" * 35}"
|
||||
|
||||
entradas.each do |e|
|
||||
id = e['id'].to_s
|
||||
inicio = e['inicio'][0..7] # HH:MM:SS -> HH:MM
|
||||
inicio = e['inicio'] ? e['inicio'][0..7] : '--:--'
|
||||
fin = e['fin'] ? e['fin'][0..7] : '--:--'
|
||||
nodo = e['nodo_nombre'] || '(sin nodo)'
|
||||
nodo = nodo[0..13] + '...' if nodo.length > 15
|
||||
nodo = "#{e['ambito_nombre'] || '-'} / #{e['nodo_nombre'] || '-'}"
|
||||
nodo = nodo[0..23] + '...' if nodo.length > 25
|
||||
estado = e['estado']
|
||||
desc = e['descripcion']
|
||||
desc = desc[0..37] + '...' if desc.length > 40
|
||||
desc = e['descripcion'] || ''
|
||||
desc = desc.split("\n").first || '' # Tomar solo la primera línea
|
||||
desc = desc[0..32] + '...' if desc.length > 35
|
||||
|
||||
puts "#{id.ljust(5)} #{inicio.ljust(8)} #{fin.ljust(8)} #{nodo.ljust(15)} #{estado.ljust(3)} #{desc.ljust(40)}"
|
||||
puts "#{id.ljust(5)} #{inicio.ljust(9)} #{fin.ljust(9)} #{nodo.ljust(25)} #{estado.ljust(3)} #{desc.ljust(35)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -397,13 +499,13 @@ module ADN
|
||||
|
||||
entradas.each do |e|
|
||||
id = e['id'].to_s
|
||||
inicio = e['inicio'][0..4] # HH:MM:SS -> HH:MM
|
||||
inicio = e['inicio'] ? e['inicio'][0..7] : '--:--:--' # HH:MM:SS
|
||||
nodo = (e['nodo_nombre'] || '(sin nodo)')[0..14]
|
||||
desc = e['descripcion']
|
||||
desc = desc[0..47] + '...' if desc.length > 50
|
||||
estado_icon = e['estado']
|
||||
|
||||
puts " #{estado_icon} #{Color::YELLOW}ID #{id.ljust(5)}#{Color::RESET} #{inicio.ljust(5)} #{nodo.ljust(15)} #{desc}"
|
||||
puts " #{estado_icon} #{Color::YELLOW}ID #{id.ljust(5)}#{Color::RESET} #{inicio.ljust(11)} #{nodo.ljust(15)} #{desc}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -86,8 +86,9 @@ module ADN
|
||||
puts " #{Color::DIM}Entradas completadas:#{Color::RESET} #{porcentaje_completadas}%"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def comando_backup(args)
|
||||
def comando_backup(args)
|
||||
opts = { output: nil, compress: true, timestamp: true }
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
@@ -185,10 +186,45 @@ module ADN
|
||||
end
|
||||
end
|
||||
|
||||
def comando_migrar_sql(args)
|
||||
opts = {}
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Uso: adn db migrar:sql ARCHIVO_SQL"
|
||||
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
|
||||
|
||||
sql_file = args.first
|
||||
if sql_file.nil? || !File.exist?(sql_file)
|
||||
puts "#{Color::RED}✗ Error: Archivo SQL no especificado o no encontrado.#{Color::RESET}"
|
||||
puts parser
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}🔄 Ejecutando script SQL: #{sql_file}...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
sql = File.read(sql_file)
|
||||
db.send(:execute, sql)
|
||||
puts "#{Color::GREEN}✅ Script SQL ejecutado exitosamente.#{Color::RESET}"
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error ejecutando SQL: #{e.message}#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Comando: archivar:md - Mover bitácoras y proyectos MD importados/obsoletos a carpetas _hist
|
||||
def comando_archivar_md
|
||||
require 'fileutils'
|
||||
|
||||
+121
-5
@@ -21,6 +21,9 @@ module ADN
|
||||
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
|
||||
@@ -45,18 +48,30 @@ module ADN
|
||||
|
||||
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]
|
||||
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'}"
|
||||
@@ -86,6 +101,9 @@ module ADN
|
||||
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
|
||||
@@ -103,7 +121,18 @@ module ADN
|
||||
puts "#{Color::CYAN}🖥️ Listando nodos...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
nodos = db.list_nodos(activo: opts[:activo], tipo: opts[:tipo], limit: 100)
|
||||
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}"
|
||||
@@ -111,19 +140,21 @@ module ADN
|
||||
end
|
||||
|
||||
puts ""
|
||||
puts "#{Color::BOLD}#{"ID".ljust(5)} #{"Nombre".ljust(20)} #{"IP".ljust(15)} #{"Tipo".ljust(12)} #{"Activo".ljust(6)} #{"Descripción".ljust(30)}#{Color::RESET}"
|
||||
puts "#{"─" * 5} #{"─" * 20} #{"─" * 15} #{"─" * 12} #{"─" * 6} #{"─" * 30}"
|
||||
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)} #{ip.ljust(15)} #{tipo.ljust(12)} #{activo.ljust(6)} #{desc.ljust(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 ""
|
||||
@@ -135,5 +166,90 @@ module ADN
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user