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
This commit is contained in:
2026-01-18 03:26:42 -05:00
parent ad9b46bdc5
commit 8822e9e6b5
21 changed files with 3246 additions and 642 deletions

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
@@ -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) {
@@ -516,6 +542,11 @@
`;
}
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>
@@ -238,9 +242,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 +323,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 =

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>