Files
SIAX-MONITOR/web/api-docs.html
pablinux 8822e9e6b5 feat: Mejorar estructura de monitored_apps.json con metadata completa
- Añadir campos al modelo MonitoredApp:
  * service_name: Nombre del servicio systemd
  * path: WorkingDirectory de la aplicación
  * entry_point: Archivo de entrada (server.js, app.js, etc.)
  * node_bin: Ruta completa al binario de node/python
  * mode: Modo de ejecución (production, development, test)
  * service_file_path: Ruta al archivo .service de systemd
  * registered_at: Timestamp de registro (ISO 8601)

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

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

- Mantener retrocompatibilidad con JSON antiguo mediante campos deprecated
- Todos los nuevos campos usan #[serde(default)] para evitar errores de deserialización
2026-01-18 03:26:42 -05:00

912 lines
39 KiB
HTML

<!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>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"
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"],
},
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;
}
.code-block {
font-family: "JetBrains Mono", monospace;
}
</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-[1400px] 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"
>
<span class="material-symbols-outlined text-white"
>monitoring</span
>
</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="/register"
>Registrar Nueva</a
>
<a
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
href="/api-docs"
>Documentación API</a
>
</nav>
</div>
</div>
</header>
<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"
>
<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
>
</nav>
</div>
<div class="pt-6 border-t border-[#283039]">
<h3 class="text-white font-bold text-sm mb-3">INFO</h3>
<div class="space-y-2 text-xs">
<div>
<span class="text-[#9dabb9]">Versión:</span>
<span class="text-white font-mono">v1.0.0</span>
</div>
<div>
<span class="text-[#9dabb9]">Base URL:</span>
<span class="text-white font-mono"
>localhost:8080</span
>
</div>
<div>
<span class="text-[#9dabb9]">Protocolo:</span>
<span class="text-white font-mono">HTTP/WS</span>
</div>
</div>
</div>
</aside>
<!-- Main Content - API Documentation -->
<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>
<p class="text-[#9dabb9] text-lg mb-6">
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="flex items-start gap-3">
<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"
>/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>
<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>
<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>
</section>
<!-- 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
>
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.
</p>
<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
>
<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>
</div>
</div>
</div>
</section>
<!-- 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
>
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="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
>
</div>
<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"
>
{
"success": true,
"data": {
"apps": ["app_tareas", "fidelizacion"],
"total": 2
},
"error": null
}</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
>
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="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
>
</div>
<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"
>
{
"app_name": "mi-app",
"script_path": "/opt/apps/mi-app/index.js",
"working_directory": "/opt/apps/mi-app",
"user": "nodejs",
"environment": {
"NODE_ENV": "production",
"PORT": "3000"
},
"restart_policy": "always",
"app_type": "nodejs",
"description": "Mi aplicación Node.js"
}</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"
>
{
"success": true,
"data": {
"app_name": "mi-app",
"operation": "register",
"success": true,
"message": "Aplicación registrada exitosamente"
},
"error": null
}</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="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
>
</div>
<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>
<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
>
</li>
</ul>
</div>
</div>
</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="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
>
</div>
<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"
>
{
"success": true,
"data": {
"name": "mi-app",
"status": "Running",
"pid": 12345,
"cpu_usage": 2.5,
"memory_usage": "128.50 MB",
"systemd_status": "active",
"last_updated": "2026-01-13T12:34:56"
}
}</pre
>
</div>
</div>
</div>
</section>
<!-- 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
>
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="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
>
</div>
<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"
>
{
"success": true,
"data": {
"processes": [
{
"pid": 5769,
"name": "node",
"user": "1000",
"cpu_usage": 2.5,
"memory_mb": 112.54,
"process_type": "nodejs"
}
],
"total": 1
}
}</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
>
Probar endpoint
</button>
</div>
</div>
</section>
<!-- 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
>
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="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
>
</div>
<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="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
>
</div>
<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="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
>
</div>
<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="flex items-start gap-3">
<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>
</div>
</div>
</div>
</section>
<!-- 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
>
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="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
>
</div>
<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');
ws.onopen = () => {
console.log('Conectado a logs');
};
ws.onmessage = (event) => {
const log = JSON.parse(event.data);
console.log(log.MESSAGE);
};
ws.onerror = (error) => {
console.error('Error:', error);
};
ws.onclose = () => {
console.log('Desconectado');
};</pre
>
</div>
<div>
<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
>
</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
>
</li>
</ul>
</div>
</div>
</div>
</section>
<!-- 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
>
Códigos de Error
</h2>
<div class="space-y-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>
</div>
<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="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>
</div>
<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="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>
</div>
<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="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>
</div>
<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"
>
{
"success": false,
"data": null,
"error": "Descripción del error"
}</pre
>
</div>
</section>
</main>
</div>
<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...";
try {
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>`;
} catch (error) {
resultDiv.innerHTML = `<pre class="text-red-400">Error: ${error.message}</pre>`;
}
}
// Smooth scroll for anchor links
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
const target = document.querySelector(
this.getAttribute("href"),
);
if (target) {
target.scrollIntoView({
behavior: "smooth",
block: "start",
});
}
});
});
</script>
</body>
</html>