Compare commits

...

9 Commits

Author SHA1 Message Date
3798f911f1 fix: Corregir formato de service_name en WebSocket de logs
Problema:
- WebSocket de logs usaba formato incorrecto: {app_name}.service
- Debería ser: siax-app-{app_name}.service
- Esto causaba que journalctl no encontrara el servicio
- Los logs de aplicaciones NO funcionaban

Solución:
- Corregir format!() en websocket.rs línea 96
- Ahora: format!("siax-app-{}.service", app_name)
- journalctl ahora busca el servicio correcto

Los logs de aplicaciones ahora funcionan correctamente vía:
journalctl -u siax-app-IDEAS.service -f --output=json -n 50
2026-01-18 04:12:30 -05:00
fbc89e9bf0 feat: Agregar sistema de tabs en logs.html con visualización de errores del sistema
Backend (handlers.rs + main.rs):
- Nuevo endpoint GET /api/logs/errors
- Lee logs/errors.log y retorna últimas 500 líneas
- Parsea y formatea logs con niveles (INFO, WARN, ERROR)

Frontend (logs.html):
- Sistema de tabs con 2 pestañas:
  * Tab 1: "Logs de App" - logs en tiempo real vía WebSocket (journalctl)
  * Tab 2: "Errores del Sistema" - logs del archivo errors.log
- Carga apps desde /api/apps (ya usaba el JSON correctamente)
- Colorización por nivel de log:
  * ERROR = rojo
  * WARN = amarillo
  * INFO = azul
- Auto-scroll en ambos tabs
- Diseño consistente con el resto de la UI

Ahora logs.html muestra:
 Logs de aplicaciones individuales (systemd/journalctl)
 Logs de errores del sistema SIAX Monitor (logs/errors.log)
 Navegación por tabs
 Lista de apps desde monitored_apps.json
2026-01-18 04:07:37 -05:00
868f3a2d30 feat: Agregar controles de Iniciar/Detener/Reiniciar en panel web
Cambios en el frontend (index.html):
- Cambiar header "Actions" a "Acciones"
- Agregar botones de control según estado de la app:
  * Si está Running: botones Detener (rojo) y Reiniciar (amarillo)
  * Si está Stopped: botón Iniciar (verde)
  * Siempre: botón Ver logs (azul)
- Agregar función controlApp() para llamar a la API
- Diálogo de confirmación antes de ejecutar acciones
- Recarga automática de la tabla después de ejecutar acción

Cambios en el backend (lifecycle.rs):
- Corregir formato de service_name en start_app()
- Corregir formato de service_name en stop_app()
- Corregir formato de service_name en restart_app()
- Ahora usa: siax-app-{app_name}.service en lugar de {app_name}.service

Los botones ahora funcionan correctamente con los servicios systemd
2026-01-18 03:55:07 -05:00
87ce154789 fix: Corregir renderizado de apps en index.html
Problema:
- La tabla mostraba [object Object] en lugar del nombre de la app
- El estado siempre aparecía como Unknown
- No usaba las propiedades del objeto JSON (name, status, port, service_name)

Solución:
- Actualizar displayApps() para acceder a app.name, app.status, app.service_name
- Agregar badges de colores según estado:
  * Running: verde
  * Stopped: gris
  * Failed: rojo
  * Starting: azul
  * Stopping: amarillo
  * Unknown: gris
- Cambiar botón de more_vert a visibility para ver logs
- Mostrar service_name debajo del nombre de la app

Ahora la tabla muestra correctamente la información de las apps detectadas
2026-01-18 03:49:53 -05:00
f9e6439b24 fix: Leer apps desde monitored_apps.json en lugar de AppManager en memoria
Problema:
- El panel web y logs.html no mostraban las apps descubiertas
- /api/apps solo listaba apps registradas vía API (AppManager en memoria)
- Las apps descubiertas por discovery solo estaban en monitored_apps.json
- El AppManager no conocía las apps existentes en systemd

Solución:
- Modificar /api/apps para leer directamente desde monitored_apps.json
- Consultar estado de systemd en tiempo real para cada app
- Modificar get_app_status para leer desde JSON y consultar métricas
- Buscar procesos por nombre de app o entry_point

Cambios:
- list_apps_handler: Lee desde ConfigManager, consulta systemd
- get_app_status_handler: Lee desde JSON, obtiene PID/CPU/RAM de sysinfo
- Retorna status correcto: Running, Stopped, Failed, Starting, Stopping
- Incluye puerto y service_name en respuesta de /api/apps

Ahora el panel web mostrará todas las apps (descubiertas + registradas)
2026-01-18 03:43:53 -05:00
246b5c8342 feat: Mejorar logging del discovery y agregar endpoint /api/monitored
- Agregar logs detallados en discovery.rs:
  * Mostrar cuántos archivos se escanean
  * Mostrar cuántos servicios siax-app-* se encuentran
  * Mostrar cuántos se parsean exitosamente
  * Logs tanto en logger como en stdout para debugging

- Agregar endpoint GET /api/monitored:
  * Retorna el contenido completo de monitored_apps.json
  * Permite verificar qué apps están siendo monitoreadas
  * Útil para debugging y diagnóstico

- Mejorar mensajes de error con emojis para mejor visibilidad
- Logs en cada paso del proceso de sincronización
2026-01-18 03:40:19 -05:00
8822e9e6b5 feat: Mejorar estructura de monitored_apps.json con metadata completa
- Añadir campos al modelo MonitoredApp:
  * service_name: Nombre del servicio systemd
  * path: WorkingDirectory de la aplicación
  * entry_point: Archivo de entrada (server.js, app.js, etc.)
  * node_bin: Ruta completa al binario de node/python
  * mode: Modo de ejecución (production, development, test)
  * service_file_path: Ruta al archivo .service de systemd
  * registered_at: Timestamp de registro (ISO 8601)

- Actualizar discovery.rs para extraer toda la información:
  * Parsear ExecStart para obtener node_bin y entry_point
  * Extraer NODE_ENV para determinar el modo
  * Guardar ruta completa al archivo .service
  * Usar add_app_full() con información completa

- Integrar ConfigManager en AppManager:
  * Guardar automáticamente en monitored_apps.json al registrar apps
  * Eliminar del JSON al desregistrar apps
  * Extraer metadata desde ServiceConfig (puerto, entry_point, mode, etc.)

- Mantener retrocompatibilidad con JSON antiguo mediante campos deprecated
- Todos los nuevos campos usan #[serde(default)] para evitar errores de deserialización
2026-01-18 03:26:42 -05:00
ad9b46bdc5 feat: Descubrimiento automático de servicios systemd existentes
Implementa escaneo automático de servicios siax-app-*.service al iniciar
el agente, sincronizando automáticamente con monitored_apps.json.

PROBLEMA RESUELTO:
- Servicios systemd creados manualmente no eran detectados
- monitored_apps.json desincronizado con servicios reales
- app_IDEAS.service existía pero NO era monitoreada
- Requería agregar manualmente cada app al JSON

SOLUCIÓN IMPLEMENTADA:

Nuevo módulo src/discovery.rs:

1. discover_services():
   - Escanea /etc/systemd/system/
   - Busca archivos siax-app-*.service
   - Parsea configuración (User, WorkingDirectory, ExecStart, PORT)
   - Retorna lista de DiscoveredService

2. parse_service_file():
   - Lee archivo .service línea por línea
   - Extrae: WorkingDirectory, User, ExecStart
   - Extrae PORT de Environment="PORT=XXXX"
   - Maneja múltiples formatos

3. sync_discovered_services():
   - Compara con monitored_apps.json actual
   - Agrega solo servicios nuevos (evita duplicados)
   - Detecta puerto automáticamente:
     - Desde Environment=PORT=XXXX
     - Desde nombre de app (fallback)
   - Guarda en monitored_apps.json

FLUJO AL INICIAR:
1. Logger inicia
2. 🔍 Discovery escanea /etc/systemd/system/
3.  Encuentra: siax-app-IDEAS, siax-app-TAREAS, siax-app-fidelizacion
4. 📝 Sincroniza con monitored_apps.json
5. ConfigManager carga JSON actualizado
6. Monitor comienza vigilancia de TODAS las apps

LOGS GENERADOS:
📡 "Escaneando servicios systemd existentes..."
 "Encontrado: siax-app-IDEAS.service"
📊 "App: IDEAS, User: Some("user_apps"), WorkDir: Some("/path")"
 "Agregando IDEAS (puerto: 2000)"
 "IDEAS agregado exitosamente"
📊 "Resumen: 1 agregadas, 2 ya existían"

DETECCIÓN DE PUERTO:
1. Lee Environment=PORT=XXXX del .service
2. Si no existe, usa detección por nombre:
   - tareas → 3000
   - fidelizacion → 3001
   - ideas → 2000
   - otros → 8080 (default)

BENEFICIOS:
 Auto-descubre servicios existentes al iniciar
 Sincroniza monitored_apps.json automáticamente
 No requiere intervención manual
 Evita duplicados (compara antes de agregar)
 Extrae configuración del .service
 Logging detallado de proceso
 Tests unitarios incluidos

EJEMPLO DE USO:
# Crear servicio manualmente
sudo nano /etc/systemd/system/siax-app-NUEVA.service
sudo systemctl daemon-reload
sudo systemctl start siax-app-NUEVA

# Reiniciar agente → Auto-descubre la nueva app
sudo systemctl restart siax-agent

# Verificar que fue detectada
cat /opt/siax-agent/config/monitored_apps.json
# Ahora incluye: NUEVA

Archivos modificados:
- src/discovery.rs: +265 líneas (nuevo módulo)
- src/lib.rs: +2 líneas (export discovery)
- src/main.rs: +9 líneas (ejecuta discovery al inicio)
2026-01-18 03:03:22 -05:00
b6fa1fa472 feat: Mejora generador de servicios systemd con soporte completo NVM
Mejora la generación de archivos .service para incluir automáticamente
el PATH de NVM y NODE_ENV, siguiendo las mejores prácticas de systemd.

PROBLEMA RESUELTO:
- Servicios generados no incluían Environment=PATH con NVM
- Faltaba NODE_ENV=production por defecto
- Variables de entorno desordenadas
- Sin logging de detección de NVM

MEJORAS IMPLEMENTADAS:

1. Auto-detección de NVM en ejecutable:
   - Si el ejecutable contiene '/.nvm/', extrae el directorio bin
   - Agrega Environment=PATH=/path/to/.nvm/.../bin:...
   - Log: "Agregando PATH de NVM: /path/to/bin"

2. NODE_ENV por defecto:
   - Apps Node.js obtienen NODE_ENV=production automáticamente
   - Solo si el usuario no lo definió explícitamente
   - Previene errores de módulos dev en producción

3. Orden lógico de variables:
   - PATH primero (crítico para encontrar node/npm)
   - NODE_ENV segundo
   - Variables del usuario después
   - ExecStart después de todas las env vars

4. Construcción mejorada del servicio:
   - Usa StringBuilder para mejor control
   - Separa secciones lógicamente
   - Más fácil de leer y mantener

SERVICIO GENERADO (ejemplo):

[Unit]
Description=App para gestionar Tareas
After=network.target

[Service]
Type=simple
User=user_apps
WorkingDirectory=/home/user_apps/apps/app_tareas
Environment=PATH=/home/user_apps/.nvm/versions/node/v24.12.0/bin:/usr/local/bin:/usr/bin:/bin
Environment=NODE_ENV=production
Environment="PORT=3000"
ExecStart=/home/user_apps/.nvm/versions/node/v24.12.0/bin/npm start

Restart=always
RestartSec=10
SyslogIdentifier=siax-app-TAREAS

[Install]
WantedBy=multi-user.target

COMANDOS SYSTEMD (ejecutados automáticamente por AppManager):
 sudo systemctl daemon-reload
 sudo systemctl enable siax-app-TAREAS.service
 sudo systemctl start siax-app-TAREAS.service

BENEFICIOS:
 Servicios funcionan con NVM sin configuración manual
 PATH correcto para encontrar node/npm
 NODE_ENV=production mejora rendimiento y seguridad
 Logging claro cuando se detecta NVM
 Orden profesional de variables de entorno

Archivos modificados:
- src/systemd/service_generator.rs: +50/-20 líneas
- test_service_generation.sh: nuevo (ejemplo de uso)
2026-01-18 02:58:15 -05:00
28 changed files with 4058 additions and 686 deletions

View File

@@ -1,347 +1,51 @@
#!/bin/bash
#######################################
# SIAX Agent - Script de Despliegue
# Instalación automática production-ready
#######################################
# --- CONFIGURACIÓN ---
BINARY_NAME="siax_monitor"
TARGET="x86_64-unknown-linux-gnu"
LOCAL_PATH="target/$TARGET/release/$BINARY_NAME"
set -e # Salir si hay errores
# 1. Preguntar método de transferencia
echo "Selecciona el método de transferencia:"
select METODO in "scp" "rsync"; do
case $METODO in
scp|rsync) break ;;
*) echo "Opción inválida, elige 1 o 2." ;;
esac
done
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 2. Compilar
echo "📦 Compilando..."
cargo build --release --target $TARGET
if [ $? -ne 0 ]; then echo "❌ Error en compilación"; exit 1; fi
# Variables
INSTALL_DIR="/opt/siax-agent"
SERVICE_USER="siax-agent"
BACKUP_DIR="/tmp/siax-agent-backup-$(date +%s)"
# --- FUNCIÓN DE SUBIDA ---
upload_file() {
local IP=$1
local USER=$2
local DEST=$3
#######################################
# Funciones
#######################################
echo "🚀 Subiendo a $USER@$IP vía $METODO..."
print_header() {
echo -e "${BLUE}"
echo "============================================"
echo " SIAX Agent - Deployment Script"
echo "============================================"
echo -e "${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
check_root() {
if [ "$EUID" -ne 0 ]; then
print_error "Este script debe ejecutarse como root"
echo "Usa: sudo ./desplegar_agent.sh"
exit 1
fi
}
check_dependencies() {
print_info "Verificando dependencias..."
local deps=("systemctl" "cargo" "rustc")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v $dep &> /dev/null; then
missing+=($dep)
fi
done
if [ ${#missing[@]} -ne 0 ]; then
print_error "Faltan dependencias: ${missing[*]}"
echo ""
echo "Instalación de Rust:"
echo " curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
echo ""
echo "Instalación de systemd (debería estar instalado por defecto):"
echo " sudo apt-get install systemd # Debian/Ubuntu"
echo " sudo yum install systemd # RedHat/CentOS"
exit 1
fi
print_success "Todas las dependencias están instaladas"
}
backup_existing() {
if [ -d "$INSTALL_DIR" ]; then
print_warning "Instalación existente detectada"
print_info "Creando backup en: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
cp -r "$INSTALL_DIR" "$BACKUP_DIR/"
print_success "Backup creado"
fi
}
compile_release() {
print_info "Compilando SIAX Agent en modo release..."
if cargo build --release; then
print_success "Compilación exitosa"
if [ "$METODO" = "scp" ]; then
scp "$LOCAL_PATH" "$USER@$IP:$DEST/"
else
print_error "Error en la compilación"
rollback
exit 1
# rsync -avz: a (archivo/permisos), v (visual), z (comprimido)
rsync -avz "$LOCAL_PATH" "$USER@$IP:$DEST/"
fi
}
create_user() {
if id "$SERVICE_USER" &>/dev/null; then
print_info "Usuario $SERVICE_USER ya existe"
if [ $? -eq 0 ]; then
echo "$IP: Completado."
else
print_info "Creando usuario del sistema: $SERVICE_USER"
useradd --system --no-create-home --shell /bin/false "$SERVICE_USER"
print_success "Usuario creado"
echo "$IP: Falló la subida."
fi
}
install_binary() {
print_info "Instalando binario en $INSTALL_DIR..."
# --- LISTA DE SERVIDORES ---
# Formato: upload_file "IP" "USUARIO" "RUTA_DESTINO"
upload_file "192.168.10.145" "root" "/root/app"
upload_file "192.168.10.150" "pablinux" "/home/pablinux/app"
upload_file "192.168.10.160" "user_apps" "/home/user_apps/apps"
mkdir -p "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR/config"
mkdir -p "$INSTALL_DIR/logs"
cp target/release/siax_monitor "$INSTALL_DIR/siax-agent"
chmod +x "$INSTALL_DIR/siax-agent"
# Copiar archivos de configuración si existen
if [ -f "config/monitored_apps.json" ]; then
cp config/monitored_apps.json "$INSTALL_DIR/config/"
fi
# Copiar archivos web
if [ -d "web" ]; then
cp -r web "$INSTALL_DIR/"
fi
# Permisos
chown -R $SERVICE_USER:$SERVICE_USER "$INSTALL_DIR"
print_success "Binario instalado"
}
configure_sudoers() {
print_info "Configurando permisos sudo para systemctl..."
local sudoers_file="/etc/sudoers.d/siax-agent"
cat > "$sudoers_file" << EOF
# SIAX Agent - Permisos para gestionar servicios systemd
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl start *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl stop *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl restart *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl status *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl enable *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl disable *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl is-active *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl list-unit-files *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/journalctl *
EOF
chmod 0440 "$sudoers_file"
# Validar sintaxis
if visudo -c -f "$sudoers_file" &>/dev/null; then
print_success "Configuración de sudoers creada"
else
print_error "Error en configuración de sudoers"
rm -f "$sudoers_file"
exit 1
fi
}
create_systemd_service() {
print_info "Creando servicio systemd para SIAX Agent..."
cat > /etc/systemd/system/siax-agent.service << EOF
[Unit]
Description=SIAX Agent - Process Monitor and Manager
After=network.target
[Service]
Type=simple
User=$SERVICE_USER
WorkingDirectory=$INSTALL_DIR
ExecStart=$INSTALL_DIR/siax-agent
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=$INSTALL_DIR/config $INSTALL_DIR/logs /etc/systemd/system
ProtectHome=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable siax-agent.service
print_success "Servicio systemd creado y habilitado"
}
verify_installation() {
print_info "Verificando instalación..."
local errors=0
# Verificar binario
if [ ! -f "$INSTALL_DIR/siax-agent" ]; then
print_error "Binario no encontrado"
((errors++))
fi
# Verificar permisos
if [ ! -r "$INSTALL_DIR/siax-agent" ]; then
print_error "Permisos incorrectos en binario"
((errors++))
fi
# Verificar servicio
if ! systemctl is-enabled siax-agent.service &>/dev/null; then
print_error "Servicio no habilitado"
((errors++))
fi
# Verificar sudoers
if [ ! -f "/etc/sudoers.d/siax-agent" ]; then
print_warning "Configuración de sudoers no encontrada"
echo " El agente podría tener problemas para gestionar servicios"
fi
if [ $errors -eq 0 ]; then
print_success "Verificación exitosa"
return 0
else
print_error "Verificación falló con $errors errores"
return 1
fi
}
start_service() {
print_info "Iniciando SIAX Agent..."
if systemctl start siax-agent.service; then
sleep 2
if systemctl is-active siax-agent.service &>/dev/null; then
print_success "SIAX Agent iniciado correctamente"
return 0
else
print_error "SIAX Agent no pudo iniciarse"
echo ""
echo "Ver logs con: journalctl -u siax-agent.service -n 50"
return 1
fi
else
print_error "Error al iniciar el servicio"
return 1
fi
}
rollback() {
print_warning "Ejecutando rollback..."
systemctl stop siax-agent.service 2>/dev/null || true
systemctl disable siax-agent.service 2>/dev/null || true
if [ -d "$BACKUP_DIR" ]; then
rm -rf "$INSTALL_DIR"
cp -r "$BACKUP_DIR/siax-agent" "$INSTALL_DIR"
systemctl start siax-agent.service 2>/dev/null || true
print_success "Rollback completado"
else
print_warning "No hay backup disponible para rollback"
fi
}
print_summary() {
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} ✅ SIAX Agent instalado exitosamente${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo "📊 Interface Web: http://localhost:8080"
echo "🔌 API REST: http://localhost:8081/api"
echo "📡 WebSocket: ws://localhost:8081/ws/logs/:app_name"
echo ""
echo "Comandos útiles:"
echo " Estado: sudo systemctl status siax-agent"
echo " Logs: sudo journalctl -u siax-agent -f"
echo " Reiniciar: sudo systemctl restart siax-agent"
echo " Detener: sudo systemctl stop siax-agent"
echo ""
echo "Directorio de instalación: $INSTALL_DIR"
echo "Configuración: $INSTALL_DIR/config/monitored_apps.json"
echo ""
}
#######################################
# Main
#######################################
main() {
print_header
check_root
check_dependencies
backup_existing
compile_release
create_user
install_binary
configure_sudoers
create_systemd_service
if verify_installation; then
if start_service; then
print_summary
exit 0
else
print_error "El servicio no pudo iniciarse correctamente"
print_info "Revisa los logs: journalctl -u siax-agent -n 50"
echo ""
echo "¿Deseas hacer rollback? (y/n)"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
rollback
fi
exit 1
fi
else
print_error "La verificación falló"
echo ""
echo "¿Deseas hacer rollback? (y/n)"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
rollback
fi
exit 1
fi
}
main
echo "------------------------------------------------"
echo "Done!"

347
instalador.sh Executable file
View File

@@ -0,0 +1,347 @@
#!/bin/bash
#######################################
# SIAX Agent - Script de Despliegue
# Instalación automática production-ready
#######################################
set -e # Salir si hay errores
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Variables
INSTALL_DIR="/opt/siax-agent"
SERVICE_USER="siax-agent"
BACKUP_DIR="/tmp/siax-agent-backup-$(date +%s)"
#######################################
# Funciones
#######################################
print_header() {
echo -e "${BLUE}"
echo "============================================"
echo " SIAX Agent - Deployment Script"
echo "============================================"
echo -e "${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
check_root() {
if [ "$EUID" -ne 0 ]; then
print_error "Este script debe ejecutarse como root"
echo "Usa: sudo ./desplegar_agent.sh"
exit 1
fi
}
check_dependencies() {
print_info "Verificando dependencias..."
local deps=("systemctl" "cargo" "rustc")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v $dep &> /dev/null; then
missing+=($dep)
fi
done
if [ ${#missing[@]} -ne 0 ]; then
print_error "Faltan dependencias: ${missing[*]}"
echo ""
echo "Instalación de Rust:"
echo " curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
echo ""
echo "Instalación de systemd (debería estar instalado por defecto):"
echo " sudo apt-get install systemd # Debian/Ubuntu"
echo " sudo yum install systemd # RedHat/CentOS"
exit 1
fi
print_success "Todas las dependencias están instaladas"
}
backup_existing() {
if [ -d "$INSTALL_DIR" ]; then
print_warning "Instalación existente detectada"
print_info "Creando backup en: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
cp -r "$INSTALL_DIR" "$BACKUP_DIR/"
print_success "Backup creado"
fi
}
compile_release() {
print_info "Compilando SIAX Agent en modo release..."
if cargo build --release; then
print_success "Compilación exitosa"
else
print_error "Error en la compilación"
rollback
exit 1
fi
}
create_user() {
if id "$SERVICE_USER" &>/dev/null; then
print_info "Usuario $SERVICE_USER ya existe"
else
print_info "Creando usuario del sistema: $SERVICE_USER"
useradd --system --no-create-home --shell /bin/false "$SERVICE_USER"
print_success "Usuario creado"
fi
}
install_binary() {
print_info "Instalando binario en $INSTALL_DIR..."
mkdir -p "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR/config"
mkdir -p "$INSTALL_DIR/logs"
cp target/release/siax_monitor "$INSTALL_DIR/siax-agent"
chmod +x "$INSTALL_DIR/siax-agent"
# Copiar archivos de configuración si existen
if [ -f "config/monitored_apps.json" ]; then
cp config/monitored_apps.json "$INSTALL_DIR/config/"
fi
# Copiar archivos web
if [ -d "web" ]; then
cp -r web "$INSTALL_DIR/"
fi
# Permisos
chown -R $SERVICE_USER:$SERVICE_USER "$INSTALL_DIR"
print_success "Binario instalado"
}
configure_sudoers() {
print_info "Configurando permisos sudo para systemctl..."
local sudoers_file="/etc/sudoers.d/siax-agent"
cat > "$sudoers_file" << EOF
# SIAX Agent - Permisos para gestionar servicios systemd
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl start *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl stop *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl restart *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl status *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl enable *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl disable *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl is-active *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl list-unit-files *
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/journalctl *
EOF
chmod 0440 "$sudoers_file"
# Validar sintaxis
if visudo -c -f "$sudoers_file" &>/dev/null; then
print_success "Configuración de sudoers creada"
else
print_error "Error en configuración de sudoers"
rm -f "$sudoers_file"
exit 1
fi
}
create_systemd_service() {
print_info "Creando servicio systemd para SIAX Agent..."
cat > /etc/systemd/system/siax-agent.service << EOF
[Unit]
Description=SIAX Agent - Process Monitor and Manager
After=network.target
[Service]
Type=simple
User=$SERVICE_USER
WorkingDirectory=$INSTALL_DIR
ExecStart=$INSTALL_DIR/siax-agent
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=$INSTALL_DIR/config $INSTALL_DIR/logs /etc/systemd/system
ProtectHome=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable siax-agent.service
print_success "Servicio systemd creado y habilitado"
}
verify_installation() {
print_info "Verificando instalación..."
local errors=0
# Verificar binario
if [ ! -f "$INSTALL_DIR/siax-agent" ]; then
print_error "Binario no encontrado"
((errors++))
fi
# Verificar permisos
if [ ! -r "$INSTALL_DIR/siax-agent" ]; then
print_error "Permisos incorrectos en binario"
((errors++))
fi
# Verificar servicio
if ! systemctl is-enabled siax-agent.service &>/dev/null; then
print_error "Servicio no habilitado"
((errors++))
fi
# Verificar sudoers
if [ ! -f "/etc/sudoers.d/siax-agent" ]; then
print_warning "Configuración de sudoers no encontrada"
echo " El agente podría tener problemas para gestionar servicios"
fi
if [ $errors -eq 0 ]; then
print_success "Verificación exitosa"
return 0
else
print_error "Verificación falló con $errors errores"
return 1
fi
}
start_service() {
print_info "Iniciando SIAX Agent..."
if systemctl start siax-agent.service; then
sleep 2
if systemctl is-active siax-agent.service &>/dev/null; then
print_success "SIAX Agent iniciado correctamente"
return 0
else
print_error "SIAX Agent no pudo iniciarse"
echo ""
echo "Ver logs con: journalctl -u siax-agent.service -n 50"
return 1
fi
else
print_error "Error al iniciar el servicio"
return 1
fi
}
rollback() {
print_warning "Ejecutando rollback..."
systemctl stop siax-agent.service 2>/dev/null || true
systemctl disable siax-agent.service 2>/dev/null || true
if [ -d "$BACKUP_DIR" ]; then
rm -rf "$INSTALL_DIR"
cp -r "$BACKUP_DIR/siax-agent" "$INSTALL_DIR"
systemctl start siax-agent.service 2>/dev/null || true
print_success "Rollback completado"
else
print_warning "No hay backup disponible para rollback"
fi
}
print_summary() {
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} ✅ SIAX Agent instalado exitosamente${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo "📊 Interface Web: http://localhost:8080"
echo "🔌 API REST: http://localhost:8081/api"
echo "📡 WebSocket: ws://localhost:8081/ws/logs/:app_name"
echo ""
echo "Comandos útiles:"
echo " Estado: sudo systemctl status siax-agent"
echo " Logs: sudo journalctl -u siax-agent -f"
echo " Reiniciar: sudo systemctl restart siax-agent"
echo " Detener: sudo systemctl stop siax-agent"
echo ""
echo "Directorio de instalación: $INSTALL_DIR"
echo "Configuración: $INSTALL_DIR/config/monitored_apps.json"
echo ""
}
#######################################
# Main
#######################################
main() {
print_header
check_root
check_dependencies
backup_existing
compile_release
create_user
install_binary
configure_sudoers
create_systemd_service
if verify_installation; then
if start_service; then
print_summary
exit 0
else
print_error "El servicio no pudo iniciarse correctamente"
print_info "Revisa los logs: journalctl -u siax-agent -n 50"
echo ""
echo "¿Deseas hacer rollback? (y/n)"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
rollback
fi
exit 1
fi
else
print_error "La verificación falló"
echo ""
echo "¿Deseas hacer rollback? (y/n)"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
rollback
fi
exit 1
fi
}
main

315
install-remote.sh Normal file
View File

@@ -0,0 +1,315 @@
#!/bin/bash
#######################################
# SIAX Agent - Script de Instalación Remota
# Descarga e instala SIAX Agent desde servidor central
#######################################
set -e # Salir si hay errores
# Variables (CONFIGURAR AQUÍ)
CENTRAL_SERVER="${SIAX_SERVER:-localhost:8080}" # Servidor central
INSTALL_DIR="/opt/siax-agent"
SERVICE_USER="siax-agent"
BACKUP_DIR="/tmp/siax-agent-backup-$(date +%s)"
DOWNLOAD_DIR="/tmp/siax-agent-download-$(date +%s)"
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
#######################################
# Funciones
#######################################
print_header() {
echo -e "${BLUE}"
echo "============================================"
echo " SIAX Agent - Remote Installation"
echo " Server: $CENTRAL_SERVER"
echo "============================================"
echo -e "${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
check_root() {
if [ "$EUID" -ne 0 ]; then
print_error "Este script debe ejecutarse como root"
echo "Usa: curl -sSL http://$CENTRAL_SERVER/install.sh | sudo bash"
echo "O con variable: curl -sSL http://$CENTRAL_SERVER/install.sh | sudo SIAX_SERVER=tu-servidor:8080 bash"
exit 1
fi
}
check_dependencies() {
print_info "Verificando dependencias..."
local deps=("systemctl" "curl")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v $dep &> /dev/null; then
missing+=($dep)
fi
done
if [ ${#missing[@]} -ne 0 ]; then
print_error "Faltan dependencias: ${missing[*]}"
echo ""
echo "Instalación en Debian/Ubuntu:"
echo " sudo apt-get update && sudo apt-get install -y curl systemd"
echo ""
echo "Instalación en RedHat/CentOS:"
echo " sudo yum install -y curl systemd"
exit 1
fi
print_success "Todas las dependencias están instaladas"
}
download_binary() {
print_info "Descargando binario desde $CENTRAL_SERVER..."
mkdir -p "$DOWNLOAD_DIR"
# Intentar descargar el binario pre-compilado
if curl -f -L -o "$DOWNLOAD_DIR/siax-agent" "http://$CENTRAL_SERVER/static/binary/siax-agent"; then
chmod +x "$DOWNLOAD_DIR/siax-agent"
print_success "Binario descargado"
else
print_error "No se pudo descargar el binario desde http://$CENTRAL_SERVER/static/binary/siax-agent"
echo ""
echo "Asegúrate de que:"
echo " 1. El servidor $CENTRAL_SERVER está accesible"
echo " 2. El binario está en web/static/binary/siax-agent"
echo " 3. Compilaste con: cargo build --release && cp target/release/siax_monitor web/static/binary/siax-agent"
rm -rf "$DOWNLOAD_DIR"
exit 1
fi
}
download_web_files() {
print_info "Descargando archivos web..."
mkdir -p "$DOWNLOAD_DIR/web"
# Descargar archivos HTML principales (opcional, solo si quieres que cada agente tenga su propia interfaz)
# Para agentes worker, probablemente no necesites esto
print_info "Archivos web no necesarios para worker nodes (omitiendo)"
}
backup_existing() {
if [ -d "$INSTALL_DIR" ]; then
print_warning "Instalación existente detectada"
print_info "Creando backup en: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
cp -r "$INSTALL_DIR" "$BACKUP_DIR/"
print_success "Backup creado"
fi
}
create_user() {
if id "$SERVICE_USER" &>/dev/null; then
print_info "Usuario $SERVICE_USER ya existe"
else
print_info "Creando usuario del sistema: $SERVICE_USER"
useradd --system --no-create-home --shell /bin/false "$SERVICE_USER"
print_success "Usuario creado"
fi
}
install_binary() {
print_info "Instalando binario en $INSTALL_DIR..."
mkdir -p "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR/config"
mkdir -p "$INSTALL_DIR/logs"
mkdir -p "$INSTALL_DIR/web/static"
# Copiar binario
cp "$DOWNLOAD_DIR/siax-agent" "$INSTALL_DIR/siax-agent"
chmod +x "$INSTALL_DIR/siax-agent"
# Crear configuración inicial vacía si no existe
if [ ! -f "$INSTALL_DIR/config/monitored_apps.json" ]; then
echo '{"apps":[]}' > "$INSTALL_DIR/config/monitored_apps.json"
fi
# Permisos
chown -R $SERVICE_USER:$SERVICE_USER "$INSTALL_DIR"
print_success "Binario instalado"
}
configure_sudoers() {
print_info "Configurando permisos sudo para systemctl..."
local sudoers_file="/etc/sudoers.d/siax-agent"
cat > "$sudoers_file" << 'EOF'
# SIAX Agent - Permisos para gestionar servicios systemd
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl start *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl stop *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl restart *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl status *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl enable *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl disable *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl is-active *
siax-agent ALL=(ALL) NOPASSWD: /bin/systemctl list-unit-files *
siax-agent ALL=(ALL) NOPASSWD: /bin/journalctl *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl start *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl list-unit-files *
siax-agent ALL=(ALL) NOPASSWD: /usr/bin/journalctl *
EOF
chmod 0440 "$sudoers_file"
# Validar sintaxis
if visudo -c -f "$sudoers_file" &>/dev/null; then
print_success "Configuración de sudoers creada"
else
print_error "Error en configuración de sudoers"
rm -f "$sudoers_file"
exit 1
fi
}
create_systemd_service() {
print_info "Creando servicio systemd para SIAX Agent..."
cat > /etc/systemd/system/siax-agent.service << EOF
[Unit]
Description=SIAX Agent - Process Monitor and Manager
After=network.target
[Service]
Type=simple
User=$SERVICE_USER
WorkingDirectory=$INSTALL_DIR
ExecStart=$INSTALL_DIR/siax-agent
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=$INSTALL_DIR/config $INSTALL_DIR/logs /etc/systemd/system
ProtectHome=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable siax-agent.service
print_success "Servicio systemd creado y habilitado"
}
start_service() {
print_info "Iniciando SIAX Agent..."
if systemctl start siax-agent.service; then
sleep 2
if systemctl is-active siax-agent.service &>/dev/null; then
print_success "SIAX Agent iniciado correctamente"
return 0
else
print_error "SIAX Agent no pudo iniciarse"
echo ""
echo "Ver logs con: journalctl -u siax-agent.service -n 50"
return 1
fi
else
print_error "Error al iniciar el servicio"
return 1
fi
}
cleanup() {
print_info "Limpiando archivos temporales..."
rm -rf "$DOWNLOAD_DIR"
print_success "Limpieza completada"
}
print_summary() {
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} ✅ SIAX Agent instalado exitosamente${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo "📊 Interface Web: http://localhost:8080"
echo "🔌 API REST: http://localhost:8080/api"
echo "📡 WebSocket: ws://localhost:8080/api/apps/:name/logs"
echo ""
echo "Comandos útiles:"
echo " Estado: sudo systemctl status siax-agent"
echo " Logs: sudo journalctl -u siax-agent -f"
echo " Reiniciar: sudo systemctl restart siax-agent"
echo " Detener: sudo systemctl stop siax-agent"
echo ""
echo "Directorio de instalación: $INSTALL_DIR"
echo "Configuración: $INSTALL_DIR/config/monitored_apps.json"
echo ""
echo "🌐 Servidor Central: $CENTRAL_SERVER"
echo ""
}
#######################################
# Main
#######################################
main() {
print_header
check_root
check_dependencies
backup_existing
download_binary
create_user
install_binary
configure_sudoers
create_systemd_service
if start_service; then
cleanup
print_summary
exit 0
else
print_error "El servicio no pudo iniciarse correctamente"
print_info "Revisa los logs: journalctl -u siax-agent -n 50"
cleanup
exit 1
fi
}
main

View File

@@ -117,3 +117,300 @@
[2026-01-13 08:16:16] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 08:16:16] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 08:16:16] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 11:34:33] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 11:34:33] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 11:34:33] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 11:34:33] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 11:37:28] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 11:37:28] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 11:37:28] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 11:37:28] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 11:43:43] [ERROR] [Monitor] Error enviando app_tareas | error sending request for url (https://api.siax-system.net/api/apps_servcs/apps): operation timed out
[2026-01-13 11:43:54] [ERROR] [Monitor] Error enviando fidelizacion | error sending request for url (https://api.siax-system.net/api/apps_servcs/apps): operation timed out
[2026-01-13 11:48:54] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 11:48:54] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 11:48:54] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 11:48:54] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 11:54:16] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 11:54:16] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 11:54:16] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 11:54:16] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 11:57:20] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 11:57:20] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 11:57:20] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 11:57:20] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 11:58:31] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 11:58:31] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 11:58:31] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 11:58:31] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:01:28] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:01:28] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:01:28] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:01:28] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:02:46] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:02:46] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:02:46] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:02:46] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:03:45] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:03:45] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:03:45] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:03:46] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:06:23] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:06:23] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:06:23] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:06:23] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:07:35] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:07:35] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:07:35] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:07:35] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:09:36] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:09:36] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:09:36] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:09:36] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:12:40] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:12:40] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:12:40] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:12:41] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:14:45] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:14:45] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:14:45] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:14:45] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:16:41] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:16:41] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:16:41] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:16:41] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:16:58] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:16:58] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:16:58] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:16:58] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:18:26] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:18:26] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:18:26] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:18:26] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:21:10] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:21:10] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:21:10] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:21:10] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:24:50] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:24:50] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:24:50] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:24:50] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:30:18] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:30:18] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:30:18] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:30:18] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:32:13] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:32:13] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:32:13] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:32:13] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 12:36:39] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 12:36:39] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 12:36:39] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 12:36:39] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 20:54:14] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 20:54:14] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 20:54:14] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 20:54:14] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 21:24:19] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 21:24:19] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 21:24:19] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 21:24:19] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 21:26:58] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 21:26:58] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 21:26:58] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 21:26:59] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 21:44:25] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 21:44:25] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 21:44:25] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 21:44:25] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 22:15:29] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 22:15:29] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 22:15:29] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 22:15:29] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-13 23:10:54] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-13 23:10:54] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-13 23:10:54] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-13 23:10:54] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:04:25] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:04:25] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:04:25] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:04:25] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:04:25] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:13:17] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:13:17] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:13:17] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:13:17] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:13:17] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:20:19] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:20:19] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:20:19] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:20:19] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:20:19] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:26:24] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:26:24] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:26:24] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:26:24] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:26:24] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:26:31] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:26:31] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:26:31] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:26:31] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:26:31] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:27:52] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:27:52] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:27:52] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:27:52] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:27:52] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:29:23] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:29:23] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:29:23] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:29:23] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:29:23] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:32:59] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:32:59] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:32:59] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:32:59] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:32:59] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:34:44] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:34:44] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:34:44] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:34:44] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:34:44] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:35:49] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:35:49] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:35:49] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:35:49] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:35:49] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:39:01] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:39:01] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:39:01] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:39:01] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:39:01] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:42:49] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:42:49] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:42:49] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:42:49] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:42:49] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:43:09] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:43:09] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:43:09] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:43:09] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:43:09] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:50:38] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:50:38] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:50:38] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:50:38] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:50:38] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 00:51:57] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 00:51:57] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 00:51:57] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 00:51:57] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 00:51:57] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 01:13:51] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 01:13:51] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 01:13:51] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 01:13:51] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 01:13:51] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 01:14:41] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 01:14:41] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 01:14:41] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 01:14:41] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 01:14:41] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 01:16:36] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 01:16:36] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 01:16:36] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 01:16:36] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 01:16:36] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 01:19:24] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 01:19:24] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 01:19:24] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 01:19:24] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 01:19:24] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 01:19:52] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 01:19:52] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 01:19:52] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 01:19:52] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 01:19:52] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-14 01:20:03] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-14 01:20:03] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-14 01:20:03] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-14 01:20:03] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-14 01:20:03] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-15 02:36:15] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-15 02:36:15] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-15 02:36:15] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-15 02:36:15] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-15 02:36:15] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-18 03:26:07] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-18 03:26:07] [INFO] [Sistema] Escaneando servicios systemd existentes...
[2026-01-18 03:26:07] [INFO] [Discovery] Escaneando servicios systemd existentes...
[2026-01-18 03:26:07] [INFO] [Discovery] ✅ Descubiertos 0 servicios
[2026-01-18 03:26:07] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
[2026-01-18 03:26:07] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-18 03:26:07] [INFO] [Sistema] Servidor detectado: siax-intel
[2026-01-18 03:26:07] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-18 03:26:07] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-18 03:26:07] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-18 03:26:07] [INFO] [Monitor] Buscando app app_tareas en API central...
[2026-01-18 03:26:07] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
[2026-01-18 03:26:07] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:26:07] [INFO] [Monitor] Buscando app fidelizacion en API central...
[2026-01-18 03:26:07] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
[2026-01-18 03:26:07] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:27:14] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-18 03:27:14] [INFO] [Sistema] Escaneando servicios systemd existentes...
[2026-01-18 03:27:14] [INFO] [Discovery] Escaneando servicios systemd existentes...
[2026-01-18 03:27:14] [INFO] [Discovery] ✅ Descubiertos 0 servicios
[2026-01-18 03:27:14] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
[2026-01-18 03:27:14] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-18 03:27:14] [INFO] [Sistema] Servidor detectado: siax-intel
[2026-01-18 03:27:14] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-18 03:27:14] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-18 03:27:15] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-18 03:27:15] [INFO] [Monitor] Buscando app app_tareas en API central...
[2026-01-18 03:27:15] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
[2026-01-18 03:27:15] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:27:15] [INFO] [Monitor] Buscando app fidelizacion en API central...
[2026-01-18 03:27:15] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
[2026-01-18 03:27:15] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:28:15] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
[2026-01-18 03:28:15] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:28:15] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
[2026-01-18 03:28:15] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:29:15] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
[2026-01-18 03:29:15] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:29:15] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
[2026-01-18 03:29:15] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:30:15] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
[2026-01-18 03:30:15] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:30:15] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
[2026-01-18 03:30:15] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:31:15] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
[2026-01-18 03:31:15] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 03:31:16] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
[2026-01-18 03:31:16] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 04:10:00] [INFO] [Sistema] Iniciando SIAX Agent
[2026-01-18 04:10:00] [INFO] [Sistema] Escaneando servicios systemd existentes...
[2026-01-18 04:10:00] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
[2026-01-18 04:10:00] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
[2026-01-18 04:10:00] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
[2026-01-18 04:10:00] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
[2026-01-18 04:10:00] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
[2026-01-18 04:10:00] [INFO] [Sistema] Servidor detectado: siax-intel
[2026-01-18 04:10:00] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
[2026-01-18 04:10:00] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
[2026-01-18 04:10:00] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
[2026-01-18 04:10:00] [INFO] [Monitor] Buscando app app_tareas en API central...
[2026-01-18 04:10:00] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
[2026-01-18 04:10:00] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 04:10:00] [INFO] [Monitor] Buscando app fidelizacion en API central...
[2026-01-18 04:10:00] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
[2026-01-18 04:10:00] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 04:11:00] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
[2026-01-18 04:11:00] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 04:11:00] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
[2026-01-18 04:11:00] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 04:12:00] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
[2026-01-18 04:12:00] [ERROR] [Monitor] Error sincronizando app_tareas | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}
[2026-01-18 04:12:00] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
[2026-01-18 04:12:00] [ERROR] [Monitor] Error sincronizando fidelizacion | HTTP 500 Internal Server Error: {"success":false,"message":"Error al actualizar el estado","error":"Table 'webControl.app_service_history' doesn't exist"}

66
preparar_binario.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
#######################################
# SIAX Agent - Preparar Binario para Distribución
# Compila y copia el binario a web/static/binary/
#######################################
set -e
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m'
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE} Preparando SIAX Agent para Distribución${NC}"
echo -e "${BLUE}============================================${NC}"
echo ""
# Compilar en release
echo -e "${BLUE}📦 Compilando en modo release...${NC}"
cargo build --release
if [ ! -f "target/release/siax_monitor" ]; then
echo -e "${RED}❌ Error: No se pudo compilar el binario${NC}"
exit 1
fi
echo -e "${GREEN}✅ Compilación exitosa${NC}"
echo ""
# Crear directorio para binarios
echo -e "${BLUE}📁 Creando directorio web/static/binary/${NC}"
mkdir -p web/static/binary
# Copiar binario
echo -e "${BLUE}📋 Copiando binario...${NC}"
cp target/release/siax_monitor web/static/binary/siax-agent
chmod +x web/static/binary/siax-agent
echo -e "${GREEN}✅ Binario copiado a web/static/binary/siax-agent${NC}"
echo ""
# Mostrar información
BINARY_SIZE=$(du -h web/static/binary/siax-agent | cut -f1)
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} ✅ Preparación completada${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo "📊 Tamaño del binario: $BINARY_SIZE"
echo "📂 Ubicación: web/static/binary/siax-agent"
echo ""
echo "🚀 Ahora puedes:"
echo ""
echo " 1. Iniciar el servidor:"
echo " cargo run --release"
echo ""
echo " 2. Desde otro servidor, instalar con:"
echo " curl -sSL http://TU-SERVIDOR:8080/install.sh | sudo bash"
echo ""
echo " O especificar el servidor:"
echo " curl -sSL http://TU-SERVIDOR:8080/install.sh | sudo SIAX_SERVER=TU-SERVIDOR:8080 bash"
echo ""
echo "Ejemplo VPN:"
echo " curl -sSL http://10.8.0.1:8080/install.sh | sudo SIAX_SERVER=10.8.0.1:8080 bash"
echo ""

View File

@@ -125,21 +125,62 @@ pub async fn restart_app_handler(
}
pub async fn get_app_status_handler(
State(state): State<Arc<ApiState>>,
State(_state): State<Arc<ApiState>>,
Path(app_name): Path<String>,
) -> Result<Json<ApiResponse<AppStatusResponse>>, StatusCode> {
use crate::config::get_config_manager;
use crate::systemd::SystemCtl;
use crate::models::{AppStatus, ServiceStatus};
match state.app_manager.get_app_status(&app_name) {
Some(managed_app) => {
let response = AppStatusResponse {
name: managed_app.name,
status: managed_app.status.as_str().to_string(),
pid: managed_app.pid,
cpu_usage: managed_app.cpu_usage,
memory_usage: format!("{:.2} MB", managed_app.memory_usage as f64 / 1024.0 / 1024.0),
systemd_status: managed_app.systemd_status.as_str().to_string(),
last_updated: managed_app.last_updated,
let config_manager = get_config_manager();
let apps = config_manager.get_apps();
// Buscar la app en monitored_apps.json
let app = apps.iter().find(|a| a.name == app_name);
match app {
Some(app) => {
let service_name = format!("siax-app-{}.service", app.name);
let systemd_status = SystemCtl::status(&service_name);
// Obtener métricas del proceso
let mut sys = System::new_all();
sys.refresh_all();
let mut pid = None;
let mut cpu_usage = 0.0;
let mut memory_mb = 0.0;
// Buscar proceso por nombre de app
for (process_pid, process) in sys.processes() {
let cmd = process.cmd().join(" ");
if cmd.contains(&app.name) || cmd.contains(&app.entry_point) {
pid = Some(process_pid.as_u32() as i32);
cpu_usage = process.cpu_usage();
memory_mb = process.memory() as f64 / 1024.0 / 1024.0;
break;
}
}
let status = match systemd_status {
ServiceStatus::Active => "Running",
ServiceStatus::Inactive => "Stopped",
ServiceStatus::Failed => "Failed",
ServiceStatus::Activating => "Starting",
ServiceStatus::Deactivating => "Stopping",
ServiceStatus::Unknown => "Unknown",
};
let response = AppStatusResponse {
name: app.name.clone(),
status: status.to_string(),
pid,
cpu_usage,
memory_usage: format!("{:.2} MB", memory_mb),
systemd_status: systemd_status.as_str().to_string(),
last_updated: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
};
Ok(Json(ApiResponse::success(response)))
}
None => Ok(Json(ApiResponse::error(
@@ -149,13 +190,49 @@ pub async fn get_app_status_handler(
}
pub async fn list_apps_handler(
State(state): State<Arc<ApiState>>,
) -> Result<Json<ApiResponse<AppListResponse>>, StatusCode> {
State(_state): State<Arc<ApiState>>,
) -> Result<Json<serde_json::Value>, StatusCode> {
use crate::config::get_config_manager;
use crate::systemd::SystemCtl;
let apps = state.app_manager.list_apps();
let total = apps.len();
// Leer apps desde monitored_apps.json (apps descubiertas + registradas)
let config_manager = get_config_manager();
let monitored_apps = config_manager.get_apps();
Ok(Json(ApiResponse::success(AppListResponse { apps, total })))
// Crear respuesta con información de cada app
let mut apps_with_status = Vec::new();
for app in monitored_apps {
// Verificar estado en systemd
let service_name = format!("siax-app-{}.service", app.name);
let systemd_status = SystemCtl::status(&service_name);
let status = match systemd_status {
crate::models::ServiceStatus::Active => "Running",
crate::models::ServiceStatus::Inactive => "Stopped",
crate::models::ServiceStatus::Failed => "Failed",
crate::models::ServiceStatus::Activating => "Starting",
crate::models::ServiceStatus::Deactivating => "Stopping",
crate::models::ServiceStatus::Unknown => "Unknown",
};
apps_with_status.push(serde_json::json!({
"name": app.name,
"status": status,
"port": app.port,
"service_name": app.service_name,
}));
}
let total = apps_with_status.len();
Ok(Json(serde_json::json!({
"success": true,
"data": {
"apps": apps_with_status,
"total": total
}
})))
}
pub async fn scan_processes_handler() -> Result<Json<ApiResponse<ProcessScanResponse>>, StatusCode> {
@@ -222,3 +299,62 @@ pub async fn health_handler(
version: env!("CARGO_PKG_VERSION").to_string(),
})))
}
/// Endpoint para ver las apps monitoreadas desde el JSON
pub async fn get_monitored_apps_handler() -> Result<Json<serde_json::Value>, StatusCode> {
use crate::config::get_config_manager;
let config_manager = get_config_manager();
let apps = config_manager.get_apps();
let response = serde_json::json!({
"success": true,
"count": apps.len(),
"apps": apps
});
Ok(Json(response))
}
/// Endpoint para obtener los logs de errores del sistema
pub async fn get_system_error_logs() -> Result<Json<serde_json::Value>, StatusCode> {
use std::fs;
use std::path::Path;
let log_path = "logs/errors.log";
// Verificar si el archivo existe
if !Path::new(log_path).exists() {
return Ok(Json(serde_json::json!({
"success": true,
"logs": [],
"message": "Archivo de logs no encontrado"
})));
}
// Leer el archivo
match fs::read_to_string(log_path) {
Ok(content) => {
// Dividir en líneas y tomar las últimas 500
let lines: Vec<&str> = content.lines().collect();
let total = lines.len();
let recent_lines: Vec<&str> = if lines.len() > 500 {
lines[lines.len() - 500..].to_vec()
} else {
lines
};
Ok(Json(serde_json::json!({
"success": true,
"logs": recent_lines,
"total_lines": total
})))
}
Err(e) => {
Ok(Json(serde_json::json!({
"success": false,
"error": format!("Error leyendo archivo: {}", e)
})))
}
}
}

View File

@@ -94,7 +94,7 @@ async fn handle_logs_socket(
ws_manager: Arc<WebSocketManager>,
) {
let logger = get_logger();
let service_name = format!("{}.service", app_name);
let service_name = format!("siax-app-{}.service", app_name);
// Iniciar journalctl
let mut child = match TokioCommand::new("journalctl")

View File

@@ -6,14 +6,51 @@ use crate::logger::get_logger;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitoredApp {
/// Nombre de la aplicación
pub name: String,
/// Nombre del servicio systemd (ej: siax-app-TAREAS.service)
#[serde(default)]
pub service_name: String,
/// Ruta completa al directorio de la aplicación (WorkingDirectory)
#[serde(default)]
pub path: String,
/// Puerto donde escucha la aplicación
pub port: i32,
/// Archivo de entrada (ej: server.js, app.js)
#[serde(default)]
pub entry_point: String,
/// Ruta completa al binario de node/python
#[serde(default)]
pub node_bin: String,
/// Modo de ejecución (production, development, test)
#[serde(default = "default_mode")]
pub mode: String,
/// Ruta completa al archivo .service de systemd
#[serde(default)]
pub service_file_path: String,
/// Fecha de registro (ISO 8601)
#[serde(default, skip_serializing_if = "String::is_empty", rename = "reg")]
pub registered_at: String,
// DEPRECATED: Mantener por compatibilidad con versiones antiguas
#[serde(skip_serializing_if = "Option::is_none")]
pub systemd_service: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>,
}
fn default_mode() -> String {
"production".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub apps: Vec<MonitoredApp>,
@@ -99,23 +136,16 @@ impl ConfigManager {
config.apps.clone()
}
pub fn add_app(&self, name: String, port: i32) -> Result<(), String> {
/// Agrega una app con información completa
pub fn add_app_full(&self, app: MonitoredApp) -> Result<(), String> {
let mut config = self.config.write().unwrap();
// Verificar si ya existe
if config.apps.iter().any(|app| app.name == name) {
return Err(format!("La app '{}' ya está siendo monitoreada", name));
if config.apps.iter().any(|a| a.name == app.name) {
return Err(format!("La app '{}' ya está siendo monitoreada", app.name));
}
let systemd_service = format!("siax-app-{}.service", name);
let created_at = chrono::Local::now().to_rfc3339();
config.apps.push(MonitoredApp {
name,
port,
systemd_service: Some(systemd_service),
created_at: Some(created_at),
});
config.apps.push(app);
// Guardar en disco
match Self::save_config_to_file(&self.config_path, &config) {
@@ -124,6 +154,29 @@ impl ConfigManager {
}
}
/// Método simplificado para compatibilidad (DEPRECATED)
#[deprecated(note = "Usar add_app_full() con MonitoredApp completo")]
pub fn add_app(&self, name: String, port: i32) -> Result<(), String> {
let service_name = format!("siax-app-{}.service", name);
let registered_at = chrono::Local::now().to_rfc3339();
let app = MonitoredApp {
name,
service_name,
path: String::new(),
port,
entry_point: String::new(),
node_bin: String::new(),
mode: "production".to_string(),
service_file_path: String::new(),
registered_at,
systemd_service: None,
created_at: None,
};
self.add_app_full(app)
}
pub fn remove_app(&self, name: &str) -> Result<(), String> {
let mut config = self.config.write().unwrap();

321
src/discovery.rs Normal file
View File

@@ -0,0 +1,321 @@
/// Módulo para descubrir servicios systemd existentes
use std::fs;
use std::path::Path;
use crate::logger::get_logger;
use crate::config::{get_config_manager, MonitoredApp};
const SYSTEMD_DIR: &str = "/etc/systemd/system";
const SERVICE_PREFIX: &str = "siax-app-";
/// Descubre servicios systemd existentes con prefijo siax-app-*
pub fn discover_services() -> Vec<DiscoveredService> {
let logger = get_logger();
logger.info("Discovery", &format!("🔍 Escaneando servicios systemd en: {}", SYSTEMD_DIR));
println!("🔍 Discovery: Buscando servicios en {}", SYSTEMD_DIR);
let mut services = Vec::new();
// Leer directorio de systemd
let entries = match fs::read_dir(SYSTEMD_DIR) {
Ok(entries) => {
logger.info("Discovery", &format!("✅ Directorio {} accesible", SYSTEMD_DIR));
println!("✅ Discovery: Directorio {} accesible", SYSTEMD_DIR);
entries
},
Err(e) => {
logger.error("Discovery", &format!("❌ No se pudo leer directorio {}", SYSTEMD_DIR), Some(&e.to_string()));
println!("❌ Discovery: ERROR - No se pudo leer {}: {}", SYSTEMD_DIR, e);
return services;
}
};
// Buscar archivos siax-app-*.service
let mut total_files = 0;
let mut siax_files = 0;
for entry in entries.flatten() {
total_files += 1;
let path = entry.path();
if let Some(filename) = path.file_name() {
let filename_str = filename.to_string_lossy();
// Verificar que sea un archivo .service con nuestro prefijo
if filename_str.starts_with(SERVICE_PREFIX) && filename_str.ends_with(".service") {
siax_files += 1;
logger.info("Discovery", &format!("✅ Encontrado: {}", filename_str));
println!("✅ Discovery: Servicio detectado: {}", filename_str);
// Extraer nombre de la app
let app_name = extract_app_name(&filename_str);
// Leer configuración del servicio
if let Some(service) = parse_service_file(&path, &app_name) {
services.push(service);
} else {
logger.warning("Discovery", &format!("⚠️ No se pudo parsear: {}", filename_str), None);
println!("⚠️ Discovery: No se pudo parsear {}", filename_str);
}
}
}
}
logger.info("Discovery", &format!("📊 Escaneados {} archivos, {} con prefijo '{}', {} parseados exitosamente",
total_files, siax_files, SERVICE_PREFIX, services.len()));
println!("📊 Discovery: Archivos totales: {}, siax-app-*: {}, parseados: {}",
total_files, siax_files, services.len());
services
}
/// Extrae el nombre de la app desde el nombre del archivo
/// Ejemplo: "siax-app-IDEAS.service" -> "app_IDEAS"
fn extract_app_name(filename: &str) -> String {
// Remover "siax-app-" del inicio y ".service" del final
filename
.trim_start_matches(SERVICE_PREFIX)
.trim_end_matches(".service")
.to_string()
}
/// Servicio descubierto en systemd
#[derive(Debug, Clone)]
pub struct DiscoveredService {
pub app_name: String,
pub service_file: String,
pub working_directory: Option<String>,
pub user: Option<String>,
pub exec_start: Option<String>,
pub port: Option<i32>,
pub node_env: String,
pub entry_point: Option<String>,
pub node_bin: Option<String>,
}
/// Parsea un archivo .service para extraer configuración completa
fn parse_service_file(path: &Path, app_name: &str) -> Option<DiscoveredService> {
let logger = get_logger();
let content = match fs::read_to_string(path) {
Ok(content) => content,
Err(e) => {
logger.error("Discovery", &format!("Error leyendo {}", path.display()), Some(&e.to_string()));
return None;
}
};
let mut service = DiscoveredService {
app_name: app_name.to_string(),
service_file: path.to_string_lossy().to_string(),
working_directory: None,
user: None,
exec_start: None,
port: None,
node_env: String::from("production"),
entry_point: None,
node_bin: None,
};
// Parsear líneas del archivo
for line in content.lines() {
let line = line.trim();
// WorkingDirectory
if line.starts_with("WorkingDirectory=") {
service.working_directory = Some(line.trim_start_matches("WorkingDirectory=").to_string());
}
// User
if line.starts_with("User=") {
service.user = Some(line.trim_start_matches("User=").to_string());
}
// ExecStart
if line.starts_with("ExecStart=") {
let exec_start = line.trim_start_matches("ExecStart=").to_string();
// Extraer node_bin y entry_point del ExecStart
// Ejemplo: /home/user/.nvm/versions/node/v24.12.0/bin/node server.js
let parts: Vec<&str> = exec_start.split_whitespace().collect();
if !parts.is_empty() {
service.node_bin = Some(parts[0].to_string());
// Buscar el archivo .js como entry_point
for part in &parts[1..] {
if part.ends_with(".js") {
service.entry_point = Some(part.to_string());
break;
}
}
}
service.exec_start = Some(exec_start);
}
// Environment con PORT
if line.starts_with("Environment=") && line.contains("PORT") {
if let Some(port) = extract_port_from_env(line) {
service.port = Some(port);
}
}
// Environment con NODE_ENV
if line.starts_with("Environment=") && line.contains("NODE_ENV") {
if let Some(env) = extract_env_value(line, "NODE_ENV") {
service.node_env = env;
}
}
}
logger.info("Discovery", &format!(" App: {}, User: {:?}, WorkDir: {:?}, Env: {}, EntryPoint: {:?}",
service.app_name,
service.user,
service.working_directory,
service.node_env,
service.entry_point
));
Some(service)
}
/// Extrae el puerto de una línea Environment
/// Ejemplo: Environment="PORT=3000" -> Some(3000)
fn extract_port_from_env(line: &str) -> Option<i32> {
// Buscar PORT=número
if let Some(start) = line.find("PORT=") {
let after_port = &line[start + 5..];
// Extraer números
let port_str: String = after_port.chars()
.take_while(|c| c.is_numeric())
.collect();
port_str.parse::<i32>().ok()
} else {
None
}
}
/// Extrae un valor de variable de entorno de una línea Environment
/// Ejemplo: Environment="NODE_ENV=production" -> Some("production")
fn extract_env_value(line: &str, var_name: &str) -> Option<String> {
let pattern = format!("{}=", var_name);
if let Some(start) = line.find(&pattern) {
let after_var = &line[start + pattern.len()..];
// Extraer hasta espacios, comillas o fin de línea
let value: String = after_var.chars()
.take_while(|c| !c.is_whitespace() && *c != '"')
.collect();
if !value.is_empty() {
Some(value)
} else {
None
}
} else {
None
}
}
/// Sincroniza los servicios descubiertos con monitored_apps.json
pub fn sync_discovered_services(services: Vec<DiscoveredService>) {
let logger = get_logger();
let config_manager = get_config_manager();
logger.info("Discovery", &format!("🔄 Sincronizando {} servicios descubiertos...", services.len()));
println!("🔄 Discovery: Sincronizando {} servicios con monitored_apps.json", services.len());
let mut added_count = 0;
let mut skipped_count = 0;
for service in services {
// Intentar detectar el puerto si no se encontró en Environment
let port = service.port.unwrap_or_else(|| {
detect_port_from_name(&service.app_name)
});
// Verificar si ya existe en la configuración
let existing_apps = config_manager.get_apps();
let already_exists = existing_apps.iter().any(|app| app.name == service.app_name);
if already_exists {
logger.info("Discovery", &format!("⏭️ {} ya existe en configuración", service.app_name));
println!("⏭️ Discovery: {} ya existe, omitiendo", service.app_name);
skipped_count += 1;
continue;
}
// Crear MonitoredApp con información completa
let service_name = format!("siax-app-{}.service", service.app_name);
let registered_at = chrono::Local::now().to_rfc3339();
let app = MonitoredApp {
name: service.app_name.clone(),
service_name,
path: service.working_directory.unwrap_or_default(),
port,
entry_point: service.entry_point.unwrap_or_default(),
node_bin: service.node_bin.unwrap_or_default(),
mode: service.node_env,
service_file_path: service.service_file.clone(),
registered_at,
systemd_service: None,
created_at: None,
};
// Agregar a monitored_apps.json
logger.info("Discovery", &format!(" Agregando {} (puerto: {}, entry: {})",
app.name, app.port, app.entry_point));
match config_manager.add_app_full(app) {
Ok(_) => {
logger.info("Discovery", &format!("{} agregado exitosamente", service.app_name));
println!("✅ Discovery: {} agregado a monitored_apps.json", service.app_name);
added_count += 1;
}
Err(e) => {
logger.error("Discovery", &format!("Error agregando {}", service.app_name), Some(&e));
println!("❌ Discovery: Error agregando {}: {}", service.app_name, e);
}
}
}
logger.info("Discovery", &format!("📊 Resumen: {} agregadas, {} ya existían", added_count, skipped_count));
println!("📊 Discovery: Resumen final - {} apps nuevas, {} existentes", added_count, skipped_count);
}
/// Intenta detectar el puerto desde el nombre de la app
/// Esto es un fallback simple si no se encuentra en el .service
fn detect_port_from_name(app_name: &str) -> i32 {
// Algunos puertos conocidos por nombre
match app_name.to_lowercase().as_str() {
name if name.contains("tareas") => 3000,
name if name.contains("fidelizacion") => 3001,
name if name.contains("ideas") => 2000,
_ => 8080, // Puerto por defecto genérico
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_app_name() {
assert_eq!(extract_app_name("siax-app-IDEAS.service"), "IDEAS");
assert_eq!(extract_app_name("siax-app-TAREAS.service"), "TAREAS");
assert_eq!(extract_app_name("siax-app-fidelizacion.service"), "fidelizacion");
}
#[test]
fn test_extract_port_from_env() {
assert_eq!(extract_port_from_env("Environment=PORT=3000"), Some(3000));
assert_eq!(extract_port_from_env("Environment=\"PORT=8080\""), Some(8080));
assert_eq!(extract_port_from_env("Environment=NODE_ENV=production"), None);
}
#[test]
fn test_detect_port_from_name() {
assert_eq!(detect_port_from_name("app_tareas"), 3000);
assert_eq!(detect_port_from_name("IDEAS"), 2000);
assert_eq!(detect_port_from_name("unknown_app"), 8080);
}
}

View File

@@ -6,8 +6,10 @@ pub mod logger;
pub mod config;
pub mod monitor;
pub mod interface;
pub mod discovery;
// Re-exportar solo lo necesario para evitar conflictos
pub use models::{ServiceConfig, ManagedApp, AppStatus, ServiceStatus, AppType, RestartPolicy};
pub use logger::{Logger, LogEntry, LogLevel, get_logger};
pub use config::{ConfigManager, MonitoredApp, AppConfig, get_config_manager};
pub use discovery::{discover_services, sync_discovered_services, DiscoveredService};

View File

@@ -6,11 +6,13 @@ mod models;
mod systemd;
mod orchestrator;
mod api;
mod discovery;
use logger::get_logger;
use config::get_config_manager;
use orchestrator::{AppManager, LifecycleManager};
use api::{ApiState, WebSocketManager};
use discovery::{discover_services, sync_discovered_services};
use std::sync::Arc;
use axum::{
routing::{get, post, delete},
@@ -24,7 +26,14 @@ async fn main() {
let logger = get_logger();
logger.info("Sistema", "Iniciando SIAX Agent");
// Inicializar config manager
// 🔍 Descubrir servicios systemd existentes
logger.info("Sistema", "Escaneando servicios systemd existentes...");
let discovered = discover_services();
if !discovered.is_empty() {
sync_discovered_services(discovered);
}
// Inicializar config manager (ahora con servicios descubiertos)
let config_manager = get_config_manager();
let apps = config_manager.get_apps();
println!("📋 Apps a monitorear: {:?}", apps);
@@ -58,6 +67,8 @@ async fn main() {
// Router para la API REST
let api_router = Router::new()
.route("/api/health", get(api::health_handler))
.route("/api/monitored", get(api::get_monitored_apps_handler))
.route("/api/logs/errors", get(api::get_system_error_logs))
.route("/api/apps", get(api::list_apps_handler).post(api::register_app_handler))
.route("/api/apps/:name", delete(api::unregister_app_handler))
.route("/api/apps/:name/status", get(api::get_app_status_handler))

View File

@@ -2,6 +2,7 @@ use super::{Result, OrchestratorError};
use crate::models::{ServiceConfig, ManagedApp, AppStatus};
use crate::systemd::{ServiceGenerator, SystemCtl};
use crate::logger::get_logger;
use crate::config::{get_config_manager, MonitoredApp};
use dashmap::DashMap;
use std::sync::Arc;
@@ -52,6 +53,49 @@ impl AppManager {
// Guardar en memoria
self.apps.insert(config.app_name.clone(), config.clone());
// Guardar en monitored_apps.json con información completa
let config_manager = get_config_manager();
let service_file_path = format!("/etc/systemd/system/{}", config.service_name());
let registered_at = chrono::Local::now().to_rfc3339();
// Extraer el puerto del environment si existe
let port = config.environment.get("PORT")
.and_then(|p| p.parse::<i32>().ok())
.unwrap_or(8080);
// Determinar el entry_point desde script_path
let entry_point = std::path::Path::new(&config.script_path)
.file_name()
.and_then(|f| f.to_str())
.unwrap_or("server.js")
.to_string();
// Determinar node_bin (será resuelto por el ServiceGenerator)
let node_bin = config.custom_executable.clone().unwrap_or_default();
// Determinar mode desde NODE_ENV
let mode = config.environment.get("NODE_ENV")
.cloned()
.unwrap_or_else(|| "production".to_string());
let monitored_app = MonitoredApp {
name: config.app_name.clone(),
service_name: config.service_name(),
path: config.working_directory.clone(),
port,
entry_point,
node_bin,
mode,
service_file_path,
registered_at,
systemd_service: None,
created_at: None,
};
if let Err(e) = config_manager.add_app_full(monitored_app) {
logger.warning("AppManager", "No se pudo guardar en monitored_apps.json", Some(&e));
}
logger.info("AppManager", &format!("Aplicación {} registrada exitosamente", config.app_name));
Ok(())
@@ -84,6 +128,12 @@ impl AppManager {
// Eliminar de memoria
self.apps.remove(app_name);
// Eliminar de monitored_apps.json
let config_manager = get_config_manager();
if let Err(e) = config_manager.remove_app(app_name) {
logger.warning("AppManager", "No se pudo eliminar de monitored_apps.json", Some(&e));
}
logger.info("AppManager", &format!("Aplicación {} desregistrada exitosamente", app_name));
Ok(())

View File

@@ -26,7 +26,7 @@ impl LifecycleManager {
logger.info("Lifecycle", &format!("Iniciando aplicación: {}", app_name));
let service_name = format!("{}.service", app_name);
let service_name = format!("siax-app-{}.service", app_name);
SystemCtl::start(&service_name)?;
// Actualizar rate limiter
@@ -45,7 +45,7 @@ impl LifecycleManager {
logger.info("Lifecycle", &format!("Deteniendo aplicación: {}", app_name));
let service_name = format!("{}.service", app_name);
let service_name = format!("siax-app-{}.service", app_name);
SystemCtl::stop(&service_name)?;
// Actualizar rate limiter
@@ -64,7 +64,7 @@ impl LifecycleManager {
logger.info("Lifecycle", &format!("Reiniciando aplicación: {}", app_name));
let service_name = format!("{}.service", app_name);
let service_name = format!("siax-app-{}.service", app_name);
SystemCtl::restart(&service_name)?;
// Actualizar rate limiter

View File

@@ -74,17 +74,38 @@ impl ServiceGenerator {
format!("{} {}", executable, config.script_path)
};
// Generar variables de entorno
let env_vars = config.environment
// Generar variables de entorno del usuario
let mut env_lines: Vec<String> = config.environment
.iter()
.map(|(key, value)| format!("Environment=\"{}={}\"", key, value))
.collect::<Vec<_>>()
.join("\n");
.collect();
// Agregar PATH con directorio de NVM si se detectó npm o node en NVM
let using_nvm = executable.contains("/.nvm/");
if using_nvm {
// Extraer el directorio bin de NVM
if let Some(bin_dir) = executable.rfind("/bin/") {
let nvm_bin = &executable[..bin_dir + 4]; // Incluye /bin
let path_env = format!("Environment=PATH={}:/usr/local/bin:/usr/bin:/bin", nvm_bin);
env_lines.insert(0, path_env);
logger.info("ServiceGenerator", &format!("Agregando PATH de NVM: {}", nvm_bin));
}
}
// Agregar NODE_ENV=production por defecto para Node.js si no está definido
if matches!(config.app_type, crate::models::AppType::NodeJs) {
if !config.environment.contains_key("NODE_ENV") {
env_lines.push("Environment=NODE_ENV=production".to_string());
}
}
let env_vars = env_lines.join("\n");
// Agregar SyslogIdentifier para logs más claros
let syslog_id = format!("SyslogIdentifier=siax-app-{}", config.app_name);
format!(
// Construir el servicio con orden lógico
let mut service = format!(
r#"[Unit]
Description={}
After=network.target
@@ -93,23 +114,35 @@ After=network.target
Type=simple
User={}
WorkingDirectory={}
ExecStart={}
"#,
description,
config.user,
config.working_directory
);
// Agregar variables de entorno (PATH primero, luego las demás)
if !env_vars.is_empty() {
service.push_str(&env_vars);
service.push('\n');
}
// Agregar comando de ejecución
service.push_str(&format!("ExecStart={}\n", exec_start));
// Agregar políticas de reinicio
service.push_str(&format!(r#"
Restart={}
RestartSec=10
{}
{}
[Install]
WantedBy=multi-user.target
"#,
description,
config.user,
config.working_directory,
exec_start,
config.restart_policy.as_systemd_str(),
env_vars,
syslog_id
)
));
service
}
/// Resuelve el ejecutable a usar (con auto-detección)

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Script de prueba para verificar la generación de servicios con NVM
echo "=== Test: Generación de servicio con NVM ==="
echo ""
# Simular generación de servicio
cat << 'EOF'
SERVICIO GENERADO (simulado):
[Unit]
Description=App para gestionar Tareas
After=network.target
[Service]
Type=simple
User=user_apps
WorkingDirectory=/home/user_apps/apps/app_tareas
Environment=PATH=/home/user_apps/.nvm/versions/node/v24.12.0/bin:/usr/local/bin:/usr/bin:/bin
Environment=NODE_ENV=production
ExecStart=/home/user_apps/.nvm/versions/node/v24.12.0/bin/npm start
Restart=always
RestartSec=10
SyslogIdentifier=siax-app-TAREAS
[Install]
WantedBy=multi-user.target
CARACTERÍSTICAS:
✅ Environment=PATH incluye directorio NVM automáticamente
✅ Environment=NODE_ENV=production por defecto
✅ SyslogIdentifier para logs claros
✅ Orden lógico: PATH primero, luego env vars del usuario
COMANDOS PARA APLICAR (ejecutados por AppManager automáticamente):
sudo systemctl daemon-reload
sudo systemctl enable siax-app-TAREAS.service
sudo systemctl start siax-app-TAREAS.service
EOF

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Documentación API - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
@@ -112,17 +113,47 @@
<div class="flex-1 flex max-w-[1400px] mx-auto w-full">
<!-- Sidebar - Table of Contents -->
<aside class="w-64 border-r border-[#283039] bg-[#161f2a] p-6 space-y-6 overflow-y-auto">
<aside
class="w-64 border-r border-[#283039] bg-[#161f2a] p-6 space-y-6 overflow-y-auto"
>
<div>
<h3 class="text-white font-bold text-sm mb-3">CONTENIDO</h3>
<nav class="space-y-2">
<a href="#intro" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">Introducción</a>
<a href="#auth" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">Autenticación</a>
<a href="#apps" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">Gestión de Apps</a>
<a href="#scan" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">Escaneo</a>
<a href="#lifecycle" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">Ciclo de Vida</a>
<a href="#websocket" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">WebSocket</a>
<a href="#errors" class="block text-[#9dabb9] text-sm hover:text-primary transition-colors">Códigos de Error</a>
<a
href="#intro"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>Introducción</a
>
<a
href="#auth"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>Autenticación</a
>
<a
href="#apps"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>Gestión de Apps</a
>
<a
href="#scan"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>Escaneo</a
>
<a
href="#lifecycle"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>Ciclo de Vida</a
>
<a
href="#websocket"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>WebSocket</a
>
<a
href="#errors"
class="block text-[#9dabb9] text-sm hover:text-primary transition-colors"
>Códigos de Error</a
>
</nav>
</div>
@@ -135,7 +166,9 @@
</div>
<div>
<span class="text-[#9dabb9]">Base URL:</span>
<span class="text-white font-mono">localhost:8080</span>
<span class="text-white font-mono"
>localhost:8080</span
>
</div>
<div>
<span class="text-[#9dabb9]">Protocolo:</span>
@@ -149,35 +182,70 @@
<main class="flex-1 p-8 overflow-y-auto">
<!-- Introduction -->
<section id="intro" class="mb-12">
<h1 class="text-white text-4xl font-black mb-4">Documentación API REST</h1>
<h1 class="text-white text-4xl font-black mb-4">
Documentación API REST
</h1>
<p class="text-[#9dabb9] text-lg mb-6">
API para gestión y monitoreo de aplicaciones Node.js y Python con systemd.
API para gestión y monitoreo de aplicaciones Node.js y
Python con systemd.
</p>
<div class="rounded-xl border border-primary/30 bg-primary/10 p-4 mb-6">
<div
class="rounded-xl border border-primary/30 bg-primary/10 p-4 mb-6"
>
<div class="flex items-start gap-3">
<span class="material-symbols-outlined text-primary mt-0.5">info</span>
<span
class="material-symbols-outlined text-primary mt-0.5"
>info</span
>
<div>
<p class="text-white font-semibold mb-1">Endpoint Base</p>
<code class="text-primary font-mono text-sm">http://localhost:8080/api</code>
<p class="text-white font-semibold mb-1">
Endpoint Base
</p>
<code class="text-primary font-mono text-sm"
>/api</code
>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<span class="material-symbols-outlined text-green-400 mb-2">check_circle</span>
<p class="text-white font-semibold text-sm">REST API</p>
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<span
class="material-symbols-outlined text-green-400 mb-2"
>check_circle</span
>
<p class="text-white font-semibold text-sm">
REST API
</p>
<p class="text-[#9dabb9] text-xs">JSON responses</p>
</div>
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<span class="material-symbols-outlined text-blue-400 mb-2">bolt</span>
<p class="text-white font-semibold text-sm">WebSocket</p>
<p class="text-[#9dabb9] text-xs">Logs en tiempo real</p>
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<span
class="material-symbols-outlined text-blue-400 mb-2"
>bolt</span
>
<p class="text-white font-semibold text-sm">
WebSocket
</p>
<p class="text-[#9dabb9] text-xs">
Logs en tiempo real
</p>
</div>
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<span class="material-symbols-outlined text-purple-400 mb-2">schedule</span>
<p class="text-white font-semibold text-sm">Rate Limiting</p>
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<span
class="material-symbols-outlined text-purple-400 mb-2"
>schedule</span
>
<p class="text-white font-semibold text-sm">
Rate Limiting
</p>
<p class="text-[#9dabb9] text-xs">1 op/segundo</p>
</div>
</div>
@@ -185,19 +253,34 @@
<!-- Authentication -->
<section id="auth" class="mb-12">
<h2 class="text-white text-2xl font-bold mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">lock</span>
<h2
class="text-white text-2xl font-bold mb-4 flex items-center gap-2"
>
<span class="material-symbols-outlined text-primary"
>lock</span
>
Autenticación
</h2>
<p class="text-[#9dabb9] mb-4">
Actualmente la API no requiere autenticación ya que está diseñada para acceso local vía VPN.
Actualmente la API no requiere autenticación ya que está
diseñada para acceso local vía VPN.
</p>
<div class="rounded-xl border border-yellow-500/30 bg-yellow-500/10 p-4">
<div
class="rounded-xl border border-yellow-500/30 bg-yellow-500/10 p-4"
>
<div class="flex items-start gap-3">
<span class="material-symbols-outlined text-yellow-400">warning</span>
<span
class="material-symbols-outlined text-yellow-400"
>warning</span
>
<div>
<p class="text-yellow-400 font-semibold">Nota de Seguridad</p>
<p class="text-[#9dabb9] text-sm">Esta API debe ser accesible solo desde redes privadas o VPN.</p>
<p class="text-yellow-400 font-semibold">
Nota de Seguridad
</p>
<p class="text-[#9dabb9] text-sm">
Esta API debe ser accesible solo desde redes
privadas o VPN.
</p>
</div>
</div>
</div>
@@ -205,52 +288,98 @@
<!-- Apps Management -->
<section id="apps" class="mb-12">
<h2 class="text-white text-2xl font-bold mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">apps</span>
<h2
class="text-white text-2xl font-bold mb-6 flex items-center gap-2"
>
<span class="material-symbols-outlined text-primary"
>apps</span
>
Gestión de Aplicaciones
</h2>
<!-- List Apps -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold font-mono">GET</span>
<code class="text-white font-mono text-sm">/api/apps</code>
<span
class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold font-mono"
>GET</span
>
<code class="text-white font-mono text-sm"
>/api/apps</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Listar todas las aplicaciones registradas</p>
<p class="text-[#9dabb9] text-sm mt-2">
Listar todas las aplicaciones registradas
</p>
</div>
<div class="p-6 space-y-4">
<div>
<p class="text-white font-semibold text-sm mb-2">Respuesta exitosa (200)</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto">{
<p
class="text-white font-semibold text-sm mb-2"
>
Respuesta exitosa (200)
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto"
>
{
"success": true,
"data": {
"apps": ["app_tareas", "fidelizacion"],
"total": 2
},
"error": null
}</pre>
}</pre
>
</div>
<button onclick="tryEndpoint('GET', '/api/apps')" class="flex items-center gap-2 px-4 py-2 bg-primary hover:brightness-110 rounded-lg text-white text-sm font-medium transition-all">
<span class="material-symbols-outlined text-sm">play_arrow</span>
<button
onclick="tryEndpoint('GET', '/api/apps')"
class="flex items-center gap-2 px-4 py-2 bg-primary hover:brightness-110 rounded-lg text-white text-sm font-medium transition-all"
>
<span class="material-symbols-outlined text-sm"
>play_arrow</span
>
Probar endpoint
</button>
</div>
</div>
<!-- Register App -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono">POST</span>
<code class="text-white font-mono text-sm">/api/apps</code>
<span
class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono"
>POST</span
>
<code class="text-white font-mono text-sm"
>/api/apps</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Registrar una nueva aplicación</p>
<p class="text-[#9dabb9] text-sm mt-2">
Registrar una nueva aplicación
</p>
</div>
<div class="p-6 space-y-4">
<div>
<p class="text-white font-semibold text-sm mb-2">Body (JSON)</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-blue-400 overflow-x-auto">{
<p
class="text-white font-semibold text-sm mb-2"
>
Body (JSON)
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-blue-400 overflow-x-auto"
>
{
"app_name": "mi-app",
"script_path": "/opt/apps/mi-app/index.js",
"working_directory": "/opt/apps/mi-app",
@@ -262,11 +391,19 @@
"restart_policy": "always",
"app_type": "nodejs",
"description": "Mi aplicación Node.js"
}</pre>
}</pre
>
</div>
<div>
<p class="text-white font-semibold text-sm mb-2">Respuesta exitosa (200)</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto">{
<p
class="text-white font-semibold text-sm mb-2"
>
Respuesta exitosa (200)
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto"
>
{
"success": true,
"data": {
"app_name": "mi-app",
@@ -275,27 +412,48 @@
"message": "Aplicación registrada exitosamente"
},
"error": null
}</pre>
}</pre
>
</div>
</div>
</div>
<!-- Delete App -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono">DELETE</span>
<code class="text-white font-mono text-sm">/api/apps/:name</code>
<span
class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono"
>DELETE</span
>
<code class="text-white font-mono text-sm"
>/api/apps/:name</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Eliminar una aplicación registrada</p>
<p class="text-[#9dabb9] text-sm mt-2">
Eliminar una aplicación registrada
</p>
</div>
<div class="p-6 space-y-4">
<div>
<p class="text-white font-semibold text-sm mb-2">Parámetros</p>
<p
class="text-white font-semibold text-sm mb-2"
>
Parámetros
</p>
<ul class="space-y-2">
<li class="flex items-start gap-2">
<code class="text-primary font-mono text-sm">name</code>
<span class="text-[#9dabb9] text-sm">- Nombre de la aplicación</span>
<code
class="text-primary font-mono text-sm"
>name</code
>
<span class="text-[#9dabb9] text-sm"
>- Nombre de la aplicación</span
>
</li>
</ul>
</div>
@@ -303,18 +461,36 @@
</div>
<!-- Get Status -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold font-mono">GET</span>
<code class="text-white font-mono text-sm">/api/apps/:name/status</code>
<span
class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold font-mono"
>GET</span
>
<code class="text-white font-mono text-sm"
>/api/apps/:name/status</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Obtener estado de una aplicación</p>
<p class="text-[#9dabb9] text-sm mt-2">
Obtener estado de una aplicación
</p>
</div>
<div class="p-6 space-y-4">
<div>
<p class="text-white font-semibold text-sm mb-2">Respuesta exitosa (200)</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto">{
<p
class="text-white font-semibold text-sm mb-2"
>
Respuesta exitosa (200)
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto"
>
{
"success": true,
"data": {
"name": "mi-app",
@@ -325,7 +501,8 @@
"systemd_status": "active",
"last_updated": "2026-01-13T12:34:56"
}
}</pre>
}</pre
>
</div>
</div>
</div>
@@ -333,23 +510,45 @@
<!-- Scan -->
<section id="scan" class="mb-12">
<h2 class="text-white text-2xl font-bold mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">search</span>
<h2
class="text-white text-2xl font-bold mb-6 flex items-center gap-2"
>
<span class="material-symbols-outlined text-primary"
>search</span
>
Escaneo de Procesos
</h2>
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold font-mono">GET</span>
<code class="text-white font-mono text-sm">/api/scan</code>
<span
class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold font-mono"
>GET</span
>
<code class="text-white font-mono text-sm"
>/api/scan</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Escanear procesos Node.js y Python en ejecución</p>
<p class="text-[#9dabb9] text-sm mt-2">
Escanear procesos Node.js y Python en ejecución
</p>
</div>
<div class="p-6 space-y-4">
<div>
<p class="text-white font-semibold text-sm mb-2">Respuesta exitosa (200)</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto">{
<p
class="text-white font-semibold text-sm mb-2"
>
Respuesta exitosa (200)
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-green-400 overflow-x-auto"
>
{
"success": true,
"data": {
"processes": [
@@ -364,10 +563,16 @@
],
"total": 1
}
}</pre>
}</pre
>
</div>
<button onclick="tryEndpoint('GET', '/api/scan')" class="flex items-center gap-2 px-4 py-2 bg-primary hover:brightness-110 rounded-lg text-white text-sm font-medium transition-all">
<span class="material-symbols-outlined text-sm">play_arrow</span>
<button
onclick="tryEndpoint('GET', '/api/scan')"
class="flex items-center gap-2 px-4 py-2 bg-primary hover:brightness-110 rounded-lg text-white text-sm font-medium transition-all"
>
<span class="material-symbols-outlined text-sm"
>play_arrow</span
>
Probar endpoint
</button>
</div>
@@ -376,50 +581,97 @@
<!-- Lifecycle -->
<section id="lifecycle" class="mb-12">
<h2 class="text-white text-2xl font-bold mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">settings_power</span>
<h2
class="text-white text-2xl font-bold mb-6 flex items-center gap-2"
>
<span class="material-symbols-outlined text-primary"
>settings_power</span
>
Ciclo de Vida
</h2>
<!-- Start -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono">POST</span>
<code class="text-white font-mono text-sm">/api/apps/:name/start</code>
<span
class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono"
>POST</span
>
<code class="text-white font-mono text-sm"
>/api/apps/:name/start</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Iniciar una aplicación</p>
<p class="text-[#9dabb9] text-sm mt-2">
Iniciar una aplicación
</p>
</div>
</div>
<!-- Stop -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono">POST</span>
<code class="text-white font-mono text-sm">/api/apps/:name/stop</code>
<span
class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono"
>POST</span
>
<code class="text-white font-mono text-sm"
>/api/apps/:name/stop</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Detener una aplicación</p>
<p class="text-[#9dabb9] text-sm mt-2">
Detener una aplicación
</p>
</div>
</div>
<!-- Restart -->
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono">POST</span>
<code class="text-white font-mono text-sm">/api/apps/:name/restart</code>
<span
class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 text-xs font-bold font-mono"
>POST</span
>
<code class="text-white font-mono text-sm"
>/api/apps/:name/restart</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Reiniciar una aplicación</p>
<p class="text-[#9dabb9] text-sm mt-2">
Reiniciar una aplicación
</p>
</div>
</div>
<div class="rounded-xl border border-yellow-500/30 bg-yellow-500/10 p-4">
<div
class="rounded-xl border border-yellow-500/30 bg-yellow-500/10 p-4"
>
<div class="flex items-start gap-3">
<span class="material-symbols-outlined text-yellow-400">schedule</span>
<span
class="material-symbols-outlined text-yellow-400"
>schedule</span
>
<div>
<p class="text-yellow-400 font-semibold">Rate Limiting</p>
<p class="text-[#9dabb9] text-sm">Las operaciones están limitadas a 1 por segundo por aplicación.</p>
<p class="text-yellow-400 font-semibold">
Rate Limiting
</p>
<p class="text-[#9dabb9] text-sm">
Las operaciones están limitadas a 1 por
segundo por aplicación.
</p>
</div>
</div>
</div>
@@ -427,23 +679,45 @@
<!-- WebSocket -->
<section id="websocket" class="mb-12">
<h2 class="text-white text-2xl font-bold mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">cable</span>
<h2
class="text-white text-2xl font-bold mb-6 flex items-center gap-2"
>
<span class="material-symbols-outlined text-primary"
>cable</span
>
WebSocket (Logs en tiempo real)
</h2>
<div class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden">
<div class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]">
<div
class="mb-8 rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
>
<div
class="bg-[#1c2730] px-6 py-4 border-b border-[#283039]"
>
<div class="flex items-center gap-3">
<span class="px-2 py-1 rounded bg-purple-500/20 text-purple-400 text-xs font-bold font-mono">WS</span>
<code class="text-white font-mono text-sm">ws://localhost:8080/api/apps/:name/logs</code>
<span
class="px-2 py-1 rounded bg-purple-500/20 text-purple-400 text-xs font-bold font-mono"
>WS</span
>
<code class="text-white font-mono text-sm"
>ws://localhost:8080/api/apps/:name/logs</code
>
</div>
<p class="text-[#9dabb9] text-sm mt-2">Stream de logs en tiempo real desde journalctl</p>
<p class="text-[#9dabb9] text-sm mt-2">
Stream de logs en tiempo real desde journalctl
</p>
</div>
<div class="p-6 space-y-4">
<div>
<p class="text-white font-semibold text-sm mb-2">Ejemplo JavaScript</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-purple-400 overflow-x-auto">const ws = new WebSocket('ws://localhost:8080/api/apps/mi-app/logs');
<p
class="text-white font-semibold text-sm mb-2"
>
Ejemplo JavaScript
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-purple-400 overflow-x-auto"
>
const ws = new WebSocket('ws://localhost:8080/api/apps/mi-app/logs');
ws.onopen = () => {
console.log('Conectado a logs');
@@ -460,18 +734,35 @@ ws.onerror = (error) => {
ws.onclose = () => {
console.log('Desconectado');
};</pre>
};</pre
>
</div>
<div>
<p class="text-white font-semibold text-sm mb-2">Límites</p>
<p
class="text-white font-semibold text-sm mb-2"
>
Límites
</p>
<ul class="space-y-2">
<li class="flex items-start gap-2">
<span class="material-symbols-outlined text-primary text-sm">check</span>
<span class="text-[#9dabb9] text-sm">Máximo 5 conexiones concurrentes por aplicación</span>
<span
class="material-symbols-outlined text-primary text-sm"
>check</span
>
<span class="text-[#9dabb9] text-sm"
>Máximo 5 conexiones concurrentes
por aplicación</span
>
</li>
<li class="flex items-start gap-2">
<span class="material-symbols-outlined text-primary text-sm">check</span>
<span class="text-[#9dabb9] text-sm">Formato JSON desde systemd journalctl</span>
<span
class="material-symbols-outlined text-primary text-sm"
>check</span
>
<span class="text-[#9dabb9] text-sm"
>Formato JSON desde systemd
journalctl</span
>
</li>
</ul>
</div>
@@ -481,52 +772,98 @@ ws.onclose = () => {
<!-- Error Codes -->
<section id="errors" class="mb-12">
<h2 class="text-white text-2xl font-bold mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">error</span>
<h2
class="text-white text-2xl font-bold mb-6 flex items-center gap-2"
>
<span class="material-symbols-outlined text-primary"
>error</span
>
Códigos de Error
</h2>
<div class="space-y-4">
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<div class="flex items-center gap-3 mb-2">
<span class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono">400</span>
<p class="text-white font-semibold">Bad Request</p>
<span
class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono"
>400</span
>
<p class="text-white font-semibold">
Bad Request
</p>
</div>
<p class="text-[#9dabb9] text-sm">Datos de entrada inválidos o faltantes</p>
<p class="text-[#9dabb9] text-sm">
Datos de entrada inválidos o faltantes
</p>
</div>
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<div class="flex items-center gap-3 mb-2">
<span class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono">404</span>
<p class="text-white font-semibold">Not Found</p>
<span
class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono"
>404</span
>
<p class="text-white font-semibold">
Not Found
</p>
</div>
<p class="text-[#9dabb9] text-sm">Aplicación no encontrada</p>
<p class="text-[#9dabb9] text-sm">
Aplicación no encontrada
</p>
</div>
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<div class="flex items-center gap-3 mb-2">
<span class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono">429</span>
<p class="text-white font-semibold">Too Many Requests</p>
<span
class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono"
>429</span
>
<p class="text-white font-semibold">
Too Many Requests
</p>
</div>
<p class="text-[#9dabb9] text-sm">Rate limit excedido (1 operación/segundo)</p>
<p class="text-[#9dabb9] text-sm">
Rate limit excedido (1 operación/segundo)
</p>
</div>
<div class="rounded-xl border border-[#283039] bg-[#161f2a] p-4">
<div
class="rounded-xl border border-[#283039] bg-[#161f2a] p-4"
>
<div class="flex items-center gap-3 mb-2">
<span class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono">500</span>
<p class="text-white font-semibold">Internal Server Error</p>
<span
class="px-2 py-1 rounded bg-red-500/20 text-red-400 text-xs font-bold font-mono"
>500</span
>
<p class="text-white font-semibold">
Internal Server Error
</p>
</div>
<p class="text-[#9dabb9] text-sm">Error interno del servidor</p>
<p class="text-[#9dabb9] text-sm">
Error interno del servidor
</p>
</div>
</div>
<div class="mt-6">
<p class="text-white font-semibold text-sm mb-2">Estructura de error</p>
<pre class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-red-400 overflow-x-auto">{
<p class="text-white font-semibold text-sm mb-2">
Estructura de error
</p>
<pre
class="code-block bg-[#0a0f16] p-4 rounded-lg text-sm text-red-400 overflow-x-auto"
>
{
"success": false,
"data": null,
"error": "Descripción del error"
}</pre>
}</pre
>
</div>
</section>
</main>
@@ -534,14 +871,18 @@ ws.onclose = () => {
<script>
async function tryEndpoint(method, path) {
const resultDiv = event.target.parentElement.querySelector('.result') ||
event.target.parentElement.appendChild(document.createElement('div'));
resultDiv.className = 'result mt-4 code-block bg-[#0a0f16] p-4 rounded-lg text-sm overflow-x-auto';
resultDiv.textContent = 'Ejecutando...';
const resultDiv =
event.target.parentElement.querySelector(".result") ||
event.target.parentElement.appendChild(
document.createElement("div"),
);
resultDiv.className =
"result mt-4 code-block bg-[#0a0f16] p-4 rounded-lg text-sm overflow-x-auto";
resultDiv.textContent = "Ejecutando...";
try {
const response = await fetch(`http://localhost:8080${path}`, {
method: method
const response = await fetch(path, {
method: method,
});
const data = await response.json();
resultDiv.innerHTML = `<pre class="text-green-400">${JSON.stringify(data, null, 2)}</pre>`;
@@ -551,12 +892,17 @@ ws.onclose = () => {
}
// Smooth scroll for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
const target = document.querySelector(
this.getAttribute("href"),
);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
target.scrollIntoView({
behavior: "smooth",
block: "start",
});
}
});
});

764
web/blog.html Normal file
View File

@@ -0,0 +1,764 @@
<!doctype html>
<html class="dark" lang="es" dir="ltr">
<head>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>
SIAX Monitor: Sistema de Monitoreo de Aplicaciones en Rust - Blog
Telcotronics
</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet"
/>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#137fec",
"background-light": "#f6f7f8",
"background-dark": "#101922",
},
fontFamily: {
display: ["Inter", "sans-serif"],
mono: ["JetBrains Mono", "monospace"],
},
},
},
};
</script>
<style>
body {
font-family: "Inter", sans-serif;
}
.material-symbols-outlined {
font-variation-settings:
"FILL" 0,
"wght" 400,
"GRAD" 0,
"opsz" 24;
}
.blog-content h2 {
font-size: 2rem;
font-weight: 800;
margin-top: 3rem;
margin-bottom: 1.5rem;
color: white;
}
.blog-content h3 {
font-size: 1.5rem;
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
color: #e2e8f0;
}
.blog-content p {
margin-bottom: 1.25rem;
line-height: 1.75;
color: #cbd5e1;
}
.blog-content ul,
.blog-content ol {
margin-bottom: 1.5rem;
padding-left: 1.5rem;
}
.blog-content li {
margin-bottom: 0.75rem;
line-height: 1.75;
color: #cbd5e1;
}
.blog-content ul li {
list-style-type: disc;
}
.blog-content ol li {
list-style-type: decimal;
}
.blog-content strong {
color: white;
font-weight: 600;
}
.blog-content code {
background: #1e293b;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.9em;
color: #60a5fa;
}
.blog-content pre {
background: #0f172a;
padding: 1.5rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1.5rem;
border: 1px solid #334155;
}
.blog-content pre code {
background: none;
padding: 0;
color: #94a3b8;
}
.blog-content blockquote {
border-left: 4px solid #137fec;
padding-left: 1.5rem;
margin: 1.5rem 0;
font-style: italic;
color: #94a3b8;
}
</style>
</head>
<body class="bg-background-dark text-slate-300">
<!-- Header -->
<header class="border-b border-slate-800 bg-[#0a0f16]">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex items-center justify-between">
<a
href="/"
class="text-2xl font-black text-white hover:text-primary transition-colors"
>
SIAX Monitor
</a>
<nav class="flex items-center gap-6">
<a
href="/"
class="text-slate-400 hover:text-white transition-colors"
>Dashboard</a
>
<a
href="/api-docs"
class="text-slate-400 hover:text-white transition-colors"
>API</a
>
<a
href="https://git.telcotronics.net/pablinux/SIAX-MONITOR"
target="_blank"
class="text-slate-400 hover:text-primary transition-colors flex items-center gap-1"
>
<span class="material-symbols-outlined text-sm"
>code</span
>
Git
</a>
</nav>
</div>
</div>
</header>
<!-- Article Container -->
<article class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<!-- Article Header -->
<header class="mb-12">
<!-- Categories -->
<div class="flex flex-wrap gap-2 mb-6">
<span
class="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-primary/20 text-primary text-sm font-semibold"
>
<span class="material-symbols-outlined text-xs"
>folder</span
>
DevOps
</span>
<span
class="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-green-500/20 text-green-400 text-sm font-semibold"
>
<span class="material-symbols-outlined text-xs"
>code</span
>
Rust
</span>
<span
class="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-blue-500/20 text-blue-400 text-sm font-semibold"
>
<span class="material-symbols-outlined text-xs"
>monitoring</span
>
Monitoring
</span>
</div>
<!-- Title -->
<h1
class="text-4xl md:text-5xl font-black text-white mb-6 leading-tight"
>
SIAX Monitor: Sistema de Monitoreo y Gestión de Aplicaciones
Node.js y Python
</h1>
<!-- Meta info -->
<div
class="flex flex-wrap items-center gap-4 text-slate-400 text-sm"
>
<div class="flex items-center gap-2">
<div
class="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center"
>
<span
class="material-symbols-outlined text-primary text-lg"
>person</span
>
</div>
<span
>Por
<strong class="text-white">pablinux</strong></span
>
</div>
<div class="flex items-center gap-1">
<span class="material-symbols-outlined text-sm"
>calendar_today</span
>
<time datetime="2026-01-13">13 de enero, 2026</time>
</div>
<div class="flex items-center gap-1">
<span class="material-symbols-outlined text-sm"
>schedule</span
>
<span>10 min de lectura</span>
</div>
</div>
</header>
<!-- Featured Image -->
<div
class="mb-12 rounded-2xl overflow-hidden border border-slate-800 bg-gradient-to-br from-primary/20 via-background-dark to-background-dark p-12"
>
<div class="flex items-center justify-center">
<div class="text-center">
<div
class="inline-flex items-center justify-center w-24 h-24 rounded-2xl bg-primary/20 border-2 border-primary/30 mb-4"
>
<span
class="material-symbols-outlined text-primary"
style="font-size: 3rem"
>monitoring</span
>
</div>
<p class="text-slate-400 text-lg">
Monitoreo inteligente con Rust + Systemd
</p>
</div>
</div>
</div>
<!-- Article Content -->
<div class="blog-content">
<p class="text-xl text-slate-300 mb-8 leading-relaxed">
En el mundo del desarrollo moderno, gestionar múltiples
aplicaciones Node.js y Python en servidores de producción
puede convertirse rápidamente en un dolor de cabeza. SIAX
Monitor nace como una solución elegante, ligera y poderosa
para este problema, aprovechando la velocidad y seguridad de
Rust.
</p>
<h2>¿Qué es SIAX Monitor?</h2>
<p>
SIAX Monitor es un
<strong>agente de monitoreo inteligente</strong> diseñado
específicamente para entornos Linux con systemd. A
diferencia de soluciones enterprise como Prometheus o
Grafana que pueden resultar excesivas para equipos pequeños,
SIAX Monitor ofrece exactamente lo que necesitas sin
complicaciones innecesarias.
</p>
<p>
Desarrollado completamente en Rust, combina alto rendimiento
con un consumo mínimo de recursos. El proyecto utiliza
tecnologías modernas como Tokio para async runtime, Axum
para el servidor web, y se integra nativamente con systemd y
journalctl.
</p>
<h2>Características Principales</h2>
<h3>🔍 Escaneo Automático de Procesos</h3>
<p>
El sistema detecta automáticamente procesos Node.js y Python
en ejecución, recopilando información detallada como:
</p>
<ul>
<li>PID y nombre del proceso</li>
<li>Usuario propietario</li>
<li>Uso de CPU en tiempo real</li>
<li>Consumo de memoria RAM</li>
<li>Comando completo de ejecución</li>
</ul>
<h3>⚙️ Gestión de Ciclo de Vida</h3>
<p>
Control total sobre tus aplicaciones mediante la API REST:
</p>
<ul>
<li>
<code>POST /api/apps</code> - Registrar nueva aplicación
</li>
<li>
<code>POST /api/apps/:name/start</code> - Iniciar
servicio
</li>
<li>
<code>POST /api/apps/:name/stop</code> - Detener
servicio
</li>
<li>
<code>POST /api/apps/:name/restart</code> - Reiniciar
servicio
</li>
<li>
<code>GET /api/apps/:name/status</code> - Consultar
estado
</li>
</ul>
<p>
El sistema incluye <strong>rate limiting</strong> (1
operación/segundo por app) para evitar abusos y validaciones
de seguridad en todos los endpoints.
</p>
<h3>📝 Logs en Tiempo Real</h3>
<p>
Uno de los puntos más fuertes es el streaming de logs vía
WebSocket. Conectándote al endpoint
<code>ws://localhost:8080/api/apps/:name/logs</code>,
recibes logs en tiempo real desde journalctl sin necesidad
de SSH al servidor.
</p>
<p>La interfaz web incluye un visor tipo terminal con:</p>
<ul>
<li>Auto-scroll inteligente</li>
<li>Colores para niveles de log (ERROR, WARN, INFO)</li>
<li>Timestamps formateados</li>
<li>Botón para pausar/reanudar</li>
</ul>
<h3>🛡️ Seguridad y Validaciones</h3>
<p>SIAX Monitor toma la seguridad en serio:</p>
<ul>
<li>
Validación estricta de paths de trabajo (previene
directory traversal)
</li>
<li>Lista blanca de usuarios permitidos</li>
<li>
Configuración automatizada de sudoers para systemctl
</li>
<li>Hardening de servicios systemd generados</li>
<li>Rate limiting en operaciones críticas</li>
</ul>
<h3>🎨 Dashboard Moderno</h3>
<p>
La interfaz web está construida con
<strong>Tailwind CSS</strong> en tema oscuro (#101922 de
fondo, #137fec como color primario). Incluye:
</p>
<ul>
<li>
<strong>/</strong> - Dashboard con estadísticas y lista
de apps
</li>
<li>
<strong>/scan</strong> - Escaneo de procesos activos
</li>
<li>
<strong>/select</strong> - Selección de procesos para
registrar
</li>
<li>
<strong>/register</strong> - Formulario de registro
manual
</li>
<li>
<strong>/logs</strong> - Visor de logs en tiempo real
</li>
<li>
<strong>/api-docs</strong> - Documentación completa de
la API
</li>
</ul>
<h2>¿Cómo Funciona?</h2>
<h3>Arquitectura Multi-Threaded</h3>
<p>
SIAX Monitor utiliza una arquitectura basada en tres
componentes principales:
</p>
<p><strong>1. Monitor en Background</strong></p>
<p>Un thread dedicado ejecuta cada 60 segundos para:</p>
<ul>
<li>
Recopilar métricas de CPU y RAM usando
<code>sysinfo</code>
</li>
<li>Reconciliar estados entre sysinfo y systemd</li>
<li>Reportar al cloud API de SIAX (opcional)</li>
</ul>
<p><strong>2. Servidor Web Unificado</strong></p>
<p>Un servidor HTTP en puerto 8080 que fusiona:</p>
<ul>
<li>API REST (JSON responses)</li>
<li>WebSocket para logs</li>
<li>Interfaz web HTML estática</li>
<li>Archivos estáticos (favicon, logos)</li>
</ul>
<p>
Esto elimina problemas de CORS al servir todo desde el mismo
origen.
</p>
<p><strong>3. Integración Systemd</strong></p>
<p>
El módulo <code>systemd_manager</code> genera archivos
<code>.service</code> automáticamente con:
</p>
<pre><code>[Unit]
Description=App gestionada por SIAX Monitor
After=network.target
[Service]
Type=simple
User=app-user
WorkingDirectory=/opt/app
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target</code></pre>
<h2>Stack Tecnológico</h2>
<p>
El proyecto está construido sobre tecnologías modernas y
probadas:
</p>
<ul>
<li>
<strong>Rust</strong> - Lenguaje core (seguridad de
memoria, velocidad)
</li>
<li><strong>Tokio</strong> - Runtime asíncrono</li>
<li><strong>Axum 0.7</strong> - Framework web moderno</li>
<li><strong>Serde</strong> - Serialización JSON</li>
<li><strong>Sysinfo</strong> - Información del sistema</li>
<li>
<strong>Tower-HTTP</strong> - Middleware (CORS, static
files)
</li>
<li><strong>DashMap</strong> - HashMap thread-safe</li>
<li>
<strong>Tailwind CSS</strong> - Estilos del frontend
</li>
<li><strong>Material Symbols</strong> - Iconos</li>
</ul>
<h2>Ventajas y Consideraciones</h2>
<h3>✅ Ventajas</h3>
<ul>
<li>
<strong>Alto Rendimiento</strong>: Rust ofrece velocidad
cercana a C con seguridad de memoria garantizada
</li>
<li>
<strong>Ligero</strong>: Binario compilado de ~15MB,
consumo mínimo de RAM
</li>
<li>
<strong>Sin Dependencias</strong>: No requiere Node.js,
Python o base de datos
</li>
<li>
<strong>Integración Nativa</strong>: Aprovecha systemd y
journalctl del sistema
</li>
<li>
<strong>Fácil Despliegue</strong>: Single binary +
script de instalación
</li>
<li>
<strong>Open Source</strong>: Código auditable y
personalizable
</li>
</ul>
<h3>⚠️ Consideraciones</h3>
<ul>
<li>
<strong>Solo Linux + Systemd</strong>: Requiere
distribuciones con systemd (no macOS/Windows)
</li>
<li>
<strong>Permisos Sudo</strong>: Necesita configurar
sudoers para systemctl
</li>
<li>
<strong>Sin Métricas Históricas</strong>: No almacena
histórico, solo tiempo real
</li>
<li>
<strong>Solo Node.js y Python</strong>: Otros lenguajes
requieren extensión del código
</li>
<li>
<strong>Sin Autenticación</strong>: Diseñado para acceso
local/VPN, no exponer públicamente
</li>
</ul>
<h2>Casos de Uso</h2>
<h3>👔 Equipos DevOps</h3>
<p>
Gestión centralizada de microservicios en múltiples
servidores. El monitor actúa como worker node que reporta al
cloud API central, permitiendo visibilidad de toda la
infraestructura desde un solo panel.
</p>
<h3>💻 Desarrolladores</h3>
<p>
Monitoreo de aplicaciones en entornos de desarrollo y
staging sin la complejidad de herramientas enterprise.
Perfecto para proyectos pequeños a medianos que necesitan
control básico de servicios.
</p>
<h3>🖥️ Administradores de Sistemas</h3>
<p>
Control de servicios systemd con una interfaz web moderna.
Alternativa visual a comandos
<code>systemctl</code> repetitivos, con la ventaja de logs
centralizados y accesibles desde el navegador.
</p>
<h2>Instalación Rápida</h2>
<p>El proceso de instalación es extremadamente simple:</p>
<pre><code># Clonar el repositorio
git clone https://git.telcotronics.net/pablinux/SIAX-MONITOR.git
cd SIAX-MONITOR
# Compilar en modo release
cargo build --release
# Ejecutar instalador (crea usuario, configura sudoers, instala servicio)
sudo ./instalador.sh
# El servicio estará disponible en http://localhost:8080
</code></pre>
<p>
El script <code>instalador.sh</code> realiza
automáticamente:
</p>
<ul>
<li>Crear usuario del sistema <code>siax-agent</code></li>
<li>Configurar permisos sudoers para systemctl</li>
<li>Copiar binario a <code>/opt/siax-agent/</code></li>
<li>Instalar y habilitar servicio systemd</li>
<li>Verificar salud del servicio</li>
</ul>
<h2>Arquitectura de Despliegue</h2>
<p>
SIAX Monitor fue diseñado pensando en una arquitectura
distribuida:
</p>
<ul>
<li>
<strong>Cloud API</strong>:
<code>https://api.siax-system.net</code> - Panel central
de control
</li>
<li>
<strong>Worker Nodes</strong>: Agentes SIAX Monitor en
cada servidor
</li>
<li>
<strong>Comunicación</strong>: VPN segura entre workers
y cloud API
</li>
</ul>
<p>
Cada worker reporta cada 60 segundos su estado, permitiendo
monitoreo centralizado de toda la infraestructura sin
exponer puertos públicamente.
</p>
<h2>Conclusión</h2>
<p>
SIAX Monitor demuestra que no siempre necesitas soluciones
enterprise complejas para problemas simples. Con menos de
2,000 líneas de código Rust bien estructurado, ofrece
exactamente lo necesario para gestionar aplicaciones Node.js
y Python en producción.
</p>
<p>
La combinación de Rust + Systemd + WebSocket resulta en una
herramienta rápida, confiable y fácil de mantener. Es
perfecta para equipos pequeños o medianos que buscan
simplicidad sin sacrificar funcionalidad.
</p>
<p>
Si administras servidores Linux con aplicaciones Node.js o
Python, definitivamente vale la pena darle una oportunidad.
El código está disponible en
<a
href="https://git.telcotronics.net/pablinux/SIAX-MONITOR"
target="_blank"
class="text-primary hover:underline"
>Git Telcotronics</a
>
bajo licencia open source.
</p>
<blockquote>
"A veces la mejor solución no es la más compleja, sino la
que resuelve tu problema específico de la manera más
elegante posible." - Filosofía detrás de SIAX Monitor
</blockquote>
</div>
<!-- Article Footer -->
<footer class="mt-16 pt-8 border-t border-slate-800">
<div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center gap-3">
<div
class="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center"
>
<span class="material-symbols-outlined text-primary"
>person</span
>
</div>
<div>
<p class="text-white font-semibold">pablinux</p>
<p class="text-slate-400 text-sm">
DevOps Engineer · Rust Enthusiast
</p>
</div>
</div>
<div class="flex gap-3">
<a
href="https://git.telcotronics.net/pablinux/SIAX-MONITOR"
target="_blank"
class="inline-flex items-center gap-2 px-6 py-3 bg-primary hover:brightness-110 rounded-lg text-white font-semibold transition-all"
>
<span class="material-symbols-outlined text-sm"
>code</span
>
Ver en Git
</a>
<a
href="/api-docs"
class="inline-flex items-center gap-2 px-6 py-3 bg-slate-800 hover:bg-slate-700 rounded-lg text-white font-semibold transition-all"
>
<span class="material-symbols-outlined text-sm"
>description</span
>
Documentación
</a>
</div>
</div>
</footer>
<!-- Related Articles / Tags -->
<div
class="mt-12 p-6 rounded-2xl border border-slate-800 bg-[#0a0f16]"
>
<h3 class="text-lg font-bold text-white mb-4">Etiquetas</h3>
<div class="flex flex-wrap gap-2">
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>rust</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>systemd</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>monitoring</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>devops</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>nodejs</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>python</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>websocket</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>axum</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>tokio</span
>
<span
class="px-3 py-1 rounded-full bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition-colors cursor-pointer"
>linux</span
>
</div>
</div>
</article>
<!-- Footer -->
<footer class="bg-[#0a0f16] border-t border-slate-800 mt-20 py-12">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-6">
<h3 class="text-2xl font-bold text-white mb-2">
SIAX Monitor
</h3>
<p class="text-slate-400">
Sistema de Monitoreo y Gestión de Aplicaciones
</p>
</div>
<div class="flex justify-center gap-8 mb-6">
<a
href="https://git.telcotronics.net/pablinux/SIAX-MONITOR"
target="_blank"
class="text-slate-400 hover:text-primary transition-colors"
>Git</a
>
<a
href="/api-docs"
class="text-slate-400 hover:text-primary transition-colors"
>API Docs</a
>
<a
href="/"
class="text-slate-400 hover:text-primary transition-colors"
>Dashboard</a
>
</div>
<p class="text-slate-500 text-sm text-center">
© 2026 SIAX Monitor. Desarrollado con 🦀 Rust y ❤️ por la
comunidad
</p>
</div>
</footer>
</body>
</html>

563
web/health.html Normal file
View File

@@ -0,0 +1,563 @@
<!doctype html>
<html class="dark" lang="es" dir="ltr">
<head>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>System Health - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet"
/>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#137fec",
"background-light": "#f6f7f8",
"background-dark": "#101922",
},
fontFamily: {
display: ["Inter", "sans-serif"],
mono: ["JetBrains Mono", "monospace"],
},
},
},
};
</script>
<style>
body {
font-family: "Inter", sans-serif;
}
.material-symbols-outlined {
font-variation-settings:
"FILL" 0,
"wght" 400,
"GRAD" 0,
"opsz" 24;
}
</style>
</head>
<body class="bg-background-dark text-white min-h-screen">
<!-- Header -->
<header class="border-b border-[#283039] bg-[#0a0f16]">
<div class="container mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div
class="rounded-full size-9 border-2 border-slate-700 overflow-hidden"
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<div>
<h1 class="text-xl font-bold">SIAX Monitor</h1>
<p class="text-xs text-slate-400">System Health</p>
</div>
</div>
<!-- Desktop Navigation -->
<nav class="hidden md:flex items-center gap-2">
<a
href="/scan"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
<span class="material-symbols-outlined text-lg"
>search</span
>
<span>Escanear</span>
</a>
<a
href="/logs"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
<span class="material-symbols-outlined text-lg"
>article</span
>
<span>Logs</span>
</a>
<a
href="/register"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
<span class="material-symbols-outlined text-lg"
>app_registration</span
>
<span>Registrar</span>
</a>
<a
href="/"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
<span class="material-symbols-outlined text-lg"
>dashboard</span
>
<span>Dashboard</span>
</a>
</nav>
<!-- Mobile Menu Button -->
<button
onclick="toggleMenu()"
class="md:hidden px-3 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors"
>
<span class="material-symbols-outlined text-2xl"
>menu</span
>
</button>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden mt-4 pb-4 space-y-2"
>
<a
href="/health"
class="block px-4 py-3 rounded-lg bg-[#161f2a] text-primary transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined"
>monitor_heart</span
>
<span>Health</span>
</a>
<a
href="/scan"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined">search</span>
<span>Escanear</span>
</a>
<a
href="/logs"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined">article</span>
<span>Logs</span>
</a>
<a
href="/"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Page Header -->
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h2 class="text-3xl font-black mb-2">System Health</h2>
<p class="text-slate-400">
Diagnóstico y estado del sistema de monitoreo
</p>
</div>
<button
onclick="refreshHealth()"
class="px-4 py-2 rounded-lg bg-primary hover:brightness-110 transition-all flex items-center gap-2"
>
<span class="material-symbols-outlined text-sm"
>refresh</span
>
<span>Actualizar</span>
</button>
</div>
</div>
<!-- Loading State -->
<div id="loading-state" class="text-center py-12">
<div
class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"
></div>
<p class="mt-4 text-slate-400">
Cargando estado del sistema...
</p>
</div>
<!-- Health Cards Container -->
<div id="health-content" class="hidden">
<!-- Status Overview -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<!-- Overall Status -->
<div
class="rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center justify-between mb-2">
<span class="text-slate-400">Estado General</span>
<span
class="material-symbols-outlined text-green-400"
id="status-icon"
>check_circle</span
>
</div>
<div class="text-2xl font-bold" id="overall-status">
OK
</div>
</div>
<!-- Config Status -->
<div
class="rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center justify-between mb-2">
<span class="text-slate-400">Configuración</span>
<span
class="material-symbols-outlined text-blue-400"
>settings</span
>
</div>
<div class="text-2xl font-bold" id="config-status">
Cargada
</div>
</div>
<!-- Apps Count -->
<div
class="rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center justify-between mb-2">
<span class="text-slate-400">Apps Registradas</span>
<span
class="material-symbols-outlined text-purple-400"
>apps</span
>
</div>
<div class="text-2xl font-bold" id="apps-count">0</div>
</div>
<!-- Version -->
<div
class="rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center justify-between mb-2">
<span class="text-slate-400">Versión</span>
<span
class="material-symbols-outlined text-yellow-400"
>info</span
>
</div>
<div class="text-2xl font-bold font-mono" id="version">
-
</div>
</div>
</div>
<!-- Detailed Information -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Configuration Details -->
<div
class="rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center gap-3 mb-6">
<div
class="w-10 h-10 rounded-xl bg-blue-500/20 flex items-center justify-center"
>
<span
class="material-symbols-outlined text-blue-400"
>folder</span
>
</div>
<div>
<h3 class="text-lg font-bold">Configuración</h3>
<p class="text-sm text-slate-400">
Detalles del archivo de configuración
</p>
</div>
</div>
<div class="space-y-4">
<div
class="flex items-start justify-between py-3 border-b border-[#283039]"
>
<div>
<p class="text-sm text-slate-400">
Ruta del archivo
</p>
<p
class="font-mono text-sm text-primary"
id="config-path"
>
-
</p>
</div>
<span
class="material-symbols-outlined text-green-400"
id="config-loaded-icon"
>check_circle</span
>
</div>
<div
class="flex items-start justify-between py-3 border-b border-[#283039]"
>
<div>
<p class="text-sm text-slate-400">Estado</p>
<p
class="font-semibold"
id="config-loaded-text"
>
Archivo cargado correctamente
</p>
</div>
</div>
<div class="flex items-start justify-between py-3">
<div>
<p class="text-sm text-slate-400">
Aplicaciones en config
</p>
<p
class="text-2xl font-bold"
id="config-apps-count"
>
0
</p>
</div>
</div>
</div>
</div>
<!-- Systemd Services -->
<div
class="rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center gap-3 mb-6">
<div
class="w-10 h-10 rounded-xl bg-green-500/20 flex items-center justify-center"
>
<span
class="material-symbols-outlined text-green-400"
>settings_system_daydream</span
>
</div>
<div>
<h3 class="text-lg font-bold">
Servicios Systemd
</h3>
<p class="text-sm text-slate-400">
Servicios creados por SIAX Monitor
</p>
</div>
</div>
<div
id="systemd-services-list"
class="space-y-2 max-h-80 overflow-y-auto"
>
<!-- Services will be injected here -->
</div>
<div
id="no-services"
class="hidden text-center py-8 text-slate-400"
>
<span
class="material-symbols-outlined text-4xl mb-2 opacity-50"
>info</span
>
<p>No hay servicios systemd registrados</p>
</div>
</div>
</div>
<!-- System Commands -->
<div
class="mt-6 rounded-2xl border border-[#283039] bg-[#161f2a] p-6"
>
<div class="flex items-center gap-3 mb-6">
<div
class="w-10 h-10 rounded-xl bg-purple-500/20 flex items-center justify-center"
>
<span
class="material-symbols-outlined text-purple-400"
>terminal</span
>
</div>
<div>
<h3 class="text-lg font-bold">Comandos Útiles</h3>
<p class="text-sm text-slate-400">
Comandos para gestionar servicios systemd
</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="rounded-lg bg-[#0a0f16] p-4">
<p class="text-xs text-slate-400 mb-2">
Listar servicios SIAX
</p>
<code class="text-sm text-primary font-mono"
>systemctl list-units 'siax-app-*'</code
>
</div>
<div class="rounded-lg bg-[#0a0f16] p-4">
<p class="text-xs text-slate-400 mb-2">
Ver estado de un servicio
</p>
<code class="text-sm text-primary font-mono"
>systemctl status siax-app-nombre.service</code
>
</div>
<div class="rounded-lg bg-[#0a0f16] p-4">
<p class="text-xs text-slate-400 mb-2">
Ver logs de un servicio
</p>
<code class="text-sm text-primary font-mono"
>journalctl -u siax-app-nombre -f</code
>
</div>
<div class="rounded-lg bg-[#0a0f16] p-4">
<p class="text-xs text-slate-400 mb-2">
Recargar daemon de systemd
</p>
<code class="text-sm text-primary font-mono"
>systemctl daemon-reload</code
>
</div>
</div>
</div>
</div>
</main>
<script>
async function loadHealth() {
const loading = document.getElementById("loading-state");
const content = document.getElementById("health-content");
try {
loading.classList.remove("hidden");
content.classList.add("hidden");
const response = await fetch("/api/health");
if (!response.ok) throw new Error("Failed to fetch health");
const result = await response.json();
const data = result.data;
loading.classList.add("hidden");
content.classList.remove("hidden");
// Update status cards
document.getElementById("overall-status").textContent =
data.status.toUpperCase();
document.getElementById("config-status").textContent =
data.config_loaded ? "Cargada" : "No encontrada";
document.getElementById("apps-count").textContent =
data.apps_count;
document.getElementById("version").textContent =
"v" + data.version;
// Update config details
document.getElementById("config-path").textContent =
data.config_path;
document.getElementById("config-apps-count").textContent =
data.apps_count;
const configIcon =
document.getElementById("config-loaded-icon");
const configText =
document.getElementById("config-loaded-text");
if (data.config_loaded) {
configIcon.textContent = "check_circle";
configIcon.className =
"material-symbols-outlined text-green-400";
configText.textContent =
"Archivo cargado correctamente";
} else {
configIcon.textContent = "error";
configIcon.className =
"material-symbols-outlined text-yellow-400";
configText.textContent =
"Archivo no encontrado (se creará automáticamente)";
}
// Update systemd services list
const servicesList = document.getElementById(
"systemd-services-list",
);
const noServices = document.getElementById("no-services");
if (
data.systemd_services &&
data.systemd_services.length > 0
) {
servicesList.innerHTML = data.systemd_services
.map(
(service) => `
<div class="flex items-center justify-between p-3 rounded-lg bg-[#0a0f16] hover:bg-[#0d1218] transition-colors">
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-green-400 text-sm">check_circle</span>
<span class="font-mono text-sm">${service}</span>
</div>
<button onclick="copyToClipboard('${service}')" class="text-slate-400 hover:text-primary transition-colors">
<span class="material-symbols-outlined text-sm">content_copy</span>
</button>
</div>
`,
)
.join("");
noServices.classList.add("hidden");
} else {
servicesList.innerHTML = "";
noServices.classList.remove("hidden");
}
} catch (error) {
console.error("Error loading health:", error);
loading.classList.add("hidden");
content.innerHTML = `
<div class="text-center py-12 text-red-400">
<span class="material-symbols-outlined text-6xl mb-4">error</span>
<p class="text-xl font-bold mb-2">Error al cargar el estado del sistema</p>
<p class="text-slate-400">${error.message}</p>
<button onclick="loadHealth()" class="mt-4 px-6 py-2 bg-primary rounded-lg hover:brightness-110">
Reintentar
</button>
</div>
`;
content.classList.remove("hidden");
}
}
function refreshHealth() {
loadHealth();
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
// Simple feedback - you could add a toast notification here
console.log("Copied:", text);
});
}
function toggleMenu() {
const menu = document.getElementById("mobile-menu");
menu.classList.toggle("hidden");
}
// Load on page load
document.addEventListener("DOMContentLoaded", loadHealth);
</script>
</body>
</html>

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Panel de Monitoreo</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
@@ -54,90 +55,117 @@
>
<div class="flex h-full grow flex-col">
<!-- Sticky Top Navigation -->
<header
class="sticky top-0 z-50 w-full border-b border-slate-200 dark:border-slate-800 bg-background-light/80 dark:bg-background-dark/80 backdrop-blur-md"
>
<div
class="max-w-[1200px] mx-auto px-4 lg:px-10 py-3 flex items-center justify-between"
>
<div class="flex items-center gap-8">
<div class="flex items-center gap-3 text-primary">
<header class="border-b border-[#283039] bg-[#0a0f16]">
<div class="container mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center text-white"
class="rounded-full size-9 border-2 border-slate-700 overflow-hidden"
>
<span class="material-symbols-outlined"
>monitoring</span
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<div>
<h1 class="text-xl font-bold">SIAX Monitor</h1>
<p class="text-xs text-slate-400">Dashboard</p>
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
>
SIAX Monitor
</h2>
</div>
<nav class="hidden md:flex items-center gap-6">
<!-- Desktop Navigation -->
<nav class="hidden md:flex items-center gap-2">
<a
class="text-primary text-sm font-semibold leading-normal"
href="/"
>Inicio</a
href="/health"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
<span class="material-symbols-outlined text-lg"
>monitor_heart</span
>
<span>Health</span>
</a>
<a
class="text-slate-600 dark:text-slate-400 hover:text-white text-sm font-medium transition-colors"
href="/scan"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
Escanear
<span class="material-symbols-outlined text-lg"
>search</span
>
<span>Escanear</span>
</a>
<a
class="text-slate-600 dark:text-slate-400 hover:text-white text-sm font-medium transition-colors"
href="/select"
>
Agregar
</a>
<a
class="text-slate-600 dark:text-slate-400 hover:text-white text-sm font-medium transition-colors"
href="/register"
>
Nueva App
</a>
<a
class="text-slate-600 dark:text-slate-400 hover:text-white text-sm font-medium transition-colors"
href="/logs"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
Registros
<span class="material-symbols-outlined text-lg"
>article</span
>
<span>Logs</span>
</a>
<a
href="/register"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-2"
>
<span class="material-symbols-outlined text-lg"
>app_registration</span
>
<span>Registrar</span>
</a>
</nav>
</div>
<div class="flex items-center gap-4">
<div class="hidden sm:block">
<label class="relative block">
<span
class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-500"
>
<span
class="material-symbols-outlined text-sm"
>
search
</span>
</span>
<input
class="form-input w-64 rounded-lg border-none bg-slate-200 dark:bg-slate-800 text-sm py-2 pl-10 pr-4 placeholder:text-slate-500 focus:ring-1 focus:ring-primary"
placeholder="Buscar..."
type="text"
/>
</label>
</div>
<!-- Mobile Menu Button -->
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
onclick="toggleMenu()"
class="md:hidden px-3 py-2 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors"
>
<span>Registrar App</span>
<span class="material-symbols-outlined text-2xl"
>menu</span
>
</button>
<div
class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-9 border-2 border-slate-700"
style="
background-image: url(&quot;https://lh3.googleusercontent.com/aida-public/AB6AXuCT0iINTncUFHp353HCJXRR5C0OKbSp_7IBOVNoDU07yuF2aToQQdnXNOeGI9RLUjVBsVNcU--ZoTMY90FFJvrQvYvRzKvq-CFCzBlVkCeoi5AgG84cB71wW0NIMg626M_sCjmDjxqmAJwIbkAcSmSlAg3TUThW1U2A3StNVgqFXEpgFbpJcU5nxLs6vuRkfYR1kIXcV44TQpgOosbsjSB1Pk1UTOQJ_OEcQtY-5c3FJw7gXBDxlp6y3jsY3rBm0xWGJi8NWnrUrhpl&quot;);
"
></div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden mt-4 pb-4 space-y-2"
>
<a
href="/health"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined"
>monitor_heart</span
>
<span>Health</span>
</a>
<a
href="/scan"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined"
>search</span
>
<span>Escanear</span>
</a>
<a
href="/logs"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined"
>article</span
>
<span>Logs</span>
</a>
<a
href="/register"
class="block px-4 py-3 rounded-lg text-slate-300 hover:bg-[#161f2a] transition-colors flex items-center gap-3"
>
<span class="material-symbols-outlined"
>app_registration</span
>
<span>Registrar</span>
</a>
</div>
</div>
</header>
@@ -150,7 +178,7 @@
<h1
class="text-slate-900 dark:text-white text-3xl font-black tracking-tight"
>
Dashboard Index
Panel de Control
</h1>
<p class="text-slate-500 text-sm mt-1">
Monitoreo de salud del sistema y procesos en tiempo
@@ -336,7 +364,7 @@
<th class="px-6 py-4">Mem %</th>
<th class="px-6 py-4">Tiempo Activo</th>
<th class="px-6 py-4 text-right">
Actions
Acciones
</th>
</tr>
</thead>
@@ -436,7 +464,7 @@
<a class="hover:text-primary" href="#"
>Política de Privacidad</a
>
<a class="hover:text-primary" href="#"
<a class="hover:text-primary" href="/api-docs"
>Documentación de API</a
>
</div>
@@ -446,9 +474,7 @@
<script>
async function loadApps() {
try {
const response = await fetch(
"http://localhost:8080/api/apps",
);
const response = await fetch("/api/apps");
const result = await response.json();
if (result.success && result.data && result.data.apps) {
@@ -470,8 +496,45 @@
function displayApps(apps) {
const tbody = document.getElementById("apps-tbody");
tbody.innerHTML = apps
.map(
(app) => `
.map((app) => {
// Determinar color del badge según estado
const statusColors = {
Running: {
bg: "bg-green-100 dark:bg-green-900/30",
text: "text-green-700 dark:text-green-400",
dot: "bg-green-500",
},
Stopped: {
bg: "bg-gray-100 dark:bg-gray-800",
text: "text-gray-700 dark:text-gray-400",
dot: "bg-gray-400",
},
Failed: {
bg: "bg-red-100 dark:bg-red-900/30",
text: "text-red-700 dark:text-red-400",
dot: "bg-red-500",
},
Starting: {
bg: "bg-blue-100 dark:bg-blue-900/30",
text: "text-blue-700 dark:text-blue-400",
dot: "bg-blue-500",
},
Stopping: {
bg: "bg-yellow-100 dark:bg-yellow-900/30",
text: "text-yellow-700 dark:text-yellow-400",
dot: "bg-yellow-500",
},
Unknown: {
bg: "bg-slate-100 dark:bg-slate-800",
text: "text-slate-700 dark:text-slate-400",
dot: "bg-slate-400",
},
};
const statusStyle =
statusColors[app.status] || statusColors["Unknown"];
return `
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/40 transition-colors">
<td class="px-6 py-4">
<div class="flex items-center gap-3">
@@ -479,28 +542,54 @@
<span class="material-symbols-outlined text-primary text-lg">terminal</span>
</div>
<div>
<p class="text-slate-900 dark:text-white font-semibold text-sm">${app}</p>
<p class="text-slate-500 text-xs">Servicio</p>
<p class="text-slate-900 dark:text-white font-semibold text-sm">${app.name}</p>
<p class="text-slate-500 text-xs">${app.service_name || "Servicio"}</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-400">
<span class="size-1.5 rounded-full bg-slate-400"></span>
Unknown
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium ${statusStyle.bg} ${statusStyle.text}">
<span class="size-1.5 rounded-full ${statusStyle.dot}"></span>
${app.status}
</span>
</td>
<td class="px-6 py-4 text-sm">-</td>
<td class="px-6 py-4 text-sm">-</td>
<td class="px-6 py-4 text-sm text-slate-500">-</td>
<td class="px-6 py-4 text-right">
<button class="text-slate-400 hover:text-white transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
<div class="flex items-center justify-end gap-2">
${
app.status === "Running"
? `
<button class="text-red-400 hover:text-red-300 transition-colors p-1.5 rounded hover:bg-red-900/20"
onclick="controlApp('${app.name}', 'stop')"
title="Detener">
<span class="material-symbols-outlined text-[20px]">stop</span>
</button>
<button class="text-yellow-400 hover:text-yellow-300 transition-colors p-1.5 rounded hover:bg-yellow-900/20"
onclick="controlApp('${app.name}', 'restart')"
title="Reiniciar">
<span class="material-symbols-outlined text-[20px]">refresh</span>
</button>
`
: `
<button class="text-green-400 hover:text-green-300 transition-colors p-1.5 rounded hover:bg-green-900/20"
onclick="controlApp('${app.name}', 'start')"
title="Iniciar">
<span class="material-symbols-outlined text-[20px]">play_arrow</span>
</button>
`
}
<button class="text-blue-400 hover:text-blue-300 transition-colors p-1.5 rounded hover:bg-blue-900/20"
onclick="window.location.href='/logs'"
title="Ver logs">
<span class="material-symbols-outlined text-[20px]">visibility</span>
</button>
</div>
</td>
</tr>
`,
)
`;
})
.join("");
}
@@ -516,6 +605,49 @@
`;
}
async function controlApp(appName, action) {
const actionNames = {
start: "Iniciar",
stop: "Detener",
restart: "Reiniciar",
};
const confirmed = confirm(
`¿Estás seguro de ${actionNames[action]} la aplicación "${appName}"?`,
);
if (!confirmed) return;
try {
const response = await fetch(
`/api/apps/${appName}/${action}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
},
);
const result = await response.json();
if (result.success) {
alert(`${result.data.message}`);
// Recargar la lista de apps
loadApps();
} else {
alert(`❌ Error: ${result.error}`);
}
} catch (error) {
console.error("Error:", error);
alert("❌ Error al ejecutar la acción");
}
}
function toggleMenu() {
const menu = document.getElementById("mobile-menu");
menu.classList.toggle("hidden");
}
window.addEventListener("DOMContentLoaded", loadApps);
</script>
</body>

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Visor de Registros - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
@@ -69,9 +70,11 @@
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center"
>
<span class="material-symbols-outlined text-white"
>monitoring</span
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
@@ -95,12 +98,7 @@
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/select"
>Agregar Detectada</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/register"
>Nueva App</a
>Selecionar Detectada</a
>
<a
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
@@ -108,6 +106,12 @@
>Registros</a
>
</nav>
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
>
<span>Nueva App</span>
</button>
</div>
</div>
</header>
@@ -203,10 +207,38 @@
</div>
</div>
<!-- Terminal Log Output -->
<!-- Tabs -->
<div class="border-b border-[#283039] bg-[#161f2a] px-4">
<div class="flex gap-1">
<button
id="tab-app-logs"
onclick="switchTab('app-logs')"
class="tab-button px-4 py-2 text-sm font-medium transition-colors rounded-t-lg border-b-2 border-primary text-primary"
>
<span
class="material-symbols-outlined text-[16px] align-middle"
>terminal</span
>
Logs de App
</button>
<button
id="tab-system-errors"
onclick="switchTab('system-errors')"
class="tab-button px-4 py-2 text-sm font-medium transition-colors rounded-t-lg border-b-2 border-transparent text-[#9dabb9] hover:text-white"
>
<span
class="material-symbols-outlined text-[16px] align-middle"
>error</span
>
Errores del Sistema
</button>
</div>
</div>
<!-- Tab Content: App Logs -->
<div
class="flex-1 bg-[#0a0f16] overflow-y-auto p-4 font-mono text-sm"
id="log-terminal"
id="content-app-logs"
class="flex-1 bg-[#0a0f16] overflow-y-auto p-4 font-mono text-sm tab-content"
>
<div id="log-container" class="space-y-1">
<!-- Welcome Message -->
@@ -220,6 +252,19 @@
</div>
</div>
</div>
<!-- Tab Content: System Errors -->
<div
id="content-system-errors"
class="hidden flex-1 bg-[#0a0f16] overflow-y-auto p-4 font-mono text-sm tab-content"
>
<div id="system-errors-container" class="space-y-1">
<div class="text-[#9dabb9] opacity-50">
<span class="text-yellow-400"></span> Cargando logs
de errores del sistema...
</div>
</div>
</div>
</main>
</div>
@@ -238,9 +283,7 @@
empty.classList.add("hidden");
try {
const response = await fetch(
"http://localhost:8080/api/apps",
);
const response = await fetch("/api/apps");
const data = await response.json();
loading.classList.add("hidden");
@@ -321,9 +364,10 @@
});
// Connect WebSocket
ws = new WebSocket(
`ws://localhost:8080/api/apps/${appName}/logs`,
);
const protocol =
window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/api/apps/${appName}/logs`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
document.getElementById("connection-status").textContent =
@@ -399,8 +443,9 @@
logContainer.appendChild(logEntry);
// Auto-scroll
if (autoScroll) {
const terminal = document.getElementById("log-terminal");
if (autoScroll && currentTab === "app-logs") {
const terminal =
document.getElementById("content-app-logs");
terminal.scrollTop = terminal.scrollHeight;
}
@@ -457,6 +502,100 @@
`;
}
// Tab switching
let currentTab = "app-logs";
function switchTab(tabName) {
currentTab = tabName;
// Update tab buttons
document.querySelectorAll(".tab-button").forEach((btn) => {
btn.classList.remove("border-primary", "text-primary");
btn.classList.add("border-transparent", "text-[#9dabb9]");
});
const activeTab = document.getElementById(`tab-${tabName}`);
activeTab.classList.remove(
"border-transparent",
"text-[#9dabb9]",
);
activeTab.classList.add("border-primary", "text-primary");
// Update tab content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.add("hidden");
});
document
.getElementById(`content-${tabName}`)
.classList.remove("hidden");
// Load system errors if switching to that tab
if (tabName === "system-errors") {
loadSystemErrors();
}
}
async function loadSystemErrors() {
const container = document.getElementById(
"system-errors-container",
);
try {
const response = await fetch("/api/logs/errors");
const result = await response.json();
if (
result.success &&
result.logs &&
result.logs.length > 0
) {
container.innerHTML = result.logs
.map((line) => {
// Parse log line
let icon = "●";
let color = "text-white";
if (line.includes("[ERROR]")) {
icon = "✖";
color = "text-red-400";
} else if (line.includes("[WARN]")) {
icon = "⚠";
color = "text-yellow-400";
} else if (line.includes("[INFO]")) {
icon = "";
color = "text-blue-400";
}
return `<div class="log-line ${color}">${icon} ${escapeHtml(line)}</div>`;
})
.join("");
// Auto scroll to bottom
container.scrollTop = container.scrollHeight;
} else if (result.message) {
container.innerHTML = `
<div class="text-[#9dabb9]">
<span class="text-yellow-400">⚠</span> ${result.message}
</div>
`;
} else {
container.innerHTML = `
<div class="text-[#9dabb9]">
<span class="text-blue-400"></span> No hay logs de errores disponibles
</div>
`;
}
} catch (error) {
console.error("Error loading system errors:", error);
container.innerHTML = `
<div class="text-red-400">
<span class="text-red-400">✖</span> Error cargando logs del sistema
</div>
`;
}
}
// Load apps on page load
document.addEventListener("DOMContentLoaded", loadApps);

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Registrar Aplicación - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
@@ -62,9 +63,11 @@
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center"
>
<span class="material-symbols-outlined text-white"
>monitoring</span
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
@@ -88,12 +91,7 @@
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/select"
>Agregar Detectada</a
>
<a
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
href="/register"
>Nueva App</a
>Selecionar Detectada</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
@@ -101,6 +99,12 @@
>Registros</a
>
</nav>
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
>
<span>Nueva App</span>
</button>
</div>
</div>
</header>
@@ -461,16 +465,13 @@
});
try {
const response = await fetch(
"http://localhost:8080/api/apps",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
const response = await fetch("/api/apps", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
);
body: JSON.stringify(formData),
});
const result = await response.json();
@@ -485,7 +486,7 @@
confirm("¿Deseas iniciar la aplicación ahora?")
) {
const startResponse = await fetch(
`http://localhost:8080/api/apps/${formData.app_name}/start`,
`/api/apps/${formData.app_name}/start`,
{ method: "POST" },
);

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Escaneo de Procesos - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
@@ -62,9 +63,7 @@
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center"
>
<span class="material-symbols-outlined text-white"
>monitoring</span
>
<img src="/static/icon/logo.png" alt="Logo" class="w-full h-full object-cover">
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
@@ -88,19 +87,21 @@
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/select"
>Agregar Detectada</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/register"
>Registrar Nueva</a
>Selecionar Detectada</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/logs"
>Registros</a
>
</nav>
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
>
<span>Nueva App</span>
</button>
</div>
</div>
</header>
@@ -112,7 +113,7 @@
<h1
class="text-white text-4xl font-black leading-tight tracking-[-0.033em]"
>
Process Scan View
Visualización de escaneo de procesos
</h1>
<p class="text-[#9dabb9] text-base font-normal">
Monitoreo activo de procesos Node.js y Python.
@@ -253,7 +254,7 @@
loadingState.classList.remove('hidden');
emptyState.classList.add('hidden');
const response = await fetch('http://localhost:8080/api/scan');
const response = await fetch('/api/scan');
if (!response.ok) throw new Error('Failed to fetch processes');
const data = await response.json();

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Agregar App Detectada - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
@@ -62,9 +63,11 @@
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center"
>
<span class="material-symbols-outlined text-white"
>monitoring</span
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
@@ -88,12 +91,7 @@
<a
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
href="/select"
>Agregar Detectada</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/register"
>Nueva App</a
>Selecionar Detectada</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
@@ -101,6 +99,12 @@
>Registros</a
>
</nav>
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
>
<span>Nueva App</span>
</button>
</div>
</div>
</header>
@@ -111,7 +115,7 @@
<h1
class="text-white text-4xl font-black leading-tight tracking-[-0.033em]"
>
Add Detected Application
Agregar la aplicación detectada
</h1>
<p class="text-[#9dabb9] text-base font-normal">
Selecciona un proceso detectado y configúralo para
@@ -280,9 +284,7 @@
const empty = document.getElementById("empty-state");
try {
const response = await fetch(
"http://localhost:8080/api/scan",
);
const response = await fetch("/api/scan");
if (!response.ok)
throw new Error("Failed to fetch processes");

BIN
web/static/icon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="128.000000pt" height="128.000000pt" viewBox="0 0 128.000000 128.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M518 1244 c-75 -18 -188 -75 -239 -121 l-40 -36 -39 38 c-22 21 -42
36 -46 32 -3 -3 3 -33 14 -66 20 -58 20 -61 3 -83 -66 -84 -105 -212 -105
-343 0 -166 49 -288 164 -408 55 -58 71 -70 81 -60 18 18 64 16 79 -2 9 -11
35 -15 106 -15 l94 0 0 165 0 166 -24 -18 c-23 -17 -25 -25 -28 -128 l-3 -110
-45 -3 c-24 -2 -50 -8 -57 -14 -9 -7 -17 -7 -28 2 -17 14 -18 20 -5 40 7 12
13 12 33 1 13 -7 37 -11 53 -9 l29 3 3 87 c2 74 0 88 -13 88 -12 0 -15 -13
-15 -65 l0 -65 -45 0 c-25 0 -54 -6 -65 -12 -23 -15 -46 1 -37 26 5 12 18 13
61 9 l56 -6 0 47 c0 25 -4 46 -10 46 -5 0 -10 -11 -10 -25 0 -24 -3 -25 -60
-25 -32 0 -71 -5 -85 -12 -21 -9 -29 -9 -42 4 -14 15 -13 17 6 27 15 8 27 9
40 1 31 -16 111 -13 111 5 0 11 -11 15 -38 15 -103 0 -207 61 -257 151 -26 47
-30 64 -30 129 1 112 50 201 137 246 29 15 30 15 86 -37 66 -60 72 -69 42 -69
-13 0 -37 -9 -55 -20 -93 -58 -84 -193 17 -246 59 -31 144 -6 184 53 l22 33
22 -52 c12 -28 33 -68 46 -88 24 -36 24 -42 22 -200 l-3 -163 -96 2 c-72 2
-99 -1 -107 -12 -10 -12 -4 -18 35 -35 48 -20 138 -42 173 -42 13 0 17 5 13
20 -3 11 0 20 6 20 8 0 11 51 11 170 0 107 4 170 10 170 6 0 10 -61 10 -164 0
-108 4 -167 11 -172 6 -3 8 -15 5 -26 -4 -19 -1 -20 54 -14 71 8 148 30 174
49 37 27 9 37 -105 37 l-109 0 0 159 c0 96 4 162 10 166 7 4 10 -48 10 -149
l0 -156 93 0 c65 1 98 5 111 15 23 18 67 20 83 4 16 -16 43 2 106 71 97 108
147 240 148 390 0 125 -27 227 -87 322 l-27 42 18 55 c10 31 20 62 22 69 9 23
-16 12 -51 -23 -20 -19 -38 -35 -42 -35 -3 0 -25 16 -47 36 -52 44 -163 99
-242 119 -72 18 -200 18 -277 -1z m310 -90 c72 -15 187 -56 205 -74 5 -4 -40
-31 -100 -60 -89 -44 -121 -65 -182 -126 -52 -53 -80 -74 -99 -74 -17 0 -48
23 -106 78 -66 63 -99 85 -178 122 -54 25 -98 48 -98 52 0 11 160 69 230 83
91 18 237 18 328 -1z m325 -253 c52 -48 81 -117 81 -196 -1 -82 -17 -126 -70
-185 -51 -56 -120 -89 -200 -97 -49 -4 -64 -9 -60 -19 3 -8 -1 -14 -9 -14 -8
0 -15 9 -15 19 0 10 -7 21 -15 25 -12 4 -15 -4 -15 -44 0 -50 0 -50 33 -50 18
0 38 5 44 11 13 13 33 4 33 -16 0 -20 -20 -29 -33 -16 -6 6 -30 11 -54 11
l-43 0 0 56 c0 42 -4 60 -17 70 -10 7 -21 14 -25 14 -5 0 -8 -45 -8 -100 l0
-100 34 0 c19 0 38 5 41 10 12 20 35 10 35 -14 0 -32 -19 -44 -35 -23 -8 11
-27 17 -54 17 l-41 0 0 120 c0 66 -4 120 -9 120 -22 0 -18 35 13 101 l33 71
21 -36 c78 -132 275 -81 275 71 0 56 -25 91 -88 122 l-49 25 59 53 c58 52 59
53 84 37 14 -9 39 -28 54 -43z m-103 -510 c0 -13 -27 -21 -45 -15 -25 10 -17
24 15 24 17 0 30 -4 30 -9z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
web/static/icon/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Éxito - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
@@ -62,9 +63,11 @@
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center"
>
<span class="material-symbols-outlined text-white"
>monitoring</span
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
@@ -91,13 +94,7 @@
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/select"
>
Agregar Detectada
</a>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/register"
>
Nueva App
Selecionar Detectada
</a>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
@@ -106,6 +103,12 @@
Registros
</a>
</nav>
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
>
<span>Nueva App</span>
</button>
</div>
</div>
</header>