Compare commits
11 Commits
13b36dda5f
...
058e4781e6
| Author | SHA1 | Date | |
|---|---|---|---|
| 058e4781e6 | |||
| 93d178b216 | |||
| cd14cc5c06 | |||
| bb25004e67 | |||
| 9e56490b05 | |||
| d2b8d0222c | |||
| d8b3214ede | |||
| 2f867cb7ed | |||
| 6fa7b5c86c | |||
| fb3db3c713 | |||
| 7a66f25150 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
logs/*.log
|
||||||
|
config/monitored_apps.json
|
||||||
|
|||||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -281,6 +281,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dotenvy"
|
||||||
|
version = "0.15.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
@@ -1332,6 +1338,7 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
"dotenvy",
|
||||||
"futures",
|
"futures",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ tokio-stream = "0.1"
|
|||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
dashmap = "5.5"
|
dashmap = "5.5"
|
||||||
|
dotenvy = "0.15"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.8"
|
tempfile = "3.8"
|
||||||
|
|||||||
@@ -2,11 +2,27 @@
|
|||||||
"apps": [
|
"apps": [
|
||||||
{
|
{
|
||||||
"name": "app_tareas",
|
"name": "app_tareas",
|
||||||
"port": 3000
|
"service_name": "",
|
||||||
|
"path": "",
|
||||||
|
"port": 3000,
|
||||||
|
"entry_point": "",
|
||||||
|
"node_bin": "",
|
||||||
|
"mode": "production",
|
||||||
|
"service_file_path": "",
|
||||||
|
"deleted": true,
|
||||||
|
"deleted_at": "2026-01-21T18:01:42.273756980-05:00",
|
||||||
|
"deleted_reason": "Eliminada desde el panel de control"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fidelizacion",
|
"name": "fidelizacion",
|
||||||
"port": 3001
|
"service_name": "",
|
||||||
|
"path": "",
|
||||||
|
"port": 3001,
|
||||||
|
"entry_point": "",
|
||||||
|
"node_bin": "",
|
||||||
|
"mode": "production",
|
||||||
|
"service_file_path": "",
|
||||||
|
"deleted": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
170
logs/errors.log
170
logs/errors.log
@@ -2328,3 +2328,173 @@
|
|||||||
[2026-01-19 08:27:21] [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-19 08:27:21] [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-19 08:27:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
[2026-01-19 08:27:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
[2026-01-19 08:27:21] [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-19 08:27:21] [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-20 06:35:19] [INFO] [Sistema] Iniciando SIAX Agent
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Sistema] Escaneando servicios systemd existentes...
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Sistema] Servidor detectado: siax-intel
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Monitor] Buscando app app_tareas en API central...
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
|
||||||
|
[2026-01-20 06:35:19] [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-20 06:35:19] [INFO] [Monitor] Buscando app fidelizacion en API central...
|
||||||
|
[2026-01-20 06:35:19] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
|
||||||
|
[2026-01-20 06:35:19] [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-20 06:36:20] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:36:20] [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-20 06:36:20] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:36:20] [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-20 06:37:20] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:37:20] [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-20 06:37:20] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:37:20] [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-20 06:38:20] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:38:20] [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-20 06:38:20] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:38:20] [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-20 06:39:20] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:39:20] [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-20 06:39:20] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:39:20] [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-20 06:40:20] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:40:21] [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-20 06:40:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:40:21] [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-20 06:41:21] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:41:21] [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-20 06:41:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:41:21] [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-20 06:42:21] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:42:21] [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-20 06:42:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:42:21] [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-20 06:43:21] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:43:21] [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-20 06:43:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:43:21] [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-20 06:44:21] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-20 06:44:21] [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-20 06:44:21] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-20 06:44:21] [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-20 06:44:46] [INFO] [Sistema] Iniciando SIAX Agent
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Sistema] Escaneando servicios systemd existentes...
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Sistema] Servidor detectado: siax-intel
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Monitor] Buscando app app_tareas en API central...
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
|
||||||
|
[2026-01-20 06:44:46] [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-20 06:44:46] [INFO] [Monitor] Buscando app fidelizacion en API central...
|
||||||
|
[2026-01-20 06:44:46] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
|
||||||
|
[2026-01-20 06:44:46] [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-20 06:45:08] [INFO] [WebSocket] Nueva conexión para logs de: fidelizacion
|
||||||
|
[2026-01-20 06:45:09] [INFO] [WebSocket] Nueva conexión para logs de: app_tareas
|
||||||
|
[2026-01-20 06:45:09] [INFO] [WebSocket] Conexión cerrada para: fidelizacion
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Sistema] Iniciando SIAX Agent
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Sistema] Escaneando servicios systemd existentes...
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Sistema] Servidor detectado: siax-intel
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
|
||||||
|
[2026-01-21 17:08:02] [INFO] [Monitor] Buscando app app_tareas en API central...
|
||||||
|
[2026-01-21 17:08:03] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
|
||||||
|
[2026-01-21 17:08:03] [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-21 17:08:03] [INFO] [Monitor] Buscando app fidelizacion en API central...
|
||||||
|
[2026-01-21 17:08:03] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
|
||||||
|
[2026-01-21 17:08:04] [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-21 17:55:31] [INFO] [Sistema] Iniciando SIAX Agent
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Sistema] Escaneando servicios systemd existentes...
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Sistema] Servidor detectado: siax-intel
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
|
||||||
|
[2026-01-21 17:55:31] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
|
||||||
|
[2026-01-21 17:55:32] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
|
||||||
|
[2026-01-21 17:55:32] [INFO] [Monitor] Buscando app app_tareas en API central...
|
||||||
|
[2026-01-21 17:55:32] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
|
||||||
|
[2026-01-21 17:55:32] [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-21 17:55:32] [INFO] [Monitor] Buscando app fidelizacion en API central...
|
||||||
|
[2026-01-21 17:55:33] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
|
||||||
|
[2026-01-21 17:55:33] [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-21 17:56:33] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-21 17:56:33] [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-21 17:56:33] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-21 17:56:33] [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-21 17:57:30] [INFO] [Sistema] Iniciando SIAX Agent
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Sistema] Escaneando servicios systemd existentes...
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Sistema] Servidor detectado: siax-intel
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Monitor] Buscando app app_tareas en API central...
|
||||||
|
[2026-01-21 17:57:30] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
|
||||||
|
[2026-01-21 17:57:30] [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-21 17:57:30] [INFO] [Monitor] Buscando app fidelizacion en API central...
|
||||||
|
[2026-01-21 17:57:31] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
|
||||||
|
[2026-01-21 17:57:31] [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-21 17:58:31] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-21 17:58:31] [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-21 17:58:31] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-21 17:58:31] [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-21 17:59:32] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-21 17:59:32] [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-21 17:59:32] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-21 17:59:32] [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-21 18:00:32] [INFO] [Monitor] App app_tareas ya existe (ID: 3), actualizando...
|
||||||
|
[2026-01-21 18:00:33] [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-21 18:00:33] [INFO] [Monitor] App fidelizacion ya existe (ID: 4), actualizando...
|
||||||
|
[2026-01-21 18:00:33] [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-21 18:01:28] [INFO] [Sistema] Iniciando SIAX Agent
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Sistema] Escaneando servicios systemd existentes...
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Discovery] 🔍 Escaneando servicios systemd en: /etc/systemd/system
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Discovery] ✅ Directorio /etc/systemd/system accesible
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Discovery] 📊 Escaneados 131 archivos, 0 con prefijo 'siax-app-', 0 parseados exitosamente
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Config] Usando archivo de configuración: config/monitored_apps.json
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Config] ✅ Configuración cargada: 2 apps desde config/monitored_apps.json
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Sistema] Servidor detectado: siax-intel
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Sistema] Iniciando servidor unificado en puerto 8080
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Sistema] Sistema SIAX completamente operativo en puerto 8080
|
||||||
|
[2026-01-21 18:01:28] [INFO] [Monitor] Vigilando procesos para siax-intel [SIAX-Agent/0.1.0 (linux/x86_64; Rust-Monitor)]
|
||||||
|
[2026-01-21 18:01:29] [INFO] [Monitor] Buscando app app_tareas en API central...
|
||||||
|
[2026-01-21 18:01:29] [INFO] [Monitor] App app_tareas encontrada en cloud (ID: 3)
|
||||||
|
[2026-01-21 18:01:29] [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-21 18:01:29] [INFO] [Monitor] Buscando app fidelizacion en API central...
|
||||||
|
[2026-01-21 18:01:29] [INFO] [Monitor] App fidelizacion encontrada en cloud (ID: 4)
|
||||||
|
[2026-01-21 18:01:30] [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-21 18:01:42] [INFO] [API] 🗑️ Solicitud de eliminación para: app_tareas
|
||||||
|
[2026-01-21 18:01:42] [INFO] [AppManager] Desregistrando aplicación (soft delete): app_tareas
|
||||||
|
[2026-01-21 18:01:42] [WARNING] [API] App no encontrada en AppManager: Aplicación no encontrada: app_tareas
|
||||||
|
[2026-01-21 18:01:42] [INFO] [API] ✅ Soft delete en JSON: app_tareas
|
||||||
|
[2026-01-21 18:01:42] [INFO] [SystemCtl] Deteniendo servicio: siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:50] [INFO] [API] Deteniendo servicio: siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:50] [INFO] [SystemCtl] Deshabilitando servicio: siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:56] [INFO] [API] Deshabilitando servicio: siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:56] [INFO] [ServiceGenerator] Eliminando servicio: /etc/systemd/system/siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:56] [WARNING] [ServiceGenerator] Servicio no encontrado | siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:56] [INFO] [API] ✅ Archivo .service eliminado: siax-app-app_tareas.service
|
||||||
|
[2026-01-21 18:01:56] [INFO] [SystemCtl] Recargando daemon de systemd
|
||||||
|
[2026-01-21 18:01:59] [INFO] [API] 🔄 daemon-reload ejecutado
|
||||||
|
|||||||
@@ -60,20 +60,216 @@ pub async fn register_app_handler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_app_handler(
|
||||||
|
State(state): State<Arc<ApiState>>,
|
||||||
|
Path(app_name): Path<String>,
|
||||||
|
Json(payload): Json<RegisterAppRequest>,
|
||||||
|
) -> Result<Json<ApiResponse<OperationResponse>>, StatusCode> {
|
||||||
|
use crate::config::get_config_manager;
|
||||||
|
use crate::systemd::{SystemCtl, ServiceGenerator};
|
||||||
|
|
||||||
|
let logger = crate::logger::get_logger();
|
||||||
|
logger.info("API", &format!("✏️ Solicitud de actualización para: {}", app_name));
|
||||||
|
|
||||||
|
// Validar que el app_name coincida
|
||||||
|
if app_name != payload.app_name {
|
||||||
|
return Ok(Json(ApiResponse::error(
|
||||||
|
"El nombre de la app en la URL no coincide con el payload".to_string()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsear tipo de aplicación
|
||||||
|
let app_type = match payload.app_type.to_lowercase().as_str() {
|
||||||
|
"nodejs" | "node" => AppType::NodeJs,
|
||||||
|
"python" | "py" => AppType::Python,
|
||||||
|
_ => return Ok(Json(ApiResponse::error(
|
||||||
|
"Tipo de aplicación inválido. Use 'nodejs' o 'python'".to_string()
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parsear política de reinicio
|
||||||
|
let restart_policy = match payload.restart_policy.to_lowercase().as_str() {
|
||||||
|
"always" => RestartPolicy::Always,
|
||||||
|
"on-failure" | "onfailure" => RestartPolicy::OnFailure,
|
||||||
|
"no" | "never" => RestartPolicy::No,
|
||||||
|
_ => RestartPolicy::Always,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = ServiceConfig {
|
||||||
|
app_name: payload.app_name.clone(),
|
||||||
|
script_path: payload.script_path,
|
||||||
|
working_directory: payload.working_directory,
|
||||||
|
user: payload.user,
|
||||||
|
environment: payload.environment,
|
||||||
|
restart_policy,
|
||||||
|
app_type,
|
||||||
|
description: payload.description,
|
||||||
|
custom_executable: payload.custom_executable,
|
||||||
|
use_npm_start: payload.use_npm_start,
|
||||||
|
};
|
||||||
|
|
||||||
|
let service_name = format!("siax-app-{}.service", app_name);
|
||||||
|
|
||||||
|
// 1. Detener el servicio
|
||||||
|
logger.info("API", &format!("🛑 Deteniendo servicio: {}", service_name));
|
||||||
|
let _ = SystemCtl::stop(&service_name);
|
||||||
|
|
||||||
|
// 2. Regenerar el archivo .service
|
||||||
|
logger.info("API", "📝 Regenerando archivo .service con nueva configuración");
|
||||||
|
match ServiceGenerator::create_service(&config) {
|
||||||
|
Ok(service_content) => {
|
||||||
|
match ServiceGenerator::write_service_file(&config, &service_content) {
|
||||||
|
Ok(_) => {
|
||||||
|
logger.info("API", "✅ Archivo .service actualizado");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Ok(Json(ApiResponse::error(
|
||||||
|
format!("Error escribiendo archivo .service: {}", e)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Ok(Json(ApiResponse::error(
|
||||||
|
format!("Error generando .service: {}", e)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Recargar daemon
|
||||||
|
logger.info("API", "🔄 Ejecutando daemon-reload");
|
||||||
|
let _ = SystemCtl::daemon_reload();
|
||||||
|
|
||||||
|
// 4. Actualizar monitored_apps.json
|
||||||
|
let config_manager = get_config_manager();
|
||||||
|
let service_file_path = format!("/etc/systemd/system/{}", service_name);
|
||||||
|
let port = config.environment.get("PORT")
|
||||||
|
.and_then(|p| p.parse::<i32>().ok())
|
||||||
|
.unwrap_or(8080);
|
||||||
|
|
||||||
|
let entry_point = std::path::Path::new(&config.script_path)
|
||||||
|
.file_name()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.unwrap_or("server.js")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let node_bin = config.custom_executable.clone().unwrap_or_default();
|
||||||
|
let mode = config.environment.get("NODE_ENV")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| "production".to_string());
|
||||||
|
|
||||||
|
// Primero intentar hacer soft delete de la app anterior
|
||||||
|
let _ = config_manager.soft_delete_app(&app_name, Some("Actualizada - versión anterior".to_string()));
|
||||||
|
|
||||||
|
// Luego agregar la nueva configuración
|
||||||
|
let monitored_app = crate::config::MonitoredApp {
|
||||||
|
name: config.app_name.clone(),
|
||||||
|
service_name: service_name.clone(),
|
||||||
|
path: config.working_directory.clone(),
|
||||||
|
port,
|
||||||
|
entry_point,
|
||||||
|
node_bin,
|
||||||
|
mode,
|
||||||
|
user: config.user.clone(),
|
||||||
|
service_file_path,
|
||||||
|
registered_at: chrono::Local::now().to_rfc3339(),
|
||||||
|
deleted: false,
|
||||||
|
deleted_at: None,
|
||||||
|
deleted_reason: None,
|
||||||
|
environment: config.environment.clone(),
|
||||||
|
systemd_service: None,
|
||||||
|
created_at: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match config_manager.add_app_full(monitored_app) {
|
||||||
|
Ok(_) => {
|
||||||
|
logger.info("API", "✅ JSON actualizado");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logger.warning("API", &format!("No se pudo actualizar JSON: {}", e), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Iniciar el servicio nuevamente
|
||||||
|
logger.info("API", &format!("▶️ Iniciando servicio: {}", service_name));
|
||||||
|
match SystemCtl::start(&service_name) {
|
||||||
|
Ok(_) => {
|
||||||
|
logger.info("API", "✅ Servicio iniciado exitosamente");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logger.warning("API", &format!("Error al iniciar servicio: {}", e), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(ApiResponse::success(OperationResponse {
|
||||||
|
app_name: app_name.clone(),
|
||||||
|
operation: "update".to_string(),
|
||||||
|
success: true,
|
||||||
|
message: format!("Aplicación '{}' actualizada exitosamente", app_name),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn unregister_app_handler(
|
pub async fn unregister_app_handler(
|
||||||
State(state): State<Arc<ApiState>>,
|
State(state): State<Arc<ApiState>>,
|
||||||
Path(app_name): Path<String>,
|
Path(app_name): Path<String>,
|
||||||
) -> Result<Json<ApiResponse<OperationResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<OperationResponse>>, StatusCode> {
|
||||||
|
use crate::config::get_config_manager;
|
||||||
|
use crate::systemd::{SystemCtl, ServiceGenerator};
|
||||||
|
|
||||||
|
let logger = crate::logger::get_logger();
|
||||||
|
let service_name = format!("siax-app-{}.service", app_name);
|
||||||
|
|
||||||
|
logger.info("API", &format!("🗑️ Solicitud de eliminación para: {}", app_name));
|
||||||
|
|
||||||
|
// Intentar 1: Eliminar desde AppManager (si está en memoria)
|
||||||
|
let mut deleted_from_memory = false;
|
||||||
match state.app_manager.unregister_app(&app_name) {
|
match state.app_manager.unregister_app(&app_name) {
|
||||||
Ok(_) => Ok(Json(ApiResponse::success(OperationResponse {
|
Ok(_) => {
|
||||||
app_name: app_name.clone(),
|
logger.info("API", &format!("✅ Eliminado desde AppManager: {}", app_name));
|
||||||
operation: "unregister".to_string(),
|
deleted_from_memory = true;
|
||||||
success: true,
|
}
|
||||||
message: "Aplicación eliminada exitosamente".to_string(),
|
Err(e) => {
|
||||||
}))),
|
logger.warning("API", &format!("App no encontrada en AppManager: {}", e), None);
|
||||||
Err(e) => Ok(Json(ApiResponse::error(e.to_string()))),
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intentar 2: Soft delete en JSON (siempre intentar)
|
||||||
|
let config_manager = get_config_manager();
|
||||||
|
let delete_reason = Some("Eliminada desde el panel de control".to_string());
|
||||||
|
match config_manager.soft_delete_app(&app_name, delete_reason) {
|
||||||
|
Ok(_) => {
|
||||||
|
logger.info("API", &format!("✅ Soft delete en JSON: {}", app_name));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logger.warning("API", &format!("No se pudo hacer soft delete en JSON: {}", e), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentar 3: Eliminar servicio systemd físicamente (siempre intentar)
|
||||||
|
let _ = SystemCtl::stop(&service_name);
|
||||||
|
logger.info("API", &format!("Deteniendo servicio: {}", service_name));
|
||||||
|
|
||||||
|
let _ = SystemCtl::disable(&service_name);
|
||||||
|
logger.info("API", &format!("Deshabilitando servicio: {}", service_name));
|
||||||
|
|
||||||
|
match ServiceGenerator::delete_service_file(&service_name) {
|
||||||
|
Ok(_) => {
|
||||||
|
logger.info("API", &format!("✅ Archivo .service eliminado: {}", service_name));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logger.warning("API", &format!("No se pudo eliminar .service: {}", e), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = SystemCtl::daemon_reload();
|
||||||
|
logger.info("API", "🔄 daemon-reload ejecutado");
|
||||||
|
|
||||||
|
Ok(Json(ApiResponse::success(OperationResponse {
|
||||||
|
app_name: app_name.clone(),
|
||||||
|
operation: "unregister".to_string(),
|
||||||
|
success: true,
|
||||||
|
message: format!("Aplicación '{}' eliminada exitosamente (servicio systemd + soft delete en JSON)", app_name),
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_app_handler(
|
pub async fn start_app_handler(
|
||||||
@@ -124,6 +320,30 @@ pub async fn restart_app_handler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_app_details_handler(
|
||||||
|
Path(app_name): Path<String>,
|
||||||
|
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||||
|
use crate::config::get_config_manager;
|
||||||
|
|
||||||
|
let config_manager = get_config_manager();
|
||||||
|
let apps = config_manager.get_apps();
|
||||||
|
|
||||||
|
match apps.iter().find(|a| a.name == app_name) {
|
||||||
|
Some(app) => {
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"success": true,
|
||||||
|
"data": app
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"success": false,
|
||||||
|
"error": format!("Aplicación '{}' no encontrada", app_name)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_app_status_handler(
|
pub async fn get_app_status_handler(
|
||||||
State(_state): State<Arc<ApiState>>,
|
State(_state): State<Arc<ApiState>>,
|
||||||
Path(app_name): Path<String>,
|
Path(app_name): Path<String>,
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ pub struct MonitoredApp {
|
|||||||
#[serde(default = "default_mode")]
|
#[serde(default = "default_mode")]
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
|
|
||||||
|
/// Usuario del sistema que ejecuta la aplicación
|
||||||
|
#[serde(default = "default_user")]
|
||||||
|
pub user: String,
|
||||||
|
|
||||||
/// Ruta completa al archivo .service de systemd
|
/// Ruta completa al archivo .service de systemd
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub service_file_path: String,
|
pub service_file_path: String,
|
||||||
@@ -53,6 +57,12 @@ pub struct MonitoredApp {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub deleted_reason: Option<String>,
|
pub deleted_reason: Option<String>,
|
||||||
|
|
||||||
|
// --- VARIABLES DE ENTORNO ADICIONALES ---
|
||||||
|
/// Variables de entorno ADICIONALES (las del .env se cargan con EnvironmentFile)
|
||||||
|
/// Solo almacenamos aquí las variables que el usuario agrega manualmente desde el panel
|
||||||
|
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
|
||||||
|
pub environment: std::collections::HashMap<String, String>,
|
||||||
|
|
||||||
// DEPRECATED: Mantener por compatibilidad con versiones antiguas
|
// DEPRECATED: Mantener por compatibilidad con versiones antiguas
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub systemd_service: Option<String>,
|
pub systemd_service: Option<String>,
|
||||||
@@ -64,6 +74,13 @@ fn default_mode() -> String {
|
|||||||
"production".to_string()
|
"production".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_user() -> String {
|
||||||
|
// Intentar obtener el usuario actual del sistema
|
||||||
|
std::env::var("USER")
|
||||||
|
.or_else(|_| std::env::var("LOGNAME"))
|
||||||
|
.unwrap_or_else(|_| "root".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub apps: Vec<MonitoredApp>,
|
pub apps: Vec<MonitoredApp>,
|
||||||
@@ -200,11 +217,13 @@ impl ConfigManager {
|
|||||||
entry_point: String::new(),
|
entry_point: String::new(),
|
||||||
node_bin: String::new(),
|
node_bin: String::new(),
|
||||||
mode: "production".to_string(),
|
mode: "production".to_string(),
|
||||||
|
user: default_user(),
|
||||||
service_file_path: String::new(),
|
service_file_path: String::new(),
|
||||||
registered_at,
|
registered_at,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: std::collections::HashMap::new(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -255,11 +255,13 @@ pub fn sync_discovered_services(services: Vec<DiscoveredService>) {
|
|||||||
entry_point: service.entry_point.unwrap_or_default(),
|
entry_point: service.entry_point.unwrap_or_default(),
|
||||||
node_bin: service.node_bin.unwrap_or_default(),
|
node_bin: service.node_bin.unwrap_or_default(),
|
||||||
mode: service.node_env,
|
mode: service.node_env,
|
||||||
|
user: service.user.clone().unwrap_or_else(|| "root".to_string()),
|
||||||
service_file_path: service.service_file.clone(),
|
service_file_path: service.service_file.clone(),
|
||||||
registered_at,
|
registered_at,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: std::collections::HashMap::new(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ pub fn create_web_router() -> Router {
|
|||||||
.route("/scan", get(scan_processes_handler))
|
.route("/scan", get(scan_processes_handler))
|
||||||
.route("/select", get(select_processes_handler))
|
.route("/select", get(select_processes_handler))
|
||||||
.route("/register", get(register_handler))
|
.route("/register", get(register_handler))
|
||||||
|
.route("/edit", get(edit_handler))
|
||||||
.route("/add-process", post(add_process_handler))
|
.route("/add-process", post(add_process_handler))
|
||||||
.route("/logs", get(logs_handler))
|
.route("/logs", get(logs_handler))
|
||||||
.route("/clear-logs", post(clear_logs_handler))
|
.route("/clear-logs", post(clear_logs_handler))
|
||||||
@@ -122,6 +123,11 @@ async fn register_handler() -> Html<String> {
|
|||||||
Html(template.to_string())
|
Html(template.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn edit_handler() -> Html<String> {
|
||||||
|
let template = include_str!("../web/edit.html");
|
||||||
|
Html(template.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
async fn api_docs_handler() -> Html<String> {
|
async fn api_docs_handler() -> Html<String> {
|
||||||
let template = include_str!("../web/api-docs.html");
|
let template = include_str!("../web/api-docs.html");
|
||||||
Html(template.to_string())
|
Html(template.to_string())
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ async fn main() {
|
|||||||
.route("/api/logs/errors", get(api::get_system_error_logs))
|
.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", get(api::list_apps_handler).post(api::register_app_handler))
|
||||||
.route("/api/apps/deleted", get(api::get_deleted_apps_handler))
|
.route("/api/apps/deleted", get(api::get_deleted_apps_handler))
|
||||||
.route("/api/apps/:name", delete(api::unregister_app_handler))
|
.route("/api/apps/:name", get(api::get_app_details_handler).delete(api::unregister_app_handler).put(api::update_app_handler))
|
||||||
.route("/api/apps/:name/status", get(api::get_app_status_handler))
|
.route("/api/apps/:name/status", get(api::get_app_status_handler))
|
||||||
.route("/api/apps/:name/start", post(api::start_app_handler))
|
.route("/api/apps/:name/start", post(api::start_app_handler))
|
||||||
.route("/api/apps/:name/stop", post(api::stop_app_handler))
|
.route("/api/apps/:name/stop", post(api::stop_app_handler))
|
||||||
|
|||||||
@@ -86,11 +86,13 @@ impl AppManager {
|
|||||||
entry_point,
|
entry_point,
|
||||||
node_bin,
|
node_bin,
|
||||||
mode,
|
mode,
|
||||||
|
user: config.user.clone(),
|
||||||
service_file_path,
|
service_file_path,
|
||||||
registered_at,
|
registered_at,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: config.environment.clone(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::models::ServiceConfig;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::collections::HashMap;
|
||||||
use crate::logger::get_logger;
|
use crate::logger::get_logger;
|
||||||
|
|
||||||
pub struct ServiceGenerator;
|
pub struct ServiceGenerator;
|
||||||
@@ -74,7 +75,7 @@ impl ServiceGenerator {
|
|||||||
format!("{} {}", executable, config.script_path)
|
format!("{} {}", executable, config.script_path)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generar variables de entorno del usuario
|
// Generar líneas de variables de entorno ADICIONALES (solo las del config, no del .env)
|
||||||
let mut env_lines: Vec<String> = config.environment
|
let mut env_lines: Vec<String> = config.environment
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, value)| format!("Environment=\"{}={}\"", key, value))
|
.map(|(key, value)| format!("Environment=\"{}={}\"", key, value))
|
||||||
@@ -99,11 +100,17 @@ impl ServiceGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let env_vars = env_lines.join("\n");
|
|
||||||
|
|
||||||
// Agregar SyslogIdentifier para logs más claros
|
// Agregar SyslogIdentifier para logs más claros
|
||||||
let syslog_id = format!("SyslogIdentifier=siax-app-{}", config.app_name);
|
let syslog_id = format!("SyslogIdentifier=siax-app-{}", config.app_name);
|
||||||
|
|
||||||
|
// Verificar si existe .env en el proyecto
|
||||||
|
let env_file_path = Path::new(&config.working_directory).join(".env");
|
||||||
|
let has_env_file = env_file_path.exists();
|
||||||
|
|
||||||
|
if has_env_file {
|
||||||
|
logger.info("ServiceGenerator", &format!("📄 .env encontrado, usando EnvironmentFile: {}", env_file_path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
// Construir el servicio con orden lógico
|
// Construir el servicio con orden lógico
|
||||||
let mut service = format!(
|
let mut service = format!(
|
||||||
r#"[Unit]
|
r#"[Unit]
|
||||||
@@ -120,9 +127,26 @@ WorkingDirectory={}
|
|||||||
config.working_directory
|
config.working_directory
|
||||||
);
|
);
|
||||||
|
|
||||||
// Agregar variables de entorno (PATH primero, luego las demás)
|
// Agregar PATH si usa NVM (debe ir primero)
|
||||||
if !env_vars.is_empty() {
|
// Extraer PATH de env_lines si está en la primera posición
|
||||||
service.push_str(&env_vars);
|
let mut path_line: Option<String> = None;
|
||||||
|
if !env_lines.is_empty() && env_lines[0].starts_with("Environment=PATH=") {
|
||||||
|
path_line = Some(env_lines.remove(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = path_line {
|
||||||
|
service.push_str(&path);
|
||||||
|
service.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ AGREGAR EnvironmentFile si existe .env en el proyecto
|
||||||
|
if has_env_file {
|
||||||
|
service.push_str(&format!("EnvironmentFile={}\n", env_file_path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar variables de entorno ADICIONALES (las del formulario/JSON)
|
||||||
|
if !env_lines.is_empty() {
|
||||||
|
service.push_str(&env_lines.join("\n"));
|
||||||
service.push('\n');
|
service.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,4 +319,55 @@ WantedBy=multi-user.target
|
|||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lee el archivo .env del directorio de trabajo y retorna las variables
|
||||||
|
pub fn read_env_file(working_directory: &str) -> HashMap<String, String> {
|
||||||
|
let logger = get_logger();
|
||||||
|
let env_path = Path::new(working_directory).join(".env");
|
||||||
|
|
||||||
|
let mut env_vars = HashMap::new();
|
||||||
|
|
||||||
|
if !env_path.exists() {
|
||||||
|
logger.info("ServiceGenerator", &format!("No se encontró archivo .env en: {}", env_path.display()));
|
||||||
|
return env_vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("ServiceGenerator", &format!("Leyendo archivo .env desde: {}", env_path.display()));
|
||||||
|
|
||||||
|
match fs::read_to_string(&env_path) {
|
||||||
|
Ok(content) => {
|
||||||
|
for line in content.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
|
||||||
|
// Ignorar líneas vacías y comentarios
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsear línea KEY=VALUE
|
||||||
|
if let Some(pos) = line.find('=') {
|
||||||
|
let key = line[..pos].trim().to_string();
|
||||||
|
let mut value = line[pos + 1..].trim().to_string();
|
||||||
|
|
||||||
|
// Remover comillas simples o dobles
|
||||||
|
if (value.starts_with('\'') && value.ends_with('\'')) ||
|
||||||
|
(value.starts_with('"') && value.ends_with('"')) {
|
||||||
|
value = value[1..value.len()-1].to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !key.is_empty() {
|
||||||
|
env_vars.insert(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("ServiceGenerator", &format!("✅ Cargadas {} variables desde .env", env_vars.len()));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logger.warning("ServiceGenerator", &format!("Error leyendo .env: {}", e), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env_vars
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
84
tareas.txt
84
tareas.txt
@@ -497,24 +497,92 @@ sudo journalctl -u siax_monitor -f
|
|||||||
4. Verificar logs en pestaña "Logs de App"
|
4. Verificar logs en pestaña "Logs de App"
|
||||||
5. Verificar errores del sistema en pestaña "Errores del Sistema"
|
5. Verificar errores del sistema en pestaña "Errores del Sistema"
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
🔮 FASE 5 - MEJORAS FUTURAS Y TAREAS PENDIENTES
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
**Fase 5.1: Script de Inicialización de .env** 🔄 PENDIENTE
|
||||||
|
[ ] Crear script/comando para sincronizar .env desde servidor central
|
||||||
|
[ ] Implementar endpoint en API Central para servir .env de producción
|
||||||
|
[ ] Script de deploy que descargue .env automáticamente:
|
||||||
|
- Opción 1: GET https://api-central.com/env/{app_name}
|
||||||
|
- Opción 2: SCP desde servidor de secrets
|
||||||
|
- Opción 3: Integración con Vault/Secrets Manager
|
||||||
|
[ ] Validación de variables requeridas antes de iniciar servicio
|
||||||
|
[ ] Logging de variables faltantes (sin exponer valores sensibles)
|
||||||
|
[ ] Documentación de variables requeridas por app
|
||||||
|
|
||||||
|
**Motivación:**
|
||||||
|
- Actualmente .env está en .gitignore (correcto para seguridad)
|
||||||
|
- Al deployar, el .env NO se copia al servidor
|
||||||
|
- Las apps fallan con "DB param: undefined"
|
||||||
|
- Proceso manual de copiar .env es propenso a errores
|
||||||
|
- Necesario automatizar la distribución segura de secrets
|
||||||
|
|
||||||
|
**Implementación Sugerida:**
|
||||||
|
```bash
|
||||||
|
# Script: sync_env.sh
|
||||||
|
#!/bin/bash
|
||||||
|
APP_NAME=$1
|
||||||
|
API_CENTRAL="https://api-central.telcotronics.com"
|
||||||
|
|
||||||
|
# Descargar .env desde servidor central
|
||||||
|
curl -H "Authorization: Bearer $SECRET_TOKEN" \
|
||||||
|
"$API_CENTRAL/secrets/$APP_NAME/.env" \
|
||||||
|
-o /home/user_apps/apps/$APP_NAME/.env
|
||||||
|
|
||||||
|
# Verificar descarga
|
||||||
|
if [ -f "/home/user_apps/apps/$APP_NAME/.env" ]; then
|
||||||
|
echo "✅ .env descargado correctamente"
|
||||||
|
# Re-registrar app para cargar variables
|
||||||
|
curl -X PUT http://localhost:8080/api/apps/$APP_NAME \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @/tmp/app_config.json
|
||||||
|
else
|
||||||
|
echo "❌ Error descargando .env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fase 5.2: Template de .env** 🔄 PENDIENTE
|
||||||
|
[ ] Crear .env.example en cada proyecto
|
||||||
|
[ ] Documentar variables requeridas vs opcionales
|
||||||
|
[ ] Script de validación: check_env.sh
|
||||||
|
[ ] Generar .env desde template interactivo
|
||||||
|
|
||||||
|
**Fase 5.3: Gestión Centralizada de Secrets** 🔄 PENDIENTE
|
||||||
|
[ ] Integración con HashiCorp Vault
|
||||||
|
[ ] Soporte para AWS Secrets Manager
|
||||||
|
[ ] Rotación automática de passwords
|
||||||
|
[ ] Auditoría de acceso a secrets
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
📊 MÉTRICAS DEL PROYECTO
|
📊 MÉTRICAS DEL PROYECTO
|
||||||
===============================================================================
|
===============================================================================
|
||||||
|
|
||||||
**Líneas de código:** ~3,500
|
**Líneas de código:** ~4,200
|
||||||
**Archivos Rust:** 15
|
**Archivos Rust:** 15
|
||||||
**Archivos HTML:** 8
|
**Archivos HTML:** 9 (agregado edit.html)
|
||||||
**Endpoints API:** 12
|
**Endpoints API:** 15 (GET/PUT/DELETE /apps/:name, GET /apps/deleted, POST /apps/:name/restore)
|
||||||
**Commits totales:** 15+
|
**Commits totales:** 25+
|
||||||
**Tiempo desarrollo:** ~3 días
|
**Tiempo desarrollo:** ~4 días
|
||||||
**Bugs críticos resueltos:** 8
|
**Bugs críticos resueltos:** 12
|
||||||
**Fase actual:** 4.8 (Completada)
|
**Fase actual:** 4.8 (Completada) + Mejoras (Soft Delete, CRUD Update, Auto .env)
|
||||||
|
|
||||||
|
**Nuevas Features:**
|
||||||
|
✅ Soft Delete con historial
|
||||||
|
✅ Función EDITAR apps (CRUD completo)
|
||||||
|
✅ Auto-carga de variables desde .env
|
||||||
|
✅ Campo 'user' en configuración
|
||||||
|
✅ Eliminación robusta (3 fuentes)
|
||||||
|
✅ UI mejorada (overflow logs, modal claro)
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
🎉 FIN DEL DOCUMENTO
|
🎉 FIN DEL DOCUMENTO
|
||||||
===============================================================================
|
===============================================================================
|
||||||
|
|
||||||
Última actualización: 2026-01-18 23:45:00
|
Última actualización: 2026-01-21 22:30:00
|
||||||
Actualizado por: Claude AI Assistant
|
Actualizado por: Claude AI Assistant
|
||||||
Proyecto: SIAX Monitor v0.1.0
|
Proyecto: SIAX Monitor v0.1.0
|
||||||
Estado: PRODUCTION-READY ✅
|
Estado: PRODUCTION-READY ✅
|
||||||
|
Próxima fase: 5.1 (Script inicialización .env)
|
||||||
|
|||||||
615
web/edit.html
Normal file
615
web/edit.html
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
<!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>Editar 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"
|
||||||
|
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"] },
|
||||||
|
borderRadius: {
|
||||||
|
DEFAULT: "0.25rem",
|
||||||
|
lg: "0.5rem",
|
||||||
|
xl: "0.75rem",
|
||||||
|
full: "9999px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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-light dark:bg-background-dark font-display text-white min-h-screen flex flex-col"
|
||||||
|
>
|
||||||
|
<!-- Sticky Top Navigation -->
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-50 w-full border-b border-solid border-[#283039] bg-background-dark/80 backdrop-blur-md px-4 md:px-10 py-3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="max-w-[1200px] mx-auto flex items-center justify-between whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-8">
|
||||||
|
<div class="flex items-center gap-4 text-white">
|
||||||
|
<div
|
||||||
|
class="size-8 bg-primary rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<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]"
|
||||||
|
>
|
||||||
|
SIAX Monitor
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-1 justify-end gap-6 items-center">
|
||||||
|
<nav class="hidden md:flex items-center gap-6">
|
||||||
|
<a
|
||||||
|
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||||
|
href="/"
|
||||||
|
>Panel</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||||
|
href="/scan"
|
||||||
|
>Escanear</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||||
|
href="/select"
|
||||||
|
>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>
|
||||||
|
|
||||||
|
<main class="flex-1 max-w-[900px] mx-auto w-full px-4 py-8 space-y-6">
|
||||||
|
<!-- Page Heading -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<h1
|
||||||
|
class="text-white text-4xl font-black leading-tight tracking-[-0.033em]"
|
||||||
|
>
|
||||||
|
Register New Application
|
||||||
|
</h1>
|
||||||
|
<p class="text-[#9dabb9] text-base font-normal">
|
||||||
|
Register a Node.js or Python application to manage with
|
||||||
|
systemd.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alert Messages -->
|
||||||
|
<div
|
||||||
|
id="alert-success"
|
||||||
|
class="hidden rounded-xl p-4 bg-green-500/20 border border-green-500/30"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="material-symbols-outlined text-green-400"
|
||||||
|
>check_circle</span
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-green-400 text-sm font-medium"
|
||||||
|
id="success-message"
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="alert-error"
|
||||||
|
class="hidden rounded-xl p-4 bg-red-500/20 border border-red-500/30"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="material-symbols-outlined text-red-400"
|
||||||
|
>error</span
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-red-400 text-sm font-medium"
|
||||||
|
id="error-message"
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Registration Form -->
|
||||||
|
<form id="registerForm" class="space-y-6">
|
||||||
|
<!-- Basic Information Card -->
|
||||||
|
<div
|
||||||
|
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||||
|
>
|
||||||
|
<h3 class="text-white text-lg font-bold">
|
||||||
|
Basic Information
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||||
|
Application Name <span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="app_name"
|
||||||
|
name="app_name"
|
||||||
|
required
|
||||||
|
placeholder="mi-app"
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<p class="text-[#9dabb9] text-xs">
|
||||||
|
Solo letras, números, guiones y guiones bajos
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label
|
||||||
|
class="block text-[#9dabb9] text-sm font-medium"
|
||||||
|
>
|
||||||
|
Application Type
|
||||||
|
<span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="app_type"
|
||||||
|
name="app_type"
|
||||||
|
required
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
>
|
||||||
|
<option value="nodejs">Node.js</option>
|
||||||
|
<option value="python">Python / FastAPI</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label
|
||||||
|
class="block text-[#9dabb9] text-sm font-medium"
|
||||||
|
>
|
||||||
|
Restart Policy
|
||||||
|
<span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="restart_policy"
|
||||||
|
name="restart_policy"
|
||||||
|
required
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
>
|
||||||
|
<option value="always">
|
||||||
|
Always (Always restart)
|
||||||
|
</option>
|
||||||
|
<option value="on-failure">
|
||||||
|
On-Failure (Only if fails)
|
||||||
|
</option>
|
||||||
|
<option value="no">No (No reiniciar)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
name="description"
|
||||||
|
rows="2"
|
||||||
|
placeholder="Descripción de la aplicación..."
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Paths & User Card -->
|
||||||
|
<div
|
||||||
|
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||||
|
>
|
||||||
|
<h3 class="text-white text-lg font-bold">
|
||||||
|
Rutas y Usuario
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||||
|
Script Path <span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="script_path"
|
||||||
|
name="script_path"
|
||||||
|
required
|
||||||
|
placeholder="/opt/apps/mi-app/index.js"
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<p class="text-[#9dabb9] text-xs">
|
||||||
|
Ruta completa al archivo principal (.js o .py)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||||
|
Working Directory
|
||||||
|
<span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="working_directory"
|
||||||
|
name="working_directory"
|
||||||
|
required
|
||||||
|
placeholder="/opt/apps/mi-app"
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<p class="text-[#9dabb9] text-xs">
|
||||||
|
Directorio desde el cual se ejecutará la aplicación
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||||
|
System User <span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="user"
|
||||||
|
name="user"
|
||||||
|
required
|
||||||
|
placeholder="nodejs"
|
||||||
|
value="nodejs"
|
||||||
|
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<p class="text-[#9dabb9] text-xs">
|
||||||
|
Usuario bajo el cual se ejecutará el proceso
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Environment Variables Card -->
|
||||||
|
<div
|
||||||
|
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="text-white text-lg font-bold">
|
||||||
|
Environment Variables
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick="addEnvVar()"
|
||||||
|
class="flex items-center gap-2 px-4 py-2 bg-[#1c2730] hover:bg-[#283039] border border-[#283039] rounded-lg text-white text-sm font-medium transition-colors"
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined text-[18px]"
|
||||||
|
>add</span
|
||||||
|
>
|
||||||
|
Add Variable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="env-container" class="space-y-3">
|
||||||
|
<div
|
||||||
|
class="env-item grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="KEY"
|
||||||
|
class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="valor"
|
||||||
|
class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick="removeEnvVar(this)"
|
||||||
|
class="flex items-center justify-center w-full md:w-auto px-4 py-2.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 transition-colors"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="material-symbols-outlined text-[18px]"
|
||||||
|
>delete</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col-reverse sm:flex-row gap-3 justify-between pt-4"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick="window.location.href = '/'"
|
||||||
|
class="flex items-center justify-center rounded-lg h-12 px-6 bg-[#1c2730] hover:bg-[#283039] border border-[#283039] text-white text-sm font-bold transition-colors"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="flex items-center justify-center rounded-lg h-12 px-6 bg-primary hover:brightness-110 text-white text-sm font-bold transition-all gap-2"
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined text-[18px]"
|
||||||
|
>check_circle</span
|
||||||
|
>
|
||||||
|
<span>Editar Aplicación</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addEnvVar() {
|
||||||
|
addEnvironmentVariable("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEnvironmentVariable(key, value) {
|
||||||
|
const container = document.getElementById("env-container");
|
||||||
|
const envItem = document.createElement("div");
|
||||||
|
envItem.className =
|
||||||
|
"env-item grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3";
|
||||||
|
envItem.innerHTML = `
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="KEY"
|
||||||
|
value="${key}"
|
||||||
|
class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="valor"
|
||||||
|
value="${value}"
|
||||||
|
class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick="removeEnvVar(this)"
|
||||||
|
class="flex items-center justify-center w-full md:w-auto px-4 py-2.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 transition-colors"
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
container.appendChild(envItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeEnvVar(btn) {
|
||||||
|
const container = document.getElementById("env-container");
|
||||||
|
if (container.children.length > 1) {
|
||||||
|
btn.closest(".env-item").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert(message, type) {
|
||||||
|
const successAlert = document.getElementById("alert-success");
|
||||||
|
const errorAlert = document.getElementById("alert-error");
|
||||||
|
|
||||||
|
if (type === "success") {
|
||||||
|
document.getElementById("success-message").textContent =
|
||||||
|
message;
|
||||||
|
successAlert.classList.remove("hidden");
|
||||||
|
errorAlert.classList.add("hidden");
|
||||||
|
} else {
|
||||||
|
document.getElementById("error-message").textContent =
|
||||||
|
message;
|
||||||
|
errorAlert.classList.remove("hidden");
|
||||||
|
successAlert.classList.add("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to top
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
|
||||||
|
// Hide after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
successAlert.classList.add("hidden");
|
||||||
|
errorAlert.classList.add("hidden");
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener nombre de app desde URL
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const appName = urlParams.get("app");
|
||||||
|
|
||||||
|
if (!appName) {
|
||||||
|
alert("No se especificó el nombre de la aplicación");
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar datos de la app
|
||||||
|
async function loadAppData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/apps/${appName}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
alert("Error: " + result.error);
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = result.data;
|
||||||
|
|
||||||
|
console.log("App data:", app); // Debug
|
||||||
|
|
||||||
|
// Llenar formulario con datos actuales
|
||||||
|
document.getElementById("app_name").value = app.name || "";
|
||||||
|
document.getElementById("app_name").readOnly = true; // No cambiar nombre
|
||||||
|
|
||||||
|
// Construir script_path completo
|
||||||
|
const scriptPath =
|
||||||
|
app.path && app.entry_point
|
||||||
|
? `${app.path}/${app.entry_point}`.replace(
|
||||||
|
"//",
|
||||||
|
"/",
|
||||||
|
)
|
||||||
|
: app.entry_point || "";
|
||||||
|
|
||||||
|
document.getElementById("script_path").value = scriptPath;
|
||||||
|
document.getElementById("working_directory").value =
|
||||||
|
app.path || "";
|
||||||
|
|
||||||
|
// Cargar usuario desde JSON (sin fallback)
|
||||||
|
document.getElementById("user").value = app.user;
|
||||||
|
document.getElementById("restart_policy").value = "always";
|
||||||
|
document.getElementById("app_type").value = "nodejs";
|
||||||
|
document.getElementById("description").value = "";
|
||||||
|
|
||||||
|
// ✅ Cargar variables de entorno ADICIONALES desde JSON
|
||||||
|
// (Las del .env se cargan automáticamente con EnvironmentFile)
|
||||||
|
if (
|
||||||
|
app.environment &&
|
||||||
|
Object.keys(app.environment).length > 0
|
||||||
|
) {
|
||||||
|
// Limpiar el campo vacío por defecto
|
||||||
|
document.getElementById("env-container").innerHTML = "";
|
||||||
|
|
||||||
|
// Agregar cada variable del JSON
|
||||||
|
Object.entries(app.environment).forEach(
|
||||||
|
([key, value]) => {
|
||||||
|
addEnvironmentVariable(key, value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`✅ Cargadas ${Object.keys(app.environment).length} variables adicionales desde JSON`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cargando app:", error);
|
||||||
|
alert("Error al cargar los datos de la aplicación");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAppData();
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("registerForm")
|
||||||
|
.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
app_name: document.getElementById("app_name").value,
|
||||||
|
script_path:
|
||||||
|
document.getElementById("script_path").value,
|
||||||
|
working_directory:
|
||||||
|
document.getElementById("working_directory").value,
|
||||||
|
user: document.getElementById("user").value,
|
||||||
|
environment: {},
|
||||||
|
restart_policy:
|
||||||
|
document.getElementById("restart_policy").value,
|
||||||
|
app_type: document.getElementById("app_type").value,
|
||||||
|
description:
|
||||||
|
document.getElementById("description").value ||
|
||||||
|
null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect environment variables
|
||||||
|
const envItems = document.querySelectorAll(".env-item");
|
||||||
|
envItems.forEach((item) => {
|
||||||
|
const key = item.querySelector(".env-key").value;
|
||||||
|
const value = item.querySelector(".env-value").value;
|
||||||
|
if (key && value) {
|
||||||
|
formData.environment[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/apps/${appName}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showAlert(
|
||||||
|
`✅ Aplicación actualizada: ${formData.app_name}`,
|
||||||
|
"success",
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/";
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
document.getElementById("registerForm").reset();
|
||||||
|
document.getElementById("env-container").innerHTML =
|
||||||
|
`
|
||||||
|
<div class="env-item grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3">
|
||||||
|
<input type="text" placeholder="KEY" class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" />
|
||||||
|
<input type="text" placeholder="valor" class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" />
|
||||||
|
<button type="button" onclick="removeEnvVar(this)" class="flex items-center justify-center w-full md:w-auto px-4 py-2.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
showAlert(
|
||||||
|
"Error: " +
|
||||||
|
(result.error || "Error desconocido"),
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showAlert(
|
||||||
|
"Connection error: " + error.message,
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-fill working_directory based on script_path
|
||||||
|
document
|
||||||
|
.getElementById("script_path")
|
||||||
|
.addEventListener("blur", function () {
|
||||||
|
const scriptPath = this.value;
|
||||||
|
const workingDirInput =
|
||||||
|
document.getElementById("working_directory");
|
||||||
|
|
||||||
|
if (scriptPath && !workingDirInput.value) {
|
||||||
|
const dir = scriptPath.substring(
|
||||||
|
0,
|
||||||
|
scriptPath.lastIndexOf("/"),
|
||||||
|
);
|
||||||
|
workingDirInput.value = dir;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -626,28 +626,35 @@
|
|||||||
class="material-symbols-outlined text-[14px] text-red-500"
|
class="material-symbols-outlined text-[14px] text-red-500"
|
||||||
>close</span
|
>close</span
|
||||||
>
|
>
|
||||||
Servicio systemd (siax-app-*.service)
|
Servicio systemd:
|
||||||
|
<span
|
||||||
|
id="delete-service-name"
|
||||||
|
class="font-mono"
|
||||||
|
></span>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-2">
|
<li class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="material-symbols-outlined text-[14px] text-red-500"
|
class="material-symbols-outlined text-[14px] text-red-500"
|
||||||
>close</span
|
>close</span
|
||||||
>
|
>
|
||||||
Archivo de configuración en /etc/systemd/system/
|
Archivo .service en /etc/systemd/system/
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-2">
|
<li class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="material-symbols-outlined text-[14px] text-red-500"
|
class="material-symbols-outlined text-[14px] text-amber-500"
|
||||||
>close</span
|
>archive</span
|
||||||
>
|
>
|
||||||
Registro en monitored_apps.json
|
Se marcará como eliminada en monitored_apps.json
|
||||||
|
(soft delete)
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-2">
|
<li
|
||||||
|
class="flex items-center gap-2 text-green-600 dark:text-green-400"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
class="material-symbols-outlined text-[14px] text-red-500"
|
class="material-symbols-outlined text-[14px]"
|
||||||
>close</span
|
>info</span
|
||||||
>
|
>
|
||||||
Historial de monitoreo
|
Podrás restaurarla desde el historial
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -785,6 +792,11 @@
|
|||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
<button class="text-purple-400 hover:text-purple-300 transition-colors p-1.5 rounded hover:bg-purple-900/20"
|
||||||
|
onclick="window.location.href='/edit?app=${app.name}'"
|
||||||
|
title="Editar">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">edit</span>
|
||||||
|
</button>
|
||||||
<button class="text-blue-400 hover:text-blue-300 transition-colors p-1.5 rounded hover:bg-blue-900/20"
|
<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'"
|
onclick="window.location.href='/logs'"
|
||||||
title="Ver logs">
|
title="Ver logs">
|
||||||
@@ -866,6 +878,8 @@
|
|||||||
appToDelete = appName;
|
appToDelete = appName;
|
||||||
document.getElementById("delete-app-name").textContent =
|
document.getElementById("delete-app-name").textContent =
|
||||||
appName;
|
appName;
|
||||||
|
document.getElementById("delete-service-name").textContent =
|
||||||
|
`siax-app-${appName}.service`;
|
||||||
document
|
document
|
||||||
.getElementById("delete-modal")
|
.getElementById("delete-modal")
|
||||||
.classList.remove("hidden");
|
.classList.remove("hidden");
|
||||||
|
|||||||
@@ -238,9 +238,12 @@
|
|||||||
<!-- Tab Content: App Logs -->
|
<!-- Tab Content: App Logs -->
|
||||||
<div
|
<div
|
||||||
id="content-app-logs"
|
id="content-app-logs"
|
||||||
class="flex-1 bg-[#0a0f16] overflow-y-auto p-4 font-mono text-sm tab-content"
|
class="flex-1 bg-[#0a0f16] overflow-y-auto overflow-x-auto p-4 font-mono text-sm tab-content"
|
||||||
>
|
>
|
||||||
<div id="log-container" class="space-y-1">
|
<div
|
||||||
|
id="log-container"
|
||||||
|
class="space-y-1 break-words overflow-wrap-anywhere"
|
||||||
|
>
|
||||||
<!-- Welcome Message -->
|
<!-- Welcome Message -->
|
||||||
<div class="text-[#9dabb9] opacity-50">
|
<div class="text-[#9dabb9] opacity-50">
|
||||||
<span class="text-green-400">●</span> SIAX Monitor
|
<span class="text-green-400">●</span> SIAX Monitor
|
||||||
@@ -256,9 +259,12 @@
|
|||||||
<!-- Tab Content: System Errors -->
|
<!-- Tab Content: System Errors -->
|
||||||
<div
|
<div
|
||||||
id="content-system-errors"
|
id="content-system-errors"
|
||||||
class="hidden flex-1 bg-[#0a0f16] overflow-y-auto p-4 font-mono text-sm tab-content"
|
class="hidden flex-1 bg-[#0a0f16] overflow-y-auto overflow-x-auto p-4 font-mono text-sm tab-content"
|
||||||
>
|
>
|
||||||
<div id="system-errors-container" class="space-y-1">
|
<div
|
||||||
|
id="system-errors-container"
|
||||||
|
class="space-y-1 break-words overflow-wrap-anywhere"
|
||||||
|
>
|
||||||
<div class="text-[#9dabb9] opacity-50">
|
<div class="text-[#9dabb9] opacity-50">
|
||||||
<span class="text-yellow-400">⚠</span> Cargando logs
|
<span class="text-yellow-400">⚠</span> Cargando logs
|
||||||
de errores del sistema...
|
de errores del sistema...
|
||||||
@@ -405,7 +411,8 @@
|
|||||||
function appendLog(type, message, logData = null) {
|
function appendLog(type, message, logData = null) {
|
||||||
const logContainer = document.getElementById("log-container");
|
const logContainer = document.getElementById("log-container");
|
||||||
const logEntry = document.createElement("div");
|
const logEntry = document.createElement("div");
|
||||||
logEntry.className = "log-line";
|
logEntry.className =
|
||||||
|
"log-line break-words overflow-wrap-anywhere";
|
||||||
|
|
||||||
const timestamp = new Date()
|
const timestamp = new Date()
|
||||||
.toISOString()
|
.toISOString()
|
||||||
@@ -572,7 +579,7 @@
|
|||||||
color = "text-blue-400";
|
color = "text-blue-400";
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<div class="log-line ${color}">${icon} ${escapeHtml(line)}</div>`;
|
return `<div class="log-line break-words overflow-wrap-anywhere ${color}">${icon} ${escapeHtml(line)}</div>`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user